From f2e1c6488053034de44b35796f85e6d7e4fb42d2 Mon Sep 17 00:00:00 2001 From: Emmanuel AYME Date: Wed, 31 Dec 2025 14:26:56 +0100 Subject: [PATCH] ImGui dependency update --- external/reshade/deps/imgui/imconfig.h | 29 +- external/reshade/deps/imgui/imgui.cpp | 4074 +++++++++++++++-- external/reshade/deps/imgui/imgui.h | 1249 +++-- external/reshade/deps/imgui/imgui_demo.cpp | 1775 +++++-- external/reshade/deps/imgui/imgui_draw.cpp | 3650 +++++++++++---- external/reshade/deps/imgui/imgui_internal.h | 901 +++- external/reshade/deps/imgui/imgui_stdlib.cpp | 14 + external/reshade/deps/imgui/imgui_stdlib.h | 9 +- external/reshade/deps/imgui/imgui_tables.cpp | 545 ++- external/reshade/deps/imgui/imgui_widgets.cpp | 1586 ++++++- external/reshade/deps/imgui/imstb_rectpack.h | 74 + external/reshade/deps/imgui/imstb_textedit.h | 246 +- external/reshade/deps/imgui/imstb_truetype.h | 594 ++- 13 files changed, 12646 insertions(+), 2100 deletions(-) diff --git a/external/reshade/deps/imgui/imconfig.h b/external/reshade/deps/imgui/imconfig.h index 9ccc55e..4dab1b6 100644 --- a/external/reshade/deps/imgui/imconfig.h +++ b/external/reshade/deps/imgui/imconfig.h @@ -11,11 +11,14 @@ // Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. // Call IMGUI_CHECKVERSION() from your .cpp file to verify that the data structures your files are using are matching the ones imgui.cpp is using. //----------------------------------------------------------------------------- + #pragma once + //---- Define assertion handler. Defaults to calling assert(). // If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts + //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows // Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. // - Windows DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() @@ -23,13 +26,16 @@ //#define IMGUI_API __declspec(dllexport) // MSVC Windows: DLL export //#define IMGUI_API __declspec(dllimport) // MSVC Windows: DLL import //#define IMGUI_API __attribute__((visibility("default"))) // GCC/Clang: override visibility when set is hidden + //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names. //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS + //---- Disable all of Dear ImGui or don't implement standard windows/tools. // It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp. //#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. //#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. //#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowIDStackToolWindow() will be empty. + //---- Don't implement some functions to reduce linkage requirements. //#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a) //#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW) @@ -44,18 +50,24 @@ //#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). //#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded font (ProggyClean.ttf), remove ~9.5 KB from output binary. AddFontDefault() will assert. //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available + //---- Enable Test Engine / Automation features. //#define IMGUI_ENABLE_TEST_ENGINE // Enable imgui_test_engine hooks. Generally set automatically by include "imgui_te_config.h", see Test Engine for details. + //---- Include imgui_user.h at the end of imgui.h as a convenience // May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included. //#define IMGUI_INCLUDE_IMGUI_USER_H //#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h" + //---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support. //#define IMGUI_USE_BGRA_PACKED_COLOR + //---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate. //#define IMGUI_USE_LEGACY_CRC32_ADLER + //---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...) //#define IMGUI_USE_WCHAR32 + //---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version // By default the embedded implementations are declared static and not available outside of Dear ImGui sources files. //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" @@ -64,13 +76,16 @@ //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION //#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION //#define IMGUI_DISABLE_STB_SPRINTF_IMPLEMENTATION // only disabled if IMGUI_USE_STB_SPRINTF is defined. + //---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined) // Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h. //#define IMGUI_USE_STB_SPRINTF + //---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui) // Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided). // On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'. //#define IMGUI_ENABLE_FREETYPE + //---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT) // Only works in combination with IMGUI_ENABLE_FREETYPE. // - plutosvg is currently easier to install, as e.g. it is part of vcpkg. It will support more fonts and may load them faster. See misc/freetype/README for instructions. @@ -78,41 +93,53 @@ // - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement) //#define IMGUI_ENABLE_FREETYPE_PLUTOSVG //#define IMGUI_ENABLE_FREETYPE_LUNASVG + //---- Use stb_truetype to build and rasterize the font atlas (default) // The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend. //#define IMGUI_ENABLE_STB_TRUETYPE + //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. // This will be inlined as part of ImVec2 and ImVec4 class declarations. /* #define IM_VEC2_CLASS_EXTRA \ constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \ operator MyVec2() const { return MyVec2(x,y); } + #define IM_VEC4_CLASS_EXTRA \ constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ operator MyVec4() const { return MyVec4(x,y,z,w); } */ //---- ...Or use Dear ImGui's own very basic math operators. //#define IMGUI_DEFINE_MATH_OPERATORS + //---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. // Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices). // Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer. // Read about ImGuiBackendFlags_RendererHasVtxOffset for details. //#define ImDrawIdx unsigned int + //---- Override ImDrawCallback signature (will need to modify renderer backends accordingly) //struct ImDrawList; //struct ImDrawCmd; //typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data); //#define ImDrawCallback MyImDrawCallback + //---- Debug Tools: Macro to break in Debugger (we provide a default implementation of this in the codebase) // (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.) //#define IM_DEBUG_BREAK IM_ASSERT(0) //#define IM_DEBUG_BREAK __debugbreak() + +//---- Debug Tools: Enable highlight ID conflicts _before_ hovering items. When io.ConfigDebugHighlightIdConflicts is set. +// (THIS WILL SLOW DOWN DEAR IMGUI. Only use occasionally and disable after use) +//#define IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + //---- Debug Tools: Enable slower asserts //#define IMGUI_DEBUG_PARANOID + //---- Tip: You can add extra functions within the ImGui:: namespace from anywhere (e.g. your own sources/header files) /* namespace ImGui { void MyFunction(const char* name, MyMatrix44* mtx); } -*/ \ No newline at end of file +*/ diff --git a/external/reshade/deps/imgui/imgui.cpp b/external/reshade/deps/imgui/imgui.cpp index 51ee3d6..9c9b842 100644 --- a/external/reshade/deps/imgui/imgui.cpp +++ b/external/reshade/deps/imgui/imgui.cpp @@ -1,9 +1,11 @@ -// dear imgui, v1.91.9b +// dear imgui, v1.92.2b // (main code and documentation) + // Help: // - See links below. // - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that. // - Read top of imgui.cpp for more details, links and comments. + // Resources: // - FAQ ........................ https://dearimgui.com/faq (in repository as docs/FAQ.md) // - Homepage ................... https://github.com/ocornut/imgui @@ -18,9 +20,12 @@ // - Software using Dear ImGui https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui // - Issues & support ........... https://github.com/ocornut/imgui/issues // - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your apps) -// For first-time users having issues compiling/linking/running/loading fonts: + +// For first-time users having issues compiling/linking/running: // please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above. // Everything else should be asked in 'Issues'! We are building a database of cross-linked knowledge there. +// Since 1.92, we encourage font loading questions to also be posted in 'Issues'. + // Copyright (c) 2014-2025 Omar Cornut // Developed by Omar Cornut and every direct or indirect contributors to the GitHub. // See LICENSE.txt for copyright and licensing details (standard MIT License). @@ -28,14 +33,19 @@ // Businesses: you can support continued development via B2B invoiced technical support, maintenance and sponsoring contracts. // PLEASE reach out at omar AT dearimgui DOT com. See https://github.com/ocornut/imgui/wiki/Funding // Businesses: you can also purchase licenses for the Dear ImGui Automation/Test Engine. + // It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library. // Note that 'ImGui::' being a namespace, you can add functions into the namespace from your own source files, without // modifying imgui.h or imgui.cpp. You may include imgui_internal.h to access internal data structures, but it doesn't // come with any guarantee of forward compatibility. Discussing your changes on the GitHub Issue Tracker may lead you // to a better solution or official support for them. + /* + Index of this file: + DOCUMENTATION + - MISSION STATEMENT - CONTROLS GUIDE - PROGRAMMER GUIDE @@ -43,12 +53,14 @@ DOCUMENTATION - HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI - GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE - HOW A SIMPLE APPLICATION MAY LOOK LIKE - - HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE + - USING CUSTOM BACKEND / CUSTOM ENGINE - API BREAKING CHANGES (read me when you update!) - FREQUENTLY ASKED QUESTIONS (FAQ) - Read all answers online: https://www.dearimgui.com/faq, or in docs/FAQ.md (with a Markdown viewer) + CODE (search for "[SECTION]" in the code to find them) + // [SECTION] INCLUDES // [SECTION] FORWARD DECLARATIONS // [SECTION] CONTEXT AND MEMORY ALLOCATORS @@ -66,6 +78,7 @@ CODE // [SECTION] RENDER HELPERS // [SECTION] INITIALIZATION, SHUTDOWN // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) +// [SECTION] FONTS, TEXTURES // [SECTION] ID STACK // [SECTION] INPUTS // [SECTION] ERROR CHECKING, STATE RECOVERY @@ -86,13 +99,18 @@ CODE // [SECTION] METRICS/DEBUGGER WINDOW // [SECTION] DEBUG LOG WINDOW // [SECTION] OTHER DEBUG TOOLS (ITEM PICKER, ID STACK TOOL) + */ + //----------------------------------------------------------------------------- // DOCUMENTATION //----------------------------------------------------------------------------- + /* + MISSION STATEMENT ================= + - Easy to use to create code-driven and data-driven tools. - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools. - Easy to hack and improve. @@ -101,12 +119,17 @@ CODE - Minimize state synchronization. - Portable, minimize dependencies, run on target (consoles, phones, etc.). - Efficient runtime and memory consumption. + Designed primarily for developers and content-creators, not the typical end-user! Some of the current weaknesses (which we aim to address in the future) includes: + - Doesn't look fancy. - Limited layout features, intricate layouts are typically crafted in code. + + CONTROLS GUIDE ============== + - MOUSE CONTROLS - Mouse wheel: Scroll vertically. - SHIFT+Mouse wheel: Scroll horizontally. @@ -115,6 +138,7 @@ CODE - Drag on corner/border: Resize window (double-click to auto fit window to its contents). - Drag on any empty space: Move window (unless io.ConfigWindowsMoveFromTitleBarOnly = true). - Left-click outside popup: Close popup stack (right-click over underlying popup: Partially close popup stack). + - TEXT EDITOR - Hold SHIFT or Drag Mouse: Select text. - CTRL+Left/Right: Word jump. @@ -125,6 +149,7 @@ CODE - CTRL+Y or CTRL+Shift+Z: Redo. - ESCAPE: Revert text to its original value. - On OSX, controls are automatically adjusted to match standard OSX text editing 2ts and behaviors. + - KEYBOARD CONTROLS - Basic: - Tab, SHIFT+Tab Cycle through text editable fields. @@ -145,6 +170,7 @@ CODE - io.WantCaptureKeyboard flag is set when keyboard is claimed. - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set. - io.NavVisible: true when the navigation cursor is visible (usually goes to back false when mouse is used). + - GAMEPAD CONTROLS - Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. - Particularly useful to use Dear ImGui on a console system (e.g. PlayStation, Switch, Xbox) without a mouse! @@ -155,6 +181,7 @@ CODE Backend code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, etc.). - If you need to share inputs between your game and the Dear ImGui interface, the easiest approach is to go all-or-nothing, with a buttons combo to toggle the target. Please reach out if you think the game vs navigation input sharing could be improved. + - REMOTE INPUTS SHARING & MOUSE EMULATION - PS4/PS5 users: Consider emulating a mouse cursor with DualShock touch pad or a spare analog stick as a mouse-emulation fallback. - Consoles/Tablet/Phone users: Consider using a Synergy 1.x server (on your PC) + run examples/libs/synergy/uSynergy.c (on your console/tablet/phone app) @@ -167,8 +194,11 @@ CODE (If you set the NavEnableSetMousePos flag but don't honor 'io.WantSetMousePos' properly, Dear ImGui will misbehave as it will see your mouse moving back & forth!) (In a setup when you may not have easy control over the mouse cursor, e.g. uSynergy.c doesn't expose moving remote mouse cursor, you may want to set a boolean to ignore your other external mouse positions until the external source is moved again.) + + PROGRAMMER GUIDE ================ + READ FIRST ---------- - Remember to check the wonderful Wiki (https://github.com/ocornut/imgui/wiki) @@ -205,6 +235,8 @@ CODE This is in an effort to ensure that it works in the real world aka with any esoteric build setup. This is also because providing a build system for the main library would be of little-value. The build problems are almost never coming from the main library but from specific backends. + + HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI ---------------------------------------------- - Update submodule or copy/overwrite every file. @@ -213,6 +245,7 @@ CODE - or you may locally branch to modify imconfig.h and merge/rebase latest. - or you may '#define IMGUI_USER_CONFIG "my_config_file.h"' globally from your build system to specify a custom path for your imconfig.h file and instead not have to modify the default one. + - Overwrite all the sources files except for imconfig.h (if you have modified your copy of imconfig.h) - Or maintain your own branch where you have imconfig.h modified as a top-most commit which you can regularly rebase over "master". - You can also use '#define IMGUI_USER_CONFIG "my_config_file.h" to redirect configuration to your own file. @@ -222,6 +255,8 @@ CODE likely be a comment about it. Please report any issue to the GitHub page! - To find out usage of old API, you can add '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' in your configuration file. - Try to keep your copy of Dear ImGui reasonably up to date! + + GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE --------------------------------------------------------------- - See https://github.com/ocornut/imgui/wiki/Getting-Started. @@ -236,19 +271,25 @@ CODE phases of your own application. All rendering information is stored into command-lists that you will retrieve after calling ImGui::Render(). - Refer to the backends and demo applications in the examples/ folder for instruction on how to setup your code. - If you are running over a standard OS with a common graphics API, you should be able to use unmodified imgui_impl_*** files from the examples/ folder. + + HOW A SIMPLE APPLICATION MAY LOOK LIKE -------------------------------------- - EXHIBIT 1: USING THE EXAMPLE BACKENDS (= imgui_impl_XXX.cpp files from the backends/ folder). + + USING THE EXAMPLE BACKENDS (= imgui_impl_XXX.cpp files from the backends/ folder). The sub-folders in examples/ contain examples applications following this structure. + // Application init: create a dear imgui context, setup some options, load fonts ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls. // TODO: Fill optional fields of the io structure later. // TODO: Load TTF/OTF fonts if you don't want to use the default font. + // Initialize helper Platform and Renderer backends (here we are using imgui_impl_win32.cpp and imgui_impl_dx11.cpp) ImGui_ImplWin32_Init(hwnd); ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext); + // Application main loop while (true) { @@ -256,34 +297,52 @@ CODE ImGui_ImplDX11_NewFrame(); ImGui_ImplWin32_NewFrame(); ImGui::NewFrame(); + // Any application code here ImGui::Text("Hello, world!"); - // Render dear imgui into screen + + // Render dear imgui into framebuffer ImGui::Render(); ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); g_pSwapChain->Present(1, 0); } + // Shutdown ImGui_ImplDX11_Shutdown(); ImGui_ImplWin32_Shutdown(); ImGui::DestroyContext(); - EXHIBIT 2: IMPLEMENTING CUSTOM BACKEND / CUSTOM ENGINE - // Application init: create a dear imgui context, setup some options, load fonts + + To decide whether to dispatch mouse/keyboard inputs to Dear ImGui to the rest of your application, + you should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags! + Please read the FAQ entry "How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" about this. + + +USING CUSTOM BACKEND / CUSTOM ENGINE +------------------------------------ + +IMPLEMENTING YOUR PLATFORM BACKEND: + -> see https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md for basic instructions. + -> the Platform backends in impl_impl_XXX.cpp files contain many implementations. + +IMPLEMENTING YOUR RenderDrawData() function: + -> see https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md + -> the Renderer Backends in impl_impl_XXX.cpp files contain many implementations of a ImGui_ImplXXXX_RenderDrawData() function. + +IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: + -> see https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md + -> the Renderer Backends in impl_impl_XXX.cpp files contain many implementations of a ImGui_ImplXXXX_UpdateTexture() function. + + Basic application/backend skeleton: + + // Application init: create a Dear ImGui context, setup some options, load fonts ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); - // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls. - // TODO: Fill optional fields of the io structure later. + // TODO: set io.ConfigXXX values, e.g. + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable keyboard controls + // TODO: Load TTF/OTF fonts if you don't want to use the default font. - // Build and load the texture atlas into a texture - // (In the examples/ app this is usually done within the ImGui_ImplXXX_Init() function from one of the demo Renderer) - int width, height; - unsigned char* pixels = nullptr; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - // At this point you've got the texture data and you need to upload that to your graphic system: - // After we have created the texture, store its pointer/identifier (_in whichever format your engine uses_) in 'io.Fonts->TexID'. - // This will be passed back to your via the renderer. Basically ImTextureID == void*. Read FAQ for details about ImTextureID. - MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA32) - io.Fonts->SetTexID((void*)texture); + io.Fonts->AddFontFromFileTTF("NotoSans.ttf"); + // Application main loop while (true) { @@ -295,89 +354,148 @@ CODE io.AddMousePosEvent(mouse_x, mouse_y); // update mouse position io.AddMouseButtonEvent(0, mouse_b[0]); // update mouse button states io.AddMouseButtonEvent(1, mouse_b[1]); // update mouse button states + // Call NewFrame(), after this point you can use ImGui::* functions anytime // (So you want to try calling NewFrame() as early as you can in your main loop to be able to use Dear ImGui everywhere) ImGui::NewFrame(); + // Most of your application code here ImGui::Text("Hello, world!"); MyGameUpdate(); // may use any Dear ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End(); MyGameRender(); // may use any Dear ImGui functions as well! - // Render dear imgui, swap buffers + + // End the dear imgui frame // (You want to try calling EndFrame/Render as late as you can, to be able to use Dear ImGui in your own game rendering code) - ImGui::EndFrame(); + ImGui::EndFrame(); // this is automatically called by Render(), but available ImGui::Render(); + + // Update textures ImDrawData* draw_data = ImGui::GetDrawData(); - MyImGuiRenderFunction(draw_data); + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + MyImGuiBackend_UpdateTexture(tex); + + // Render dear imgui contents, swap buffers + MyImGuiBackend_RenderDrawData(draw_data); SwapBuffers(); } + // Shutdown ImGui::DestroyContext(); - To decide whether to dispatch mouse/keyboard inputs to Dear ImGui to the rest of your application, - you should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags! - Please read the FAQ entry "How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" about this. - HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE - --------------------------------------------- - The backends in impl_impl_XXX.cpp files contain many working implementations of a rendering function. - void MyImGuiRenderFunction(ImDrawData* draw_data) - { - // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled - // TODO: Setup texture sampling state: sample with bilinear filtering (NOT point/nearest filtering). Use 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines;' to allow point/nearest filtering. - // TODO: Setup viewport covering draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize - // TODO: Setup orthographic projection matrix cover draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize - // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color. - ImVec2 clip_off = draw_data->DisplayPos; - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by Dear ImGui - const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by Dear ImGui - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { - const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; - if (pcmd->UserCallback) - { - pcmd->UserCallback(cmd_list, pcmd); - } - else - { - // Project scissor/clipping rectangles into framebuffer space - ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y); - ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y); - if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) - continue; - // We are using scissoring to clip some objects. All low-level graphics API should support it. - // - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some small glitches - // (some elements visible outside their bounds) but you can fix that once everything else works! - // - Clipping coordinates are provided in imgui coordinates space: - // - For a given viewport, draw_data->DisplayPos == viewport->Pos and draw_data->DisplaySize == viewport->Size - // - In a single viewport application, draw_data->DisplayPos == (0,0) and draw_data->DisplaySize == io.DisplaySize, but always use GetMainViewport()->Pos/Size instead of hardcoding those values. - // - In the interest of supporting multi-viewport applications (see 'docking' branch on github), - // always subtract draw_data->DisplayPos from clipping bounds to convert them to your viewport space. - // - Note that pcmd->ClipRect contains Min+Max bounds. Some graphics API may use Min+Max, other may use Min+Size (size being Max-Min) - MyEngineSetScissor(clip_min.x, clip_min.y, clip_max.x, clip_max.y); - // The texture for the draw call is specified by pcmd->GetTexID(). - // The vast majority of draw calls will use the Dear ImGui texture atlas, which value you have set yourself during initialization. - MyEngineBindTexture((MyTexture*)pcmd->GetTexID()); - // Render 'pcmd->ElemCount/3' indexed triangles. - // By default the indices ImDrawIdx are 16-bit, you can change them to 32-bit in imconfig.h if your engine doesn't support 16-bit indices. - MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer + pcmd->IdxOffset, vtx_buffer, pcmd->VtxOffset); - } - } - } - } + + + API BREAKING CHANGES ==================== + Occasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix. Below is a change-log of API breaking changes only. If you are using one of the functions listed, expect to have to fix some code. When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + (Docking/Viewport Branch) - 2025/XX/XX (1.XXXX) - when multi-viewports are enabled, all positions will be in your natural OS coordinates space. It means that: - reference to hard-coded positions such as in SetNextWindowPos(ImVec2(0,0)) are probably not what you want anymore. you may use GetMainViewport()->Pos to offset hard-coded positions, e.g. SetNextWindowPos(GetMainViewport()->Pos) - likewise io.MousePos and GetMousePos() will use OS coordinates. If you query mouse positions to interact with non-imgui coordinates you will need to offset them, e.g. subtract GetWindowViewport()->Pos. + + - 2025/08/08 (1.92.2) - Backends: SDL_GPU3: Changed ImTextureID type from SDL_GPUTextureSamplerBinding* to SDL_GPUTexture*, which is more natural and easier for user to manage. If you need to change the current sampler, you can access the ImGui_ImplSDLGPU3_RenderState struct. (#8866, #8163, #7998, #7988) + - 2025/07/31 (1.92.2) - Tabs: Renamed ImGuiTabBarFlags_FittingPolicyResizeDown to ImGuiTabBarFlags_FittingPolicyShrink. Kept inline redirection enum (will obsolete). + - 2025/06/25 (1.92.0) - Layout: commented out legacy ErrorCheckUsingSetCursorPosToExtendParentBoundaries() fallback obsoleted in 1.89 (August 2022) which allowed a SetCursorPos()/SetCursorScreenPos() call WITHOUT AN ITEM + to extend parent window/cell boundaries. Replaced with assert/tooltip that would already happens if previously using IMGUI_DISABLE_OBSOLETE_FUNCTIONS. (#5548, #4510, #3355, #1760, #1490, #4152, #150) + - Incorrect way to make a window content size 200x200: + Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); + - Correct ways to make a window content size 200x200: + Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End(); + Begin(...) + Dummy(ImVec2(200,200)) + End(); + - TL;DR; if the assert triggers, you can add a Dummy({0,0}) call to validate extending parent boundaries. + - 2025/06/11 (1.92.0) - Renamed/moved ImGuiConfigFlags_DpiEnableScaleFonts -> bool io.ConfigDpiScaleFonts. + - Renamed/moved ImGuiConfigFlags_DpiEnableScaleViewports -> bool io.ConfigDpiScaleViewports. **Neither of those flags are very useful in current code. They will be useful once we merge font changes.** + - 2025/06/11 (1.92.0) - THIS VERSION CONTAINS THE LARGEST AMOUNT OF BREAKING CHANGES SINCE 2015! I TRIED REALLY HARD TO KEEP THEM TO A MINIMUM, REDUCE THE AMOUNT OF INTERFERENCES, BUT INEVITABLY SOME USERS WILL BE AFFECTED. + IN ORDER TO HELP US IMPROVE THE TRANSITION PROCESS, INCL. DOCUMENTATION AND COMMENTS, PLEASE REPORT **ANY** DOUBT, CONFUSION, QUESTIONS, FEEDBACK TO: https://github.com/ocornut/imgui/issues/ + As part of the plan to reduce impact of API breaking changes, several unfinished changes/features/refactors related to font and text systems and scaling will be part of subsequent releases (1.92.1+). + If you are updating from an old version, and expecting a massive or difficult update, consider first updating to 1.91.9 to reduce the amount of changes. + - Hard to read? Refer to 'docs/Changelog.txt' for a less compact and more complete version of this! + - Fonts: **IMPORTANT**: if your app was solving the OSX/iOS Retina screen specific logical vs display scale problem by setting io.DisplayFramebufferScale (e.g. to 2.0f) + setting io.FontGlobalScale (e.g. to 1.0f/2.0f) + loading fonts at scaled sizes (e.g. size X * 2.0f): + This WILL NOT map correctly to the new system! Because font will rasterize as requested size. + - With a legacy backend (< 1.92): Instead of setting io.FontGlobalScale = 1.0f/N -> set ImFontCfg::RasterizerDensity = N. This already worked before, but is now pretty much required. + - With a new backend (1.92+): This should be all automatic. FramebufferScale is automatically used to set current font RasterizerDensity. FramebufferScale is a per-viewport property provided by backend through the Platform_GetWindowFramebufferScale() handler in 'docking' branch. + - Fonts: **IMPORTANT** on Font Sizing: Before 1.92, fonts were of a single size. They can now be dynamically sized. + - PushFont() API now has a REQUIRED size parameter. + - Before 1.92: PushFont() always used font "default" size specified in AddFont() call. It is equivalent to calling PushFont(font, font->LegacySize). + - Since 1.92: PushFont(font, 0.0f) preserve the current font size which is a shared value. + - To use old behavior: use 'ImGui::PushFont(font, font->LegacySize)' at call site. + - Kept inline single parameter function. Will obsolete. + - Fonts: **IMPORTANT** on Font Merging: + - When searching for a glyph in multiple merged fonts: we search for the FIRST font source which contains the desired glyph. + Because the user doesn't need to provide glyph ranges any more, it is possible that a glyph that you expected to fetch from a secondary/merged icon font may be erroneously fetched from the primary font. + - When searching for a glyph in multiple merged fonts: we now search for the FIRST font source which contains the desired glyph. This is technically a different behavior than before! + - e.g. If you are merging fonts you may have glyphs that you expected to load from Font Source 2 which exists in Font Source 1. + After the update and when using a new backend, those glyphs may now loaded from Font Source 1! + - We added `ImFontConfig::GlyphExcludeRanges[]` to specify ranges to exclude from a given font source: + // Add Font Source 1 but ignore ICON_MIN_FA..ICON_MAX_FA range + static ImWchar exclude_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; + ImFontConfig cfg1; + cfg1.GlyphExcludeRanges = exclude_ranges; + io.Fonts->AddFontFromFileTTF("segoeui.ttf", 0.0f, &cfg1); + // Add Font Source 2, which expects to use the range above + ImFontConfig cfg2; + cfg2.MergeMode = true; + io.Fonts->AddFontFromFileTTF("FontAwesome4.ttf", 0.0f, &cfg2); + - You can use `Metrics/Debugger->Fonts->Font->Input Glyphs Overlap Detection Tool` to see list of glyphs available in multiple font sources. This can facilitate unde + - Fonts: **IMPORTANT** on Thread Safety: + - A few functions such as font->CalcTextSizeA() were, by sheer luck (== accidentally) thread-safe even thou we had never provided that guarantee. They are definitively not thread-safe anymore as new glyphs may be loaded. + - Fonts: ImFont::FontSize was removed and does not make sense anymore. ImFont::LegacySize is the size passed to AddFont(). + - Fonts: Removed support for PushFont(NULL) which was a shortcut for "default font". + - Fonts: Renamed/moved 'io.FontGlobalScale' to 'style.FontScaleMain'. + - Textures: all API functions taking a 'ImTextureID' parameter are now taking a 'ImTextureRef'. Affected functions are: ImGui::Image(), ImGui::ImageWithBg(), ImGui::ImageButton(), ImDrawList::AddImage(), ImDrawList::AddImageQuad(), ImDrawList::AddImageRounded(). + - Fonts: obsoleted ImFontAtlas::GetTexDataAsRGBA32(), GetTexDataAsAlpha8(), Build(), SetTexID(), IsBuilt() functions. The new protocol for backends to handle textures doesn't need them. Kept redirection functions (will obsolete). + - Fonts: ImFontConfig::OversampleH/OversampleV default to automatic (== 0) since v1.91.8. It is quite important you keep it automatic until we decide if we want to provide a way to express finer policy, otherwise you will likely waste texture space when using large glyphs. Note that the imgui_freetype backend doesn't use and does not need oversampling. + - Fonts: specifying glyph ranges is now unnecessary. The value of ImFontConfig::GlyphRanges[] is only useful for legacy backends. All GetGlyphRangesXXXX() functions are now marked obsolete: GetGlyphRangesDefault(), GetGlyphRangesGreek(), GetGlyphRangesKorean(), GetGlyphRangesJapanese(), GetGlyphRangesChineseSimplifiedCommon(), GetGlyphRangesChineseFull(), GetGlyphRangesCyrillic(), GetGlyphRangesThai(), GetGlyphRangesVietnamese(). + - Fonts: removed ImFontAtlas::TexDesiredWidth to enforce a texture width. (#327) + - Fonts: if you create and manage ImFontAtlas instances yourself (instead of relying on ImGuiContext to create one, you'll need to call ImFontAtlasUpdateNewFrame() yourself. An assert will trigger if you don't. + - Fonts: obsolete ImGui::SetWindowFontScale() which is not useful anymore. Prefer using 'PushFont(NULL, style.FontSizeBase * factor)' or to manipulate other scaling factors. + - Fonts: obsoleted ImFont::Scale which is not useful anymore. + - Fonts: generally reworked Internals of ImFontAtlas and ImFont. While in theory a vast majority of users shouldn't be affected, some use cases or extensions might be. Among other things: + - ImDrawCmd::TextureId has been changed to ImDrawCmd::TexRef. + - ImFontAtlas::TexID has been changed to ImFontAtlas::TexRef. + - ImFontAtlas::ConfigData[] has been renamed to ImFontAtlas::Sources[] + - ImFont::ConfigData[], ConfigDataCount has been renamed to Sources[], SourceCount. + - Each ImFont has a number of ImFontBaked instances corresponding to actively used sizes. ImFont::GetFontBaked(size) retrieves the one for a given size. + - Fields moved from ImFont to ImFontBaked: IndexAdvanceX[], Glyphs[], Ascent, Descent, FindGlyph(), FindGlyphNoFallback(), GetCharAdvance(). + - Fields moved from ImFontAtlas to ImFontAtlas->Tex: ImFontAtlas::TexWidth => TexData->Width, ImFontAtlas::TexHeight => TexData->Height, ImFontAtlas::TexPixelsAlpha8/TexPixelsRGBA32 => TexData->GetPixels(). + - Widget code may use ImGui::GetFontBaked() instead of ImGui::GetFont() to access font data for current font at current font size (and you may use font->GetFontBaked(size) to access it for any other size.) + - Fonts: (users of imgui_freetype): renamed ImFontAtlas::FontBuilderFlags to ImFontAtlas::FontLoaderFlags. Renamed ImFontConfig::FontBuilderFlags to ImFontConfig::FontLoaderFlags. Renamed ImGuiFreeTypeBuilderFlags to ImGuiFreeTypeLoaderFlags. + If you used runtime imgui_freetype selection rather than the default IMGUI_ENABLE_FREETYPE compile-time option: Renamed/reworked ImFontBuilderIO into ImFontLoader. Renamed ImGuiFreeType::GetBuilderForFreeType() to ImGuiFreeType::GetFontLoader(). + - old: io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType() + - new: io.Fonts->FontLoader = ImGuiFreeType::GetFontLoader() + - new: io.Fonts->SetFontLoader(ImGuiFreeType::GetFontLoader()) to change dynamically at runtime [from 1.92.1] + - Fonts: (users of custom rectangles, see #8466): Renamed AddCustomRectRegular() to AddCustomRect(). Added GetCustomRect() as a replacement for GetCustomRectByIndex() + CalcCustomRectUV(). + - The output type of GetCustomRect() is now ImFontAtlasRect, which include UV coordinates. X->x, Y->y, Width->w, Height->h. + - old: + const ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(custom_rect_id); + ImVec2 uv0, uv1; + atlas->GetCustomRectUV(r, &uv0, &uv1); + ImGui::Image(atlas->TexRef, ImVec2(r->w, r->h), uv0, uv1); + - new; + ImFontAtlasRect r; + atlas->GetCustomRect(custom_rect_id, &r); + ImGui::Image(atlas->TexRef, ImVec2(r.w, r.h), r.uv0, r.uv1); + - We added a redirecting typedef but haven't attempted to magically redirect the field names, as this API is rarely used and the fix is simple. + - Obsoleted AddCustomRectFontGlyph() as the API does not make sense for scalable fonts. Kept existing function which uses the font "default size" (Sources[0]->LegacySize). Added a helper AddCustomRectFontGlyphForSize() which is immediately marked obsolete, but can facilitate transitioning old code. + - Prefer adding a font source (ImFontConfig) using a custom/procedural loader. + - DrawList: Renamed ImDrawList::PushTextureID()/PopTextureID() to PushTexture()/PopTexture(). + - Backends: removed ImGui_ImplXXXX_CreateFontsTexture()/ImGui_ImplXXXX_DestroyFontsTexture() for all backends that had them. They should not be necessary any more. + - 2025/05/23 (1.92.0) - Fonts: changed ImFont::CalcWordWrapPositionA() to ImFont::CalcWordWrapPosition() + - old: const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, ....); + - new: const char* ImFont::CalcWordWrapPosition (float size, const char* text, ....); + The leading 'float scale' parameters was changed to 'float size'. This was necessary as 'scale' is assuming standard font size which is a concept we aim to eliminate in an upcoming update. Kept inline redirection function. + - 2025/05/15 (1.92.0) - TreeNode: renamed ImGuiTreeNodeFlags_NavLeftJumpsBackHere to ImGuiTreeNodeFlags_NavLeftJumpsToParent for clarity. Kept inline redirection enum (will obsolete). + - 2025/05/15 (1.92.0) - Commented out PushAllowKeyboardFocus()/PopAllowKeyboardFocus() which was obsoleted in 1.89.4. Use PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop)/PopItemFlag() instead. (#3092) + - 2025/05/15 (1.92.0) - Commented out ImGuiListClipper::ForceDisplayRangeByIndices() which was obsoleted in 1.89.6. Use ImGuiListClipper::IncludeItemsByIndex() instead. - 2025/03/05 (1.91.9) - BeginMenu(): Internals: reworked mangling of menu windows to use "###Menu_00" etc. instead of "##Menu_00", allowing them to also store the menu name before it. This shouldn't affect code unless directly accessing menu window from their mangled name. + - 2025/04/16 (1.91.9) - Internals: RenderTextEllipsis() function removed the 'float clip_max_x' parameter directly preceding 'float ellipsis_max_x'. Values were identical for a vast majority of users. (#8387) - 2025/02/27 (1.91.9) - Image(): removed 'tint_col' and 'border_col' parameter from Image() function. Added ImageWithBg() replacement. (#8131, #8238) - old: void Image (ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2 uv1 = (1,1), ImVec4 tint_col = (1,1,1,1), ImVec4 border_col = (0,0,0,0)); - new: void Image (ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2 uv1 = (1,1)); @@ -423,7 +541,7 @@ CODE - 2024/10/03 (1.91.3) - drags: treat v_min==v_max as a valid clamping range when != 0.0f. Zero is a still special value due to legacy reasons, unless using ImGuiSliderFlags_ClampZeroRange. (#7968, #3361, #76) - drags: extended behavior of ImGuiSliderFlags_AlwaysClamp to include _ClampZeroRange. It considers v_min==v_max==0.0f as a valid clamping range (aka edits not allowed). although unlikely, it you wish to only clamp on text input but want v_min==v_max==0.0f to mean unclamped drags, you can use _ClampOnInput instead of _AlwaysClamp. (#7968, #3361, #76) - - 2024/09/10 (1.91.2) - internals: using multiple overlayed ButtonBehavior() with same ID will now have io.ConfigDebugHighlightIdConflicts=true feature emit a warning. (#8030) + - 2024/09/10 (1.91.2) - internals: using multiple overlaid ButtonBehavior() with same ID will now have io.ConfigDebugHighlightIdConflicts=true feature emit a warning. (#8030) it was one of the rare case where using same ID is legal. workarounds: (1) use single ButtonBehavior() call with multiple _MouseButton flags, or (2) surround the calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag() - 2024/08/23 (1.91.1) - renamed ImGuiChildFlags_Border to ImGuiChildFlags_Borders for consistency. kept inline redirection flag. - 2024/08/22 (1.91.1) - moved some functions from ImGuiIO to ImGuiPlatformIO structure: @@ -616,7 +734,7 @@ CODE - 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(), ImGui::IsKeyDown(). Removed GetKeyIndex(), now unnecessary. All IsKeyXXX() functions now take ImGuiKey values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release Notes for details. - IsKeyPressed(MY_NATIVE_KEY_XXX) -> use IsKeyPressed(ImGuiKey_XXX) - IsKeyPressed(GetKeyIndex(ImGuiKey_XXX)) -> use IsKeyPressed(ImGuiKey_XXX) - - Backend writing to io.KeyMap[],io.KeysDown[] -> backend should call io.AddKeyEvent() (+ call io.SetKeyEventNativeData() if you want legacy user code to stil function with legacy key codes). + - Backend writing to io.KeyMap[],io.KeysDown[] -> backend should call io.AddKeyEvent() (+ call io.SetKeyEventNativeData() if you want legacy user code to still function with legacy key codes). - Backend writing to io.KeyCtrl, io.KeyShift.. -> backend should call io.AddKeyEvent() with ImGuiMod_XXX values. *IF YOU PULLED CODE BETWEEN 2021/01/10 and 2021/01/27: We used to have a io.AddKeyModsEvent() function which was now replaced by io.AddKeyEvent() with ImGuiMod_XXX values.* - one case won't work with backward compatibility: if your custom backend used ImGuiKey as mock native indices (e.g. "io.KeyMap[ImGuiKey_A] = ImGuiKey_A") because those values are now larger than the legacy KeyDown[] array. Will assert. - inputs: added ImGuiKey_ModCtrl/ImGuiKey_ModShift/ImGuiKey_ModAlt/ImGuiKey_ModSuper values to submit keyboard modifiers using io.AddKeyEvent(), instead of writing directly to io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper. @@ -825,7 +943,7 @@ CODE - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete). - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete). - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency. - - 2017/08/20 (1.51) - added PushStyleColor(ImGuiCol idx, ImU32 col) overload, which _might_ cause an "ambiguous call" compilation error if you are using ImColor() with implicit cast. Cast to ImU32 or ImVec4 explicily to fix. + - 2017/08/20 (1.51) - added PushStyleColor(ImGuiCol idx, ImU32 col) overload, which _might_ cause an "ambiguous call" compilation error if you are using ImColor() with implicit cast. Cast to ImU32 or ImVec4 explicitly to fix. - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame type. - 2017/08/15 (1.51) - changed parameter order for BeginPopupContextWindow() from (const char*,int buttons,bool also_over_items) to (const char*,int buttons,bool also_over_items). Note that most calls relied on default parameters completely. - 2017/08/13 (1.51) - renamed ImGuiCol_Column to ImGuiCol_Separator, ImGuiCol_ColumnHovered to ImGuiCol_SeparatorHovered, ImGuiCol_ColumnActive to ImGuiCol_SeparatorActive. Kept redirection enums (will obsolete). @@ -921,15 +1039,20 @@ CODE - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically) - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes + + FREQUENTLY ASKED QUESTIONS (FAQ) ================================ + Read all answers online: https://www.dearimgui.com/faq or https://github.com/ocornut/imgui/blob/master/docs/FAQ.md (same url) Read all answers locally (with a text editor or ideally a Markdown viewer): docs/FAQ.md Some answers are copied down here to facilitate searching in code. + Q&A: Basics =========== + Q: Where is the documentation? A: This library is poorly documented at the moment and expects the user to be acquainted with C/C++. - Run the examples/ applications and explore them. @@ -943,25 +1066,32 @@ CODE - The Glossary (https://github.com/ocornut/imgui/wiki/Glossary) page also may be useful. - Your programming IDE is your friend, find the type or function declaration to find comments associated with it. + Q: What is this library called? Q: Which version should I get? >> This library is called "Dear ImGui", please don't call it "ImGui" :) >> See https://www.dearimgui.com/faq for details. + Q&A: Integration ================ + Q: How to get started? A: Read https://github.com/ocornut/imgui/wiki/Getting-Started. Read 'PROGRAMMER GUIDE' above. Read examples/README.txt. + Q: How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application? A: You should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags! >> See https://www.dearimgui.com/faq for a fully detailed answer. You really want to read this. + Q. How can I enable keyboard or gamepad controls? Q: How can I use this on a machine without mouse, keyboard or screen? (input share, remote display) Q: I integrated Dear ImGui in my engine and little squares are showing instead of text... Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around... Q: I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries... >> See https://www.dearimgui.com/faq + Q&A: Usage ---------- + Q: About the ID Stack system.. - Why is my widget not reacting when I click on it? - How can I have widgets with an empty label? @@ -972,23 +1102,29 @@ CODE Q: How can I interact with standard C++ types (such as std::string and std::vector)? Q: How can I display custom shapes? (using low-level ImDrawList API) >> See https://www.dearimgui.com/faq + Q&A: Fonts, Text ================ + Q: How should I handle DPI in my application? Q: How can I load a different font than the default? Q: How can I easily use icons in my application? Q: How can I load multiple fonts? Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic? >> See https://www.dearimgui.com/faq and https://github.com/ocornut/imgui/blob/master/docs/FONTS.md + Q&A: Concerns ============= + Q: Who uses Dear ImGui? Q: Can you create elaborate/serious tools with Dear ImGui? Q: Can you reskin the look of Dear ImGui? Q: Why using C++ (as opposed to C)? >> See https://www.dearimgui.com/faq + Q&A: Community ============== + Q: How can I help? A: - Businesses: please reach out to "omar AT dearimgui DOT com" if you work in a place using Dear ImGui! We can discuss ways for your company to fund development via invoiced technical support, maintenance or sponsoring contacts. @@ -1000,26 +1136,34 @@ CODE You may post screenshot or links in the gallery threads. Visuals are ideal as they inspire other programmers. But even without visuals, disclosing your use of dear imgui helps the library grow credibility, and help other teams and programmers with taking decisions. - If you have issues or if you need to hack into the library, even if you don't expect any support it is useful that you share your issues (on GitHub or privately). + */ + //------------------------------------------------------------------------- // [SECTION] INCLUDES //------------------------------------------------------------------------- + #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif + #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif + #include "imgui.h" #ifndef IMGUI_DISABLE #include "imgui_internal.h" + // System includes #include // vsnprintf, sscanf, printf #include // intptr_t + // [Windows] On non-Visual Studio compilers, we default to IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS unless explicitly enabled #if defined(_WIN32) && !defined(_MSC_VER) && !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) #define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS #endif + // [Windows] OS specific includes (optional) #if defined(_WIN32) && defined(IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) && defined(IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) #define IMGUI_DISABLE_WIN32_FUNCTIONS @@ -1043,10 +1187,12 @@ CODE #define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS #endif #endif + // [Apple] OS specific includes #if defined(__APPLE__) #include #endif + // Visual Studio warnings #ifdef _MSC_VER #pragma warning (disable: 4127) // condition expression is constant @@ -1058,6 +1204,7 @@ CODE #pragma warning (disable: 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6). #pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). #endif + // Clang/GCC warnings with -Weverything #if defined(__clang__) #if __has_warning("-Wunknown-warning-option") @@ -1093,46 +1240,61 @@ CODE #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers #endif + // Debug options #define IMGUI_DEBUG_NAV_SCORING 0 // Display navigation scoring preview when hovering items. Hold CTRL to display for all candidates. CTRL+Arrow to change last direction. #define IMGUI_DEBUG_NAV_RECTS 0 // Display the reference navigation rectangle for each window + +// Default font size if unspecified in both style.FontSizeBase and AddFontXXX() calls. +static const float FONT_DEFAULT_SIZE = 20.0f; + // When using CTRL+TAB (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast switch. static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time before the highlight and screen dimming starts fading in static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear static const float NAV_ACTIVATE_HIGHLIGHT_TIMER = 0.10f; // Time to highlight an item activated by a shortcut. static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04f; // Reduce visual noise by only highlighting the border after a certain time. static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 0.70f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved. + // Tooltip offset static const ImVec2 TOOLTIP_DEFAULT_OFFSET_MOUSE = ImVec2(16, 10); // Multiplied by g.Style.MouseCursorScale static const ImVec2 TOOLTIP_DEFAULT_OFFSET_TOUCH = ImVec2(0, -20); // Multiplied by g.Style.MouseCursorScale static const ImVec2 TOOLTIP_DEFAULT_PIVOT_TOUCH = ImVec2(0.5f, 1.0f); // Multiplied by g.Style.MouseCursorScale + // Docking static const float DOCKING_TRANSPARENT_PAYLOAD_ALPHA = 0.50f; // For use with io.ConfigDockingTransparentPayload. Apply to Viewport _or_ WindowBg in host viewport. + //------------------------------------------------------------------------- // [SECTION] FORWARD DECLARATIONS //------------------------------------------------------------------------- + static void SetCurrentWindow(ImGuiWindow* window); static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags); static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window); + static void AddWindowToSortBuffer(ImVector* out_sorted_windows, ImGuiWindow* window); + // Settings static void WindowSettingsHandler_ClearAll(ImGuiContext*, ImGuiSettingsHandler*); static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name); static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line); static void WindowSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSettingsHandler*); static void WindowSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTextBuffer* buf); + // Platform Dependents default implementation for ImGuiPlatformIO functions static const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx); static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const char* text); static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext* ctx, ImGuiViewport* viewport, ImGuiPlatformImeData* data); static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext* ctx, const char* path); + namespace ImGui { // Item static void ItemHandleShortcut(ImGuiID id); + // Window Focus static int FindWindowFocusIndex(ImGuiWindow* window); static void UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, ImGuiWindowFlags new_flags); + // Navigation static void NavUpdate(); static void NavUpdateWindowing(); @@ -1145,7 +1307,7 @@ static float NavUpdatePageUpPageDown(); static inline void NavUpdateAnyRequestFlag(); static void NavUpdateCreateWrappingRequest(); static void NavEndFrame(); -static bool NavScoreItem(ImGuiNavItemData* result); +static bool NavScoreItem(ImGuiNavItemData* result, const ImRect& nav_bb); static void NavApplyItemToResult(ImGuiNavItemData* result); static void NavProcessItem(); static void NavProcessItemForTabbingRequest(ImGuiID id, ImGuiItemFlags item_flags, ImGuiNavMoveFlags move_flags); @@ -1154,6 +1316,7 @@ static ImVec2 NavCalcPreferredRefPos(); static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window); static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window); static void NavRestoreLayer(ImGuiNavLayer layer); + // Error Checking and Debug Tools static void ErrorCheckNewFrameSanityChecks(); static void ErrorCheckEndFrameSanityChecks(); @@ -1162,12 +1325,18 @@ static void UpdateDebugToolItemPicker(); static void UpdateDebugToolStackQueries(); static void UpdateDebugToolFlashStyleColor(); #endif + // Inputs static void UpdateKeyboardInputs(); static void UpdateMouseInputs(); static void UpdateMouseWheel(); static void UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt); + // Misc +static void UpdateFontsNewFrame(); +static void UpdateFontsEndFrame(); +static void UpdateTexturesNewFrame(); +static void UpdateTexturesEndFrame(); static void UpdateSettings(); static int UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_hovered, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect); static void RenderWindowOuterBorders(ImGuiWindow* window); @@ -1177,6 +1346,7 @@ static void RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, static void RenderDimmedBackgrounds(); static void SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect); static void SetLastItemDataForChildWindowItem(ImGuiWindow* window, const ImRect& rect); + // Viewports const ImGuiID IMGUI_VIEWPORT_DEFAULT_ID = 0x11111111; // Using an arbitrary constant instead of e.g. ImHashStr("ViewportDefault", 0); so it's easier to spot in the debugger. The exact value doesn't matter. static ImGuiViewportP* AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const ImVec2& platform_pos, const ImVec2& size, ImGuiViewportFlags flags); @@ -1191,16 +1361,20 @@ static bool GetWindowAlwaysWantOwnViewport(ImGuiWindow* window); static int FindPlatformMonitorForPos(const ImVec2& pos); static int FindPlatformMonitorForRect(const ImRect& r); static void UpdateViewportPlatformMonitor(ImGuiViewportP* viewport); + } + //----------------------------------------------------------------------------- // [SECTION] CONTEXT AND MEMORY ALLOCATORS //----------------------------------------------------------------------------- + // DLL users: // - Heaps and globals are not shared across DLL boundaries! // - You will need to call SetCurrentContext() + SetAllocatorFunctions() for each static/DLL boundary you are calling from. // - Same applies for hot-reloading mechanisms that are reliant on reloading DLL (note that many hot-reloading mechanisms work without DLL). // - Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. // - Confused? In a debugger: add GImGui to your watch window and notice how its value changes depending on your current location (which DLL boundary you are in). + // Current context pointer. Implicitly used by all Dear ImGui functions. Always assumed to be != NULL. // - ImGui::CreateContext() will automatically set this pointer if it is NULL. // Change to a different context by calling ImGui::SetCurrentContext(). @@ -1217,6 +1391,7 @@ static void UpdateViewportPlatformMonitor(ImGuiViewportP* viewport); #ifndef GImGui ImGuiContext* GImGui = NULL; #endif + // Memory Allocator functions. Use SetAllocatorFunctions() to change them. // - You probably don't want to modify that mid-program, and if you use global/static e.g. ImVector<> instances you may need to keep them accessible during program destruction. // - DLL users: read comments above. @@ -1230,11 +1405,17 @@ static void FreeWrapper(void* ptr, void* user_data) { IM_UNUSED(user_d static ImGuiMemAllocFunc GImAllocatorAllocFunc = MallocWrapper; static ImGuiMemFreeFunc GImAllocatorFreeFunc = FreeWrapper; static void* GImAllocatorUserData = NULL; + //----------------------------------------------------------------------------- // [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO, ImGuiPlatformIO) //----------------------------------------------------------------------------- + ImGuiStyle::ImGuiStyle() { + FontSizeBase = 0.0f; // Will default to io.Fonts->Fonts[0] on first frame. + FontScaleMain = 1.0f; // Main scale factor. May be set by application once, or exposed to end-user. + FontScaleDpi = 1.0f; // Additional scale factor from viewport/monitor contents scale. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor DPI. + Alpha = 1.0f; // Global alpha applies to everything in Dear ImGui. DisabledAlpha = 0.60f; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. WindowPadding = ImVec2(8,8); // Padding within a window @@ -1265,12 +1446,17 @@ ImGuiStyle::ImGuiStyle() ImageBorderSize = 0.0f; // Thickness of border around tabs. TabRounding = 5.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. TabBorderSize = 0.0f; // Thickness of border around tabs. + TabMinWidthBase = 1.0f; // Minimum tab width, to make tabs larger than their contents. TabBar buttons are not affected. + TabMinWidthShrink = 80.0f; // Minimum tab width after shrinking, when using ImGuiTabBarFlags_FittingPolicyMixed policy. TabCloseButtonMinWidthSelected = -1.0f; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. TabCloseButtonMinWidthUnselected = 0.0f; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected. TabBarBorderSize = 1.0f; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. TabBarOverlineSize = 1.0f; // Thickness of tab-bar overline, which highlights the selected tab-bar. TableAngledHeadersAngle = 35.0f * (IM_PI / 180.0f); // Angle of angled headers (supported values range from -50 degrees to +50 degrees). TableAngledHeadersTextAlign = ImVec2(0.5f,0.0f);// Alignment of angled headers within the cell + TreeLinesFlags = ImGuiTreeNodeFlags_DrawLinesNone; + TreeLinesSize = 1.0f; // Thickness of outlines when using ImGuiTreeNodeFlags_DrawLines. + TreeLinesRounding = 0.0f; // Radius of lines connecting child nodes to the vertical line. ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. SelectableTextAlign = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. @@ -1286,19 +1472,28 @@ ImGuiStyle::ImGuiStyle() AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. CircleTessellationMaxError = 0.30f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. + // Behaviors HoverStationaryDelay = 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_Stationary). Time required to consider mouse stationary. HoverDelayShort = 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayShort). Usually used along with HoverStationaryDelay. HoverDelayNormal = 0.40f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). " HoverFlagsForTooltipMouse = ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse. HoverFlagsForTooltipNav = ImGuiHoveredFlags_NoSharedDelay | ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. + + // [Internal] + _MainScale = 1.0f; + _NextFrameFontSizeBase = 0.0f; + // Default theme ImGui::StyleColorsDark(this); } -// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper function. Scaling the fonts is done separately and is up to you. + + +// Scale all spacing/padding/thickness values. Do not scale fonts. // Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times. void ImGuiStyle::ScaleAllSizes(float scale_factor) { + _MainScale *= scale_factor; WindowPadding = ImTrunc(WindowPadding * scale_factor); WindowRounding = ImTrunc(WindowRounding * scale_factor); WindowMinSize = ImTrunc(WindowMinSize * scale_factor); @@ -1320,20 +1515,25 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor) LogSliderDeadzone = ImTrunc(LogSliderDeadzone * scale_factor); ImageBorderSize = ImTrunc(ImageBorderSize * scale_factor); TabRounding = ImTrunc(TabRounding * scale_factor); + TabMinWidthBase = ImTrunc(TabMinWidthBase * scale_factor); + TabMinWidthShrink = ImTrunc(TabMinWidthShrink * scale_factor); TabCloseButtonMinWidthSelected = (TabCloseButtonMinWidthSelected > 0.0f && TabCloseButtonMinWidthSelected != FLT_MAX) ? ImTrunc(TabCloseButtonMinWidthSelected * scale_factor) : TabCloseButtonMinWidthSelected; TabCloseButtonMinWidthUnselected = (TabCloseButtonMinWidthUnselected > 0.0f && TabCloseButtonMinWidthUnselected != FLT_MAX) ? ImTrunc(TabCloseButtonMinWidthUnselected * scale_factor) : TabCloseButtonMinWidthUnselected; TabBarOverlineSize = ImTrunc(TabBarOverlineSize * scale_factor); + TreeLinesRounding = ImTrunc(TreeLinesRounding * scale_factor); SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor); DockingSeparatorSize = ImTrunc(DockingSeparatorSize * scale_factor); DisplayWindowPadding = ImTrunc(DisplayWindowPadding * scale_factor); DisplaySafeAreaPadding = ImTrunc(DisplaySafeAreaPadding * scale_factor); MouseCursorScale = ImTrunc(MouseCursorScale * scale_factor); } + ImGuiIO::ImGuiIO() { // Most fields are initialized with zero memset(this, 0, sizeof(*this)); IM_STATIC_ASSERT(IM_ARRAYSIZE(ImGuiIO::MouseDown) == ImGuiMouseButton_COUNT && IM_ARRAYSIZE(ImGuiIO::MouseClicked) == ImGuiMouseButton_COUNT); + // Settings ConfigFlags = ImGuiConfigFlags_None; BackendFlags = ImGuiBackendFlags_None; @@ -1343,11 +1543,15 @@ ImGuiIO::ImGuiIO() IniFilename = "imgui.ini"; // Important: "imgui.ini" is relative to current working dir, most apps will want to lock this to an absolute path (e.g. same path as executables). LogFilename = "imgui_log.txt"; UserData = NULL; + Fonts = NULL; - FontGlobalScale = 1.0f; FontDefault = NULL; FontAllowUserScaling = false; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + FontGlobalScale = 1.0f; // Use style.FontScaleMain instead! +#endif DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + // Keyboard/Gamepad Navigation options ConfigNavSwapGamepadButtons = false; ConfigNavMoveSetMousePos = false; @@ -1356,16 +1560,20 @@ ImGuiIO::ImGuiIO() ConfigNavEscapeClearFocusWindow = false; ConfigNavCursorVisibleAuto = true; ConfigNavCursorVisibleAlways = false; + // Docking options (when ImGuiConfigFlags_DockingEnable is set) ConfigDockingNoSplit = false; ConfigDockingWithShift = false; ConfigDockingAlwaysTabBar = false; ConfigDockingTransparentPayload = false; + // Viewport options (when ImGuiConfigFlags_ViewportsEnable is set) ConfigViewportsNoAutoMerge = false; ConfigViewportsNoTaskBarIcon = false; ConfigViewportsNoDecoration = true; ConfigViewportsNoDefaultParent = false; + ConfigViewportPlatformFocusSetsImGuiFocus = true; + // Miscellaneous options MouseDrawCursor = false; #ifdef __APPLE__ @@ -1387,20 +1595,24 @@ ImGuiIO::ImGuiIO() ConfigDebugHighlightIdConflictsShowItemPicker = true; ConfigDebugBeginReturnValueOnce = false; ConfigDebugBeginReturnValueLoop = false; + ConfigErrorRecovery = true; ConfigErrorRecoveryEnableAssert = true; ConfigErrorRecoveryEnableDebugLog = true; ConfigErrorRecoveryEnableTooltip = true; + // Inputs Behaviors MouseDoubleClickTime = 0.30f; MouseDoubleClickMaxDist = 6.0f; MouseDragThreshold = 6.0f; KeyRepeatDelay = 0.275f; KeyRepeatRate = 0.050f; + // Platform Functions // Note: Initialize() will setup default clipboard/ime handlers. BackendPlatformName = BackendRendererName = NULL; BackendPlatformUserData = BackendRendererUserData = BackendLanguageUserData = NULL; + // Input (NB: we already have memset zero the entire structure!) MousePos = ImVec2(-FLT_MAX, -FLT_MAX); MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX); @@ -1409,6 +1621,7 @@ ImGuiIO::ImGuiIO() for (int i = 0; i < IM_ARRAYSIZE(KeysData); i++) { KeysData[i].DownDuration = KeysData[i].DownDurationPrev = -1.0f; } AppAcceptingEvents = true; } + // Pass in translated ASCII characters for text input. // - with glfw you can get those from the callback set in glfwSetCharCallback() // - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message @@ -1419,6 +1632,7 @@ void ImGuiIO::AddInputCharacter(unsigned int c) ImGuiContext& g = *Ctx; if (c == 0 || !AppAcceptingEvents) return; + ImGuiInputEvent e; e.Type = ImGuiInputEventType_Text; e.Source = ImGuiInputSource_Keyboard; @@ -1426,12 +1640,14 @@ void ImGuiIO::AddInputCharacter(unsigned int c) e.Text.Char = c; g.InputEventsQueue.push_back(e); } + // UTF16 strings use surrogate pairs to encode codepoints >= 0x10000, so // we should save the high surrogate. void ImGuiIO::AddInputCharacterUTF16(ImWchar16 c) { if ((c == 0 && InputQueueSurrogate == 0) || !AppAcceptingEvents) return; + if ((c & 0xFC00) == 0xD800) // High surrogate, must save { if (InputQueueSurrogate != 0) @@ -1439,6 +1655,7 @@ void ImGuiIO::AddInputCharacterUTF16(ImWchar16 c) InputQueueSurrogate = c; return; } + ImWchar cp = c; if (InputQueueSurrogate != 0) { @@ -1454,10 +1671,12 @@ void ImGuiIO::AddInputCharacterUTF16(ImWchar16 c) cp = (ImWchar)(((InputQueueSurrogate - 0xD800) << 10) + (c - 0xDC00) + 0x10000); #endif } + InputQueueSurrogate = 0; } AddInputCharacter((unsigned)cp); } + void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars) { if (!AppAcceptingEvents) @@ -1469,6 +1688,7 @@ void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars) AddInputCharacter(c); } } + // Clear all incoming events. void ImGuiIO::ClearEventsQueue() { @@ -1476,6 +1696,7 @@ void ImGuiIO::ClearEventsQueue() ImGuiContext& g = *Ctx; g.InputEventsQueue.clear(); } + // Clear current keyboard/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons. void ImGuiIO::ClearInputKeys() { @@ -1493,6 +1714,7 @@ void ImGuiIO::ClearInputKeys() KeyMods = ImGuiMod_None; InputQueueCharacters.resize(0); // Behavior of old ClearInputCharacters(). } + void ImGuiIO::ClearInputMouse() { for (ImGuiKey key = ImGuiKey_Mouse_BEGIN; key < ImGuiKey_Mouse_END; key = (ImGuiKey)(key + 1)) @@ -1510,6 +1732,7 @@ void ImGuiIO::ClearInputMouse() } MouseWheel = MouseWheelH = 0.0f; } + // Removed this as it is ambiguous/misleading and generally incorrect to use with the existence of a higher-level input queue. // Current frame character buffer is now also cleared by ClearInputKeys(). #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -1518,6 +1741,7 @@ void ImGuiIO::ClearInputCharacters() InputQueueCharacters.resize(0); } #endif + static ImGuiInputEvent* FindLatestInputEvent(ImGuiContext* ctx, ImGuiInputEventType type, int arg = -1) { ImGuiContext& g = *ctx; @@ -1534,6 +1758,7 @@ static ImGuiInputEvent* FindLatestInputEvent(ImGuiContext* ctx, ImGuiInputEventT } return NULL; } + // Queue a new key down/up event. // - ImGuiKey key: Translated key (as in, generally ImGuiKey_A matches the key end-user would use to emit an 'A' character) // - bool down: Is the key down? use false to signify a key release. @@ -1549,6 +1774,7 @@ void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) ImGuiContext& g = *Ctx; IM_ASSERT(ImGui::IsNamedKeyOrMod(key)); // Backend needs to pass a valid ImGuiKey_ constant. 0..511 values are legacy native key codes which are not accepted by this API. IM_ASSERT(ImGui::IsAliasKey(key) == false); // Backend cannot submit ImGuiKey_MouseXXX values they are automatically inferred from AddMouseXXX() events. + // MacOS: swap Cmd(Super) and Ctrl if (g.IO.ConfigMacOSXBehaviors) { @@ -1559,6 +1785,7 @@ void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) else if (key == ImGuiKey_LeftCtrl) { key = ImGuiKey_LeftSuper; } else if (key == ImGuiKey_RightCtrl) { key = ImGuiKey_RightSuper; } } + // Filter duplicate (in particular: key mods and gamepad analog values are commonly spammed) const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_Key, (int)key); const ImGuiKeyData* key_data = ImGui::GetKeyData(&g, key); @@ -1566,6 +1793,7 @@ void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) const float latest_key_analog = latest_event ? latest_event->Key.AnalogValue : key_data->AnalogValue; if (latest_key_down == down && latest_key_analog == analog_value) return; + // Add event ImGuiInputEvent e; e.Type = ImGuiInputEventType_Key; @@ -1576,12 +1804,14 @@ void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) e.Key.AnalogValue = analog_value; g.InputEventsQueue.push_back(e); } + void ImGuiIO::AddKeyEvent(ImGuiKey key, bool down) { if (!AppAcceptingEvents) return; AddKeyAnalogEvent(key, down, down ? 1.0f : 0.0f); } + // [Optional] Call after AddKeyEvent(). // Specify native keycode, scancode + Specify index for legacy <1.87 IsKeyXXX() functions with native indices. // If you are writing a backend in 2022 or don't use IsKeyXXX() with native values that are not ImGuiKey values, you can avoid calling this. @@ -1596,11 +1826,13 @@ void ImGuiIO::SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native IM_UNUSED(native_scancode); // Yet unused IM_UNUSED(native_legacy_index); // Yet unused } + // Set master flag for accepting key/mouse/text events (default to true). Useful if you have native dialog boxes that are interrupting your application loop/refresh, and you want to disable events being queued while your app is frozen. void ImGuiIO::SetAppAcceptingEvents(bool accepting_events) { AppAcceptingEvents = accepting_events; } + // Queue a mouse move event void ImGuiIO::AddMousePosEvent(float x, float y) { @@ -1608,13 +1840,16 @@ void ImGuiIO::AddMousePosEvent(float x, float y) ImGuiContext& g = *Ctx; if (!AppAcceptingEvents) return; + // Apply same flooring as UpdateMouseInputs() ImVec2 pos((x > -FLT_MAX) ? ImFloor(x) : x, (y > -FLT_MAX) ? ImFloor(y) : y); + // Filter duplicate const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_MousePos); const ImVec2 latest_pos = latest_event ? ImVec2(latest_event->MousePos.PosX, latest_event->MousePos.PosY) : g.IO.MousePos; if (latest_pos.x == pos.x && latest_pos.y == pos.y) return; + ImGuiInputEvent e; e.Type = ImGuiInputEventType_MousePos; e.Source = ImGuiInputSource_Mouse; @@ -1624,6 +1859,7 @@ void ImGuiIO::AddMousePosEvent(float x, float y) e.MousePos.MouseSource = g.InputEventsNextMouseSource; g.InputEventsQueue.push_back(e); } + void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down) { IM_ASSERT(Ctx != NULL); @@ -1631,19 +1867,22 @@ void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down) IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT); if (!AppAcceptingEvents) return; + // On MacOS X: Convert Ctrl(Super)+Left click into Right-click: handle held button. if (ConfigMacOSXBehaviors && mouse_button == 0 && MouseCtrlLeftAsRightClick) { - // Order of both statements matterns: this event will still release mouse button 1 + // Order of both statements matters: this event will still release mouse button 1 mouse_button = 1; if (!down) MouseCtrlLeftAsRightClick = false; } + // Filter duplicate const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_MouseButton, (int)mouse_button); const bool latest_button_down = latest_event ? latest_event->MouseButton.Down : g.IO.MouseDown[mouse_button]; if (latest_button_down == down) return; + // On MacOS X: Convert Ctrl(Super)+Left click into Right-click. // - Note that this is actual physical Ctrl which is ImGuiMod_Super for us. // - At this point we want from !down to down, so this is handling the initial press. @@ -1658,6 +1897,7 @@ void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down) return; } } + ImGuiInputEvent e; e.Type = ImGuiInputEventType_MouseButton; e.Source = ImGuiInputSource_Mouse; @@ -1667,14 +1907,17 @@ void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down) e.MouseButton.MouseSource = g.InputEventsNextMouseSource; g.InputEventsQueue.push_back(e); } + // Queue a mouse wheel event (some mouse/API may only have a Y component) void ImGuiIO::AddMouseWheelEvent(float wheel_x, float wheel_y) { IM_ASSERT(Ctx != NULL); ImGuiContext& g = *Ctx; + // Filter duplicate (unlike most events, wheel values are relative and easy to filter) if (!AppAcceptingEvents || (wheel_x == 0.0f && wheel_y == 0.0f)) return; + ImGuiInputEvent e; e.Type = ImGuiInputEventType_MouseWheel; e.Source = ImGuiInputSource_Mouse; @@ -1684,6 +1927,7 @@ void ImGuiIO::AddMouseWheelEvent(float wheel_x, float wheel_y) e.MouseWheel.MouseSource = g.InputEventsNextMouseSource; g.InputEventsQueue.push_back(e); } + // This is not a real event, the data is latched in order to be stored in actual Mouse events. // This is so that duplicate events (e.g. Windows sending extraneous WM_MOUSEMOVE) gets filtered and are not leading to actual source changes. void ImGuiIO::AddMouseSourceEvent(ImGuiMouseSource source) @@ -1692,6 +1936,7 @@ void ImGuiIO::AddMouseSourceEvent(ImGuiMouseSource source) ImGuiContext& g = *Ctx; g.InputEventsNextMouseSource = source; } + void ImGuiIO::AddMouseViewportEvent(ImGuiID viewport_id) { IM_ASSERT(Ctx != NULL); @@ -1699,41 +1944,49 @@ void ImGuiIO::AddMouseViewportEvent(ImGuiID viewport_id) //IM_ASSERT(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport); if (!AppAcceptingEvents) return; + // Filter duplicate const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_MouseViewport); const ImGuiID latest_viewport_id = latest_event ? latest_event->MouseViewport.HoveredViewportID : g.IO.MouseHoveredViewport; if (latest_viewport_id == viewport_id) return; + ImGuiInputEvent e; e.Type = ImGuiInputEventType_MouseViewport; e.Source = ImGuiInputSource_Mouse; e.MouseViewport.HoveredViewportID = viewport_id; g.InputEventsQueue.push_back(e); } + void ImGuiIO::AddFocusEvent(bool focused) { IM_ASSERT(Ctx != NULL); ImGuiContext& g = *Ctx; + // Filter duplicate const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_Focus); const bool latest_focused = latest_event ? latest_event->AppFocused.Focused : !g.IO.AppFocusLost; if (latest_focused == focused || (ConfigDebugIgnoreFocusLoss && !focused)) return; + ImGuiInputEvent e; e.Type = ImGuiInputEventType_Focus; e.EventId = g.InputEventsNextEventId++; e.AppFocused.Focused = focused; g.InputEventsQueue.push_back(e); } + ImGuiPlatformIO::ImGuiPlatformIO() { // Most fields are initialized with zero memset(this, 0, sizeof(*this)); Platform_LocaleDecimalPoint = '.'; } + //----------------------------------------------------------------------------- // [SECTION] MISC HELPERS/UTILITIES (Geometry functions) //----------------------------------------------------------------------------- + ImVec2 ImBezierCubicClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, int num_segments) { IM_ASSERT(num_segments > 0); // Use ImBezierCubicClosestPointCasteljau() @@ -1755,6 +2008,7 @@ ImVec2 ImBezierCubicClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec } return p_closest; } + // Closely mimics PathBezierToCasteljau() in imgui_draw.cpp static void ImBezierCubicClosestPointCasteljauStep(const ImVec2& p, ImVec2& p_closest, ImVec2& p_last, float& p_closest_dist2, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level) { @@ -1788,6 +2042,7 @@ static void ImBezierCubicClosestPointCasteljauStep(const ImVec2& p, ImVec2& p_cl ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); } } + // tess_tol is generally the same value you would find in ImGui::GetStyle().CurveTessellationTol // Because those ImXXX functions are lower-level than ImGui:: we cannot access this value automatically. ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float tess_tol) @@ -1799,6 +2054,7 @@ ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, co ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, tess_tol, 0); return p_closest; } + ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p) { ImVec2 ap = p - a; @@ -1811,6 +2067,7 @@ ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p) return b; return a + ab_dir * dot / ab_len_sqr; } + bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) { bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f; @@ -1818,6 +2075,7 @@ bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f; return ((b1 == b2) && (b2 == b3)); } + void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w) { ImVec2 v0 = b - a; @@ -1828,6 +2086,7 @@ void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& out_w = (v0.x * v2.y - v2.x * v0.y) / denom; out_u = 1.0f - out_v - out_w; } + ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) { ImVec2 proj_ab = ImLineClosestPoint(a, b, p); @@ -1843,9 +2102,11 @@ ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, return proj_bc; return proj_ca; } + //----------------------------------------------------------------------------- // [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions) //----------------------------------------------------------------------------- + // Consider using _stricmp/_strnicmp under Windows or strcasecmp/strncasecmp. We don't actually use either ImStricmp/ImStrnicmp in the codebase any more. int ImStricmp(const char* str1, const char* str2) { @@ -1853,12 +2114,14 @@ int ImStricmp(const char* str1, const char* str2) while ((d = ImToUpper(*str2) - ImToUpper(*str1)) == 0 && *str1) { str1++; str2++; } return d; } + int ImStrnicmp(const char* str1, const char* str2, size_t count) { int d = 0; while (count > 0 && (d = ImToUpper(*str2) - ImToUpper(*str1)) == 0 && *str1) { str1++; str2++; count--; } return d; } + void ImStrncpy(char* dst, const char* src, size_t count) { if (count < 1) @@ -1867,12 +2130,20 @@ void ImStrncpy(char* dst, const char* src, size_t count) strncpy(dst, src, count - 1); dst[count - 1] = 0; } + char* ImStrdup(const char* str) { size_t len = ImStrlen(str); void* buf = IM_ALLOC(len + 1); return (char*)memcpy(buf, (const void*)str, len + 1); } + +void* ImMemdup(const void* src, size_t size) +{ + void* dst = IM_ALLOC(size); + return memcpy(dst, src, size); +} + char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src) { size_t dst_buf_size = p_dst_size ? *p_dst_size : ImStrlen(dst) + 1; @@ -1886,11 +2157,13 @@ char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src) } return (char*)memcpy(dst, (const void*)src, src_size); } + const char* ImStrchrRange(const char* str, const char* str_end, char c) { const char* p = (const char*)ImMemchr(str, (int)c, str_end - str); return p; } + int ImStrlenW(const ImWchar* str) { //return (int)wcslen((const wchar_t*)str); // FIXME-OPT: Could use this when wchar_t are 16-bit @@ -1898,12 +2171,14 @@ int ImStrlenW(const ImWchar* str) while (*str++) n++; return n; } + // Find end-of-line. Return pointer will point to either first \n, either str_end. const char* ImStreolRange(const char* str, const char* str_end) { const char* p = (const char*)ImMemchr(str, '\n', str_end - str); return p ? p : str_end; } + const char* ImStrbol(const char* buf_mid_line, const char* buf_begin) // find beginning-of-line { IM_ASSERT_PARANOID(buf_mid_line >= buf_begin && buf_mid_line <= buf_begin + ImStrlen(buf_begin)); @@ -1911,10 +2186,12 @@ const char* ImStrbol(const char* buf_mid_line, const char* buf_begin) // find be buf_mid_line--; return buf_mid_line; } + const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end) { if (!needle_end) needle_end = needle + ImStrlen(needle); + const char un0 = (char)ImToUpper(*needle); while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end)) { @@ -1931,6 +2208,7 @@ const char* ImStristr(const char* haystack, const char* haystack_end, const char } return NULL; } + // Trim str by offsetting contents when there's leading data + writing a \0 at the trailing position. We use this in situation where the cost is negligible. void ImStrTrimBlanks(char* buf) { @@ -1946,16 +2224,19 @@ void ImStrTrimBlanks(char* buf) memmove(buf, p_start, p - p_start); buf[p - p_start] = 0; // Zero terminate } + const char* ImStrSkipBlank(const char* str) { while (str[0] == ' ' || str[0] == '\t') str++; return str; } + // A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size). // Ideally we would test for only one of those limits at runtime depending on the behavior the vsnprintf(), but trying to deduct it at compile time sounds like a pandora can of worm. // B) When buf==NULL vsnprintf() will return the output size. #ifndef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS + // We support stb_sprintf which is much faster (see: https://github.com/nothings/stb/blob/master/stb_sprintf.h) // You may set IMGUI_USE_STB_SPRINTF to use our default wrapper, or set IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // and setup the wrapper yourself. (FIXME-OPT: Some of our high-level operations such as ImGuiTextBuffer::appendfv() are @@ -1970,9 +2251,11 @@ const char* ImStrSkipBlank(const char* str) #include "stb_sprintf.h" #endif #endif // #ifdef IMGUI_USE_STB_SPRINTF + #if defined(_MSC_VER) && !defined(vsnprintf) #define vsnprintf _vsnprintf #endif + int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) { va_list args; @@ -1990,6 +2273,7 @@ int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) buf[w] = 0; return w; } + int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) { #ifdef IMGUI_USE_STB_SPRINTF @@ -2005,6 +2289,7 @@ int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) return w; } #endif // #ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS + void ImFormatStringToTempBuffer(const char** out_buf, const char** out_buf_end, const char* fmt, ...) { va_list args; @@ -2012,6 +2297,7 @@ void ImFormatStringToTempBuffer(const char** out_buf, const char** out_buf_end, ImFormatStringToTempBufferV(out_buf, out_buf_end, fmt, args); va_end(args); } + // FIXME: Should rework API toward allowing multiple in-flight temp buffers (easier and safer for caller) // by making the caller acquire a temp buffer token, with either explicit or destructor release, e.g. // ImGuiTempBufferToken token; @@ -2046,6 +2332,7 @@ void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end, if (out_buf_end) { *out_buf_end = g.TempBuffer.Data + buf_len; } } } + #ifndef IMGUI_ENABLE_SSE4_2_CRC // CRC32 needs a 1KB lookup table (not cache friendly) // Although the code to generate the table is simple and shorter than the table itself, using a const table allows us to easily: @@ -2091,6 +2378,7 @@ static const ImU32 GCrc32LookupTable[256] = #endif }; #endif + // Known size hash // It is ok to call ImHashData on a string with known length but the ### operator won't be supported. // FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements. @@ -2115,6 +2403,7 @@ ImGuiID ImHashData(const void* data_p, size_t data_size, ImGuiID seed) return ~crc; #endif } + // Zero-terminated string hash, with support for ### to reset back to seed value // We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed. // Because this syntax is rarely used we are optimizing for the common case. @@ -2158,11 +2447,14 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) } return ~crc; } + //----------------------------------------------------------------------------- // [SECTION] MISC HELPERS/UTILITIES (File functions) //----------------------------------------------------------------------------- + // Default file functions #ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS + ImFileHandle ImFileOpen(const char* filename, const char* mode) { #if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && (defined(__MINGW32__) || (!defined(__CYGWIN__) && !defined(__GNUC__))) @@ -2170,6 +2462,7 @@ ImFileHandle ImFileOpen(const char* filename, const char* mode) // Previously we used ImTextCountCharsFromUtf8/ImTextStrFromUtf8 here but we now need to support ImWchar16 and ImWchar32! const int filename_wsize = ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0); const int mode_wsize = ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, NULL, 0); + // Use stack buffer if possible, otherwise heap buffer. Sizes include zero terminator. // We don't rely on current ImGuiContext as this is implied to be a helper function which doesn't depend on it (see #7314). wchar_t local_temp_stack[FILENAME_MAX]; @@ -2185,12 +2478,14 @@ ImFileHandle ImFileOpen(const char* filename, const char* mode) return fopen(filename, mode); #endif } + // We should in theory be using fseeko()/ftello() with off_t and _fseeki64()/_ftelli64() with __int64, waiting for the PR that does that in a very portable pre-C++11 zero-warnings way. bool ImFileClose(ImFileHandle f) { return fclose(f) == 0; } ImU64 ImFileGetSize(ImFileHandle f) { long off = 0, sz = 0; return ((off = ftell(f)) != -1 && !fseek(f, 0, SEEK_END) && (sz = ftell(f)) != -1 && !fseek(f, off, SEEK_SET)) ? (ImU64)sz : (ImU64)-1; } ImU64 ImFileRead(void* data, ImU64 sz, ImU64 count, ImFileHandle f) { return fread(data, (size_t)sz, (size_t)count, f); } ImU64 ImFileWrite(const void* data, ImU64 sz, ImU64 count, ImFileHandle f) { return fwrite(data, (size_t)sz, (size_t)count, f); } #endif // #ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS + // Helper: Load file content into memory // Memory allocated with IM_ALLOC(), must be freed by user using IM_FREE() == ImGui::MemFree() // This can't really be used with "rt" because fseek size won't match read size. @@ -2199,15 +2494,18 @@ void* ImFileLoadToMemory(const char* filename, const char* mode, size_t* out_f IM_ASSERT(filename && mode); if (out_file_size) *out_file_size = 0; + ImFileHandle f; if ((f = ImFileOpen(filename, mode)) == NULL) return NULL; + size_t file_size = (size_t)ImFileGetSize(f); if (file_size == (size_t)-1) { ImFileClose(f); return NULL; } + void* file_data = IM_ALLOC(file_size + padding_bytes); if (file_data == NULL) { @@ -2222,15 +2520,20 @@ void* ImFileLoadToMemory(const char* filename, const char* mode, size_t* out_f } if (padding_bytes > 0) memset((void*)(((char*)file_data) + file_size), 0, (size_t)padding_bytes); + ImFileClose(f); if (out_file_size) *out_file_size = file_size; + return file_data; } + //----------------------------------------------------------------------------- // [SECTION] MISC HELPERS/UTILITIES (ImText* functions) //----------------------------------------------------------------------------- + IM_MSVC_RUNTIME_CHECKS_OFF + // Convert UTF-8 to 32-bit character, process single character input. // A nearly-branchless UTF-8 decoder, based on work of Christopher Wellons (https://github.com/skeeto/branchless-utf8). // We handle UTF-8 decoding error by skipping forward. @@ -2243,8 +2546,10 @@ int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* static const int shifte[] = { 0, 6, 4, 2, 0 }; int len = lengths[*(const unsigned char*)in_text >> 3]; int wanted = len + (len ? 0 : 1); + if (in_text_end == NULL) in_text_end = in_text + wanted; // Max length, nulls will be taken into account. + // Copy at most 'len' bytes, stop copying at 0 or past in_text_end. Branch predictor does a good job here, // so it is fast even with excessive branching. unsigned char s[4]; @@ -2252,12 +2557,14 @@ int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* s[1] = in_text + 1 < in_text_end ? in_text[1] : 0; s[2] = in_text + 2 < in_text_end ? in_text[2] : 0; s[3] = in_text + 3 < in_text_end ? in_text[3] : 0; + // Assume a four-byte character and load four bytes. Unused bits are shifted out. *out_char = (uint32_t)(s[0] & masks[len]) << 18; *out_char |= (uint32_t)(s[1] & 0x3f) << 12; *out_char |= (uint32_t)(s[2] & 0x3f) << 6; *out_char |= (uint32_t)(s[3] & 0x3f) << 0; *out_char >>= shiftc[len]; + // Accumulate the various error conditions. int e = 0; e = (*out_char < mins[len]) << 6; // non-canonical encoding @@ -2268,6 +2575,7 @@ int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* e |= (s[3] ) >> 6; e ^= 0x2a; // top two bits of each tail byte correct? e >>= shifte[len]; + if (e) { // No bytes are consumed when *in_text == 0 || in_text == in_text_end. @@ -2277,8 +2585,10 @@ int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* wanted = ImMin(wanted, !!s[0] + !!s[1] + !!s[2] + !!s[3]); *out_char = IM_UNICODE_CODEPOINT_INVALID; } + return wanted; } + int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining) { ImWchar* buf_out = buf; @@ -2294,6 +2604,7 @@ int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const cha *in_text_remaining = in_text; return (int)(buf_out - buf); } + int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end) { int char_count = 0; @@ -2305,6 +2616,7 @@ int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end) } return char_count; } + // Based on stb_to_utf8() from github.com/nothings/stb/ static inline int ImTextCharToUtf8_inline(char* buf, int buf_size, unsigned int c) { @@ -2340,18 +2652,21 @@ static inline int ImTextCharToUtf8_inline(char* buf, int buf_size, unsigned int // Invalid code point, the max unicode is 0x10FFFF return 0; } -const char* ImTextCharToUtf8(char out_buf[5], unsigned int c) + +int ImTextCharToUtf8(char out_buf[5], unsigned int c) { int count = ImTextCharToUtf8_inline(out_buf, 5, c); out_buf[count] = 0; - return out_buf; + return count; } + // Not optimal but we very rarely use this function. int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end) { unsigned int unused = 0; return ImTextCharFromUtf8(&unused, in_text, in_text_end); } + static inline int ImTextCountUtf8BytesFromChar(unsigned int c) { if (c < 0x80) return 1; @@ -2360,6 +2675,7 @@ static inline int ImTextCountUtf8BytesFromChar(unsigned int c) if (c <= 0x10FFFF) return 4; return 3; } + int ImTextStrToUtf8(char* out_buf, int out_buf_size, const ImWchar* in_text, const ImWchar* in_text_end) { char* buf_p = out_buf; @@ -2375,6 +2691,7 @@ int ImTextStrToUtf8(char* out_buf, int out_buf_size, const ImWchar* in_text, con *buf_p = 0; return (int)(buf_p - out_buf); } + int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end) { int bytes_count = 0; @@ -2388,6 +2705,7 @@ int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_e } return bytes_count; } + const char* ImTextFindPreviousUtf8Codepoint(const char* in_text_start, const char* in_text_curr) { while (in_text_curr > in_text_start) @@ -2398,6 +2716,7 @@ const char* ImTextFindPreviousUtf8Codepoint(const char* in_text_start, const cha } return in_text_start; } + int ImTextCountLines(const char* in_text, const char* in_text_end) { if (in_text_end == NULL) @@ -2411,11 +2730,14 @@ int ImTextCountLines(const char* in_text, const char* in_text_end) } return count; } + IM_MSVC_RUNTIME_CHECKS_RESTORE + //----------------------------------------------------------------------------- // [SECTION] MISC HELPERS/UTILITIES (Color functions) // Note: The Convert functions are early design which are not consistent with other API. //----------------------------------------------------------------------------- + IMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b) { float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f; @@ -2424,6 +2746,7 @@ IMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b) int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t); return IM_COL32(r, g, b, 0xFF); } + ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in) { float s = 1.0f / 255.0f; @@ -2433,6 +2756,7 @@ ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in) ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, ((in >> IM_COL32_A_SHIFT) & 0xFF) * s); } + ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in) { ImU32 out; @@ -2442,6 +2766,7 @@ ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in) out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT; return out; } + // Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592 // Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v) @@ -2457,11 +2782,13 @@ void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& ImSwap(r, g); K = -2.f / 6.f - K; } + const float chroma = r - (g < b ? g : b); out_h = ImFabs(K + (g - b) / (6.f * chroma + 1e-20f)); out_s = chroma / (r + 1e-20f); out_v = r; } + // Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593 // also http://en.wikipedia.org/wiki/HSL_and_HSV void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b) @@ -2472,12 +2799,14 @@ void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_r = out_g = out_b = v; return; } + h = ImFmod(h, 1.0f) / (60.0f / 360.0f); int i = (int)h; float f = h - (float)i; float p = v * (1.0f - s); float q = v * (1.0f - s * f); float t = v * (1.0f - s * (1.0f - f)); + switch (i) { case 0: out_r = v; out_g = t; out_b = p; break; @@ -2488,10 +2817,12 @@ void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& case 5: default: out_r = v; out_g = p; out_b = q; break; } } + //----------------------------------------------------------------------------- // [SECTION] ImGuiStorage // Helper: Key->value storage //----------------------------------------------------------------------------- + // std::lower_bound but without the bullshit ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStoragePair* in_end, ImGuiID key) { @@ -2512,6 +2843,7 @@ ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStoragePair* in_ } return in_p; } + IM_MSVC_RUNTIME_CHECKS_OFF static int IMGUI_CDECL PairComparerByID(const void* lhs, const void* rhs) { @@ -2520,11 +2852,13 @@ static int IMGUI_CDECL PairComparerByID(const void* lhs, const void* rhs) ImGuiID rhs_v = ((const ImGuiStoragePair*)rhs)->key; return (lhs_v > rhs_v ? +1 : lhs_v < rhs_v ? -1 : 0); } + // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. void ImGuiStorage::BuildSortByKey() { ImQsort(Data.Data, (size_t)Data.Size, sizeof(ImGuiStoragePair), PairComparerByID); } + int ImGuiStorage::GetInt(ImGuiID key, int default_val) const { ImGuiStoragePair* it = ImLowerBound(const_cast(Data.Data), const_cast(Data.Data + Data.Size), key); @@ -2532,10 +2866,12 @@ int ImGuiStorage::GetInt(ImGuiID key, int default_val) const return default_val; return it->val_i; } + bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const { return GetInt(key, default_val ? 1 : 0) != 0; } + float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const { ImGuiStoragePair* it = ImLowerBound(const_cast(Data.Data), const_cast(Data.Data + Data.Size), key); @@ -2543,6 +2879,7 @@ float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const return default_val; return it->val_f; } + void* ImGuiStorage::GetVoidPtr(ImGuiID key) const { ImGuiStoragePair* it = ImLowerBound(const_cast(Data.Data), const_cast(Data.Data + Data.Size), key); @@ -2550,6 +2887,7 @@ void* ImGuiStorage::GetVoidPtr(ImGuiID key) const return NULL; return it->val_p; } + // References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer. int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val) { @@ -2558,10 +2896,12 @@ int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val) it = Data.insert(it, ImGuiStoragePair(key, default_val)); return &it->val_i; } + bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val) { return (bool*)GetIntRef(key, default_val ? 1 : 0); } + float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val) { ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); @@ -2569,6 +2909,7 @@ float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val) it = Data.insert(it, ImGuiStoragePair(key, default_val)); return &it->val_f; } + void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val) { ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); @@ -2576,6 +2917,7 @@ void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val) it = Data.insert(it, ImGuiStoragePair(key, default_val)); return &it->val_p; } + // FIXME-OPT: Need a way to reuse the result of lower_bound when doing GetInt()/SetInt() - not too bad because it only happens on explicit interaction (maximum one a frame) void ImGuiStorage::SetInt(ImGuiID key, int val) { @@ -2585,10 +2927,12 @@ void ImGuiStorage::SetInt(ImGuiID key, int val) else it->val_i = val; } + void ImGuiStorage::SetBool(ImGuiID key, bool val) { SetInt(key, val ? 1 : 0); } + void ImGuiStorage::SetFloat(ImGuiID key, float val) { ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); @@ -2597,6 +2941,7 @@ void ImGuiStorage::SetFloat(ImGuiID key, float val) else it->val_f = val; } + void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val) { ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); @@ -2605,15 +2950,18 @@ void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val) else it->val_p = val; } + void ImGuiStorage::SetAllInt(int v) { for (int i = 0; i < Data.Size; i++) Data[i].val_i = v; } IM_MSVC_RUNTIME_CHECKS_RESTORE + //----------------------------------------------------------------------------- // [SECTION] ImGuiTextFilter //----------------------------------------------------------------------------- + // Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077 { @@ -2625,6 +2973,7 @@ ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077 Build(); } } + bool ImGuiTextFilter::Draw(const char* label, float width) { if (width != 0.0f) @@ -2634,6 +2983,7 @@ bool ImGuiTextFilter::Draw(const char* label, float width) Build(); return value_changed; } + void ImGuiTextFilter::ImGuiTextRange::split(char separator, ImVector* out) const { out->resize(0); @@ -2651,11 +3001,13 @@ void ImGuiTextFilter::ImGuiTextRange::split(char separator, ImVectorpush_back(ImGuiTextRange(wb, we)); } + void ImGuiTextFilter::Build() { Filters.resize(0); ImGuiTextRange input_range(InputBuf, InputBuf + ImStrlen(InputBuf)); input_range.split(',', &Filters); + CountGrep = 0; for (ImGuiTextRange& f : Filters) { @@ -2669,12 +3021,15 @@ void ImGuiTextFilter::Build() CountGrep += 1; } } + bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const { if (Filters.Size == 0) return true; + if (text == NULL) text = text_end = ""; + for (const ImGuiTextRange& f : Filters) { if (f.b == f.e) @@ -2692,14 +3047,18 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const return true; } } + // Implicit * grep if (CountGrep == 0) return true; + return false; } + //----------------------------------------------------------------------------- // [SECTION] ImGuiTextBuffer, ImGuiTextIndex //----------------------------------------------------------------------------- + // On some platform vsnprintf() takes va_list by reference and modifies it. // va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it. #ifndef va_copy @@ -2709,10 +3068,13 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const #define va_copy(dest, src) (dest = src) #endif #endif + char ImGuiTextBuffer::EmptyString[1] = { 0 }; + void ImGuiTextBuffer::append(const char* str, const char* str_end) { int len = str_end ? (int)(str_end - str) : (int)ImStrlen(str); + // Add zero-terminator the first time const int write_off = (Buf.Size != 0) ? Buf.Size : 1; const int needed_sz = write_off + len; @@ -2721,10 +3083,12 @@ void ImGuiTextBuffer::append(const char* str, const char* str_end) int new_capacity = Buf.Capacity * 2; Buf.reserve(needed_sz > new_capacity ? needed_sz : new_capacity); } + Buf.resize(needed_sz); memcpy(&Buf[write_off - 1], str, (size_t)len); Buf[write_off - 1 + len] = 0; } + void ImGuiTextBuffer::appendf(const char* fmt, ...) { va_list args; @@ -2732,17 +3096,20 @@ void ImGuiTextBuffer::appendf(const char* fmt, ...) appendfv(fmt, args); va_end(args); } + // Helper: Text buffer for logging/accumulating text void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) { va_list args_copy; va_copy(args_copy, args); + int len = ImFormatStringV(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass. if (len <= 0) { va_end(args_copy); return; } + // Add zero-terminator the first time const int write_off = (Buf.Size != 0) ? Buf.Size : 1; const int needed_sz = write_off + len; @@ -2751,10 +3118,12 @@ void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) int new_capacity = Buf.Capacity * 2; Buf.reserve(needed_sz > new_capacity ? needed_sz : new_capacity); } + Buf.resize(needed_sz); ImFormatStringV(&Buf[write_off - 1], (size_t)len + 1, fmt, args_copy); va_end(args_copy); } + void ImGuiTextIndex::append(const char* base, int old_size, int new_size) { IM_ASSERT(old_size >= 0 && new_size >= old_size && new_size >= EndOffset); @@ -2768,9 +3137,11 @@ void ImGuiTextIndex::append(const char* base, int old_size, int new_size) LineOffsets.push_back((int)(intptr_t)(p - base)); EndOffset = ImMax(EndOffset, new_size); } + //----------------------------------------------------------------------------- // [SECTION] ImGuiListClipper //----------------------------------------------------------------------------- + // FIXME-TABLE: This prevents us from using ImGuiListClipper _inside_ a table cell. // The problem we have is that without a Begin/End scheme for rows using the clipper is ambiguous. static bool GetSkipItemForListClipping() @@ -2778,15 +3149,18 @@ static bool GetSkipItemForListClipping() ImGuiContext& g = *GImGui; return (g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems); } + static void ImGuiListClipper_SortAndFuseRanges(ImVector& ranges, int offset = 0) { if (ranges.Size - offset <= 1) return; + // Helper to order ranges and fuse them together if possible (bubble sort is fine as we are only sorting 2-3 entries) for (int sort_end = ranges.Size - offset - 1; sort_end > 0; --sort_end) for (int i = offset; i < sort_end + offset; ++i) if (ranges[i].Min > ranges[i + 1].Min) ImSwap(ranges[i], ranges[i + 1]); + // Now fuse ranges together as much as possible. for (int i = 1 + offset; i < ranges.Size; i++) { @@ -2799,6 +3173,7 @@ static void ImGuiListClipper_SortAndFuseRanges(ImVector& i--; } } + static void ImGuiListClipper_SeekCursorAndSetupPrevLine(float pos_y, float line_height) { // Set cursor position and a few other things so that SetScrollHereY() and Columns() can work when seeking cursor. @@ -2819,33 +3194,40 @@ static void ImGuiListClipper_SeekCursorAndSetupPrevLine(float pos_y, float line_ ImGui::TableEndRow(table); table->RowPosY2 = window->DC.CursorPos.y; const int row_increase = (int)((off_y / line_height) + 0.5f); - //table->CurrentRow += row_increase; // Can't do without fixing TableEndRow() + table->CurrentRow += row_increase; table->RowBgColorCounter += row_increase; } } + ImGuiListClipper::ImGuiListClipper() { memset(this, 0, sizeof(*this)); } + ImGuiListClipper::~ImGuiListClipper() { End(); } + void ImGuiListClipper::Begin(int items_count, float items_height) { if (Ctx == NULL) Ctx = ImGui::GetCurrentContext(); + ImGuiContext& g = *Ctx; ImGuiWindow* window = g.CurrentWindow; IMGUI_DEBUG_LOG_CLIPPER("Clipper: Begin(%d,%.2f) in '%s'\n", items_count, items_height, window->Name); + if (ImGuiTable* table = g.CurrentTable) if (table->IsInsideRow) ImGui::TableEndRow(table); + StartPosY = window->DC.CursorPos.y; ItemsHeight = items_height; ItemsCount = items_count; DisplayStart = -1; DisplayEnd = 0; + // Acquire temporary buffer if (++g.ClipperTempDataStacked > g.ClipperTempData.Size) g.ClipperTempData.resize(g.ClipperTempDataStacked, ImGuiListClipperData()); @@ -2855,6 +3237,7 @@ void ImGuiListClipper::Begin(int items_count, float items_height) TempData = data; StartSeekOffsetY = data->LossynessOffset; } + void ImGuiListClipper::End() { if (ImGuiListClipperData* data = (ImGuiListClipperData*)TempData) @@ -2864,6 +3247,7 @@ void ImGuiListClipper::End() IMGUI_DEBUG_LOG_CLIPPER("Clipper: End() in '%s'\n", g.CurrentWindow->Name); if (ItemsCount >= 0 && ItemsCount < INT_MAX && DisplayStart >= 0) SeekCursorForItem(ItemsCount); + // Restore temporary buffer and fix back pointers which may be invalidated when nesting IM_ASSERT(data->ListClipper == this); data->StepNo = data->Ranges.Size; @@ -2876,6 +3260,7 @@ void ImGuiListClipper::End() } ItemsCount = -1; } + void ImGuiListClipper::IncludeItemsByIndex(int item_begin, int item_end) { ImGuiListClipperData* data = (ImGuiListClipperData*)TempData; @@ -2884,6 +3269,7 @@ void ImGuiListClipper::IncludeItemsByIndex(int item_begin, int item_end) if (item_begin < item_end) data->Ranges.push_back(ImGuiListClipperRange::FromIndices(item_begin, item_end)); } + // This is already called while stepping. // The ONLY reason you may want to call this is if you passed INT_MAX to ImGuiListClipper::Begin() because you couldn't step item count beforehand. void ImGuiListClipper::SeekCursorForItem(int item_n) @@ -2894,18 +3280,22 @@ void ImGuiListClipper::SeekCursorForItem(int item_n) float pos_y = (float)((double)StartPosY + StartSeekOffsetY + (double)item_n * ItemsHeight); ImGuiListClipper_SeekCursorAndSetupPrevLine(pos_y, ItemsHeight); } + static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) { ImGuiContext& g = *clipper->Ctx; ImGuiWindow* window = g.CurrentWindow; ImGuiListClipperData* data = (ImGuiListClipperData*)clipper->TempData; IM_ASSERT(data != NULL && "Called ImGuiListClipper::Step() too many times, or before ImGuiListClipper::Begin() ?"); + ImGuiTable* table = g.CurrentTable; if (table && table->IsInsideRow) ImGui::TableEndRow(table); + // No items if (clipper->ItemsCount == 0 || GetSkipItemForListClipping()) return false; + // While we are in frozen row state, keep displaying items one by one, unclipped // FIXME: Could be stored as a table-agnostic state. if (data->StepNo == 0 && table != NULL && !table->IsUnfrozenRows) @@ -2916,6 +3306,7 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) data->ItemsFrozen++; return true; } + // Step 0: Let you process the first element (regardless of it being visible or not, so we can measure the element height) bool calc_clipping = false; if (data->StepNo == 0) @@ -2932,27 +3323,38 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) } calc_clipping = true; // If on the first step with known item height, calculate clipping. } + // Step 1: Let the clipper infer height from first range if (clipper->ItemsHeight <= 0.0f) { IM_ASSERT(data->StepNo == 1); if (table) IM_ASSERT(table->RowPosY1 == clipper->StartPosY && table->RowPosY2 == window->DC.CursorPos.y); - clipper->ItemsHeight = (window->DC.CursorPos.y - clipper->StartPosY) / (float)(clipper->DisplayEnd - clipper->DisplayStart); - bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision(clipper->StartPosY) || ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y); + + bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision((float)clipper->StartPosY) || ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y); if (affected_by_floating_point_precision) + { + // Mitigation/hack for very large range: assume last time height constitute line height. clipper->ItemsHeight = window->DC.PrevLineSize.y + g.Style.ItemSpacing.y; // FIXME: Technically wouldn't allow multi-line entries. + window->DC.CursorPos.y = (float)(clipper->StartPosY + clipper->ItemsHeight); + } + else + { + clipper->ItemsHeight = (float)(window->DC.CursorPos.y - clipper->StartPosY) / (float)(clipper->DisplayEnd - clipper->DisplayStart); + } if (clipper->ItemsHeight == 0.0f && clipper->ItemsCount == INT_MAX) // Accept that no item have been submitted if in indeterminate mode. return false; IM_ASSERT(clipper->ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!"); calc_clipping = true; // If item height had to be calculated, calculate clipping afterwards. } + // Step 0 or 1: Calculate the actual ranges of visible elements. const int already_submitted = clipper->DisplayEnd; if (calc_clipping) { // Record seek offset, this is so ImGuiListClipper::Seek() can be called after ImGuiListClipperData is done clipper->StartSeekOffsetY = (double)data->LossynessOffset - data->ItemsFrozen * (double)clipper->ItemsHeight; + if (g.LogEnabled) { // If logging is active, do not perform any clipping @@ -2963,16 +3365,22 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) // Add range selected to be included for navigation const bool is_nav_request = (g.NavMoveScoringItems && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav); if (is_nav_request) + { + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(g.NavScoringRect.Min.y, g.NavScoringRect.Max.y, 0, 0)); data->Ranges.push_back(ImGuiListClipperRange::FromPositions(g.NavScoringNoClipRect.Min.y, g.NavScoringNoClipRect.Max.y, 0, 0)); + } if (is_nav_request && (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && g.NavTabbingDir == -1) data->Ranges.push_back(ImGuiListClipperRange::FromIndices(clipper->ItemsCount - 1, clipper->ItemsCount)); + // Add focused/active item ImRect nav_rect_abs = ImGui::WindowRectRelToAbs(window, window->NavRectRel[0]); if (g.NavId != 0 && window->NavLastIds[0] == g.NavId) data->Ranges.push_back(ImGuiListClipperRange::FromPositions(nav_rect_abs.Min.y, nav_rect_abs.Max.y, 0, 0)); + // Add visible range float min_y = window->ClipRect.Min.y; float max_y = window->ClipRect.Max.y; + // Add box selection range ImGuiBoxSelectState* bs = &g.BoxSelectState; if (bs->IsActive && bs->Window == window) @@ -2982,14 +3390,17 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) // As a workaround we currently half ItemSpacing worth on each side. min_y -= g.Style.ItemSpacing.y; max_y += g.Style.ItemSpacing.y; + // Box-select on 2D area requires different clipping. if (bs->UnclipMode) data->Ranges.push_back(ImGuiListClipperRange::FromPositions(bs->UnclipRect.Min.y, bs->UnclipRect.Max.y, 0, 0)); } + const int off_min = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Up) ? -1 : 0; const int off_max = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Down) ? 1 : 0; data->Ranges.push_back(ImGuiListClipperRange::FromPositions(min_y, max_y, off_min, off_max)); } + // Convert position ranges to item index ranges // - Very important: when a starting position is after our maximum item, we set Min to (ItemsCount - 1). This allows us to handle most forms of wrapping. // - Due to how Selectable extra padding they tend to be "unaligned" with exact unit in the item list, @@ -3005,6 +3416,7 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) } ImGuiListClipper_SortAndFuseRanges(data->Ranges, data->StepNo); } + // Step 0+ (if item height is given in advance) or 1+: Display the next range in line. while (data->StepNo < data->Ranges.Size) { @@ -3017,12 +3429,15 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) clipper->SeekCursorForItem(clipper->DisplayStart); return true; } + // After the last step: Let the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), // Advance the cursor to the end of the list and then returns 'false' to end the loop. if (clipper->ItemsCount < INT_MAX) clipper->SeekCursorForItem(clipper->ItemsCount); + return false; } + bool ImGuiListClipper::Step() { ImGuiContext& g = *Ctx; @@ -3045,14 +3460,17 @@ bool ImGuiListClipper::Step() } return ret; } + //----------------------------------------------------------------------------- // [SECTION] STYLING //----------------------------------------------------------------------------- + ImGuiStyle& ImGui::GetStyle() { IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?"); return GImGui->Style; } + ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul) { ImGuiStyle& style = GImGui->Style; @@ -3060,6 +3478,7 @@ ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul) c.w *= style.Alpha * alpha_mul; return ColorConvertFloat4ToU32(c); } + ImU32 ImGui::GetColorU32(const ImVec4& col) { ImGuiStyle& style = GImGui->Style; @@ -3067,11 +3486,13 @@ ImU32 ImGui::GetColorU32(const ImVec4& col) c.w *= style.Alpha; return ColorConvertFloat4ToU32(c); } + const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx) { ImGuiStyle& style = GImGui->Style; return style.Colors[idx]; } + ImU32 ImGui::GetColorU32(ImU32 col, float alpha_mul) { ImGuiStyle& style = GImGui->Style; @@ -3082,6 +3503,7 @@ ImU32 ImGui::GetColorU32(ImU32 col, float alpha_mul) a = (ImU32)(a * alpha_mul); // We don't need to clamp 0..255 because alpha is in 0..1 range. return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); } + // FIXME: This may incur a round-trip (if the end user got their data from a float4) but eventually we aim to store the in-flight colors as ImU32 void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col) { @@ -3093,6 +3515,7 @@ void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col) if (g.DebugFlashStyleColorIdx != idx) g.Style.Colors[idx] = ColorConvertU32ToFloat4(col); } + void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col) { ImGuiContext& g = *GImGui; @@ -3103,6 +3526,7 @@ void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col) if (g.DebugFlashStyleColorIdx != idx) g.Style.Colors[idx] = col; } + void ImGui::PopStyleColor(int count) { ImGuiContext& g = *GImGui; @@ -3119,10 +3543,12 @@ void ImGui::PopStyleColor(int count) count--; } } + static const ImGuiCol GWindowDockStyleColors[ImGuiWindowDockStyleCol_COUNT] = { ImGuiCol_Text, ImGuiCol_TabHovered, ImGuiCol_Tab, ImGuiCol_TabSelected, ImGuiCol_TabSelectedOverline, ImGuiCol_TabDimmed, ImGuiCol_TabDimmedSelected, ImGuiCol_TabDimmedSelectedOverline, }; + static const ImGuiStyleVarInfo GStyleVarsInfo[] = { { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha @@ -3150,10 +3576,14 @@ static const ImGuiStyleVarInfo GStyleVarsInfo[] = { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ImageBorderSize) }, // ImGuiStyleVar_ImageBorderSize { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBorderSize) }, // ImGuiStyleVar_TabBorderSize + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabMinWidthBase) }, // ImGuiStyleVar_TabMinWidthBase + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabMinWidthShrink) }, // ImGuiStyleVar_TabMinWidthShrink { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBarBorderSize) }, // ImGuiStyleVar_TabBarBorderSize { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBarOverlineSize) }, // ImGuiStyleVar_TabBarOverlineSize { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersAngle)}, // ImGuiStyleVar_TableAngledHeadersAngle { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TreeLinesSize)}, // ImGuiStyleVar_TreeLinesSize + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TreeLinesRounding)}, // ImGuiStyleVar_TreeLinesRounding { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)}, // ImGuiStyleVar_SeparatorTextBorderSize @@ -3161,12 +3591,14 @@ static const ImGuiStyleVarInfo GStyleVarsInfo[] = { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding) }, // ImGuiStyleVar_SeparatorTextPadding { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, DockingSeparatorSize) }, // ImGuiStyleVar_DockingSeparatorSize }; + const ImGuiStyleVarInfo* ImGui::GetStyleVarInfo(ImGuiStyleVar idx) { IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT); IM_STATIC_ASSERT(IM_ARRAYSIZE(GStyleVarsInfo) == ImGuiStyleVar_COUNT); return &GStyleVarsInfo[idx]; } + void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) { ImGuiContext& g = *GImGui; @@ -3180,6 +3612,7 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); *pvar = val; } + void ImGui::PushStyleVarX(ImGuiStyleVar idx, float val_x) { ImGuiContext& g = *GImGui; @@ -3193,6 +3626,7 @@ void ImGui::PushStyleVarX(ImGuiStyleVar idx, float val_x) g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); pvar->x = val_x; } + void ImGui::PushStyleVarY(ImGuiStyleVar idx, float val_y) { ImGuiContext& g = *GImGui; @@ -3206,6 +3640,7 @@ void ImGui::PushStyleVarY(ImGuiStyleVar idx, float val_y) g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); pvar->y = val_y; } + void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) { ImGuiContext& g = *GImGui; @@ -3219,6 +3654,7 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); *pvar = val; } + void ImGui::PopStyleVar(int count) { ImGuiContext& g = *GImGui; @@ -3239,6 +3675,7 @@ void ImGui::PopStyleVar(int count) count--; } } + const char* ImGui::GetStyleColorName(ImGuiCol idx) { // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1"; @@ -3277,6 +3714,7 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) case ImGuiCol_ResizeGrip: return "ResizeGrip"; case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered"; case ImGuiCol_ResizeGripActive: return "ResizeGripActive"; + case ImGuiCol_InputTextCursor: return "InputTextCursor"; case ImGuiCol_TabHovered: return "TabHovered"; case ImGuiCol_Tab: return "Tab"; case ImGuiCol_TabSelected: return "TabSelected"; @@ -3297,6 +3735,7 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) case ImGuiCol_TableRowBgAlt: return "TableRowBgAlt"; case ImGuiCol_TextLink: return "TextLink"; case ImGuiCol_TextSelectedBg: return "TextSelectedBg"; + case ImGuiCol_TreeLines: return "TreeLines"; case ImGuiCol_DragDropTarget: return "DragDropTarget"; case ImGuiCol_NavCursor: return "NavCursor"; case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight"; @@ -3306,27 +3745,32 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) IM_ASSERT(0); return "Unknown"; } + //----------------------------------------------------------------------------- // [SECTION] RENDER HELPERS // Some of those (internal) functions are currently quite a legacy mess - their signature and behavior will change, // we need a nicer separation between low-level functions and high-level functions relying on the ImGui context. // Also see imgui_draw.cpp for some more which have been reworked to not rely on ImGui:: context. //----------------------------------------------------------------------------- + const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end) { const char* text_display_end = text; if (!text_end) text_end = (const char*)-1; + while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#')) text_display_end++; return text_display_end; } + // Internal ImGui functions to render text // RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText() void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + // Hide anything after a '##' string const char* text_display_end; if (hide_text_after_hash) @@ -3339,6 +3783,7 @@ void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool text_end = text + ImStrlen(text); // FIXME-OPT text_display_end = text_end; } + if (text != text_display_end) { window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end); @@ -3346,12 +3791,15 @@ void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool LogRenderedText(&pos, text, text_display_end); } } + void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + if (!text_end) text_end = text + ImStrlen(text); // FIXME-OPT + if (text != text_end) { window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width); @@ -3359,9 +3807,10 @@ void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end LogRenderedText(&pos, text, text_end); } } + // Default clip_rect uses (pos_min,pos_max) // Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges) -// FIXME-OPT: Since we have or calculate text_size we could coarse clip whole block immediately, especally for text above draw_list->DrawList. +// FIXME-OPT: Since we have or calculate text_size we could coarse clip whole block immediately, especially for text above draw_list->DrawList. // Effectively as this is called from widget doing their own coarse clipping it's not very valuable presently. Next time function will take // better advantage of the render function taking size into account for coarse clipping. void ImGui::RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_display_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) @@ -3369,14 +3818,17 @@ void ImGui::RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, co // Perform CPU side clipping for single clipped element to avoid using scissor state ImVec2 pos = pos_min; const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f); + const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min; const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max; bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y); if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y); + // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment. if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x); if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y); + // Render if (need_clipping) { @@ -3388,6 +3840,7 @@ void ImGui::RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, co draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL); } } + void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) { // Hide anything after a '##' string @@ -3395,24 +3848,28 @@ void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, cons const int text_len = (int)(text_display_end - text); if (text_len == 0) return; + ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; RenderTextClippedEx(window->DrawList, pos_min, pos_max, text, text_display_end, text_size_if_known, align, clip_rect); if (g.LogEnabled) LogRenderedText(&pos_min, text, text_display_end); } + // Another overly complex function until we reorganize everything into a nice all-in-one helper. -// This is made more complex because we have dissociated the layout rectangle (pos_min..pos_max) which define _where_ the ellipsis is, from actual clipping of text and limit of the ellipsis display. +// This is made more complex because we have dissociated the layout rectangle (pos_min..pos_max) from 'ellipsis_max_x' which may be beyond it. // This is because in the context of tabs we selectively hide part of the text when the Close Button appears, but we don't want the ellipsis to move. -void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float clip_max_x, float ellipsis_max_x, const char* text, const char* text_end_full, const ImVec2* text_size_if_known) +// (BREAKING) On 2025/04/16 we removed the 'float clip_max_x' parameters which was preceeding 'float ellipsis_max' and was the same value for 99% of users. +void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float ellipsis_max_x, const char* text, const char* text_end_full, const ImVec2* text_size_if_known) { ImGuiContext& g = *GImGui; if (text_end_full == NULL) text_end_full = FindRenderedTextEnd(text); const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_end_full, false, 0.0f); - //draw_list->AddLine(ImVec2(pos_max.x, pos_min.y - 4), ImVec2(pos_max.x, pos_max.y + 4), IM_COL32(0, 0, 255, 255)); - //draw_list->AddLine(ImVec2(ellipsis_max_x, pos_min.y-2), ImVec2(ellipsis_max_x, pos_max.y+2), IM_COL32(0, 255, 0, 255)); - //draw_list->AddLine(ImVec2(clip_max_x, pos_min.y), ImVec2(clip_max_x, pos_max.y), IM_COL32(255, 0, 0, 255)); + + //draw_list->AddLine(ImVec2(pos_max.x, pos_min.y - 4), ImVec2(pos_max.x, pos_max.y + 6), IM_COL32(0, 0, 255, 255)); + //draw_list->AddLine(ImVec2(ellipsis_max_x, pos_min.y - 2), ImVec2(ellipsis_max_x, pos_max.y + 3), IM_COL32(0, 255, 0, 255)); + // FIXME: We could technically remove (last_glyph->AdvanceX - last_glyph->X1) from text_size.x here and save a few pixels. if (text_size.x > pos_max.x - pos_min.x) { @@ -3420,40 +3877,39 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con // | | | // min max ellipsis_max // <-> this is generally some padding value + ImFont* font = draw_list->_Data->Font; const float font_size = draw_list->_Data->FontSize; const float font_scale = draw_list->_Data->FontScale; const char* text_end_ellipsis = NULL; - const float ellipsis_width = font->EllipsisWidth * font_scale; + ImFontBaked* baked = font->GetFontBaked(font_size); + const float ellipsis_width = baked->GetCharAdvance(font->EllipsisChar) * font_scale; + // We can now claim the space between pos_max.x and ellipsis_max.x const float text_avail_width = ImMax((ImMax(pos_max.x, ellipsis_max_x) - ellipsis_width) - pos_min.x, 1.0f); float text_size_clipped_x = font->CalcTextSizeA(font_size, text_avail_width, 0.0f, text, text_end_full, &text_end_ellipsis).x; - if (text == text_end_ellipsis && text_end_ellipsis < text_end_full) - { - // Always display at least 1 character if there's no room for character + ellipsis - text_end_ellipsis = text + ImTextCountUtf8BytesFromChar(text, text_end_full); - text_size_clipped_x = font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text, text_end_ellipsis).x; - } while (text_end_ellipsis > text && ImCharIsBlankA(text_end_ellipsis[-1])) { // Trim trailing space before ellipsis (FIXME: Supporting non-ascii blanks would be nice, for this we need a function to backtrack in UTF-8 text) text_end_ellipsis--; text_size_clipped_x -= font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text_end_ellipsis, text_end_ellipsis + 1).x; // Ascii blanks are always 1 byte } + // Render text, render ellipsis - RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_ellipsis, &text_size, ImVec2(0.0f, 0.0f)); + RenderTextClippedEx(draw_list, pos_min, pos_max, text, text_end_ellipsis, &text_size, ImVec2(0.0f, 0.0f)); + ImVec4 cpu_fine_clip_rect(pos_min.x, pos_min.y, pos_max.x, pos_max.y); ImVec2 ellipsis_pos = ImTrunc(ImVec2(pos_min.x + text_size_clipped_x, pos_min.y)); - if (ellipsis_pos.x + ellipsis_width <= ellipsis_max_x) - for (int i = 0; i < font->EllipsisCharCount; i++, ellipsis_pos.x += font->EllipsisCharStep * font_scale) - font->RenderChar(draw_list, font_size, ellipsis_pos, GetColorU32(ImGuiCol_Text), font->EllipsisChar); + font->RenderChar(draw_list, font_size, ellipsis_pos, GetColorU32(ImGuiCol_Text), font->EllipsisChar, &cpu_fine_clip_rect); } else { - RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_full, &text_size, ImVec2(0.0f, 0.0f)); + RenderTextClippedEx(draw_list, pos_min, pos_max, text, text_end_full, &text_size, ImVec2(0.0f, 0.0f)); } + if (g.LogEnabled) LogRenderedText(&pos_min, text, text_end_full); } + // Render a rectangle shaped with optional rounding and borders void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders, float rounding) { @@ -3467,6 +3923,7 @@ void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size); } } + void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) { ImGuiContext& g = *GImGui; @@ -3478,6 +3935,7 @@ void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size); } } + void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags) { ImGuiContext& g = *GImGui; @@ -3490,6 +3948,7 @@ void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFl ImGuiWindow* window = g.CurrentWindow; if (window->DC.NavHideHighlightOneFrame) return; + float rounding = (flags & ImGuiNavRenderCursorFlags_NoRounding) ? 0.0f : g.Style.FrameRounding; ImRect display_rect = bb; display_rect.ClipWith(window->ClipRect); @@ -3510,12 +3969,13 @@ void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFl window->DrawList->PopClipRect(); } } + void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill, ImU32 col_border, ImU32 col_shadow) { ImGuiContext& g = *GImGui; if (mouse_cursor <= ImGuiMouseCursor_None || mouse_cursor >= ImGuiMouseCursor_COUNT) // We intentionally accept out of bound values. mouse_cursor = ImGuiMouseCursor_Arrow; - ImFontAtlas* font_atlas = g.DrawListSharedData.Font->ContainerAtlas; + ImFontAtlas* font_atlas = g.DrawListSharedData.FontAtlas; for (ImGuiViewportP* viewport : g.Viewports) { // We scale cursor with current viewport/monitor, however Windows 10 for its own hardware cursor seems to be using a different scale factor. @@ -3527,12 +3987,12 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso if (!viewport->GetMainRect().Overlaps(ImRect(pos, pos + ImVec2(size.x + 2, size.y + 2) * scale))) continue; ImDrawList* draw_list = GetForegroundDrawList(viewport); - ImTextureID tex_id = font_atlas->TexID; - draw_list->PushTextureID(tex_id); - draw_list->AddImage(tex_id, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3], col_shadow); - draw_list->AddImage(tex_id, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow); - draw_list->AddImage(tex_id, pos, pos + size * scale, uv[2], uv[3], col_border); - draw_list->AddImage(tex_id, pos, pos + size * scale, uv[0], uv[1], col_fill); + ImTextureRef tex_ref = font_atlas->TexRef; + draw_list->PushTexture(tex_ref); + draw_list->AddImage(tex_ref, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3], col_shadow); + draw_list->AddImage(tex_ref, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow); + draw_list->AddImage(tex_ref, pos, pos + size * scale, uv[2], uv[3], col_border); + draw_list->AddImage(tex_ref, pos, pos + size * scale, uv[0], uv[1], col_fill); if (mouse_cursor == ImGuiMouseCursor_Wait || mouse_cursor == ImGuiMouseCursor_Progress) { float a_min = ImFmod((float)g.Time * 5.0f, 2.0f * IM_PI); @@ -3540,18 +4000,21 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso draw_list->PathArcTo(pos + ImVec2(14, -1) * scale, 6.0f * scale, a_min, a_max); draw_list->PathStroke(col_fill, ImDrawFlags_None, 3.0f * scale); } - draw_list->PopTextureID(); + draw_list->PopTexture(); } } + //----------------------------------------------------------------------------- // [SECTION] INITIALIZATION, SHUTDOWN //----------------------------------------------------------------------------- + // Internal state access - if you want to share Dear ImGui state between modules (e.g. DLL) or allocate it yourself // Note that we still point to some static data and members (such as GFontAtlas), so the state instance you end up using will point to the static data within its module ImGuiContext* ImGui::GetCurrentContext() { return GImGui; } + void ImGui::SetCurrentContext(ImGuiContext* ctx) { #ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC @@ -3560,12 +4023,14 @@ void ImGui::SetCurrentContext(ImGuiContext* ctx) GImGui = ctx; #endif } + void ImGui::SetAllocatorFunctions(ImGuiMemAllocFunc alloc_func, ImGuiMemFreeFunc free_func, void* user_data) { GImAllocatorAllocFunc = alloc_func; GImAllocatorFreeFunc = free_func; GImAllocatorUserData = user_data; } + // This is provided to facilitate copying allocators from one static/DLL boundary to another (e.g. retrieve default allocator of your executable address space) void ImGui::GetAllocatorFunctions(ImGuiMemAllocFunc* p_alloc_func, ImGuiMemFreeFunc* p_free_func, void** p_user_data) { @@ -3573,6 +4038,7 @@ void ImGui::GetAllocatorFunctions(ImGuiMemAllocFunc* p_alloc_func, ImGuiMemFreeF *p_free_func = GImAllocatorFreeFunc; *p_user_data = GImAllocatorUserData; } + ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas) { ImGuiContext* prev_ctx = GetCurrentContext(); @@ -3583,6 +4049,7 @@ ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas) SetCurrentContext(prev_ctx); // Restore previous context if any, else keep new one. return ctx; } + void ImGui::DestroyContext(ImGuiContext* ctx) { ImGuiContext* prev_ctx = GetCurrentContext(); @@ -3593,6 +4060,7 @@ void ImGui::DestroyContext(ImGuiContext* ctx) SetCurrentContext((prev_ctx != ctx) ? prev_ctx : NULL); IM_DELETE(ctx); } + // IMPORTANT: interactive elements requires a fixed ###xxx suffix, it must be same in ALL languages to allow for automation. static const ImGuiLocEntry GLocalizationEntriesEnUS[] = { @@ -3610,16 +4078,22 @@ static const ImGuiLocEntry GLocalizationEntriesEnUS[] = { ImGuiLocKey_DockingHoldShiftToDock, "Hold SHIFT to enable Docking window." }, { ImGuiLocKey_DockingDragToUndockOrMoveNode,"Click and drag to move or undock whole node." }, }; + ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) { IO.Ctx = this; InputTextState.Ctx = this; + Initialized = false; ConfigFlagsCurrFrame = ConfigFlagsLastFrame = ImGuiConfigFlags_None; - FontAtlasOwnedByContext = shared_font_atlas ? false : true; + Font = NULL; - FontSize = FontBaseSize = FontScale = CurrentDpiScale = 0.0f; + FontBaked = NULL; + FontSize = FontSizeBase = FontBakedScale = CurrentDpiScale = 0.0f; + FontRasterizerDensity = 1.0f; IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); + if (shared_font_atlas == NULL) + IO.Fonts->OwnerContext = this; Time = 0.0f; FrameCount = 0; FrameCountEnded = FrameCountPlatformEnded = FrameCountRendered = -1; @@ -3629,8 +4103,10 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) TestEngineHookItems = false; TestEngine = NULL; memset(ContextName, 0, sizeof(ContextName)); + InputEventsNextMouseSource = ImGuiMouseSource_Mouse; InputEventsNextEventId = 1; + WindowsActiveCount = 0; WindowsBorderHoverPadding = 0.0f; CurrentWindow = NULL; @@ -3641,7 +4117,8 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) WheelingWindow = NULL; WheelingWindowStartFrame = WheelingWindowScrolledFrame = -1; WheelingWindowReleaseTimer = 0.0f; - DebugDrawIdConflicts = 0; + + DebugDrawIdConflictsId = 0; DebugHookIdInfo = 0; HoveredId = HoveredIdPreviousFrame = 0; HoveredIdPreviousFrameItemCount = 0; @@ -3662,23 +4139,29 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) ActiveIdClickOffset = ImVec2(-1, -1); ActiveIdWindow = NULL; ActiveIdSource = ImGuiInputSource_None; + ActiveIdDisabledId = 0; ActiveIdMouseButton = -1; ActiveIdPreviousFrame = 0; memset(&DeactivatedItemData, 0, sizeof(DeactivatedItemData)); memset(&ActiveIdValueOnActivation, 0, sizeof(ActiveIdValueOnActivation)); LastActiveId = 0; LastActiveIdTimer = 0.0f; + LastKeyboardKeyPressTime = LastKeyModsChangeTime = LastKeyModsChangeFromNoneTime = -1.0; + ActiveIdUsingNavDirMask = 0x00; ActiveIdUsingAllKeyboardKeys = false; + CurrentFocusScopeId = 0; CurrentItemFlags = ImGuiItemFlags_None; DebugShowGroupRects = false; + CurrentViewport = NULL; MouseViewport = MouseLastHoveredViewport = NULL; PlatformLastFocusedViewportId = 0; ViewportCreatedCount = PlatformWindowsCreatedCount = 0; ViewportFocusedStampCount = 0; + NavCursorVisible = false; NavHighlightItemUnderNav = false; NavMousePosDirty = false; @@ -3694,6 +4177,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) NavInputSource = ImGuiInputSource_Keyboard; NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; NavCursorHideFrames = 0; + NavAnyRequest = false; NavInitRequest = false; NavInitRequestFromMove = false; @@ -3707,19 +4191,25 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) NavScoringDebugCount = 0; NavTabbingDir = 0; NavTabbingCounter = 0; + NavJustMovedFromFocusScopeId = NavJustMovedToId = NavJustMovedToFocusScopeId = 0; NavJustMovedToKeyMods = ImGuiMod_None; NavJustMovedToIsTabbing = false; NavJustMovedToHasSelectionData = false; + // All platforms use Ctrl+Tab but Ctrl<>Super are swapped on Mac... // FIXME: Because this value is stored, it annoyingly interfere with toggling io.ConfigMacOSXBehaviors updating this.. + ConfigNavWindowingWithGamepad = true; ConfigNavWindowingKeyNext = IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiKey_Tab) : (ImGuiMod_Ctrl | ImGuiKey_Tab); ConfigNavWindowingKeyPrev = IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab) : (ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab); NavWindowingTarget = NavWindowingTargetAnim = NavWindowingListWindow = NULL; + NavWindowingInputSource = ImGuiInputSource_None; NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f; NavWindowingToggleLayer = false; NavWindowingToggleKey = ImGuiKey_None; + DimBgRatio = 0.0f; + DragDropActive = DragDropWithinSource = DragDropWithinTarget = false; DragDropSourceFlags = ImGuiDragDropFlags_None; DragDropSourceFrameCount = -1; @@ -3731,16 +4221,22 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) DragDropAcceptFrameCount = -1; DragDropHoldJustPressedId = 0; memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal)); + ClipperTempDataStacked = 0; + CurrentTable = NULL; TablesTempDataStacked = 0; CurrentTabBar = NULL; CurrentMultiSelect = NULL; MultiSelectTempDataStacked = 0; + HoverItemDelayId = HoverItemDelayIdPreviousFrame = HoverItemUnlockedStationaryId = HoverWindowUnlockedStationaryId = 0; HoverItemDelayTimer = HoverItemDelayClearTimer = 0.0f; + MouseCursor = ImGuiMouseCursor_Arrow; MouseStationaryTimer = 0.0f; + + InputTextPasswordFontBackupFlags = ImFontFlags_None; TempInputId = 0; memset(&DataTypeZeroValue, 0, sizeof(DataTypeZeroValue)); BeginMenuDepth = BeginComboDepth = 0; @@ -3761,14 +4257,18 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) DisabledStackSize = 0; TooltipOverrideCount = 0; TooltipPreviousWindow = NULL; + PlatformImeData.InputPos = ImVec2(0.0f, 0.0f); PlatformImeDataPrev.InputPos = ImVec2(-1.0f, -1.0f); // Different to ensure initial submission - PlatformImeViewport = 0; + DockNodeWindowMenuHandler = NULL; + SettingsLoaded = false; SettingsDirtyTimer = 0.0f; HookIdNext = 0; + memset(LocalizationTable, 0, sizeof(LocalizationTable)); + LogEnabled = false; LogFlags = ImGuiLogFlags_None; LogWindow = NULL; @@ -3778,11 +4278,13 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) LogLineFirstItem = false; LogDepthRef = 0; LogDepthToExpand = LogDepthToExpandDefault = 2; + ErrorCallback = NULL; ErrorCallbackUserData = NULL; ErrorFirst = true; ErrorCountCurrentFrame = 0; StackSizesInBeginForCurrentWindow = NULL; + DebugDrawIdConflictsCount = 0; DebugLogFlags = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_OutputToTTY; DebugLocateId = 0; @@ -3797,22 +4299,26 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) DebugFlashStyleColorTime = 0.0f; DebugFlashStyleColorIdx = ImGuiCol_COUNT; DebugHoveredDockNode = NULL; + // Same as DebugBreakClearData(). Those fields are scattered in their respective subsystem to stay in hot-data locations DebugBreakInWindow = 0; DebugBreakInTable = 0; DebugBreakInLocateId = false; DebugBreakKeyChord = ImGuiKey_Pause; DebugBreakInShortcutRouting = ImGuiKey_None; + memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame)); FramerateSecPerFrameIdx = FramerateSecPerFrameCount = 0; FramerateSecPerFrameAccum = 0.0f; WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; memset(TempKeychordName, 0, sizeof(TempKeychordName)); } + void ImGui::Initialize() { ImGuiContext& g = *GImGui; IM_ASSERT(!g.Initialized && !g.SettingsLoaded); + // Add .ini handle for ImGuiWindow and ImGuiTable types { ImGuiSettingsHandler ini_handler; @@ -3826,13 +4332,16 @@ void ImGui::Initialize() AddSettingsHandler(&ini_handler); } TableSettingsAddSettingsHandler(); + // Setup default localization table LocalizeRegisterEntries(GLocalizationEntriesEnUS, IM_ARRAYSIZE(GLocalizationEntriesEnUS)); + // Setup default ImGuiPlatformIO clipboard/IME handlers. g.PlatformIO.Platform_GetClipboardTextFn = Platform_GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations g.PlatformIO.Platform_SetClipboardTextFn = Platform_SetClipboardTextFn_DefaultImpl; g.PlatformIO.Platform_OpenInShellFn = Platform_OpenInShellFn_DefaultImpl; g.PlatformIO.Platform_SetImeDataFn = Platform_SetImeDataFn_DefaultImpl; + // Create default viewport ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)(); viewport->ID = IMGUI_VIEWPORT_DEFAULT_ID; @@ -3843,6 +4352,7 @@ void ImGui::Initialize() g.TempBuffer.resize(1024 * 3 + 1, 0); g.ViewportCreatedCount++; g.PlatformIO.Viewports.push_back(g.Viewports[0]); + // Build KeysMayBeCharInput[] lookup table (1 bool per named key) for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) if ((key >= ImGuiKey_0 && key <= ImGuiKey_9) || (key >= ImGuiKey_A && key <= ImGuiKey_Z) || (key >= ImGuiKey_Keypad0 && key <= ImGuiKey_Keypad9) @@ -3850,37 +4360,62 @@ void ImGui::Initialize() || key == ImGuiKey_Slash || key == ImGuiKey_Semicolon || key == ImGuiKey_Equal || key == ImGuiKey_LeftBracket || key == ImGuiKey_RightBracket || key == ImGuiKey_GraveAccent || key == ImGuiKey_KeypadDecimal || key == ImGuiKey_KeypadDivide || key == ImGuiKey_KeypadMultiply || key == ImGuiKey_KeypadSubtract || key == ImGuiKey_KeypadAdd || key == ImGuiKey_KeypadEqual) g.KeysMayBeCharInput.SetBit(key); + #ifdef IMGUI_HAS_DOCK // Initialize Docking DockContextInitialize(&g); #endif + + // Print a debug message when running with debug feature IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS because it is very slow. + // DO NOT COMMENT OUT THIS MESSAGE. IT IS DESIGNED TO REMIND YOU THAT IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS SHOULD ONLY BE TEMPORARILY ENABLED. +#ifdef IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + DebugLog("IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS is enabled.\nMust disable after use! Otherwise Dear ImGui will run slower.\n"); +#endif + + // ImDrawList/ImFontAtlas are designed to function without ImGui, and 99% of it works without an ImGui context. + // But this link allows us to facilitate/handle a few edge cases better. + ImFontAtlas* atlas = g.IO.Fonts; + g.DrawListSharedData.Context = &g; + RegisterFontAtlas(atlas); + g.Initialized = true; } + // This function is merely here to free heap allocations. void ImGui::Shutdown() { ImGuiContext& g = *GImGui; IM_ASSERT_USER_ERROR(g.IO.BackendPlatformUserData == NULL, "Forgot to shutdown Platform backend?"); IM_ASSERT_USER_ERROR(g.IO.BackendRendererUserData == NULL, "Forgot to shutdown Renderer backend?"); + // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) - if (g.IO.Fonts && g.FontAtlasOwnedByContext) + for (ImFontAtlas* atlas : g.FontAtlases) { - g.IO.Fonts->Locked = false; - IM_DELETE(g.IO.Fonts); + UnregisterFontAtlas(atlas); + if (atlas->OwnerContext == &g) + { + atlas->Locked = false; + IM_DELETE(atlas); + } } - g.IO.Fonts = NULL; g.DrawListSharedData.TempBuffer.clear(); + // Cleanup of other data are conditional on actually having initialized Dear ImGui. if (!g.Initialized) return; + // Save settings (unless we haven't attempted to load them: CreateContext/DestroyContext without a call to NewFrame shouldn't save an empty file) if (g.SettingsLoaded && g.IO.IniFilename != NULL) SaveIniSettingsToDisk(g.IO.IniFilename); + // Destroy platform windows DestroyPlatformWindows(); + // Shutdown extensions DockContextShutdown(&g); + CallContextHooks(&g, ImGuiContextHookType_Shutdown); + // Clear everything else g.Windows.clear_delete(); g.WindowsFocusOrder.clear(); @@ -3892,30 +4427,40 @@ void ImGui::Shutdown() g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL; g.ActiveIdWindow = NULL; g.MovingWindow = NULL; + g.KeysRoutingTable.Clear(); + g.ColorStack.clear(); g.StyleVarStack.clear(); g.FontStack.clear(); g.OpenPopupStack.clear(); g.BeginPopupStack.clear(); g.TreeNodeStack.clear(); + g.CurrentViewport = g.MouseViewport = g.MouseLastHoveredViewport = NULL; g.Viewports.clear_delete(); + g.TabBars.Clear(); g.CurrentTabBarStack.clear(); g.ShrinkWidthBuffer.clear(); + g.ClipperTempData.clear_destruct(); + g.Tables.Clear(); g.TablesTempData.clear_destruct(); g.DrawChannelsTempMergeBuffer.clear(); + g.MultiSelectStorage.Clear(); g.MultiSelectTempData.clear_destruct(); + g.ClipboardHandlerData.clear(); g.MenusIdSubmittedThisFrame.clear(); g.InputTextState.ClearFreeMemory(); g.InputTextDeactivatedState.ClearFreeMemory(); + g.SettingsWindows.clear(); g.SettingsHandlers.clear(); + if (g.LogFile) { #ifndef IMGUI_DISABLE_TTY_FUNCTIONS @@ -3927,8 +4472,10 @@ void ImGui::Shutdown() g.LogBuffer.clear(); g.DebugLogBuf.clear(); g.DebugLogIndex.clear(); + g.Initialized = false; } + // No specific ordering/dependency support, will see as needed ImGuiID ImGui::AddContextHook(ImGuiContext* ctx, const ImGuiContextHook* hook) { @@ -3938,6 +4485,7 @@ ImGuiID ImGui::AddContextHook(ImGuiContext* ctx, const ImGuiContextHook* hook) g.Hooks.back().HookId = ++g.HookIdNext; return g.HookIdNext; } + // Deferred removal, avoiding issue with changing vector while iterating it void ImGui::RemoveContextHook(ImGuiContext* ctx, ImGuiID hook_id) { @@ -3947,6 +4495,7 @@ void ImGui::RemoveContextHook(ImGuiContext* ctx, ImGuiID hook_id) if (hook.HookId == hook_id) hook.Type = ImGuiContextHookType_PendingRemoval_; } + // Call context hooks (used by e.g. test engine) // We assume a small number of hooks so all stored in same array void ImGui::CallContextHooks(ImGuiContext* ctx, ImGuiContextHookType hook_type) @@ -3956,9 +4505,11 @@ void ImGui::CallContextHooks(ImGuiContext* ctx, ImGuiContextHookType hook_type) if (hook.Type == hook_type) hook.Callback(&g, &hook); } + //----------------------------------------------------------------------------- // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) //----------------------------------------------------------------------------- + // ImGuiWindow is mostly a dumb struct. It merely has a constructor and a few helper methods ImGuiWindow::ImGuiWindow(ImGuiContext* ctx, const char* name) : DrawListInst(NULL) { @@ -3982,21 +4533,23 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* ctx, const char* name) : DrawListInst(NUL LastFrameJustFocused = -1; LastTimeActive = -1.0f; FontRefSize = 0.0f; - FontWindowScale = FontWindowScaleParents = FontDpiScale = 1.0f; + FontWindowScale = FontWindowScaleParents = 1.0f; SettingsOffset = -1; DockOrder = -1; DrawList = &DrawListInst; DrawList->_OwnerName = Name; - DrawList->_Data = &Ctx->DrawListSharedData; + DrawList->_SetDrawListSharedData(&Ctx->DrawListSharedData); NavPreferredScoringPosRel[0] = NavPreferredScoringPosRel[1] = ImVec2(FLT_MAX, FLT_MAX); IM_PLACEMENT_NEW(&WindowClass) ImGuiWindowClass(); } + ImGuiWindow::~ImGuiWindow() { IM_ASSERT(DrawList == &DrawListInst); IM_DELETE(Name); ColumnsStorage.clear_destruct(); } + static void SetCurrentWindow(ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -4005,11 +4558,19 @@ static void SetCurrentWindow(ImGuiWindow* window) g.CurrentTable = window && window->DC.CurrentTableIdx != -1 ? g.Tables.GetByIndex(window->DC.CurrentTableIdx) : NULL; if (window) { - g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); - g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize; + bool backup_skip_items = window->SkipItems; + window->SkipItems = false; + if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) + { + ImGuiViewport* viewport = window->Viewport; + g.FontRasterizerDensity = (viewport->FramebufferScale.x != 0.0f) ? viewport->FramebufferScale.x : g.IO.DisplayFramebufferScale.x; // == SetFontRasterizerDensity() + } + ImGui::UpdateCurrentFontSize(0.0f); + window->SkipItems = backup_skip_items; ImGui::NavUpdateCurrentWindowIsScrollPushableX(); } } + void ImGui::GcCompactTransientMiscBuffers() { ImGuiContext& g = *GImGui; @@ -4018,7 +4579,10 @@ void ImGui::GcCompactTransientMiscBuffers() g.MultiSelectTempDataStacked = 0; g.MultiSelectTempData.clear_destruct(); TableGcCompactSettings(); + for (ImFontAtlas* atlas : g.FontAtlases) + atlas->CompactCache(); } + // Free up/compact internal window buffers, we can use this when a window becomes unused. // Not freed: // - ImGuiWindow, ImGuiWindowSettings, Name, StateStorage, ColumnsStorage (may hold useful data) @@ -4034,6 +4598,7 @@ void ImGui::GcCompactTransientWindowBuffers(ImGuiWindow* window) window->DC.ItemWidthStack.clear(); window->DC.TextWrapPosStack.clear(); } + void ImGui::GcAwakeTransientWindowBuffers(ImGuiWindow* window) { // We stored capacity of the ImDrawList buffer to reduce growth-caused allocation/copy when awakening. @@ -4043,32 +4608,37 @@ void ImGui::GcAwakeTransientWindowBuffers(ImGuiWindow* window) window->DrawList->VtxBuffer.reserve(window->MemoryDrawListVtxCapacity); window->MemoryDrawListIdxCapacity = window->MemoryDrawListVtxCapacity = 0; } + void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) { ImGuiContext& g = *GImGui; + // Clear previous active id if (g.ActiveId != 0) { - // While most behaved code would make an effort to not steal active id during window move/drag operations, - // we at least need to be resilient to it. Canceling the move is rather aggressive and users of 'master' branch - // may prefer the weird ill-defined half working situation ('docking' did assert), so may need to rework that. - if (g.MovingWindow != NULL && g.ActiveId == g.MovingWindow->MoveId) - { - IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() cancel MovingWindow\n"); - g.MovingWindow = NULL; - } // Store deactivate data ImGuiDeactivatedItemData* deactivated_data = &g.DeactivatedItemData; deactivated_data->ID = g.ActiveId; deactivated_data->ElapseFrame = (g.LastItemData.ID == g.ActiveId) ? g.FrameCount : g.FrameCount + 1; // FIXME: OK to use LastItemData? deactivated_data->HasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore; deactivated_data->IsAlive = (g.ActiveIdIsAlive == g.ActiveId); + // This could be written in a more general way (e.g associate a hook to ActiveId), // but since this is currently quite an exception we'll leave it as is. // One common scenario leading to this is: pressing Key ->NavMoveRequestApplyResult() -> ClearActiveID() if (g.InputTextState.ID == g.ActiveId) InputTextDeactivateHook(g.ActiveId); + + // While most behaved code would make an effort to not steal active id during window move/drag operations, + // we at least need to be resilient to it. Canceling the move is rather aggressive and users of 'master' branch + // may prefer the weird ill-defined half working situation ('docking' did assert), so may need to rework that. + if (g.MovingWindow != NULL && g.ActiveId == g.MovingWindow->MoveId) + { + IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() cancel MovingWindow\n"); + StopMouseMovingWindow(); + } } + // Set active id g.ActiveIdIsJustActivated = (g.ActiveId != id); if (g.ActiveIdIsJustActivated) @@ -4090,21 +4660,25 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) g.ActiveIdWindow = window; g.ActiveIdHasBeenEditedThisFrame = false; g.ActiveIdFromShortcut = false; + g.ActiveIdDisabledId = 0; if (id) { g.ActiveIdIsAlive = id; g.ActiveIdSource = (g.NavActivateId == id || g.NavJustMovedToId == id) ? g.NavInputSource : ImGuiInputSource_Mouse; IM_ASSERT(g.ActiveIdSource != ImGuiInputSource_None); } + // Clear declaration of inputs claimed by the widget // (Please note that this is WIP and not all keys/inputs are thoroughly declared by all widgets yet) g.ActiveIdUsingNavDirMask = 0x00; g.ActiveIdUsingAllKeyboardKeys = false; } + void ImGui::ClearActiveID() { SetActiveID(0, NULL); // g.ActiveId = 0; } + void ImGui::SetHoveredID(ImGuiID id) { ImGuiContext& g = *GImGui; @@ -4113,11 +4687,13 @@ void ImGui::SetHoveredID(ImGuiID id) if (id != 0 && g.HoveredIdPreviousFrame != id) g.HoveredIdTimer = g.HoveredIdNotActiveTimer = 0.0f; } + ImGuiID ImGui::GetHoveredID() { ImGuiContext& g = *GImGui; return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame; } + void ImGui::MarkItemEdited(ImGuiID id) { // This marking is to be able to provide info for IsItemDeactivatedAfterEdit(). @@ -4133,12 +4709,15 @@ void ImGui::MarkItemEdited(ImGuiID id) if (g.DeactivatedItemData.ID == id) g.DeactivatedItemData.HasBeenEditedBefore = true; } + // We accept a MarkItemEdited() on drag and drop targets (see https://github.com/ocornut/imgui/issues/1875#issuecomment-978243343) // We accept 'ActiveIdPreviousFrame == id' for InputText() returning an edit after it has been taken ActiveId away (#4714) IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id || (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive)); + //IM_ASSERT(g.CurrentWindow->DC.LastItemId == id); g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; } + bool ImGui::IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) { // An active popup disable hovering on other windows (apart from its own children) @@ -4155,17 +4734,21 @@ bool ImGui::IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flag want_inhibit = true; else if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup)) want_inhibit = true; + // Inhibit hover unless the window is within the stack of our modal/popup if (want_inhibit) if (!IsWindowWithinBeginStackOf(window->RootWindow, focused_root_window)) return false; } + // Filter by viewport if (window->Viewport != g.MouseViewport) if (g.MovingWindow == NULL || window->RootWindowDockTree != g.MovingWindow->RootWindowDockTree) return false; + return true; } + static inline float CalcDelayFromHoveredFlags(ImGuiHoveredFlags flags) { ImGuiContext& g = *GImGui; @@ -4175,6 +4758,7 @@ static inline float CalcDelayFromHoveredFlags(ImGuiHoveredFlags flags) return g.Style.HoverDelayShort; return 0.0f; } + static ImGuiHoveredFlags ApplyHoverFlagsForTooltip(ImGuiHoveredFlags user_flags, ImGuiHoveredFlags shared_flags) { // Allow instance flags to override shared flags @@ -4182,6 +4766,7 @@ static ImGuiHoveredFlags ApplyHoverFlagsForTooltip(ImGuiHoveredFlags user_flags, shared_flags &= ~(ImGuiHoveredFlags_DelayNone | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_DelayNormal); return user_flags | shared_flags; } + // This is roughly matching the behavior of internal-facing ItemHoverable() // - we allow hovering to be true when ActiveId==window->MoveID, so that clicking on non-interactive items such as a Text() item still returns true with IsItemHovered() // - this should work even for non-interactive items that have no ID, so we cannot use LastItemId @@ -4190,12 +4775,14 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; IM_ASSERT_USER_ERROR((flags & ~ImGuiHoveredFlags_AllowedMaskForIsItemHovered) == 0, "Invalid flags for IsItemHovered()!"); + if (g.NavHighlightItemUnderNav && g.NavCursorVisible && !(flags & ImGuiHoveredFlags_NoNavOverride)) { if (!IsItemFocused()) return false; if ((g.LastItemData.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) return false; + if (flags & ImGuiHoveredFlags_ForTooltip) flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipNav); } @@ -4205,8 +4792,10 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) ImGuiItemStatusFlags status_flags = g.LastItemData.StatusFlags; if (!(status_flags & ImGuiItemStatusFlags_HoveredRect)) return false; + if (flags & ImGuiHoveredFlags_ForTooltip) flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipMouse); + // Done with rectangle culling so we can perform heavier checks now // Test if we are hovering the right window (our window could be behind another window) // [2021/03/02] Reworked / reverted the revert, finally. Note we want e.g. BeginGroup/ItemAdd/EndGroup to work as well. (#3851) @@ -4216,30 +4805,48 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) if (g.HoveredWindow != window && (status_flags & ImGuiItemStatusFlags_HoveredWindow) == 0) if ((flags & ImGuiHoveredFlags_AllowWhenOverlappedByWindow) == 0) return false; + // Test if another item is active (e.g. being dragged) const ImGuiID id = g.LastItemData.ID; if ((flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) == 0) if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) - if (g.ActiveId != window->MoveId && g.ActiveId != window->TabId) + { + // When ActiveId == MoveId it means that either: + // - (1) user clicked on void _or_ an item with no id, which triggers moving window (ActiveId is set even when window has _NoMove flag) + // - the (id == 0) test handles it, however, IsItemHovered() will leak between id==0 items (mostly visible when using _NoMove). // FIXME: May be fixed. + // - (2) user clicked a disabled item. UpdateMouseMovingWindowEndFrame() uses ActiveId == MoveId to avoid interference with item logic + sets ActiveIdDisabledId. + bool cancel_is_hovered = true; + if (g.ActiveId == window->MoveId && (id == 0 || g.ActiveIdDisabledId == id)) + cancel_is_hovered = false; + // When ActiveId == TabId it means user clicked docking tab for the window. + if (g.ActiveId == window->TabId) + cancel_is_hovered = false; + if (cancel_is_hovered) return false; + } + // Test if interactions on this window are blocked by an active popup or modal. // The ImGuiHoveredFlags_AllowWhenBlockedByPopup flag will be tested here. if (!IsWindowContentHoverable(window, flags) && !(g.LastItemData.ItemFlags & ImGuiItemFlags_NoWindowHoverableCheck)) return false; + // Test if the item is disabled if ((g.LastItemData.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) return false; + // Special handling for calling after Begin() which represent the title bar or tab. // When the window is skipped/collapsed (SkipItems==true) that last item (always ->MoveId submitted by Begin) // will never be overwritten so we need to detect the case. if (id == window->MoveId && window->WriteAccessed) return false; + // Test if using AllowOverlap and overlapped if ((g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap) && id != 0) if ((flags & ImGuiHoveredFlags_AllowWhenOverlappedByItem) == 0) if (g.HoveredIdPreviousFrame != g.LastItemData.ID) return false; } + // Handle hover delay // (some ideas: https://www.nngroup.com/articles/timing-exposing-content) const float delay = CalcDelayFromHoveredFlags(flags); @@ -4249,17 +4856,22 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) if ((flags & ImGuiHoveredFlags_NoSharedDelay) && (g.HoverItemDelayIdPreviousFrame != hover_delay_id)) g.HoverItemDelayTimer = 0.0f; g.HoverItemDelayId = hover_delay_id; + // When changing hovered item we requires a bit of stationary delay before activating hover timer, // but once unlocked on a given item we also moving. //if (g.HoverDelayTimer >= delay && (g.HoverDelayTimer - g.IO.DeltaTime < delay || g.MouseStationaryTimer - g.IO.DeltaTime < g.Style.HoverStationaryDelay)) { IMGUI_DEBUG_LOG("HoverDelayTimer = %f/%f, MouseStationaryTimer = %f\n", g.HoverDelayTimer, delay, g.MouseStationaryTimer); } if ((flags & ImGuiHoveredFlags_Stationary) != 0 && g.HoverItemUnlockedStationaryId != hover_delay_id) return false; + if (g.HoverItemDelayTimer < delay) return false; } + return true; } -// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered(). + +// Internal facing ItemHoverable() used when submitting widgets. THIS IS A SUBMISSION NOT A HOVER CHECK. +// Returns whether the item was hovered, logic differs slightly from IsItemHovered(). // (this does not rely on LastItemData it can be called from a ButtonBehavior() call not following an ItemAdd() call) // FIXME-LEGACY: the 'ImGuiItemFlags item_flags' parameter was added on 2023-06-28. // If you used this in your legacy/custom widgets code: @@ -4269,30 +4881,36 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + // Detect ID conflicts + // (this is specifically done here by comparing on hover because it allows us a detection of duplicates that is algorithmically extra cheap, 1 u32 compare per item. No O(log N) lookup whatsoever) #ifndef IMGUI_DISABLE_DEBUG_TOOLS if (id != 0 && g.HoveredIdPreviousFrame == id && (item_flags & ImGuiItemFlags_AllowDuplicateId) == 0) { g.HoveredIdPreviousFrameItemCount++; - if (g.DebugDrawIdConflicts == id) + if (g.DebugDrawIdConflictsId == id) window->DrawList->AddRect(bb.Min - ImVec2(1,1), bb.Max + ImVec2(1,1), IM_COL32(255, 0, 0, 255), 0.0f, ImDrawFlags_None, 2.0f); } #endif + if (g.HoveredWindow != window) return false; if (!IsMouseHoveringRect(bb.Min, bb.Max)) return false; + if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap) return false; if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) if (!g.ActiveIdFromShortcut) return false; - // Done with rectangle culling so we can perform heavier checks now. + + // We are done with rectangle culling so we can perform heavier checks now. if (!(item_flags & ImGuiItemFlags_NoWindowHoverableCheck) && !IsWindowContentHoverable(window, ImGuiHoveredFlags_None)) { g.HoveredIdIsDisabled = true; return false; } + // We exceptionally allow this function to be called with id==0 to allow using it for easy high-level // hover test in widgets code. We could also decide to split this function is two. if (id != 0) @@ -4300,7 +4918,9 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag // Drag source doesn't report as hovered if (g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover)) return false; + SetHoveredID(id); + // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. // This allows using patterns where a later submitted widget overlaps a previous one. Generally perceived as a front-to-back hit-test. if (item_flags & ImGuiItemFlags_AllowOverlap) @@ -4309,12 +4929,14 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag if (g.HoveredIdPreviousFrame != id) return false; } + // Display shortcut (only works with mouse) // (ImGuiItemStatusFlags_HasShortcut in LastItemData denotes we want a tooltip) if (id == g.LastItemData.ID && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasShortcut) && g.ActiveId != id) if (IsItemHovered(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_DelayNormal)) SetTooltip("%s", GetKeyChordName(g.LastItemData.Shortcut)); } + // When disabled we'll return false but still set HoveredId if (item_flags & ImGuiItemFlags_Disabled) { @@ -4324,6 +4946,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag g.HoveredIdIsDisabled = true; return false; } + #ifndef IMGUI_DISABLE_DEBUG_TOOLS if (id != 0) { @@ -4337,10 +4960,13 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag IM_DEBUG_BREAK(); } #endif + if (g.NavHighlightItemUnderNav && (item_flags & ImGuiItemFlags_NoNavDisableMouseHover) == 0) return false; + return true; } + // FIXME: This is inlined/duplicated in ItemAdd() // FIXME: The id != 0 path is not used by our codebase, may get rid of it? bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id) @@ -4353,6 +4979,7 @@ bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id) return true; return false; } + // This is also inlined in ItemAdd() // Note: if ImGuiItemStatusFlags_HasDisplayRect is set, user needs to set g.LastItemData.DisplayRect. void ImGui::SetLastItemData(ImGuiID item_id, ImGuiItemFlags item_flags, ImGuiItemStatusFlags status_flags, const ImRect& item_rect) @@ -4363,6 +4990,7 @@ void ImGui::SetLastItemData(ImGuiID item_id, ImGuiItemFlags item_flags, ImGuiIte g.LastItemData.StatusFlags = status_flags; g.LastItemData.Rect = g.LastItemData.NavRect = item_rect; } + static void ImGui::SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect) { ImGuiContext& g = *GImGui; @@ -4371,15 +4999,18 @@ static void ImGui::SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& r else SetLastItemData(window->MoveId, g.CurrentItemFlags, window->DC.WindowItemStatusFlags, rect); } + static void ImGui::SetLastItemDataForChildWindowItem(ImGuiWindow* window, const ImRect& rect) { ImGuiContext& g = *GImGui; SetLastItemData(window->ChildId, g.CurrentItemFlags, window->DC.ChildItemStatusFlags, rect); } + float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x) { if (wrap_pos_x < 0.0f) return 0.0f; + ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (wrap_pos_x == 0.0f) @@ -4395,8 +5026,10 @@ float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x) { wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space } + return ImMax(wrap_pos_x - pos.x, 1.0f); } + // IM_ALLOC() == ImGui::MemAlloc() void* ImGui::MemAlloc(size_t size) { @@ -4407,6 +5040,7 @@ void* ImGui::MemAlloc(size_t size) #endif return ptr; } + // IM_FREE() == ImGui::MemFree() void ImGui::MemFree(void* ptr) { @@ -4417,6 +5051,7 @@ void ImGui::MemFree(void* ptr) #endif return (*GImAllocatorFreeFunc)(ptr, GImAllocatorUserData); } + // We record the number of allocation in recent frames, as a way to audit/sanitize our guiding principles of "no allocations on idle/repeating frames" void ImGui::DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr, size_t size) { @@ -4442,43 +5077,51 @@ void ImGui::DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr info->TotalFreeCount++; } } + const char* ImGui::GetClipboardText() { ImGuiContext& g = *GImGui; return g.PlatformIO.Platform_GetClipboardTextFn ? g.PlatformIO.Platform_GetClipboardTextFn(&g) : ""; } + void ImGui::SetClipboardText(const char* text) { ImGuiContext& g = *GImGui; if (g.PlatformIO.Platform_SetClipboardTextFn != NULL) g.PlatformIO.Platform_SetClipboardTextFn(&g, text); } + const char* ImGui::GetVersion() { return IMGUI_VERSION; } + ImGuiIO& ImGui::GetIO() { IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?"); return GImGui->IO; } + // This variant exists to facilitate backends experimenting with multi-threaded parallel context. (#8069, #6293, #5856) ImGuiIO& ImGui::GetIO(ImGuiContext* ctx) { IM_ASSERT(ctx != NULL); return ctx->IO; } + ImGuiPlatformIO& ImGui::GetPlatformIO() { IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext()?"); return GImGui->PlatformIO; } + // This variant exists to facilitate backends experimenting with multi-threaded parallel context. (#8069, #6293, #5856) ImGuiPlatformIO& ImGui::GetPlatformIO(ImGuiContext* ctx) { IM_ASSERT(ctx != NULL); return ctx->PlatformIO; } + // Pass this to your backend rendering function! Valid after Render() and until the next call to NewFrame() ImDrawData* ImGui::GetDrawData() { @@ -4486,14 +5129,17 @@ ImDrawData* ImGui::GetDrawData() ImGuiViewportP* viewport = g.Viewports[0]; return viewport->DrawDataP.Valid ? &viewport->DrawDataP : NULL; } + double ImGui::GetTime() { return GImGui->Time; } + int ImGui::GetFrameCount() { return GImGui->FrameCount; } + static ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t drawlist_no, const char* drawlist_name) { // Create the draw list on demand, because they are not frequently used for all viewports @@ -4506,32 +5152,37 @@ static ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t draw draw_list->_OwnerName = drawlist_name; viewport->BgFgDrawLists[drawlist_no] = draw_list; } + // Our ImDrawList system requires that there is always a command if (viewport->BgFgDrawListsLastFrame[drawlist_no] != g.FrameCount) { draw_list->_ResetForNewFrame(); - draw_list->PushTextureID(g.IO.Fonts->TexID); + draw_list->PushTexture(g.IO.Fonts->TexRef); draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false); viewport->BgFgDrawListsLastFrame[drawlist_no] = g.FrameCount; } return draw_list; } + ImDrawList* ImGui::GetBackgroundDrawList(ImGuiViewport* viewport) { if (viewport == NULL) viewport = GImGui->CurrentWindow->Viewport; return GetViewportBgFgDrawList((ImGuiViewportP*)viewport, 0, "##Background"); } + ImDrawList* ImGui::GetForegroundDrawList(ImGuiViewport* viewport) { if (viewport == NULL) viewport = GImGui->CurrentWindow->Viewport; return GetViewportBgFgDrawList((ImGuiViewportP*)viewport, 1, "##Foreground"); } + ImDrawListSharedData* ImGui::GetDrawListSharedData() { return &GImGui->DrawListSharedData; } + void ImGui::StartMouseMovingWindow(ImGuiWindow* window) { // Set ActiveId even if the _NoMove flag is set. Without it, dragging away from a window with _NoMove would activate hover on other windows. @@ -4545,6 +5196,7 @@ void ImGui::StartMouseMovingWindow(ImGuiWindow* window) g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindowDockTree->Pos; g.ActiveIdNoClearOnFocusLoss = true; SetActiveIdUsingAllKeyboardKeys(); + bool can_move_window = true; if ((window->Flags & ImGuiWindowFlags_NoMove) || (window->RootWindowDockTree->Flags & ImGuiWindowFlags_NoMove)) can_move_window = false; @@ -4554,6 +5206,7 @@ void ImGui::StartMouseMovingWindow(ImGuiWindow* window) if (can_move_window) g.MovingWindow = window; } + // We use 'undock == false' when dragging from title bar to allow moving groups of floating nodes without undocking them. void ImGui::StartMouseMovingWindowOrNode(ImGuiWindow* window, ImGuiDockNode* node, bool undock) { @@ -4568,6 +5221,7 @@ void ImGui::StartMouseMovingWindowOrNode(ImGuiWindow* window, ImGuiDockNode* nod if (root_node->OnlyNodeWithWindows != node || root_node->CentralNode != NULL) // -V1051 PVS-Studio thinks node should be root_node and is wrong about that. can_undock_node = true; } + const bool clicked = IsMouseClicked(0); const bool dragging = IsMouseDragging(0); if (can_undock_node && dragging) @@ -4575,6 +5229,36 @@ void ImGui::StartMouseMovingWindowOrNode(ImGuiWindow* window, ImGuiDockNode* nod else if (!can_undock_node && (clicked || dragging) && g.MovingWindow != window) StartMouseMovingWindow(window); } + +// This is not 100% symetric with StartMouseMovingWindow(). +// We do NOT clear ActiveID, because: +// - It would lead to rather confusing recursive code paths. Caller can call ClearActiveID() if desired. +// - Some code intentionally cancel moving but keep the ActiveID to lock inputs (e.g. code path taken when clicking a disabled item). +void ImGui::StopMouseMovingWindow() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.MovingWindow; + + // Ref commits 6b7766817, 36055213c for some partial history on checking if viewport != NULL. + if (window && window->Viewport) + { + // Try to merge the window back into the main viewport. + // This works because MouseViewport should be != MovingWindow->Viewport on release (as per code in UpdateViewports) + if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) + UpdateTryMergeWindowIntoHostViewport(window, g.MouseViewport); + + // Restore the mouse viewport so that we don't hover the viewport _under_ the moved window during the frame we released the mouse button. + if (!IsDragDropPayloadBeingAccepted()) + g.MouseViewport = window->Viewport; + + // Clear the NoInputs window flag set by the Viewport system in AddUpdateViewport() + const bool window_can_use_inputs = ((window->Flags & ImGuiWindowFlags_NoMouseInputs) && (window->Flags & ImGuiWindowFlags_NoNavInputs)) == false; + if (window_can_use_inputs) + window->Viewport->Flags &= ~ImGuiViewportFlags_NoInputs; + } + g.MovingWindow = NULL; +} + // Handle mouse moving window // Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in NavUpdateWindowing() // FIXME: We don't have strong guarantee that g.MovingWindow stay synced with g.ActiveId == g.MovingWindow->MoveId. @@ -4590,9 +5274,10 @@ void ImGui::UpdateMouseMovingWindowNewFrame() KeepAliveID(g.ActiveId); IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindowDockTree); ImGuiWindow* moving_window = g.MovingWindow->RootWindowDockTree; + // When a window stop being submitted while being dragged, it may will its viewport until next Begin() - const bool window_disappared = (!moving_window->WasActive && !moving_window->Active); - if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos) && !window_disappared) + const bool window_disappeared = (!moving_window->WasActive && !moving_window->Active); + if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos) && !window_disappeared) { ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset; if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y) @@ -4608,20 +5293,7 @@ void ImGui::UpdateMouseMovingWindowNewFrame() } else { - if (!window_disappared) - { - // Try to merge the window back into the main viewport. - // This works because MouseViewport should be != MovingWindow->Viewport on release (as per code in UpdateViewports) - if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) - UpdateTryMergeWindowIntoHostViewport(moving_window, g.MouseViewport); - // Restore the mouse viewport so that we don't hover the viewport _under_ the moved window during the frame we released the mouse button. - if (moving_window->Viewport && !IsDragDropPayloadBeingAccepted()) - g.MouseViewport = moving_window->Viewport; - // Clear the NoInput window flag set by the Viewport system - if (moving_window->Viewport) - moving_window->Viewport->Flags &= ~ImGuiViewportFlags_NoInputs; - } - g.MovingWindow = NULL; + StopMouseMovingWindow(); ClearActiveID(); } } @@ -4636,6 +5308,7 @@ void ImGui::UpdateMouseMovingWindowNewFrame() } } } + // Initiate focusing and moving window when clicking on empty space or title bar. // Initiate focusing window when clicking on a disabled item. // Handle left-click and right-click focus. @@ -4644,9 +5317,11 @@ void ImGui::UpdateMouseMovingWindowEndFrame() ImGuiContext& g = *GImGui; if (g.ActiveId != 0 || (g.HoveredId != 0 && !g.HoveredIdIsDisabled)) return; + // Unless we just made a window/popup appear if (g.NavWindow && g.NavWindow->Appearing) return; + // Click on empty space to focus window and start moving // (after we're done with all our widgets, so e.g. clicking on docking tab-bar which have set HoveredId already and not get us here!) if (g.IO.MouseClicked[0]) @@ -4655,18 +5330,27 @@ void ImGui::UpdateMouseMovingWindowEndFrame() // If we try to focus it, FocusWindow() > ClosePopupsOverWindow() will accidentally close any parent popups because they are not linked together any more. ImGuiWindow* root_window = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL; const bool is_closed_popup = root_window && (root_window->Flags & ImGuiWindowFlags_Popup) && !IsPopupOpen(root_window->PopupId, ImGuiPopupFlags_AnyPopupLevel); + if (root_window != NULL && !is_closed_popup) { StartMouseMovingWindow(g.HoveredWindow); //-V595 + + // FIXME: In principal we might be able to call StopMouseMovingWindow() below. + // Please note how StartMouseMovingWindow() and StopMouseMovingWindow() and not entirely symetrical, at the later doesn't clear ActiveId. + // Cancel moving if clicked outside of title bar if (g.IO.ConfigWindowsMoveFromTitleBarOnly) if (!(root_window->Flags & ImGuiWindowFlags_NoTitleBar) || root_window->DockIsActive) if (!root_window->TitleBarRect().Contains(g.IO.MouseClickedPos[0])) g.MovingWindow = NULL; + // Cancel moving if clicked over an item which was disabled or inhibited by popups - // (when g.HoveredIdIsDisabled == true && g.HoveredId == 0 we are inhibited by popups, when g.HoveredIdIsDisabled == true && g.HoveredId != 0 we are over a disabled item)0 already) + // (when g.HoveredIdIsDisabled == true && g.HoveredId == 0 we are inhibited by popups, when g.HoveredIdIsDisabled == true && g.HoveredId != 0 we are over a disabled item) if (g.HoveredIdIsDisabled) + { g.MovingWindow = NULL; + g.ActiveIdDisabledId = g.HoveredId; + } } else if (root_window == NULL && g.NavWindow != NULL) { @@ -4674,6 +5358,7 @@ void ImGui::UpdateMouseMovingWindowEndFrame() FocusWindow(NULL, ImGuiFocusRequestFlags_UnlessBelowModal); } } + // With right mouse button we close popups without changing focus based on where the mouse is aimed // Instead, focus will be restored to the window under the bottom-most closed popup. // (The left mouse button path calls FocusWindow on the hovered window, which will lead NewFrame->ClosePopupsOverWindow to trigger) @@ -4686,6 +5371,7 @@ void ImGui::UpdateMouseMovingWindowEndFrame() ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal, true); } } + // This is called during NewFrame()->UpdateViewportsNewFrame() only. // Need to keep in sync with SetWindowPos() static void TranslateWindow(ImGuiWindow* window, const ImVec2& delta) @@ -4699,6 +5385,7 @@ static void TranslateWindow(ImGuiWindow* window, const ImVec2& delta) window->DC.CursorMaxPos += delta; window->DC.IdealMaxPos += delta; } + static void ScaleWindow(ImGuiWindow* window, float scale) { ImVec2 origin = window->Viewport->Pos; @@ -4707,33 +5394,40 @@ static void ScaleWindow(ImGuiWindow* window, float scale) window->SizeFull = ImTrunc(window->SizeFull * scale); window->ContentSize = ImTrunc(window->ContentSize * scale); } + static bool IsWindowActiveAndVisible(ImGuiWindow* window) { return (window->Active) && (!window->Hidden); } + // The reason this is exposed in imgui_internal.h is: on touch-based system that don't have hovering, we want to dispatch inputs to the right target (imgui vs imgui+app) -void ImGui::UpdateHoveredWindowAndCaptureFlags() +void ImGui::UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos) { ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; + // FIXME-DPI: This storage was added on 2021/03/31 for test engine, but if we want to multiply WINDOWS_HOVER_PADDING // by DpiScale, we need to make this window-agnostic anyhow, maybe need storing inside ImGuiWindow. g.WindowsBorderHoverPadding = ImMax(ImMax(g.Style.TouchExtraPadding.x, g.Style.TouchExtraPadding.y), g.Style.WindowBorderHoverPadding); + // Find the window hovered by mouse: // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow. // - When moving a window we can skip the search, which also conveniently bypasses the fact that window->WindowRectClipped is lagging as this point of the frame. // - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to detect windows below it, which is useful for e.g. docking mechanisms. bool clear_hovered_windows = false; - FindHoveredWindowEx(g.IO.MousePos, false, &g.HoveredWindow, &g.HoveredWindowUnderMovingWindow); + FindHoveredWindowEx(mouse_pos, false, &g.HoveredWindow, &g.HoveredWindowUnderMovingWindow); IM_ASSERT(g.HoveredWindow == NULL || g.HoveredWindow == g.MovingWindow || g.HoveredWindow->Viewport == g.MouseViewport); g.HoveredWindowBeforeClear = g.HoveredWindow; + // Modal windows prevents mouse from hovering behind them. ImGuiWindow* modal_window = GetTopMostPopupModal(); if (modal_window && g.HoveredWindow && !IsWindowWithinBeginStackOf(g.HoveredWindow->RootWindow, modal_window)) // FIXME-MERGE: RootWindowDockTree ? clear_hovered_windows = true; + // Disabled mouse hovering (we don't currently clear MousePos, we could) if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) clear_hovered_windows = true; + // We track click ownership. When clicked outside of a window the click is owned by the application and // won't report hovering nor request capture even while dragging over our windows afterward. const bool has_open_popup = (g.OpenPopupStack.Size > 0); @@ -4754,13 +5448,16 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() } const bool mouse_avail = (mouse_earliest_down == -1) || io.MouseDownOwned[mouse_earliest_down]; const bool mouse_avail_unless_popup_close = (mouse_earliest_down == -1) || io.MouseDownOwnedUnlessPopupClose[mouse_earliest_down]; + // If mouse was first clicked outside of ImGui bounds we also cancel out hovering. // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02) const bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0; if (!mouse_avail && !mouse_dragging_extern_payload) clear_hovered_windows = true; + if (clear_hovered_windows) g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL; + // Update io.WantCaptureMouse for the user application (true = dispatch mouse info to Dear ImGui only, false = dispatch mouse to Dear ImGui + underlying app) // Update io.WantCaptureMouseAllowPopupClose (experimental) to give a chance for app to react to popup closure with a drag if (g.WantCaptureMouseNextFrame != -1) @@ -4772,6 +5469,7 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() io.WantCaptureMouse = (mouse_avail && (g.HoveredWindow != NULL || mouse_any_down)) || has_open_popup; io.WantCaptureMouseUnlessPopupClose = (mouse_avail_unless_popup_close && (g.HoveredWindow != NULL || mouse_any_down)) || has_open_modal; } + // Update io.WantCaptureKeyboard for the user application (true = dispatch keyboard info to Dear ImGui only, false = dispatch keyboard info to Dear ImGui + underlying app) io.WantCaptureKeyboard = false; if ((io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) == 0) @@ -4783,9 +5481,11 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() } if (g.WantCaptureKeyboardNextFrame != -1) // Manual override io.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0); + // Update io.WantTextInput flag, this is to allow systems without a keyboard (e.g. mobile, hand-held) to show a software keyboard if possible io.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false; } + // Called once a frame. Followed by SetCurrentFont() which sets up the remaining data. // FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal! static void SetupDrawListSharedData() @@ -4808,59 +5508,74 @@ static void SetupDrawListSharedData() g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset; g.DrawListSharedData.InitialFringeScale = 1.0f; // FIXME-DPI: Change this for some DPI scaling experiments. } + void ImGui::NewFrame() { IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?"); ImGuiContext& g = *GImGui; + // Remove pending delete hooks before frame start. // This deferred removal avoid issues of removal while iterating the hook vector for (int n = g.Hooks.Size - 1; n >= 0; n--) if (g.Hooks[n].Type == ImGuiContextHookType_PendingRemoval_) g.Hooks.erase(&g.Hooks[n]); + CallContextHooks(&g, ImGuiContextHookType_NewFramePre); + // Check and assert for various common IO and Configuration mistakes g.ConfigFlagsLastFrame = g.ConfigFlagsCurrFrame; ErrorCheckNewFrameSanityChecks(); g.ConfigFlagsCurrFrame = g.IO.ConfigFlags; + // Load settings on first frame, save settings when modified (after a delay) UpdateSettings(); + g.Time += g.IO.DeltaTime; - g.WithinFrameScope = true; g.FrameCount += 1; g.TooltipOverrideCount = 0; g.WindowsActiveCount = 0; g.MenusIdSubmittedThisFrame.resize(0); + // Calculate frame-rate for the user, as a purely luxurious feature g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx]; g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime; g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame); g.FramerateSecPerFrameCount = ImMin(g.FramerateSecPerFrameCount + 1, IM_ARRAYSIZE(g.FramerateSecPerFrame)); g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)g.FramerateSecPerFrameCount)) : FLT_MAX; + // Process input queue (trickle as many events as possible), turn events into writes to IO structure g.InputEventsTrail.resize(0); UpdateInputEvents(g.IO.ConfigInputTrickleEventQueue); + // Update viewports (after processing input queue, so io.MouseHoveredViewport is set) UpdateViewportsNewFrame(); + + // Update texture list (collect destroyed textures, etc.) + UpdateTexturesNewFrame(); + // Setup current font and draw list shared data - // FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal! - g.IO.Fonts->Locked = true; SetupDrawListSharedData(); - SetCurrentFont(GetDefaultFont()); - IM_ASSERT(g.Font->IsLoaded()); + UpdateFontsNewFrame(); + + g.WithinFrameScope = true; + // Mark rendering data as invalid to prevent user who may have a handle on it to use it. for (ImGuiViewportP* viewport : g.Viewports) { viewport->DrawData = NULL; viewport->DrawDataP.Valid = false; } + // Drag and drop keep the source ID alive so even if the source disappear our state is consistent if (g.DragDropActive && g.DragDropPayload.SourceId == g.ActiveId) KeepAliveID(g.DragDropPayload.SourceId); + // [DEBUG] if (!g.IO.ConfigDebugHighlightIdConflicts || !g.IO.KeyCtrl) // Count is locked while holding CTRL - g.DebugDrawIdConflicts = 0; + g.DebugDrawIdConflictsId = 0; if (g.IO.ConfigDebugHighlightIdConflicts && g.HoveredIdPreviousFrameItemCount > 1) - g.DebugDrawIdConflicts = g.HoveredIdPreviousFrame; + g.DebugDrawIdConflictsId = g.HoveredIdPreviousFrame; + // Update HoveredId data if (!g.HoveredIdPreviousFrame) g.HoveredIdTimer = 0.0f; @@ -4875,6 +5590,7 @@ void ImGui::NewFrame() g.HoveredId = 0; g.HoveredIdAllowOverlap = false; g.HoveredIdIsDisabled = false; + // Clear ActiveID if the item is not alive anymore. // In 1.87, the common most call to KeepAliveID() was moved from GetID() to ItemAdd(). // As a result, custom widget using ButtonBehavior() _without_ ItemAdd() need to call KeepAliveID() themselves. @@ -4883,6 +5599,7 @@ void ImGui::NewFrame() IMGUI_DEBUG_LOG_ACTIVEID("NewFrame(): ClearActiveID() because it isn't marked alive anymore!\n"); ClearActiveID(); } + // Update ActiveId data (clear reference to active widget if the widget isn't alive anymore) if (g.ActiveId) g.ActiveIdTimer += g.IO.DeltaTime; @@ -4901,6 +5618,7 @@ void ImGui::NewFrame() if (g.DeactivatedItemData.ElapseFrame < g.FrameCount) g.DeactivatedItemData.ID = 0; g.DeactivatedItemData.IsAlive = false; + // Record when we have been stationary as this state is preserved while over same item. // FIXME: The way this is expressed means user cannot alter HoverStationaryDelay during the frame to use varying values. // To allow this we should store HoverItemMaxStationaryTime+ID and perform the >= check in IsItemHovered() function. @@ -4912,6 +5630,7 @@ void ImGui::NewFrame() g.HoverWindowUnlockedStationaryId = g.HoveredWindow->ID; else if (g.HoveredWindow == NULL) g.HoverWindowUnlockedStationaryId = 0; + // Update hover delay for IsItemHovered() with delays and tooltips g.HoverItemDelayIdPreviousFrame = g.HoverItemDelayId; if (g.HoverItemDelayId != 0) @@ -4928,6 +5647,7 @@ void ImGui::NewFrame() if (g.HoverItemDelayClearTimer >= ImMax(0.25f, g.IO.DeltaTime * 2.0f)) // ~7 frames at 30 Hz + allow for low framerate g.HoverItemDelayTimer = g.HoverItemDelayClearTimer = 0.0f; // May want a decaying timer, in which case need to clamp at max first, based on max of caller last requested timer. } + // Drag and drop g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr; g.DragDropAcceptIdCurr = 0; @@ -4936,22 +5656,29 @@ void ImGui::NewFrame() g.DragDropWithinTarget = false; g.DragDropHoldJustPressedId = 0; g.TooltipPreviousWindow = NULL; + // Close popups on focus lost (currently wip/opt-in) //if (g.IO.AppFocusLost) // ClosePopupsExceptModals(); + // Update keyboard input state UpdateKeyboardInputs(); + //IM_ASSERT(g.IO.KeyCtrl == IsKeyDown(ImGuiKey_LeftCtrl) || IsKeyDown(ImGuiKey_RightCtrl)); //IM_ASSERT(g.IO.KeyShift == IsKeyDown(ImGuiKey_LeftShift) || IsKeyDown(ImGuiKey_RightShift)); //IM_ASSERT(g.IO.KeyAlt == IsKeyDown(ImGuiKey_LeftAlt) || IsKeyDown(ImGuiKey_RightAlt)); //IM_ASSERT(g.IO.KeySuper == IsKeyDown(ImGuiKey_LeftSuper) || IsKeyDown(ImGuiKey_RightSuper)); + // Update keyboard/gamepad navigation NavUpdate(); + // Update mouse input state UpdateMouseInputs(); + // Undocking // (needs to be before UpdateMouseMovingWindowNewFrame so the window is already offset and following the mouse on the detaching frame) DockContextNewFrameUpdateUndocking(&g); + // Mark all windows as not visible and compact unused memory. IM_ASSERT(g.WindowsFocusOrder.Size <= g.Windows.Size); const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f) ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer; @@ -4962,28 +5689,36 @@ void ImGui::NewFrame() window->WriteAccessed = false; window->BeginCountPreviousFrame = window->BeginCount; window->BeginCount = 0; + // Garbage collect transient buffers of recently unused windows if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time) GcCompactTransientWindowBuffers(window); } + // Find hovered window // (needs to be before UpdateMouseMovingWindowNewFrame so we fill g.HoveredWindowUnderMovingWindow on the mouse release frame) // (currently needs to be done after the WasActive=Active loop and FindHoveredWindowEx uses ->Active) - UpdateHoveredWindowAndCaptureFlags(); + UpdateHoveredWindowAndCaptureFlags(g.IO.MousePos); + // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering) UpdateMouseMovingWindowNewFrame(); + // Background darkening/whitening if (GetTopMostPopupModal() != NULL || (g.NavWindowingTarget != NULL && g.NavWindowingHighlightAlpha > 0.0f)) g.DimBgRatio = ImMin(g.DimBgRatio + g.IO.DeltaTime * 6.0f, 1.0f); else g.DimBgRatio = ImMax(g.DimBgRatio - g.IO.DeltaTime * 10.0f, 0.0f); + g.MouseCursor = ImGuiMouseCursor_Arrow; g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1; + // Platform IME data: reset for the frame g.PlatformImeDataPrev = g.PlatformImeData; - g.PlatformImeData.WantVisible = false; + g.PlatformImeData.WantVisible = g.PlatformImeData.WantTextInput = false; + // Mouse wheel scrolling, scale UpdateMouseWheel(); + // Garbage collect transient buffers of recently unused tables for (int i = 0; i < g.TablesLastTimeActive.Size; i++) if (g.TablesLastTimeActive[i] >= 0.0f && g.TablesLastTimeActive[i] < memory_compact_start_time) @@ -4994,9 +5729,11 @@ void ImGui::NewFrame() if (g.GcCompactAll) GcCompactTransientMiscBuffers(); g.GcCompactAll = false; + // Closing the focused window restore focus to the first active root window in descending z-order if (g.NavWindow && !g.NavWindow->WasActive) FocusTopMostWindowUnderOne(NULL, NULL, NULL, ImGuiFocusRequestFlags_RestoreFocusedChild); + // No window should be open at the beginning of the frame. // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear. g.CurrentWindowStack.resize(0); @@ -5005,8 +5742,10 @@ void ImGui::NewFrame() g.ItemFlagsStack.push_back(ImGuiItemFlags_AutoClosePopups); // Default flags g.CurrentItemFlags = g.ItemFlagsStack.back(); g.GroupStack.resize(0); + // Docking DockContextNewFrameUpdateDocking(&g); + // [DEBUG] Update debug features #ifndef IMGUI_DISABLE_DEBUG_TOOLS UpdateDebugToolItemPicker(); @@ -5024,6 +5763,7 @@ void ImGui::NewFrame() g.DebugLogAutoDisableFlags = ImGuiDebugLogFlags_None; } #endif + // Create implicit/fallback window - which we will only render it if the user has added something to it. // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags. // This fallback is particularly important as it prevents ImGui:: calls from crashing. @@ -5031,9 +5771,11 @@ void ImGui::NewFrame() SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver); Begin("Debug##Default"); IM_ASSERT(g.CurrentWindow->IsFallbackWindow == true); + // Store stack sizes g.ErrorCountCurrentFrame = 0; ErrorRecoveryStoreState(&g.StackSizesInNewFrame); + // [DEBUG] When io.ConfigDebugBeginReturnValue is set, we make Begin()/BeginChild() return false at different level of the window-stack, // allowing to validate correct Begin/End behavior in user code. #ifndef IMGUI_DISABLE_DEBUG_TOOLS @@ -5042,8 +5784,10 @@ void ImGui::NewFrame() else g.DebugBeginReturnValueCullDepth = -1; #endif + CallContextHooks(&g, ImGuiContextHookType_NewFramePost); } + // FIXME: Add a more explicit sort order in the window structure. static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs) { @@ -5055,6 +5799,7 @@ static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs) return d; return (a->BeginOrderWithinParent - b->BeginOrderWithinParent); } + static void AddWindowToSortBuffer(ImVector* out_sorted_windows, ImGuiWindow* window) { out_sorted_windows->push_back(window); @@ -5070,6 +5815,7 @@ static void AddWindowToSortBuffer(ImVector* out_sorted_windows, Im } } } + static void AddWindowToDrawData(ImGuiWindow* window, int layer) { ImGuiContext& g = *GImGui; @@ -5083,15 +5829,18 @@ static void AddWindowToDrawData(ImGuiWindow* window, int layer) if (IsWindowActiveAndVisible(child)) // Clipped children may have been marked not active AddWindowToDrawData(child, layer); } + static inline int GetWindowDisplayLayer(ImGuiWindow* window) { return (window->Flags & ImGuiWindowFlags_Tooltip) ? 1 : 0; } + // Layer is locked for the root window, however child windows may use a different viewport (e.g. extruding menu) static inline void AddRootWindowToDrawData(ImGuiWindow* window) { AddWindowToDrawData(window, GetWindowDisplayLayer(window)); } + static void FlattenDrawDataIntoSingleLayer(ImDrawDataBuilder* builder) { int n = builder->Layers[0]->Size; @@ -5109,36 +5858,42 @@ static void FlattenDrawDataIntoSingleLayer(ImDrawDataBuilder* builder) layer->resize(0); } } + static void InitViewportDrawData(ImGuiViewportP* viewport) { ImGuiIO& io = ImGui::GetIO(); ImDrawData* draw_data = &viewport->DrawDataP; + viewport->DrawData = draw_data; // Make publicly accessible viewport->DrawDataBuilder.Layers[0] = &draw_data->CmdLists; viewport->DrawDataBuilder.Layers[1] = &viewport->DrawDataBuilder.LayerData1; viewport->DrawDataBuilder.Layers[0]->resize(0); viewport->DrawDataBuilder.Layers[1]->resize(0); + // When minimized, we report draw_data->DisplaySize as zero to be consistent with non-viewport mode, // and to allow applications/backends to easily skip rendering. // FIXME: Note that we however do NOT attempt to report "zero drawlist / vertices" into the ImDrawData structure. // This is because the work has been done already, and its wasted! We should fix that and add optimizations for // it earlier in the pipeline, rather than pretend to hide the data at the end of the pipeline. const bool is_minimized = (viewport->Flags & ImGuiViewportFlags_IsMinimized) != 0; + draw_data->Valid = true; draw_data->CmdListsCount = 0; draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0; draw_data->DisplayPos = viewport->Pos; draw_data->DisplaySize = is_minimized ? ImVec2(0.0f, 0.0f) : viewport->Size; - draw_data->FramebufferScale = io.DisplayFramebufferScale; // FIXME-VIEWPORT: This may vary on a per-monitor/viewport basis? + draw_data->FramebufferScale = (viewport->FramebufferScale.x != 0.0f) ? viewport->FramebufferScale : io.DisplayFramebufferScale; draw_data->OwnerViewport = viewport; + draw_data->Textures = &ImGui::GetPlatformIO().Textures; } + // Push a clipping rectangle for both ImGui logic (hit-testing etc.) and low-level ImDrawList rendering. // - When using this function it is sane to ensure that float are perfectly rounded to integer values, // so that e.g. (int)(max.x-min.x) in user's render produce correct result. // - If the code here changes, may need to update code of functions like NextColumn() and PushColumnClipRect(): // some frequently called functions which to modify both channels and clipping simultaneously tend to use the // more specialized SetWindowClipRectBeforeSetChannel() to avoid extraneous updates of underlying ImDrawCmds. -// - This is analoguous to PushFont()/PopFont() in the sense that are a mixing a global stack and a window stack, +// - This is analogous to PushFont()/PopFont() in the sense that are a mixing a global stack and a window stack, // which in the case of ClipRect is not so problematic but tends to be more restrictive for fonts. void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect) { @@ -5146,12 +5901,14 @@ void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_ma window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect); window->ClipRect = window->DrawList->_ClipRectStack.back(); } + void ImGui::PopClipRect() { ImGuiWindow* window = GetCurrentWindow(); window->DrawList->PopClipRect(); window->ClipRect = window->DrawList->_ClipRectStack.back(); } + static ImGuiWindow* FindFrontMostVisibleChildWindow(ImGuiWindow* window) { for (int n = window->DC.ChildWindows.Size - 1; n >= 0; n--) @@ -5159,12 +5916,15 @@ static ImGuiWindow* FindFrontMostVisibleChildWindow(ImGuiWindow* window) return FindFrontMostVisibleChildWindow(window->DC.ChildWindows[n]); return window; } + static void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col) { if ((col & IM_COL32_A_MASK) == 0) return; + ImGuiViewportP* viewport = window->Viewport; ImRect viewport_rect = viewport->GetMainRect(); + // Draw behind window by moving the draw command at the FRONT of the draw list { // Draw list have been trimmed already, hence the explicit recreation of a draw command if missing. @@ -5182,6 +5942,7 @@ static void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 draw_list->AddDrawCmd(); // We need to create a command as CmdBuffer.back().IdxOffset won't be correct if we append to same command. draw_list->PopClipRect(); } + // Draw over sibling docking nodes in a same docking tree if (window->RootWindow->DockIsActive) { @@ -5194,6 +5955,7 @@ static void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 draw_list->PopClipRect(); } } + ImGuiWindow* ImGui::FindBottomMostVisibleWindowWithinBeginStack(ImGuiWindow* parent_window) { ImGuiContext& g = *GImGui; @@ -5210,6 +5972,7 @@ ImGuiWindow* ImGui::FindBottomMostVisibleWindowWithinBeginStack(ImGuiWindow* par } return bottom_most_visible_window; } + // Important: AddWindowToDrawData() has not been called yet, meaning DockNodeHost windows needs a DrawList->ChannelsMerge() before usage. // We call ChannelsMerge() lazily here at it is faster that doing a full iteration of g.Windows[] prior to calling RenderDimmedBackgrounds(). static void ImGui::RenderDimmedBackgrounds() @@ -5222,6 +5985,7 @@ static void ImGui::RenderDimmedBackgrounds() const bool dim_bg_for_window_list = (g.NavWindowingTargetAnim != NULL && g.NavWindowingTargetAnim->Active); if (!dim_bg_for_modal && !dim_bg_for_window_list) return; + ImGuiViewport* viewports_already_dimmed[2] = { NULL, NULL }; if (dim_bg_for_modal) { @@ -5238,6 +6002,7 @@ static void ImGui::RenderDimmedBackgrounds() RenderDimmedBackgroundBehindWindow(g.NavWindowingListWindow, GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio)); viewports_already_dimmed[0] = g.NavWindowingTargetAnim->Viewport; viewports_already_dimmed[1] = g.NavWindowingListWindow ? g.NavWindowingListWindow->Viewport : NULL; + // Draw border around CTRL+Tab target window ImGuiWindow* window = g.NavWindowingTargetAnim; ImGuiViewport* viewport = window->Viewport; @@ -5250,9 +6015,10 @@ static void ImGui::RenderDimmedBackgrounds() if (window->DrawList->CmdBuffer.Size == 0) window->DrawList->AddDrawCmd(); window->DrawList->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size); - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), window->WindowRounding, 0, 3.0f); + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), window->WindowRounding, 0, 3.0f); // FIXME-DPI window->DrawList->PopClipRect(); } + // Draw dimming background on _other_ viewports than the ones our windows are in for (ImGuiViewportP* viewport : g.Viewports) { @@ -5265,41 +6031,56 @@ static void ImGui::RenderDimmedBackgrounds() draw_list->AddRectFilled(viewport->Pos, viewport->Pos + viewport->Size, dim_bg_col); } } + // This is normally called by Render(). You may want to call it directly if you want to avoid calling Render() but the gain will be very minimal. void ImGui::EndFrame() { ImGuiContext& g = *GImGui; IM_ASSERT(g.Initialized); + // Don't process EndFrame() multiple times. if (g.FrameCountEnded == g.FrameCount) return; - IM_ASSERT(g.WithinFrameScope && "Forgot to call ImGui::NewFrame()?"); + if (!g.WithinFrameScope) + { + IM_ASSERT_USER_ERROR(g.WithinFrameScope, "Forgot to call ImGui::NewFrame()?"); + return; + } + CallContextHooks(&g, ImGuiContextHookType_EndFramePre); + // [EXPERIMENTAL] Recover from errors if (g.IO.ConfigErrorRecovery) ErrorRecoveryTryToRecoverState(&g.StackSizesInNewFrame); ErrorCheckEndFrameSanityChecks(); ErrorCheckEndFrameFinalizeErrorTooltip(); + // Notify Platform/OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME) ImGuiPlatformImeData* ime_data = &g.PlatformImeData; if (g.PlatformIO.Platform_SetImeDataFn != NULL && memcmp(ime_data, &g.PlatformImeDataPrev, sizeof(ImGuiPlatformImeData)) != 0) { - ImGuiViewport* viewport = FindViewportByID(g.PlatformImeViewport); + ImGuiViewport* viewport = FindViewportByID(ime_data->ViewportId); IMGUI_DEBUG_LOG_IO("[io] Calling Platform_SetImeDataFn(): WantVisible: %d, InputPos (%.2f,%.2f)\n", ime_data->WantVisible, ime_data->InputPos.x, ime_data->InputPos.y); if (viewport == NULL) viewport = GetMainViewport(); g.PlatformIO.Platform_SetImeDataFn(&g, viewport, ime_data); } + g.WantTextInputNextFrame = ime_data->WantTextInput ? 1 : 0; + // Hide implicit/fallback "Debug" window if it hasn't been used g.WithinFrameScopeWithImplicitWindow = false; if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed) g.CurrentWindow->Active = false; End(); + // Update navigation: CTRL+Tab, wrap-around requests NavEndFrame(); + // Update docking DockContextEndFrame(&g); + SetCurrentViewport(NULL, NULL); + // Drag and Drop: Elapse payload (if delivered, or if source stops being submitted) if (g.DragDropActive) { @@ -5308,6 +6089,7 @@ void ImGui::EndFrame() if (is_delivered || is_elapsed) ClearDragDrop(); } + // Drag and Drop: Fallback for missing source tooltip. This is not ideal but better than nothing. // If you want to handle source item disappearing: instead of submitting your description tooltip // in the BeginDragDropSource() block of the dragged item, you can submit them from a safe single spot @@ -5319,13 +6101,18 @@ void ImGui::EndFrame() SetTooltip("..."); g.DragDropWithinSource = false; } + // End frame g.WithinFrameScope = false; g.FrameCountEnded = g.FrameCount; + UpdateFontsEndFrame(); + // Initiate moving window + handle left-click and right-click focus UpdateMouseMovingWindowEndFrame(); + // Update user-facing viewport list (g.Viewports -> g.PlatformIO.Viewports after filtering out some) UpdateViewportsEndFrame(); + // Sort the window list so that all child windows are after their parent // We cannot do that on FocusWindow() because children may not exist yet g.WindowsTempSortBuffer.resize(0); @@ -5336,33 +6123,44 @@ void ImGui::EndFrame() continue; AddWindowToSortBuffer(&g.WindowsTempSortBuffer, window); } + // This usually assert if there is a mismatch between the ImGuiWindowFlags_ChildWindow / ParentWindow values and DC.ChildWindows[] in parents, aka we've done something wrong. IM_ASSERT(g.Windows.Size == g.WindowsTempSortBuffer.Size); g.Windows.swap(g.WindowsTempSortBuffer); g.IO.MetricsActiveWindows = g.WindowsActiveCount; + + UpdateTexturesEndFrame(); + // Unlock font atlas - g.IO.Fonts->Locked = false; + for (ImFontAtlas* atlas : g.FontAtlases) + atlas->Locked = false; + // Clear Input data for next frame g.IO.MousePosPrev = g.IO.MousePos; g.IO.AppFocusLost = false; g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f; g.IO.InputQueueCharacters.resize(0); + CallContextHooks(&g, ImGuiContextHookType_EndFramePost); } + // Prepare the data for rendering so you can call GetDrawData() -// (As with anything within the ImGui:: namspace this doesn't touch your GPU or graphics API at all: +// (As with anything within the ImGui:: namespace this doesn't touch your GPU or graphics API at all: // it is the role of the ImGui_ImplXXXX_RenderDrawData() function provided by the renderer backend) void ImGui::Render() { ImGuiContext& g = *GImGui; IM_ASSERT(g.Initialized); + if (g.FrameCountEnded != g.FrameCount) EndFrame(); if (g.FrameCountRendered == g.FrameCount) return; g.FrameCountRendered = g.FrameCount; + g.IO.MetricsRenderWindows = 0; CallContextHooks(&g, ImGuiContextHookType_RenderPre); + // Add background ImDrawList (for each active viewport) for (ImGuiViewportP* viewport : g.Viewports) { @@ -5370,8 +6168,10 @@ void ImGui::Render() if (viewport->BgFgDrawLists[0] != NULL) AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0], GetBackgroundDrawList(viewport)); } + // Draw modal/window whitening backgrounds RenderDimmedBackgrounds(); + // Add ImDrawList to render ImGuiWindow* windows_to_render_top_most[2]; windows_to_render_top_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindowDockTree : NULL; @@ -5385,50 +6185,68 @@ void ImGui::Render() for (int n = 0; n < IM_ARRAYSIZE(windows_to_render_top_most); n++) if (windows_to_render_top_most[n] && IsWindowActiveAndVisible(windows_to_render_top_most[n])) // NavWindowingTarget is always temporarily displayed as the top-most window AddRootWindowToDrawData(windows_to_render_top_most[n]); + // Draw software mouse cursor if requested by io.MouseDrawCursor flag if (g.IO.MouseDrawCursor && g.MouseCursor != ImGuiMouseCursor_None) RenderMouseCursor(g.IO.MousePos, g.Style.MouseCursorScale, g.MouseCursor, IM_COL32_WHITE, IM_COL32_BLACK, IM_COL32(0, 0, 0, 48)); + // Setup ImDrawData structures for end-user g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = 0; for (ImGuiViewportP* viewport : g.Viewports) { FlattenDrawDataIntoSingleLayer(&viewport->DrawDataBuilder); + // Add foreground ImDrawList (for each active viewport) if (viewport->BgFgDrawLists[1] != NULL) AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0], GetForegroundDrawList(viewport)); + // We call _PopUnusedDrawCmd() last thing, as RenderDimmedBackgrounds() rely on a valid command being there (especially in docking branch). ImDrawData* draw_data = &viewport->DrawDataP; IM_ASSERT(draw_data->CmdLists.Size == draw_data->CmdListsCount); for (ImDrawList* draw_list : draw_data->CmdLists) draw_list->_PopUnusedDrawCmd(); + g.IO.MetricsRenderVertices += draw_data->TotalVtxCount; g.IO.MetricsRenderIndices += draw_data->TotalIdxCount; } + +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) + for (ImFontAtlas* atlas : g.FontAtlases) + ImFontAtlasDebugLogTextureRequests(atlas); +#endif + CallContextHooks(&g, ImGuiContextHookType_RenderPost); } + // Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker. // CalcTextSize("") should return ImVec2(0.0f, g.FontSize) ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width) { ImGuiContext& g = *GImGui; + const char* text_display_end; if (hide_text_after_double_hash) text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string else text_display_end = text_end; + ImFont* font = g.Font; const float font_size = g.FontSize; if (text == text_display_end) return ImVec2(0.0f, font_size); ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL); + // Round // FIXME: This has been here since Dec 2015 (7b0bf230) but down the line we want this out. // FIXME: Investigate using ceilf or e.g. // - https://git.musl-libc.org/cgit/musl/tree/src/math/ceilf.c // - https://embarkstudios.github.io/rust-gpu/api/src/libm/math/ceilf.rs.html text_size.x = IM_TRUNC(text_size.x + 0.99999f); + return text_size; } + // Find window given position, search front-to-back // - Typically write output back to g.HoveredWindow and g.HoveredWindowUnderMovingWindow. // - FIXME: Note that we have an inconsequential lag here: OuterRectClipped is updated in Begin(), so windows moved programmatically @@ -5440,6 +6258,7 @@ void ImGui::FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_vi ImGuiContext& g = *GImGui; ImGuiWindow* hovered_window = NULL; ImGuiWindow* hovered_window_under_moving_window = NULL; + // Special handling for the window being moved: Ignore the mouse viewport check (because it may reset/lose its viewport during the undocking frame) ImGuiViewportP* backup_moving_window_viewport = NULL; if (find_first_and_in_any_viewport == false && g.MovingWindow) @@ -5449,6 +6268,7 @@ void ImGui::FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_vi if (!(g.MovingWindow->Flags & ImGuiWindowFlags_NoMouseInputs)) hovered_window = g.MovingWindow; } + ImVec2 padding_regular = g.Style.TouchExtraPadding; ImVec2 padding_for_resize = ImMax(g.Style.TouchExtraPadding, ImVec2(g.Style.WindowBorderHoverPadding, g.Style.WindowBorderHoverPadding)); for (int i = g.Windows.Size - 1; i >= 0; i--) @@ -5462,10 +6282,12 @@ void ImGui::FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_vi IM_ASSERT(window->Viewport); if (window->Viewport != g.MouseViewport) continue; + // Using the clipped AABB, a child window will typically be clipped by its parent (not always) ImVec2 hit_padding = (window->Flags & (ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) ? padding_regular : padding_for_resize; if (!window->OuterRectClipped.ContainsWithPad(pos, hit_padding)) continue; + // Support for one rectangular hole in any given window // FIXME: Consider generalizing hit-testing override (with more generic data, callback, etc.) (#1512) if (window->HitTestHoleSize.x != 0) @@ -5475,6 +6297,7 @@ void ImGui::FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_vi if (ImRect(hole_pos, hole_pos + hole_size).Contains(pos)) continue; } + if (find_first_and_in_any_viewport) { hovered_window = window; @@ -5491,12 +6314,14 @@ void ImGui::FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_vi break; } } + *out_hovered_window = hovered_window; if (out_hovered_window_under_moving_window != NULL) *out_hovered_window_under_moving_window = hovered_window_under_moving_window; if (find_first_and_in_any_viewport == false && g.MovingWindow) g.MovingWindow->Viewport = backup_moving_window_viewport; } + bool ImGui::IsItemActive() { ImGuiContext& g = *GImGui; @@ -5504,6 +6329,7 @@ bool ImGui::IsItemActive() return g.ActiveId == g.LastItemData.ID; return false; } + bool ImGui::IsItemActivated() { ImGuiContext& g = *GImGui; @@ -5512,6 +6338,7 @@ bool ImGui::IsItemActivated() return true; return false; } + bool ImGui::IsItemDeactivated() { ImGuiContext& g = *GImGui; @@ -5519,35 +6346,42 @@ bool ImGui::IsItemDeactivated() return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Deactivated) != 0; return (g.DeactivatedItemData.ID == g.LastItemData.ID && g.LastItemData.ID != 0 && g.DeactivatedItemData.ElapseFrame >= g.FrameCount); } + bool ImGui::IsItemDeactivatedAfterEdit() { ImGuiContext& g = *GImGui; return IsItemDeactivated() && g.DeactivatedItemData.HasBeenEditedBefore; } + // == (GetItemID() == GetFocusID() && GetFocusID() != 0) bool ImGui::IsItemFocused() { ImGuiContext& g = *GImGui; if (g.NavId != g.LastItemData.ID || g.NavId == 0) return false; + // Special handling for the dummy item after Begin() which represent the title bar or tab. // When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case. ImGuiWindow* window = g.CurrentWindow; if (g.LastItemData.ID == window->ID && window->WriteAccessed) return false; + return true; } + // Important: this can be useful but it is NOT equivalent to the behavior of e.g.Button()! // Most widgets have specific reactions based on mouse-up/down state, mouse position etc. bool ImGui::IsItemClicked(ImGuiMouseButton mouse_button) { return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_None); } + bool ImGui::IsItemToggledOpen() { ImGuiContext& g = *GImGui; return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledOpen) ? true : false; } + // Call after a Selectable() or TreeNode() involved in multi-selection. // Useful if you need the per-item information before reaching EndMultiSelect(), e.g. for rendering purpose. // This is only meant to be called inside a BeginMultiSelect()/EndMultiSelect() block. @@ -5559,6 +6393,7 @@ bool ImGui::IsItemToggledSelection() IM_ASSERT(g.CurrentMultiSelect != NULL); // Can only be used inside a BeginMultiSelect()/EndMultiSelect() return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledSelection) ? true : false; } + // IMPORTANT: If you are trying to check whether your mouse should be dispatched to Dear ImGui or to your underlying app, // you should not use this function! Use the 'io.WantCaptureMouse' boolean for that! // Refer to FAQ entry "How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" for details. @@ -5567,26 +6402,31 @@ bool ImGui::IsAnyItemHovered() ImGuiContext& g = *GImGui; return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0; } + bool ImGui::IsAnyItemActive() { ImGuiContext& g = *GImGui; return g.ActiveId != 0; } + bool ImGui::IsAnyItemFocused() { ImGuiContext& g = *GImGui; return g.NavId != 0 && g.NavCursorVisible; } + bool ImGui::IsItemVisible() { ImGuiContext& g = *GImGui; return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) != 0; } + bool ImGui::IsItemEdited() { ImGuiContext& g = *GImGui; return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Edited) != 0; } + // Allow next item to be overlapped by subsequent items. // This works by requiring HoveredId to match for two subsequent frames, // so if a following items overwrite it our interactions will naturally be disabled. @@ -5595,6 +6435,7 @@ void ImGui::SetNextItemAllowOverlap() ImGuiContext& g = *GImGui; g.NextItemData.ItemFlags |= ImGuiItemFlags_AllowOverlap; } + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority. // FIXME-LEGACY: Use SetNextItemAllowOverlap() *before* your item instead. @@ -5608,6 +6449,7 @@ void ImGui::SetItemAllowOverlap() g.ActiveIdAllowOverlap = true; } #endif + // This is a shortcut for not taking ownership of 100+ keys, frequently used by drag operations. // FIXME: It might be undesirable that this will likely disable KeyOwner-aware shortcuts systems. Consider a more fine-tuned version if needed? void ImGui::SetActiveIdUsingAllKeyboardKeys() @@ -5618,26 +6460,31 @@ void ImGui::SetActiveIdUsingAllKeyboardKeys() g.ActiveIdUsingAllKeyboardKeys = true; NavMoveRequestCancel(); } + ImGuiID ImGui::GetItemID() { ImGuiContext& g = *GImGui; return g.LastItemData.ID; } + ImVec2 ImGui::GetItemRectMin() { ImGuiContext& g = *GImGui; return g.LastItemData.Rect.Min; } + ImVec2 ImGui::GetItemRectMax() { ImGuiContext& g = *GImGui; return g.LastItemData.Rect.Max; } + ImVec2 ImGui::GetItemRectSize() { ImGuiContext& g = *GImGui; return g.LastItemData.Rect.GetSize(); } + // Prior to v1.90 2023/10/16, the BeginChild() function took a 'bool border = false' parameter instead of 'ImGuiChildFlags child_flags = 0'. // ImGuiChildFlags_Borders is defined as always == 1 in order to allow old code passing 'true'. Read comments in imgui.h for details! bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags) @@ -5645,15 +6492,18 @@ bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, ImGuiChildFla ImGuiID id = GetCurrentWindow()->GetID(str_id); return BeginChildEx(str_id, id, size_arg, child_flags, window_flags); } + bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags) { return BeginChildEx(NULL, id, size_arg, child_flags, window_flags); } + bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags) { ImGuiContext& g = *GImGui; ImGuiWindow* parent_window = g.CurrentWindow; IM_ASSERT(id != 0); + // Sanity check as it is likely that some user will accidentally pass ImGuiWindowFlags into the ImGuiChildFlags argument. const ImGuiChildFlags ImGuiChildFlags_SupportedMask_ = ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle | ImGuiChildFlags_NavFlattened; IM_UNUSED(ImGuiChildFlags_SupportedMask_); @@ -5674,6 +6524,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I child_flags &= ~ImGuiChildFlags_ResizeX; if (child_flags & ImGuiChildFlags_AutoResizeY) child_flags &= ~ImGuiChildFlags_ResizeY; + // Set window flags window_flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDocking; window_flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag @@ -5681,6 +6532,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I window_flags |= ImGuiWindowFlags_AlwaysAutoResize; if ((child_flags & (ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY)) == 0) window_flags |= ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings; + // Special framed style if (child_flags & ImGuiChildFlags_FrameStyle) { @@ -5691,12 +6543,14 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I child_flags |= ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding; window_flags |= ImGuiWindowFlags_NoMove; } + // Forward size // Important: Begin() has special processing to switch condition to ImGuiCond_FirstUseEver for a given axis when ImGuiChildFlags_ResizeXXX is set. // (the alternative would to store conditional flags per axis, which is possible but more code) const ImVec2 size_avail = GetContentRegionAvail(); const ImVec2 size_default((child_flags & ImGuiChildFlags_AutoResizeX) ? 0.0f : size_avail.x, (child_flags & ImGuiChildFlags_AutoResizeY) ? 0.0f : size_avail.y); ImVec2 size = CalcItemSize(size_arg, size_default.x, size_default.y); + // A SetNextWindowSize() call always has priority (#8020) // (since the code in Begin() never supported SizeVal==0.0f aka auto-resize via SetNextWindowSize() call, we don't support it here for now) // FIXME: We only support ImGuiCond_Always in this path. Supporting other paths would requires to obtain window pointer. @@ -5714,12 +6568,14 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I } } SetNextWindowSize(size); + // Forward child flags (we allow prior settings to merge but it'll only work for adding flags) if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) g.NextWindowData.ChildFlags |= child_flags; else g.NextWindowData.ChildFlags = child_flags; g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasChildFlags; + // Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value. // FIXME: 2023/11/14: commented out shorted version. We had an issue with multiple ### in child window path names, which the trailing hash helped workaround. // e.g. "ParentName###ParentIdentifier/ChildName###ChildIdentifier" would get hashed incorrectly by ImHashStr(), trailing _%08X somehow fixes it. @@ -5731,12 +6587,15 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I ImFormatStringToTempBuffer(&temp_window_name, NULL, "%s/%s_%08X", parent_window->Name, name, id); else ImFormatStringToTempBuffer(&temp_window_name, NULL, "%s/%08X", parent_window->Name, id); + // Set style const float backup_border_size = g.Style.ChildBorderSize; if ((child_flags & ImGuiChildFlags_Borders) == 0) g.Style.ChildBorderSize = 0.0f; + // Begin into window const bool ret = Begin(temp_window_name, NULL, window_flags); + // Restore style g.Style.ChildBorderSize = backup_border_size; if (child_flags & ImGuiChildFlags_FrameStyle) @@ -5744,12 +6603,15 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I PopStyleVar(3); PopStyleColor(); } + ImGuiWindow* child_window = g.CurrentWindow; child_window->ChildId = id; + // Set the cursor to handle case where the user called SetNextWindowPos()+BeginChild() manually. // While this is not really documented/defined, it seems that the expected thing to do. if (child_window->BeginCount == 1) parent_window->DC.CursorPos = child_window->Pos; + // Process navigation-in immediately so NavInit can run on first frame // Can enter a child if (A) it has navigable items or (B) it can be scrolled. const ImGuiID temp_id_for_activation = ImHashStr("##Child", 0, id); @@ -5764,12 +6626,15 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I } return ret; } + void ImGui::EndChild() { ImGuiContext& g = *GImGui; ImGuiWindow* child_window = g.CurrentWindow; + const ImGuiID backup_within_end_child_id = g.WithinEndChildID; IM_ASSERT(child_window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() calls + g.WithinEndChildID = child_window->ID; ImVec2 child_size = child_window->Size; End(); @@ -5783,6 +6648,7 @@ void ImGui::EndChild() { ItemAdd(bb, child_window->ChildId); RenderNavCursor(bb, child_window->ChildId); + // When browsing a window that has no activable items (scroll only) we keep a highlight on the child (pass g.NavId to trick into always displaying) if (child_window->DC.NavLayersActiveMask == 0 && child_window == g.NavWindow) RenderNavCursor(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId, ImGuiNavRenderCursorFlags_Compact); @@ -5793,6 +6659,7 @@ void ImGui::EndChild() // - This is a bit of a fringe use case, mostly useful for undecorated, non-scrolling contents childs, or empty childs. // - We could later decide to not apply this path if ImGuiChildFlags_FrameStyle or ImGuiChildFlags_Borders is set. ItemAdd(bb, child_window->ChildId, NULL, ImGuiItemFlags_NoNav); + // But when flattened we directly reach items, adjust active layer mask accordingly if (nav_flattened) parent_window->DC.NavLayersActiveMaskNext |= child_window->DC.NavLayersActiveMaskNext; @@ -5806,9 +6673,11 @@ void ImGui::EndChild() { SetLastItemDataForChildWindowItem(child_window, child_window->Rect()); } + g.WithinEndChildID = backup_within_end_child_id; g.LogLinePosY = -FLT_MAX; // To enforce a carriage return } + static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled) { window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags); @@ -5816,16 +6685,19 @@ static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, b window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags); window->SetWindowDockAllowFlags = enabled ? (window->SetWindowDockAllowFlags | flags) : (window->SetWindowDockAllowFlags & ~flags); } + ImGuiWindow* ImGui::FindWindowByID(ImGuiID id) { ImGuiContext& g = *GImGui; return (ImGuiWindow*)g.WindowsById.GetVoidPtr(id); } + ImGuiWindow* ImGui::FindWindowByName(const char* name) { ImGuiID id = ImHashStr(name); return FindWindowByID(id); } + static void ApplyWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings) { const ImGuiViewport* main_viewport = ImGui::GetMainViewport(); @@ -5842,6 +6714,7 @@ static void ApplyWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settin window->DockId = settings->DockId; window->DockOrder = settings->DockOrder; } + static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings) { // Initial window state with e.g. default/arbitrary window position @@ -5851,12 +6724,14 @@ static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* s window->Size = window->SizeFull = ImVec2(0, 0); window->ViewportPos = main_viewport->Pos; window->SetWindowPosAllowFlags = window->SetWindowSizeAllowFlags = window->SetWindowCollapsedAllowFlags = window->SetWindowDockAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; + if (settings != NULL) { SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); ApplyWindowSettings(window, settings); } window->DC.CursorStartPos = window->DC.CursorMaxPos = window->DC.IdealMaxPos = window->Pos; // So first call to CalcWindowContentSizes() doesn't return crazy values + if ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) { window->AutoFitFramesX = window->AutoFitFramesY = 2; @@ -5871,6 +6746,7 @@ static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* s window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); } } + static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags) { // Create window the first time @@ -5879,25 +6755,32 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags) ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name); window->Flags = flags; g.WindowsById.SetVoidPtr(window->ID, window); + ImGuiWindowSettings* settings = NULL; if (!(flags & ImGuiWindowFlags_NoSavedSettings)) if ((settings = ImGui::FindWindowSettingsByWindow(window)) != 0) window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings); + InitOrLoadWindowSettings(window, settings); + if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus) g.Windows.push_front(window); // Quite slow but rare and only once else g.Windows.push_back(window); + return window; } + static ImGuiWindow* GetWindowForTitleDisplay(ImGuiWindow* window) { return window->DockNodeAsHost ? window->DockNodeAsHost->VisibleWindow : window; } + static ImGuiWindow* GetWindowForTitleAndMenuHeight(ImGuiWindow* window) { return (window->DockNodeAsHost && window->DockNodeAsHost->VisibleWindow) ? window->DockNodeAsHost->VisibleWindow : window; } + static inline ImVec2 CalcWindowMinSize(ImGuiWindow* window) { // We give windows non-zero minimum size to facilitate understanding problematic cases (e.g. empty popups) @@ -5915,11 +6798,13 @@ static inline ImVec2 CalcWindowMinSize(ImGuiWindow* window) size_min.x = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.x : 4.0f; size_min.y = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.y : 4.0f; } + // Reduce artifacts with very small windows ImGuiWindow* window_for_height = GetWindowForTitleAndMenuHeight(window); size_min.y = ImMax(size_min.y, window_for_height->TitleBarHeight + window_for_height->MenuBarHeight + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); return size_min; } + static ImVec2 CalcWindowSizeAfterConstraint(ImGuiWindow* window, const ImVec2& size_desired) { ImGuiContext& g = *GImGui; @@ -5943,10 +6828,12 @@ static ImVec2 CalcWindowSizeAfterConstraint(ImGuiWindow* window, const ImVec2& s new_size.x = IM_TRUNC(new_size.x); new_size.y = IM_TRUNC(new_size.y); } + // Minimum size ImVec2 size_min = CalcWindowMinSize(window); return ImMax(new_size, size_min); } + static void CalcWindowContentSizes(ImGuiWindow* window, ImVec2* content_size_current, ImVec2* content_size_ideal) { bool preserve_old_content_sizes = false; @@ -5960,11 +6847,13 @@ static void CalcWindowContentSizes(ImGuiWindow* window, ImVec2* content_size_cur *content_size_ideal = window->ContentSizeIdeal; return; } - content_size_current->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : IM_TRUNC(window->DC.CursorMaxPos.x - window->DC.CursorStartPos.x); - content_size_current->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : IM_TRUNC(window->DC.CursorMaxPos.y - window->DC.CursorStartPos.y); - content_size_ideal->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : IM_TRUNC(ImMax(window->DC.CursorMaxPos.x, window->DC.IdealMaxPos.x) - window->DC.CursorStartPos.x); - content_size_ideal->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : IM_TRUNC(ImMax(window->DC.CursorMaxPos.y, window->DC.IdealMaxPos.y) - window->DC.CursorStartPos.y); + + content_size_current->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : ImTrunc64(window->DC.CursorMaxPos.x - window->DC.CursorStartPos.x); + content_size_current->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : ImTrunc64(window->DC.CursorMaxPos.y - window->DC.CursorStartPos.y); + content_size_ideal->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : ImTrunc64(ImMax(window->DC.CursorMaxPos.x, window->DC.IdealMaxPos.x) - window->DC.CursorStartPos.x); + content_size_ideal->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : ImTrunc64(ImMax(window->DC.CursorMaxPos.y, window->DC.IdealMaxPos.y) - window->DC.CursorStartPos.y); } + static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_contents) { ImGuiContext& g = *GImGui; @@ -5973,33 +6862,37 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont const float decoration_h_without_scrollbars = window->DecoOuterSizeY1 + window->DecoOuterSizeY2 - window->ScrollbarSizes.y; ImVec2 size_pad = window->WindowPadding * 2.0f; ImVec2 size_desired = size_contents + size_pad + ImVec2(decoration_w_without_scrollbars, decoration_h_without_scrollbars); + + // Determine maximum window size + // Child windows are layed within their parent (unless they are also popups/menus) and thus have no restriction + ImVec2 size_max = ImVec2(FLT_MAX, FLT_MAX); + if ((window->Flags & ImGuiWindowFlags_ChildWindow) == 0 || (window->Flags & ImGuiWindowFlags_Popup) != 0) + { + if (!window->ViewportOwned) + size_max = ImGui::GetMainViewport()->WorkSize - style.DisplaySafeAreaPadding * 2.0f; + const int monitor_idx = window->ViewportAllowPlatformMonitorExtend; + if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size) + size_max = g.PlatformIO.Monitors[monitor_idx].WorkSize - style.DisplaySafeAreaPadding * 2.0f; + } + if (window->Flags & ImGuiWindowFlags_Tooltip) { - // Tooltip always resize - return size_desired; + // Tooltip always resize (up to maximum size) + return ImMin(size_desired, size_max); } else { - // Maximum window size is determined by the viewport size or monitor size ImVec2 size_min = CalcWindowMinSize(window); - ImVec2 size_max = ImVec2(FLT_MAX, FLT_MAX); - // Child windows are layed within their parent (unless they are also popups/menus) and thus have no restriction - if ((window->Flags & ImGuiWindowFlags_ChildWindow) == 0 || (window->Flags & ImGuiWindowFlags_Popup) != 0) - { - if (!window->ViewportOwned) - size_max = ImGui::GetMainViewport()->WorkSize - style.DisplaySafeAreaPadding * 2.0f; - const int monitor_idx = window->ViewportAllowPlatformMonitorExtend; - if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size) - size_max = g.PlatformIO.Monitors[monitor_idx].WorkSize - style.DisplaySafeAreaPadding * 2.0f; - } - ImVec2 size_auto_fit = ImClamp(size_desired, size_min, ImMax(size_min, size_max)); + ImVec2 size_auto_fit = ImClamp(size_desired, ImMin(size_min, size_max), size_max); + // FIXME: CalcWindowAutoFitSize() doesn't take into account that only one axis may be auto-fit when calculating scrollbars, // we may need to compute/store three variants of size_auto_fit, for x/y/xy. // Here we implement a workaround for child windows only, but a full solution would apply to normal windows as well: - if ((window->ChildFlags & ImGuiChildFlags_ResizeX) && !(window->ChildFlags & ImGuiChildFlags_ResizeY)) + if ((window->ChildFlags & ImGuiChildFlags_ResizeX) && !(window->ChildFlags & (ImGuiChildFlags_ResizeY | ImGuiChildFlags_AutoResizeY))) size_auto_fit.y = window->SizeFull.y; - else if (!(window->ChildFlags & ImGuiChildFlags_ResizeX) && (window->ChildFlags & ImGuiChildFlags_ResizeY)) + else if ((window->ChildFlags & ImGuiChildFlags_ResizeY) && !(window->ChildFlags & (ImGuiChildFlags_ResizeX | ImGuiChildFlags_AutoResizeX))) size_auto_fit.x = window->SizeFull.x; + // When the window cannot fit all contents (either because of constraints, either because screen is too small), // we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than ViewportSize-WindowPadding. ImVec2 size_auto_fit_after_constraint = CalcWindowSizeAfterConstraint(window, size_auto_fit); @@ -6012,6 +6905,7 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont return size_auto_fit; } } + ImVec2 ImGui::CalcWindowNextAutoFitSize(ImGuiWindow* window) { ImVec2 size_contents_current; @@ -6021,6 +6915,7 @@ ImVec2 ImGui::CalcWindowNextAutoFitSize(ImGuiWindow* window) ImVec2 size_final = CalcWindowSizeAfterConstraint(window, size_auto_fit); return size_final; } + static ImGuiCol GetWindowBgColorIdx(ImGuiWindow* window) { if (window->Flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) @@ -6029,6 +6924,7 @@ static ImGuiCol GetWindowBgColorIdx(ImGuiWindow* window) return ImGuiCol_ChildBg; return ImGuiCol_WindowBg; } + static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size) { ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left @@ -6042,6 +6938,7 @@ static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& co out_pos->y -= (size_constrained.y - size_expected.y); *out_size = size_constrained; } + // Data for resizing from resize grip / corner struct ImGuiResizeGripDef { @@ -6056,6 +6953,7 @@ static const ImGuiResizeGripDef resize_grip_def[4] = { ImVec2(0, 0), ImVec2(+1, +1), 6, 9 }, // Upper-left (Unused) { ImVec2(1, 0), ImVec2(-1, +1), 9, 12 } // Upper-right (Unused) }; + // Data for resizing from borders struct ImGuiResizeBorderDef { @@ -6070,6 +6968,7 @@ static const ImGuiResizeBorderDef resize_border_def[4] = { ImVec2(0, +1), ImVec2(0, 0), ImVec2(1, 0), IM_PI * 1.50f }, // Up { ImVec2(0, -1), ImVec2(1, 1), ImVec2(0, 1), IM_PI * 0.50f } // Down }; + static ImRect GetResizeBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness) { ImRect rect = window->Rect(); @@ -6082,6 +6981,7 @@ static ImRect GetResizeBorderRect(ImGuiWindow* window, int border_n, float perp_ IM_ASSERT(0); return ImRect(); } + // 0..3: corners (Lower-right, Lower-left, Unused, Unused) ImGuiID ImGui::GetWindowResizeCornerID(ImGuiWindow* window, int n) { @@ -6091,6 +6991,7 @@ ImGuiID ImGui::GetWindowResizeCornerID(ImGuiWindow* window, int n) id = ImHashData(&n, sizeof(int), id); return id; } + // Borders (Left, Right, Up, Down) ImGuiID ImGui::GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir) { @@ -6101,26 +7002,34 @@ ImGuiID ImGui::GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir) id = ImHashData(&n, sizeof(int), id); return id; } + // Handle resize for: Resize Grips, Borders, Gamepad // Return true when using auto-fit (double-click on resize grip) static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_hovered, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect) { ImGuiContext& g = *GImGui; ImGuiWindowFlags flags = window->Flags; - if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + + if ((flags & ImGuiWindowFlags_NoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + return false; + if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && (window->ChildFlags & (ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY)) == 0) return false; if (window->WasActive == false) // Early out to avoid running this code for e.g. a hidden implicit/fallback Debug window. return false; + int ret_auto_fit_mask = 0x00; const float grip_draw_size = IM_TRUNC(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f)); const float grip_hover_inner_size = (resize_grip_count > 0) ? IM_TRUNC(grip_draw_size * 0.75f) : 0.0f; const float grip_hover_outer_size = g.WindowsBorderHoverPadding; + ImRect clamp_rect = visibility_rect; const bool window_move_from_title_bar = g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar); if (window_move_from_title_bar) clamp_rect.Min.y -= window->TitleBarHeight; + ImVec2 pos_target(FLT_MAX, FLT_MAX); ImVec2 size_target(FLT_MAX, FLT_MAX); + // Clip mouse interaction rectangles within the viewport rectangle (in practice the narrowing is going to happen most of the time). // - Not narrowing would mostly benefit the situation where OS windows _without_ decoration have a threshold for hovering when outside their limits. // This is however not the case with current backends under Win32, but a custom borderless window implementation would benefit from it. @@ -6130,14 +7039,17 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si const bool clip_with_viewport_rect = !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport) || (g.IO.MouseHoveredViewport != window->ViewportId) || !(window->Viewport->Flags & ImGuiViewportFlags_NoDecoration); if (clip_with_viewport_rect) window->ClipRect = window->Viewport->GetMainRect(); + // Resize grips and borders are on layer 1 window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; + // Manual resize grips PushID("#RESIZE"); for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) { const ImGuiResizeGripDef& def = resize_grip_def[resize_grip_n]; const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, def.CornerPosN); + // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window bool hovered, held; ImRect resize_rect(corner - def.InnerDir * grip_hover_outer_size, corner + def.InnerDir * grip_hover_inner_size); @@ -6149,6 +7061,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si //GetForegroundDrawList(window)->AddRect(resize_rect.Min, resize_rect.Max, IM_COL32(255, 255, 0, 255)); if (hovered || held) SetMouseCursor((resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE); + if (held && g.IO.MouseDoubleClicked[0]) { // Auto-fit when double-clicking @@ -6166,10 +7079,12 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si corner_target = ImClamp(corner_target, clamp_min, clamp_max); CalcResizePosSizeFromAnyCorner(window, corner_target, def.CornerPosN, &pos_target, &size_target); } + // Only lower-left grip is visible before hovering/activating if (resize_grip_n == 0 || held || hovered) resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); } + int resize_border_mask = 0x00; if (window->Flags & ImGuiWindowFlags_ChildWindow) resize_border_mask |= ((window->ChildFlags & ImGuiChildFlags_ResizeX) ? 0x02 : 0) | ((window->ChildFlags & ImGuiChildFlags_ResizeY) ? 0x08 : 0); @@ -6181,6 +7096,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si continue; const ImGuiResizeBorderDef& def = resize_border_def[border_n]; const ImGuiAxis axis = (border_n == ImGuiDir_Left || border_n == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y; + bool hovered, held; ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, g.WindowsBorderHoverPadding); ImGuiID border_id = window->GetID(border_n + 4); // == GetWindowResizeBorderID() @@ -6217,12 +7133,15 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si } if ((window->Flags & ImGuiWindowFlags_ChildWindow) && memcmp(&g.WindowResizeBorderExpectedRect, &border_rect, sizeof(ImRect)) != 0) g.WindowResizeRelativeMode = true; + const ImVec2 border_curr = (window->Pos + ImMin(def.SegmentN1, def.SegmentN2) * window->Size); const float border_target_rel_mode_for_axis = border_curr[axis] + g.IO.MouseDelta[axis]; const float border_target_abs_mode_for_axis = g.IO.MousePos[axis] - g.ActiveIdClickOffset[axis] + g.WindowsBorderHoverPadding; // Match ButtonBehavior() padding above. + // Use absolute mode position ImVec2 border_target = window->Pos; border_target[axis] = border_target_abs_mode_for_axis; + // Use relative mode target for child window, ignore resize when moving back toward the ideal absolute position. bool ignore_resize = false; if (g.WindowResizeRelativeMode) @@ -6232,6 +7151,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si if (g.IO.MouseDelta[axis] == 0.0f || (g.IO.MouseDelta[axis] > 0.0f) == (border_target_rel_mode_for_axis > border_target_abs_mode_for_axis)) ignore_resize = true; } + // Clamp, apply ImVec2 clamp_min(border_n == ImGuiDir_Right ? clamp_rect.Min.x : -FLT_MAX, border_n == ImGuiDir_Down || (border_n == ImGuiDir_Up && window_move_from_title_bar) ? clamp_rect.Min.y : -FLT_MAX); ImVec2 clamp_max(border_n == ImGuiDir_Left ? clamp_rect.Max.x : +FLT_MAX, border_n == ImGuiDir_Up ? clamp_rect.Max.y : +FLT_MAX); @@ -6256,8 +7176,10 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si *border_held = border_n; } PopID(); + // Restore nav layer window->DC.NavLayerCurrent = ImGuiNavLayer_Main; + // Navigation resize (keyboard/gamepad) // FIXME: This cannot be moved to NavUpdateWindowing() because CalcWindowSizeAfterConstraint() need to callback into user. // Not even sure the callback works here. @@ -6286,6 +7208,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si } } } + // Apply back modified position/size to window const ImVec2 curr_pos = window->Pos; const ImVec2 curr_size = window->SizeFull; @@ -6299,11 +7222,14 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si window->Pos.y = ImTrunc(pos_target.y); if (curr_pos.x != window->Pos.x || curr_pos.y != window->Pos.y || curr_size.x != window->SizeFull.x || curr_size.y != window->SizeFull.y) MarkIniSettingsDirty(window); + // Recalculate next expected border expected coordinates if (*border_held != -1) g.WindowResizeBorderExpectedRect = GetResizeBorderRect(window, *border_held, grip_hover_inner_size, g.WindowsBorderHoverPadding); + return ret_auto_fit_mask; } + static inline void ClampWindowPos(ImGuiWindow* window, const ImRect& visibility_rect) { ImGuiContext& g = *GImGui; @@ -6314,6 +7240,7 @@ static inline void ClampWindowPos(ImGuiWindow* window, const ImRect& visibility_ size_for_clamping.y = window->TitleBarHeight; window->Pos = ImClamp(window->Pos, visibility_rect.Min - size_for_clamping, visibility_rect.Max); } + static void RenderWindowOuterSingleBorder(ImGuiWindow* window, int border_n, ImU32 border_col, float border_size) { const ImGuiResizeBorderDef& def = resize_border_def[border_n]; @@ -6323,6 +7250,7 @@ static void RenderWindowOuterSingleBorder(ImGuiWindow* window, int border_n, ImU window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN2) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle, def.OuterAngle + IM_PI * 0.25f); window->DrawList->PathStroke(border_col, ImDrawFlags_None, border_size); } + static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -6349,6 +7277,7 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) window->DrawList->AddLine(ImVec2(window->Pos.x + border_size * 0.5f, y), ImVec2(window->Pos.x + window->Size.x - border_size * 0.5f, y), border_col, g.Style.FrameBorderSize); } } + // Draw background and borders // Draw and handle scrollbars void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar_rect, bool title_bar_is_highlight, bool handle_borders_and_resize_grips, int resize_grip_count, const ImU32 resize_grip_col[4], float resize_grip_draw_size) @@ -6356,10 +7285,12 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; ImGuiWindowFlags flags = window->Flags; + // Ensure that Scrollbar() doesn't read last frame's SkipItems IM_ASSERT(window->BeginCount == 0); window->SkipItems = false; window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; + // Draw window + handle manual resize // As we highlight the title bar when want_focus is set, multiple reappearing windows will have their title bar highlighted on their reappearing frame. const float window_rounding = window->WindowRounding; @@ -6384,6 +7315,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar if (g.DragDropActive && (g.FrameCount - g.DragDropAcceptFrameCount) <= 1 && g.IO.ConfigDockingTransparentPayload) if (g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) && *(ImGuiWindow**)g.DragDropPayload.Data == window) is_docking_transparent_payload = true; + ImU32 bg_col = GetColorU32(GetWindowBgColorIdx(window)); if (window->ViewportOwned) { @@ -6409,6 +7341,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar if (override_alpha) bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT); } + // Render, for docked windows and host windows we ensure bg goes before decorations if (window->DockIsActive) window->DockNode->LastBgColor = bg_col; @@ -6421,6 +7354,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar } if (window->DockIsActive) window->DockNode->IsBgDrawnThisFrame = true; + // Title bar // (when docked, DockNode are drawing their own title bar. Individual windows however do NOT set the _NoTitleBar flag, // in order for their pos/size to be matching their undocking state.) @@ -6431,6 +7365,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar title_bar_col |= IM_COL32_A_MASK; // No alpha window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawFlags_RoundCornersTop); } + // Menu bar if (flags & ImGuiWindowFlags_MenuBar) { @@ -6440,6 +7375,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) window->DrawList->AddLine(menu_bar_rect.GetBL() + ImVec2(window_border_size * 0.5f, 0.0f), menu_bar_rect.GetBR() - ImVec2(window_border_size * 0.5f, 0.0f), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); } + // Docking: Unhide tab bar (small triangle in the corner), drag from small triangle to quickly undock ImGuiDockNode* node = window->DockNode; if (window->DockIsActive && node->IsHiddenTabBar() && !node->IsNoTabBar()) @@ -6455,15 +7391,18 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar node->WantHiddenTabBarToggle = true; else if (held && IsMouseDragging(0)) StartMouseMovingWindowOrNode(window, node, true); // Undock from tab-bar triangle = same as window/collapse menu button + // FIXME-DOCK: Ideally we'd use ImGuiCol_TitleBgActive/ImGuiCol_TitleBg here, but neither is guaranteed to be visible enough at this sort of size.. ImU32 col = GetColorU32(((held && hovered) || (node->IsFocused && !hovered)) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); window->DrawList->AddTriangleFilled(p, p + ImVec2(unhide_sz_draw, 0.0f), p + ImVec2(0.0f, unhide_sz_draw), col); } + // Scrollbars if (window->ScrollbarX) Scrollbar(ImGuiAxis_X); if (window->ScrollbarY) Scrollbar(ImGuiAxis_Y); + // Render resize grips (after their input handling so we don't have a frame of latency) if (handle_borders_and_resize_grips && !(flags & ImGuiWindowFlags_NoResize)) { @@ -6481,12 +7420,14 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar window->DrawList->PathFillConvex(col); } } + // Borders (for dock node host they will be rendered over after the tab bar) if (handle_borders_and_resize_grips && !window->DockNodeAsHost) RenderWindowOuterBorders(window); } window->DC.NavLayerCurrent = ImGuiNavLayer_Main; } + // When inside a dock node, this is handled in DockNodeCalcTabBarLayout() instead. // Render title text, collapse button, close button void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open) @@ -6494,13 +7435,16 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; ImGuiWindowFlags flags = window->Flags; + const bool has_close_button = (p_open != NULL); const bool has_collapse_button = !(flags & ImGuiWindowFlags_NoCollapse) && (style.WindowMenuButtonPosition != ImGuiDir_None); + // Close & Collapse button are on the Menu NavLayer and don't default focus (unless there's nothing else on that layer) // FIXME-NAV: Might want (or not?) to set the equivalent of ImGuiButtonFlags_NoNavFocus so that mouse clicks on standard title bar items don't necessarily set nav/keyboard ref? const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags; g.CurrentItemFlags |= ImGuiItemFlags_NoNavDefaultFocus; window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; + // Layout buttons // FIXME: Would be nice to generalize the subtleties expressed here into reusable code. float pad_l = style.FramePadding.x; @@ -6523,20 +7467,29 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl collapse_button_pos = ImVec2(title_bar_rect.Min.x + pad_l, title_bar_rect.Min.y + style.FramePadding.y); pad_l += button_sz + style.ItemInnerSpacing.x; } + // Collapse button (submitting first so it gets priority when choosing a navigation init fallback) if (has_collapse_button) if (CollapseButton(window->GetID("#COLLAPSE"), collapse_button_pos, NULL)) window->WantCollapseToggle = true; // Defer actual collapsing to next frame as we are too far in the Begin() function + // Close button if (has_close_button) + { + g.CurrentItemFlags |= ImGuiItemFlags_NoFocus; if (CloseButton(window->GetID("#CLOSE"), close_button_pos)) *p_open = false; + g.CurrentItemFlags &= ~ImGuiItemFlags_NoFocus; + } + window->DC.NavLayerCurrent = ImGuiNavLayer_Main; g.CurrentItemFlags = item_flags_backup; + // Title bar text (with: horizontal alignment, avoiding collapse/close button, optional "unsaved document" marker) // FIXME: Refactor text alignment facilities along with RenderText helpers, this is WAY too much messy code.. const float marker_size_x = (flags & ImGuiWindowFlags_UnsavedDocument) ? button_sz * 0.80f : 0.0f; const ImVec2 text_size = CalcTextSize(name, NULL, true) + ImVec2(marker_size_x, 0.0f); + // As a nice touch we try to ensure that centered title text doesn't get affected by visibility of Close/Collapse button, // while uncentered title text will still reach edges correctly. if (pad_l > style.FramePadding.x) @@ -6550,6 +7503,7 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl pad_l = ImMax(pad_l, pad_extend * centerness); pad_r = ImMax(pad_r, pad_extend * centerness); } + ImRect layout_r(title_bar_rect.Min.x + pad_l, title_bar_rect.Min.y, title_bar_rect.Max.x - pad_r, title_bar_rect.Max.y); ImRect clip_r(layout_r.Min.x, layout_r.Min.y, ImMin(layout_r.Max.x + g.Style.ItemInnerSpacing.x, title_bar_rect.Max.x), layout_r.Max.y); if (flags & ImGuiWindowFlags_UnsavedDocument) @@ -6567,6 +7521,7 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl //if (g.IO.KeyCtrl) window->DrawList->AddRect(clip_r.Min, clip_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG] RenderTextClipped(layout_r.Min, layout_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_r); } + void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window) { window->ParentWindow = parent_window; @@ -6587,6 +7542,7 @@ void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags window->RootWindowForNav = window->RootWindowForNav->ParentWindow; } } + // [EXPERIMENTAL] Called by Begin(). NextWindowData is valid at this point. // This is designed as a toy/test-bed for void ImGui::UpdateWindowSkipRefresh(ImGuiWindow* window) @@ -6612,6 +7568,7 @@ void ImGui::UpdateWindowSkipRefresh(ImGuiWindow* window) window->SkipRefresh = true; } } + static void SetWindowActiveForSkipRefresh(ImGuiWindow* window) { window->Active = true; @@ -6622,6 +7579,7 @@ static void SetWindowActiveForSkipRefresh(ImGuiWindow* window) SetWindowActiveForSkipRefresh(child); } } + // Push a new Dear ImGui window to add widgets to. // - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair. // - Begin/End can be called multiple times during the frame with the same window name to append content. @@ -6636,20 +7594,25 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) IM_ASSERT(name != NULL && name[0] != '\0'); // Window name required IM_ASSERT(g.WithinFrameScope); // Forgot to call ImGui::NewFrame() IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet + // Find or create ImGuiWindow* window = FindWindowByName(name); const bool window_just_created = (window == NULL); if (window_just_created) window = CreateNewWindow(name, flags); + // [DEBUG] Debug break requested by user if (g.DebugBreakInWindow == window->ID) IM_DEBUG_BREAK(); + // Automatically disable manual moving/resizing when NoInputs is set if ((flags & ImGuiWindowFlags_NoInputs) == ImGuiWindowFlags_NoInputs) flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; + const int current_frame = g.FrameCount; const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame); window->IsFallbackWindow = (g.CurrentWindowStack.Size == 0 && g.WithinFrameScopeWithImplicitWindow); + // Update the Appearing flag (note: the BeginDocked() path may also set this to true later) bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on if (flags & ImGuiWindowFlags_Popup) @@ -6658,6 +7621,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed window_just_activated_by_user |= (window != popup_ref.Window); } + // Update Flags, LastFrameActive, BeginOrderXXX fields const bool window_was_appearing = window->Appearing; if (first_begin_of_the_frame) @@ -6678,6 +7642,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { flags = window->Flags; } + // Docking // (NB: during the frame dock nodes are created, it is possible that (window->DockIsActive == false) even though (window->DockNode->Windows.Size > 1) IM_ASSERT(window->DockNode == NULL || window->DockNodeAsHost == NULL); // Cannot be both @@ -6698,6 +7663,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) IM_ASSERT(window->DockNode != NULL); g.NextWindowData.HasFlags &= ~ImGuiNextWindowDataFlags_HasSizeConstraint; // Docking currently override constraints } + // Amend the Appearing flag if (window->DockTabIsVisible && !dock_tab_was_visible && dock_node_was_visible && !window->Appearing && !window_was_appearing) { @@ -6710,13 +7676,16 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false; } } + // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack ImGuiWindow* parent_window_in_stack = (window->DockIsActive && window->DockNode->HostWindow) ? window->DockNode->HostWindow : g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back().Window; ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow; IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); + // We allow window memory to be compacted so recreate the base stack when needed. if (window->IDStack.Size == 0) window->IDStack.push_back(window->ID); + // Add to stack g.CurrentWindow = window; g.CurrentWindowStack.resize(g.CurrentWindowStack.Size + 1); @@ -6729,11 +7698,13 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) g.StackSizesInBeginForCurrentWindow = &window_stack_data.StackSizesInBegin; if (flags & ImGuiWindowFlags_ChildMenu) g.BeginMenuDepth++; + // Update ->RootWindow and others pointers (before any possible call to FocusWindow) if (first_begin_of_the_frame) { UpdateWindowParentAndRootLinks(window, flags, parent_window); window->ParentWindowInBeginStack = parent_window_in_stack; + // Focus route // There's little point to expose a flag to set this: because the interesting cases won't be using parent_window_in_stack, // Use for e.g. linking a tool window in a standalone viewport to a document window, regardless of their Begin() stack parenting. (#6798) @@ -6741,18 +7712,22 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->ParentWindowForFocusRoute == NULL && window->DockNode != NULL) if (window->DockNode->MergedFlags & ImGuiDockNodeFlags_DockedWindowsInFocusRoute) window->ParentWindowForFocusRoute = window->DockNode->HostWindow; + // Override with SetNextWindowClass() field or direct call to SetWindowParentWindowForFocusRoute() if (window->WindowClass.FocusRouteParentWindowId != 0) { window->ParentWindowForFocusRoute = FindWindowByID(window->WindowClass.FocusRouteParentWindowId); IM_ASSERT(window->ParentWindowForFocusRoute != 0); // Invalid value for FocusRouteParentWindowId. } + // Inherit SetWindowFontScale() from parent until we fix this system... window->FontWindowScaleParents = parent_window ? parent_window->FontWindowScaleParents * parent_window->FontWindowScale : 1.0f; } + // Add to focus scope stack PushFocusScope((window->ChildFlags & ImGuiChildFlags_NavFlattened) ? g.CurrentFocusScopeId : window->ID); window->NavRootFocusScopeId = g.CurrentFocusScopeId; + // Add to popup stacks: update OpenPopupStack[] data, push to BeginPopupStack[] if (flags & ImGuiWindowFlags_Popup) { @@ -6762,6 +7737,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) g.BeginPopupStack.push_back(popup_ref); window->PopupId = popup_ref.PopupId; } + // Process SetNextWindow***() calls // (FIXME: Consider splitting the HasXXX flags into X/Y components bool window_pos_set_by_api = false; @@ -6817,13 +7793,17 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) FocusWindow(window); if (window->Appearing) SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false); + // [EXPERIMENTAL] Skip Refresh mode UpdateWindowSkipRefresh(window); + // Nested root windows (typically tooltips) override disabled state if (window_stack_data.DisabledOverrideReenable && window->RootWindow == window) BeginDisabledOverrideReenable(); + // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow() g.CurrentWindow = NULL; + // When reusing window again multiple times a frame, just append content (don't need to setup again) if (first_begin_of_the_frame && !window->SkipRefresh) { @@ -6841,9 +7821,11 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DrawList->ChannelsSplit(2); window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG); // Render decorations on channel 1 as we will render the backgrounds manually later } + // Restore buffer capacity when woken from a compacted state, to avoid if (window->MemoryCompacted) GcAwakeTransientWindowBuffers(window); + // Update stored window name when it changes (which can _only_ happen with the "###" operator, so the ID would stay unchanged). // The title bar always display the 'name' parameter, so we only update the string storage if it needs to be visible to the end-user elsewhere. bool window_title_visible_elsewhere = false; @@ -6859,9 +7841,12 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->Name = ImStrdupcpy(window->Name, &buf_len, name); window->NameBufLen = (int)buf_len; } + // UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS + // Update contents size from last frame for auto-fitting (or use explicit size) CalcWindowContentSizes(window, &window->ContentSize, &window->ContentSizeIdeal); + // FIXME: These flags are decremented before they are used. This means that in order to have these fields produce their intended behaviors // for one frame we must set them to at least 2, which is counter-intuitive. HiddenFramesCannotSkipItems is a more complicated case because // it has a single usage before this code block and may be set below before it is finally checked. @@ -6871,9 +7856,11 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->HiddenFramesCannotSkipItems--; if (window->HiddenFramesForRenderOnly > 0) window->HiddenFramesForRenderOnly--; + // Hide new windows for one frame until they calculate their size if (window_just_created && (!window_size_x_set_by_api || !window_size_y_set_by_api)) window->HiddenFramesCannotSkipItems = 1; + // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows) // We reset Size/ContentSize for reappearing popups/tooltips early in this function, so further code won't be tempted to use the old size. if (window_just_activated_by_user && (flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0) @@ -6888,15 +7875,18 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->ContentSize = window->ContentSizeIdeal = ImVec2(0.f, 0.f); } } + // SELECT VIEWPORT // We need to do this before using any style/font sizes, as viewport with a different DPI may affect font sizes. + WindowSelectViewport(window); SetCurrentViewport(window, window->Viewport); - window->FontDpiScale = (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f; SetCurrentWindow(window); flags = window->Flags; + // LOCK BORDER SIZE AND PADDING FOR THE FRAME (so that altering them doesn't cause inconsistencies) // We read Style data after the call to UpdateSelectWindowViewport() which might be swapping the style. + if (!window->DockIsActive && (flags & ImGuiWindowFlags_ChildWindow)) window->WindowBorderSize = style.ChildBorderSize; else @@ -6904,12 +7894,14 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->WindowPadding = style.WindowPadding; if (!window->DockIsActive && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !(window->ChildFlags & ImGuiChildFlags_AlwaysUseWindowPadding) && window->WindowBorderSize == 0.0f) window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f); + // Lock menu offset so size calculation can use it as menu-bar windows need a minimum size. window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x); window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y; window->TitleBarHeight = (flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : g.FontSize + g.Style.FramePadding.y * 2.0f; window->MenuBarHeight = (flags & ImGuiWindowFlags_MenuBar) ? window->DC.MenuBarOffset.y + g.FontSize + g.Style.FramePadding.y * 2.0f : 0.0f; window->FontRefSize = g.FontSize; // Lock this to discourage calling window->CalcFontSize() outside of current window. + // Depending on condition we use previous or current window size to compare against contents size to decide if a scrollbar should be visible. // Those flags will be altered further down in the function depending on more conditions. bool use_current_size_for_scrollbar_x = window_just_created; @@ -6918,6 +7910,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) use_current_size_for_scrollbar_x = true; if (window_size_y_set_by_api && window->ContentSizeExplicit.y != 0.0f) // #7252 use_current_size_for_scrollbar_y = true; + // Collapse window by double-clicking on title bar // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse) && !window->DockIsActive) @@ -6941,7 +7934,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->Collapsed = false; } window->WantCollapseToggle = false; + // SIZE + // Outer Decoration Sizes // (we need to clear ScrollbarSize immediately as CalcWindowAutoFitSize() needs it and can be called from other locations). const ImVec2 scrollbar_sizes_from_last_frame = window->ScrollbarSizes; @@ -6950,6 +7945,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DecoOuterSizeY1 = window->TitleBarHeight + window->MenuBarHeight; window->DecoOuterSizeY2 = 0.0f; window->ScrollbarSizes = ImVec2(0.0f, 0.0f); + // Calculate auto-fit size, handle automatic resize const ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, window->ContentSizeIdeal); if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed) @@ -6983,10 +7979,13 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (!window->Collapsed) MarkIniSettingsDirty(window); } + // Apply minimum/maximum window size constraints and final size window->SizeFull = CalcWindowSizeAfterConstraint(window, window->SizeFull); window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull; + // POSITION + // Popup latch its initial position, will position itself when it appears next frame if (window_just_activated_by_user) { @@ -6994,6 +7993,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if ((flags & ImGuiWindowFlags_Popup) != 0 && !(flags & ImGuiWindowFlags_Modal) && !window_pos_set_by_api) // FIXME: BeginPopup() could use SetNextWindowPos() window->Pos = g.BeginPopupStack.back().OpenPopupPos; } + // Position child window if (flags & ImGuiWindowFlags_ChildWindow) { @@ -7003,6 +8003,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip) window->Pos = parent_window->DC.CursorPos; } + const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFramesCannotSkipItems == 0); if (window_pos_with_pivot) SetWindowPos(window, window->SetWindowPosVal - window->Size * window->SetWindowPosPivot, 0); // Position given a pivot (e.g. for centering) @@ -7012,6 +8013,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->Pos = FindBestWindowPosForPopup(window); else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip) window->Pos = FindBestWindowPosForPopup(window); + // Late create viewport if we don't fit within our current host viewport. if (window->ViewportAllowPlatformMonitorExtend >= 0 && !window->ViewportOwned && !(window->Viewport->Flags & ImGuiViewportFlags_IsMinimized)) if (!window->Viewport->GetMainRect().Contains(window->Rect())) @@ -7019,20 +8021,23 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // This is based on the assumption that the DPI will be known ahead (same as the DPI of the selection done in UpdateSelectWindowViewport) //ImGuiViewport* old_viewport = window->Viewport; window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_NoFocusOnAppearing); + // FIXME-DPI //IM_ASSERT(old_viewport->DpiScale == window->Viewport->DpiScale); // FIXME-DPI: Something went wrong SetCurrentViewport(window, window->Viewport); - window->FontDpiScale = (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f; SetCurrentWindow(window); } + if (window->ViewportOwned) WindowSyncOwnedViewport(window, parent_window_in_stack); + // Calculate the range of allowed position for that window (to be movable and visible past safe area padding) // When clamping to stay visible, we will enforce that window->Pos stays inside of visibility_rect. ImRect viewport_rect(window->Viewport->GetMainRect()); ImRect viewport_work_rect(window->Viewport->GetWorkRect()); ImVec2 visibility_padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); ImRect visibility_rect(viewport_work_rect.Min + visibility_padding, viewport_work_rect.Max - visibility_padding); + // Clamp position/size so window stays visible within its viewport or monitor // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. // FIXME: Similar to code in GetWindowAllowedExtentRect() @@ -7061,15 +8066,18 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } } window->Pos = ImTrunc(window->Pos); + // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies) // Large values tend to lead to variety of artifacts and are not recommended. if (window->ViewportOwned || window->DockIsActive) window->WindowRounding = 0.0f; else window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding; + // For windows with title bar or menu bar, we clamp to FrameHeight(FontSize + FramePadding.y * 2.0f) to completely hide artifacts. //if ((window->Flags & ImGuiWindowFlags_MenuBar) || !(window->Flags & ImGuiWindowFlags_NoTitleBar)) // window->WindowRounding = ImMin(window->WindowRounding, g.FontSize + style.FramePadding.y * 2.0f); + // Apply window focus (new and reactivated windows are moved to front) bool want_focus = false; if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing)) @@ -7079,6 +8087,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) else if ((window->DockIsActive || (flags & ImGuiWindowFlags_ChildWindow) == 0) && !(flags & ImGuiWindowFlags_Tooltip)) want_focus = true; } + // [Test Engine] Register whole window in the item system (before submitting further decorations) #ifdef IMGUI_ENABLE_TEST_ENGINE if (g.TestEngineHookItems) @@ -7090,10 +8099,17 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) IMGUI_TEST_ENGINE_ITEM_INFO(window->ID, window->Name, (g.HoveredWindow == window) ? ImGuiItemStatusFlags_HoveredRect : 0); window->IDStack.Size = 1; window->DC.NavLayerCurrent = ImGuiNavLayer_Main; + } #endif + // Decide if we are going to handle borders and resize grips - const bool handle_borders_and_resize_grips = (window->DockNodeAsHost || !window->DockIsActive); + // 'window->SkipItems' is not updated yet so for child windows we rely on ParentWindow to avoid submitting decorations. (#8815) + // Whenever we add support for full decorated child windows we will likely make this logic more general. + bool handle_borders_and_resize_grips = (window->DockNodeAsHost || !window->DockIsActive); + if ((flags & ImGuiWindowFlags_ChildWindow) && window->ParentWindow->SkipItems) + handle_borders_and_resize_grips = false; + // Handle manual resize: Resize Grips, Borders, Gamepad int border_hovered = -1, border_held = -1; ImU32 resize_grip_col[4] = {}; @@ -7109,6 +8125,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } window->ResizeBorderHovered = (signed char)border_hovered; window->ResizeBorderHeld = (signed char)border_held; + // Synchronize window --> viewport again and one last time (clamping and manual resize may have affected either) if (window->ViewportOwned) { @@ -7119,9 +8136,12 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->Viewport->UpdateWorkRect(); viewport_rect = window->Viewport->GetMainRect(); } + // Save last known viewport position within the window itself (so it can be saved in .ini file and restored) window->ViewportPos = window->Viewport->Pos; + // SCROLLBAR VISIBILITY + // Update scrollbar visibility (based on the Size that was effective during last frame or the auto-resized Size). if (!window->Collapsed) { @@ -7137,6 +8157,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) //bool scrollbar_y_from_last_frame = window->ScrollbarY; // FIXME: May want to use that in the ScrollbarX expression? How many pros vs cons? window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar)); window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((needed_size_from_last_frame.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); + // Track when ScrollbarX visibility keeps toggling, which is a sign of a feedback loop, and stabilize by enforcing visibility (#3285, #8488) // (Feedback loops of this sort can manifest in various situations, but combining horizontal + vertical scrollbar + using a clipper with varying width items is one frequent cause. // The better solution is to, either (1) enforce visibility by using ImGuiWindowFlags_AlwaysHorizontalScrollbar or (2) declare stable contents width with SetNextWindowContentSize(), if you can compute it) @@ -7148,16 +8169,20 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) //if (scrollbar_x_stabilize && !window->ScrollbarXStabilizeEnabled) // IMGUI_DEBUG_LOG("[scroll] Stabilize ScrollbarX for Window '%s'\n", window->Name); window->ScrollbarXStabilizeEnabled = scrollbar_x_stabilize; + if (window->ScrollbarX && !window->ScrollbarY) window->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); + // Amend the partially filled window->DecorationXXX values. window->DecoOuterSizeX2 += window->ScrollbarSizes.x; window->DecoOuterSizeY2 += window->ScrollbarSizes.y; } + // UPDATE RECTANGLES (1- THOSE NOT AFFECTED BY SCROLLING) // Update various regions. Variables they depend on should be set above in this function. // We set this up after processing the resize grip so that our rectangles doesn't lag by a frame. + // Outer rectangle // Not affected by window border size. Used by: // - FindHoveredWindow() (w/ extra padding when border resize is enabled) @@ -7170,6 +8195,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->DockIsActive) window->OuterRectClipped.Min.y += window->TitleBarHeight; window->OuterRectClipped.ClipWith(host_rect); + // Inner rectangle // Not affected by window border size. Used by: // - InnerClipRect @@ -7180,6 +8206,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->InnerRect.Min.y = window->Pos.y + window->DecoOuterSizeY1; window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->DecoOuterSizeX2; window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->DecoOuterSizeY2; + // Inner clipping rectangle. // - Extend a outside of normal work region up to borders. // - This is to allow e.g. Selectable or CollapsingHeader or some separators to cover that space. @@ -7190,6 +8217,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Affected by window/frame border size. Used by: // - Begin() initial clip rect float top_border_size = (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize); + // Try to match the fact that our border is drawn centered over the window rectangle, rather than inner. // This is why we do a *0.5f here. We don't currently even technically support large values for WindowBorderSize, // see e.g #7887 #7888, but may do after we move the window border to become an inner border (and then we can remove the 0.5f here). @@ -7198,26 +8226,27 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->InnerClipRect.Max.x = ImFloor(window->InnerRect.Max.x - window->WindowBorderSize * 0.5f); window->InnerClipRect.Max.y = ImFloor(window->InnerRect.Max.y - window->WindowBorderSize * 0.5f); window->InnerClipRect.ClipWithFull(host_rect); - // Default item width. Make it proportional to window size if window manually resizes - if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) - window->ItemWidthDefault = ImTrunc(window->Size.x * 0.65f); - else - window->ItemWidthDefault = ImTrunc(g.FontSize * 16.0f); + // SCROLLING + // Lock down maximum scrolling // The value of ScrollMax are ahead from ScrollbarX/ScrollbarY which is intentionally using InnerRect from previous rect in order to accommodate // for right/bottom aligned items without creating a scrollbar. window->ScrollMax.x = ImMax(0.0f, window->ContentSize.x + window->WindowPadding.x * 2.0f - window->InnerRect.GetWidth()); window->ScrollMax.y = ImMax(0.0f, window->ContentSize.y + window->WindowPadding.y * 2.0f - window->InnerRect.GetHeight()); + // Apply scrolling window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window); window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); window->DecoInnerSizeX1 = window->DecoInnerSizeY1 = 0.0f; + // DRAWING + // Setup draw list and outer clipping rectangle IM_ASSERT(window->DrawList->CmdBuffer.Size == 1 && window->DrawList->CmdBuffer[0].ElemCount == 0); - window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); + window->DrawList->PushTexture(g.Font->ContainerAtlas->TexRef); PushClipRect(host_rect.Min, host_rect.Max, false); + // Child windows can render their decoration (bg color, border, scrollbars, etc.) within their parent to save a draw call (since 1.71) // When using overlapping child windows, this will break the assumption that child z-order is mapped to submission order. // FIXME: User code may rely on explicit sorting of overlapping child window and would need to disable this somehow. Please get in contact if you are affected (github #4493) @@ -7237,14 +8266,18 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } if (render_decorations_in_parent) window->DrawList = parent_window->DrawList; + // Handle title bar, scrollbar, resize grips and resize borders const ImGuiWindow* window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow; const bool title_bar_is_highlight = want_focus || (window_to_highlight && (window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight || (window->DockNode && window->DockNode == window_to_highlight->DockNode))); RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, handle_borders_and_resize_grips, resize_grip_count, resize_grip_col, resize_grip_draw_size); + if (render_decorations_in_parent) window->DrawList = &window->DrawListInst; } + // UPDATE RECTANGLES (2- THOSE AFFECTED BY SCROLLING) + // Work rectangle. // Affected by window padding and border size. Used by: // - Columns() for right-most edge @@ -7259,6 +8292,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->WorkRect.Max.x = window->WorkRect.Min.x + work_rect_size_x; window->WorkRect.Max.y = window->WorkRect.Min.y + work_rect_size_y; window->ParentWorkRect = window->WorkRect; + // [LEGACY] Content Region // FIXME-OBSOLETE: window->ContentRegionRect.Max is currently very misleading / partly faulty, but some BeginChild() patterns relies on it. // Unless explicit content size is specified by user, this currently represent the region leading to no scrolling. @@ -7268,11 +8302,13 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->ContentRegionRect.Min.y = window->Pos.y - window->Scroll.y + window->WindowPadding.y + window->DecoOuterSizeY1; window->ContentRegionRect.Max.x = window->ContentRegionRect.Min.x + (window->ContentSizeExplicit.x != 0.0f ? window->ContentSizeExplicit.x : (window->Size.x - window->WindowPadding.x * 2.0f - (window->DecoOuterSizeX1 + window->DecoOuterSizeX2))); window->ContentRegionRect.Max.y = window->ContentRegionRect.Min.y + (window->ContentSizeExplicit.y != 0.0f ? window->ContentSizeExplicit.y : (window->Size.y - window->WindowPadding.y * 2.0f - (window->DecoOuterSizeY1 + window->DecoOuterSizeY2))); + // Setup drawing context // (NB: That term "drawing context / DC" lost its meaning a long time ago. Initially was meant to hold transient data only. Nowadays difference between window-> and window->DC-> is dubious.) window->DC.Indent.x = window->DecoOuterSizeX1 + window->WindowPadding.x - window->Scroll.x; window->DC.GroupOffset.x = 0.0f; window->DC.ColumnsOffset.x = 0.0f; + // Record the loss of precision of CursorStartPos which can happen due to really large scrolling amount. // This is used by clipper to compensate and fix the most common use case of large scroll area. Easy and cheap, next best thing compared to switching everything to double or ImU64. double start_pos_highp_x = (double)window->Pos.x + window->WindowPadding.x - (double)window->Scroll.x + window->DecoOuterSizeX1 + window->DC.ColumnsOffset.x; @@ -7286,33 +8322,44 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.CurrLineSize = window->DC.PrevLineSize = ImVec2(0.0f, 0.0f); window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f; window->DC.IsSameLine = window->DC.IsSetPos = false; + window->DC.NavLayerCurrent = ImGuiNavLayer_Main; window->DC.NavLayersActiveMask = window->DC.NavLayersActiveMaskNext; window->DC.NavLayersActiveMaskNext = 0x00; window->DC.NavIsScrollPushableX = true; window->DC.NavHideHighlightOneFrame = false; window->DC.NavWindowHasScrollY = (window->ScrollMax.y > 0.0f); + window->DC.MenuBarAppending = false; window->DC.MenuColumns.Update(style.ItemSpacing.x, window_just_activated_by_user); window->DC.TreeDepth = 0; - window->DC.TreeHasStackDataDepthMask = 0x00; + window->DC.TreeHasStackDataDepthMask = window->DC.TreeRecordsClippedNodesY2Mask = 0x00; window->DC.ChildWindows.resize(0); window->DC.StateStorage = &window->StateStorage; window->DC.CurrentColumns = NULL; window->DC.LayoutType = ImGuiLayoutType_Vertical; window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical; + + // Default item width. Make it proportional to window size if window manually resizes + if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) + window->ItemWidthDefault = ImTrunc(window->Size.x * 0.65f); + else + window->ItemWidthDefault = ImTrunc(g.FontSize * 16.0f); window->DC.ItemWidth = window->ItemWidthDefault; window->DC.TextWrapPos = -1.0f; // disabled window->DC.ItemWidthStack.resize(0); window->DC.TextWrapPosStack.resize(0); if (flags & ImGuiWindowFlags_Modal) window->DC.ModalDimBgColor = ColorConvertFloat4ToU32(GetStyleColorVec4(ImGuiCol_ModalWindowDimBg)); + if (window->AutoFitFramesX > 0) window->AutoFitFramesX--; if (window->AutoFitFramesY > 0) window->AutoFitFramesY--; + // Clear SetNextWindowXXX data (can aim to move this higher in the function) g.NextWindowData.ClearFlags(); + // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) // We ImGuiFocusRequestFlags_UnlessBelowModal to: // - Avoid focusing a window that is created outside of a modal. This will prevent active modal from being closed. @@ -7321,6 +8368,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) FocusWindow(window, ImGuiFocusRequestFlags_UnlessBelowModal); if (want_focus && window == g.NavWindow) NavInitWindow(window, false); // <-- this is in the way for us to be able to defer and sort reappearing FocusWindow() calls + // Close requested by platform window (apply to all windows in this viewport) if (p_open != NULL && window->Viewport->PlatformRequestClose && window->Viewport != GetMainViewport()) { @@ -7328,43 +8376,52 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) *p_open = false; g.NavWindowingToggleLayer = false; // Assume user mapped PlatformRequestClose on ALT-F4 so we disable ALT for menu toggle. False positive not an issue. // FIXME-NAV: Try removing. } + // Pressing CTRL+C copy window content into the clipboard // [EXPERIMENTAL] Breaks on nested Begin/End pairs. We need to work that out and add better logging scope. // [EXPERIMENTAL] Text outputs has many issues. if (g.IO.ConfigWindowsCopyContentsWithCtrlC) if (g.NavWindow && g.NavWindow->RootWindow == window && g.ActiveId == 0 && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C)) LogToClipboard(0); + // Title bar if (!(flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive) RenderWindowTitleBarContents(window, ImRect(title_bar_rect.Min.x + window->WindowBorderSize, title_bar_rect.Min.y, title_bar_rect.Max.x - window->WindowBorderSize, title_bar_rect.Max.y), name, p_open); else if (!(flags & ImGuiWindowFlags_NoTitleBar) && window->DockIsActive) LogText("%.*s\n", (int)(FindRenderedTextEnd(window->Name) - window->Name), window->Name); + // Clear hit test shape every frame window->HitTestHoleSize.x = window->HitTestHoleSize.y = 0; + if (flags & ImGuiWindowFlags_Tooltip) g.TooltipPreviousWindow = window; + if (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable) { // Docking: Dragging a dockable window (or any of its child) turns it into a drag and drop source. // We need to do this _before_ we overwrite window->DC.LastItemId below because BeginDockableDragDropSource() also overwrites it. if (g.MovingWindow == window && (window->RootWindowDockTree->Flags & ImGuiWindowFlags_NoDocking) == 0) BeginDockableDragDropSource(window); + // Docking: Any dockable window can act as a target. For dock node hosts we call BeginDockableDragDropTarget() in DockNodeUpdate() instead. if (g.DragDropActive && !(flags & ImGuiWindowFlags_NoDocking)) if (g.MovingWindow == NULL || g.MovingWindow->RootWindowDockTree != window) if ((window == window->RootWindowDockTree) && !(window->Flags & ImGuiWindowFlags_DockNodeHost)) BeginDockableDragDropTarget(window); } + // We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable after Begin(). // This is useful to allow creating context menus on title bar only, etc. window->DC.WindowItemStatusFlags = ImGuiItemStatusFlags_None; window->DC.WindowItemStatusFlags |= IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0; SetLastItemDataForWindow(window, title_bar_rect); + // [DEBUG] #ifndef IMGUI_DISABLE_DEBUG_TOOLS if (g.DebugLocateId != 0 && (window->ID == g.DebugLocateId || window->MoveId == g.DebugLocateId)) DebugLocateItemResolveWithLastItem(); #endif + // [Test Engine] Register title bar / tab with MoveId. #ifdef IMGUI_ENABLE_TEST_ENGINE if (!(window->Flags & ImGuiWindowFlags_NoTitleBar)) @@ -7380,17 +8437,21 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Skip refresh always mark active if (window->SkipRefresh) SetWindowActiveForSkipRefresh(window); + // Append SetCurrentViewport(window, window->Viewport); SetCurrentWindow(window); g.NextWindowData.ClearFlags(); SetLastItemDataForWindow(window, window->TitleBarRect()); } + if (!(flags & ImGuiWindowFlags_DockNodeHost) && !window->SkipRefresh) PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true); + // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) window->WriteAccessed = false; window->BeginCount++; + // Update visibility if (first_begin_of_the_frame && !window->SkipRefresh) { @@ -7405,6 +8466,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) else window->HiddenFramesCanSkipItems = 1; } + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ChildMenu)) { // Child window can be out of sight and have "negative" clip windows. @@ -7419,33 +8481,40 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) else window->HiddenFramesCanSkipItems = 1; } + // Hide along with parent or if parent is collapsed if (parent_window && (parent_window->Collapsed || parent_window->HiddenFramesCanSkipItems > 0)) window->HiddenFramesCanSkipItems = 1; if (parent_window && parent_window->HiddenFramesCannotSkipItems > 0) window->HiddenFramesCannotSkipItems = 1; } + // Don't render if style alpha is 0.0 at the time of Begin(). This is arbitrary and inconsistent but has been there for a long while (may remove at some point) if (style.Alpha <= 0.0f) window->HiddenFramesCanSkipItems = 1; + // Update the Hidden flag bool hidden_regular = (window->HiddenFramesCanSkipItems > 0) || (window->HiddenFramesCannotSkipItems > 0); window->Hidden = hidden_regular || (window->HiddenFramesForRenderOnly > 0); + // Disable inputs for requested number of frames if (window->DisableInputsFrames > 0) { window->DisableInputsFrames--; window->Flags |= ImGuiWindowFlags_NoInputs; } + // Update the SkipItems flag, used to early out of all items functions (no layout required) bool skip_items = false; if (window->Collapsed || !window->Active || hidden_regular) if (window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesCannotSkipItems <= 0) skip_items = true; window->SkipItems = skip_items; + // Restore NavLayersActiveMaskNext to previous value when not visible, so a CTRL+Tab back can use a safe value. if (window->SkipItems) window->DC.NavLayersActiveMaskNext = window->DC.NavLayersActiveMask; + // Sanity check: there are two spots which can set Appearing = true // - when 'window_just_activated_by_user' is set -> HiddenFramesCannotSkipItems is set -> SkipItems always false // - in BeginDocked() path when DockNodeIsVisible == DockTabIsVisible == true -> hidden _should_ be all zero // FIXME: Not formally proven, hence the assert. @@ -7457,6 +8526,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Skip refresh mode window->SkipItems = true; } + // [DEBUG] io.ConfigDebugBeginReturnValue override return value to test Begin/End and BeginChild/EndChild behaviors. // (The implicit fallback window is NOT automatically ended allowing it to always be able to receive commands without crashing) #ifndef IMGUI_DISABLE_DEBUG_TOOLS @@ -7468,12 +8538,15 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) return false; } #endif + return !window->SkipItems; } + void ImGui::End() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + // Error checking: verify that user hasn't called End() too many times! if (g.CurrentWindowStack.Size <= 1 && g.WithinFrameScopeWithImplicitWindow) { @@ -7481,9 +8554,11 @@ void ImGui::End() return; } ImGuiWindowStackData& window_stack_data = g.CurrentWindowStack.back(); + // Error checking: verify that user doesn't directly call End() on a child window. if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_DockNodeHost) && !window->DockIsActive) IM_ASSERT_USER_ERROR(g.WithinEndChildID == window->ID, "Must call EndChild() and not End()!"); + // Close anything that is open if (window->DC.CurrentColumns) EndColumns(); @@ -7492,80 +8567,42 @@ void ImGui::End() PopFocusScope(); if (window_stack_data.DisabledOverrideReenable && window->RootWindow == window) EndDisabledOverrideReenable(); + if (window->SkipRefresh) { IM_ASSERT(window->DrawList == NULL); window->DrawList = &window->DrawListInst; } + // Stop logging if (g.LogWindow == window) // FIXME: add more options for scope of logging LogFinish(); + if (window->DC.IsSetPos) ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); + // Docking: report contents sizes to parent to allow for auto-resize if (window->DockNode && window->DockTabIsVisible) if (ImGuiWindow* host_window = window->DockNode->HostWindow) // FIXME-DOCK host_window->DC.CursorMaxPos = window->DC.CursorMaxPos + window->WindowPadding - host_window->WindowPadding; + // Pop from window stack g.LastItemData = window_stack_data.ParentLastItemDataBackup; if (window->Flags & ImGuiWindowFlags_ChildMenu) g.BeginMenuDepth--; if (window->Flags & ImGuiWindowFlags_Popup) g.BeginPopupStack.pop_back(); + // Error handling, state recovery if (g.IO.ConfigErrorRecovery) ErrorRecoveryTryToRecoverWindowState(&window_stack_data.StackSizesInBegin); + g.CurrentWindowStack.pop_back(); SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window); if (g.CurrentWindow) SetCurrentViewport(g.CurrentWindow, g.CurrentWindow->Viewport); } -// Important: this alone doesn't alter current ImDrawList state. This is called by PushFont/PopFont only. -void ImGui::SetCurrentFont(ImFont* font) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? - IM_ASSERT(font->Scale > 0.0f); - g.Font = font; - g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale); - g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; - g.FontScale = g.FontSize / g.Font->FontSize; - ImFontAtlas* atlas = g.Font->ContainerAtlas; - g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; - g.DrawListSharedData.TexUvLines = atlas->TexUvLines; - g.DrawListSharedData.Font = g.Font; - g.DrawListSharedData.FontSize = g.FontSize; - g.DrawListSharedData.FontScale = g.FontScale; -} -// Use ImDrawList::_SetTextureID(), making our shared g.FontStack[] authorative against window-local ImDrawList. -// - Whereas ImDrawList::PushTextureID()/PopTextureID() is not to be used across Begin() calls. -// - Note that we don't propagate current texture id when e.g. Begin()-ing into a new window, we never really did... -// - Some code paths never really fully worked with multiple atlas textures. -// - The right-ish solution may be to remove _SetTextureID() and make AddText/RenderText lazily call PushTextureID()/PopTextureID() -// the same way AddImage() does, but then all other primitives would also need to? I don't think we should tackle this problem -// because we have a concrete need and a test bed for multiple atlas textures. -void ImGui::PushFont(ImFont* font) -{ - ImGuiContext& g = *GImGui; - if (font == NULL) - font = GetDefaultFont(); - g.FontStack.push_back(font); - SetCurrentFont(font); - g.CurrentWindow->DrawList->_SetTextureID(font->ContainerAtlas->TexID); -} -void ImGui::PopFont() -{ - ImGuiContext& g = *GImGui; - if (g.FontStack.Size <= 0) - { - IM_ASSERT_USER_ERROR(0, "Calling PopFont() too many times!"); - return; - } - g.FontStack.pop_back(); - ImFont* font = g.FontStack.Size == 0 ? GetDefaultFont() : g.FontStack.back(); - SetCurrentFont(font); - g.CurrentWindow->DrawList->_SetTextureID(font->ContainerAtlas->TexID); -} + void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) { ImGuiContext& g = *GImGui; @@ -7578,6 +8615,7 @@ void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) g.CurrentItemFlags = item_flags; g.ItemFlagsStack.push_back(item_flags); } + void ImGui::PopItemFlag() { ImGuiContext& g = *GImGui; @@ -7589,6 +8627,7 @@ void ImGui::PopItemFlag() g.ItemFlagsStack.pop_back(); g.CurrentItemFlags = g.ItemFlagsStack.back(); } + // BeginDisabled()/EndDisabled() // - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep everything disabled) // - Visually this is currently altering alpha, but it is expected that in a future styling system this would work differently. @@ -7610,6 +8649,7 @@ void ImGui::BeginDisabled(bool disabled) g.ItemFlagsStack.push_back(g.CurrentItemFlags); // FIXME-OPT: can we simply skip this and use DisabledStackSize? g.DisabledStackSize++; } + void ImGui::EndDisabled() { ImGuiContext& g = *GImGui; @@ -7626,6 +8666,7 @@ void ImGui::EndDisabled() if (was_disabled && (g.CurrentItemFlags & ImGuiItemFlags_Disabled) == 0) g.Style.Alpha = g.DisabledAlphaBackup; //PopStyleVar(); } + // Could have been called BeginDisabledDisable() but it didn't want to be award nominated for most awkward function name. // Ideally we would use a shared e.g. BeginDisabled()->BeginDisabledEx() but earlier needs to be optimal. // The whole code for this is awkward, will reevaluate if we find a way to implement SetNextItemDisabled(). @@ -7639,6 +8680,7 @@ void ImGui::BeginDisabledOverrideReenable() g.ItemFlagsStack.push_back(g.CurrentItemFlags); g.DisabledStackSize++; } + void ImGui::EndDisabledOverrideReenable() { ImGuiContext& g = *GImGui; @@ -7648,6 +8690,7 @@ void ImGui::EndDisabledOverrideReenable() g.CurrentItemFlags = g.ItemFlagsStack.back(); g.Style.Alpha = g.CurrentWindowStack.back().DisabledOverrideReenableAlphaBackup; } + void ImGui::PushTextWrapPos(float wrap_pos_x) { ImGuiContext& g = *GImGui; @@ -7655,6 +8698,7 @@ void ImGui::PushTextWrapPos(float wrap_pos_x) window->DC.TextWrapPosStack.push_back(window->DC.TextWrapPos); window->DC.TextWrapPos = wrap_pos_x; } + void ImGui::PopTextWrapPos() { ImGuiContext& g = *GImGui; @@ -7667,6 +8711,7 @@ void ImGui::PopTextWrapPos() window->DC.TextWrapPos = window->DC.TextWrapPosStack.back(); window->DC.TextWrapPosStack.pop_back(); } + static ImGuiWindow* GetCombinedRootWindow(ImGuiWindow* window, bool popup_hierarchy, bool dock_hierarchy) { ImGuiWindow* last_window = NULL; @@ -7681,6 +8726,7 @@ static ImGuiWindow* GetCombinedRootWindow(ImGuiWindow* window, bool popup_hierar } return window; } + bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent, bool popup_hierarchy, bool dock_hierarchy) { ImGuiWindow* window_root = GetCombinedRootWindow(window, popup_hierarchy, dock_hierarchy); @@ -7696,6 +8742,7 @@ bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent, } return false; } + bool ImGui::IsWindowWithinBeginStackOf(ImGuiWindow* window, ImGuiWindow* potential_parent) { if (window->RootWindow == potential_parent) @@ -7708,13 +8755,16 @@ bool ImGui::IsWindowWithinBeginStackOf(ImGuiWindow* window, ImGuiWindow* potenti } return false; } + bool ImGui::IsWindowAbove(ImGuiWindow* potential_above, ImGuiWindow* potential_below) { ImGuiContext& g = *GImGui; + // It would be saner to ensure that display layer is always reflected in the g.Windows[] order, which would likely requires altering all manipulations of that array const int display_layer_delta = GetWindowDisplayLayer(potential_above) - GetWindowDisplayLayer(potential_below); if (display_layer_delta != 0) return display_layer_delta > 0; + for (int i = g.Windows.Size - 1; i >= 0; i--) { ImGuiWindow* candidate_window = g.Windows[i]; @@ -7725,6 +8775,7 @@ bool ImGui::IsWindowAbove(ImGuiWindow* potential_above, ImGuiWindow* potential_b } return false; } + // Is current window hovered and hoverable (e.g. not blocked by a popup/modal)? See ImGuiHoveredFlags_ for options. // IMPORTANT: If you are trying to check whether your mouse should be dispatched to Dear ImGui or to your underlying app, // you should not use this function! Use the 'io.WantCaptureMouse' boolean for that! @@ -7733,10 +8784,12 @@ bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) { ImGuiContext& g = *GImGui; IM_ASSERT_USER_ERROR((flags & ~ImGuiHoveredFlags_AllowedMaskForIsWindowHovered) == 0, "Invalid flags for IsWindowHovered()!"); + ImGuiWindow* ref_window = g.HoveredWindow; ImGuiWindow* cur_window = g.CurrentWindow; if (ref_window == NULL) return false; + if ((flags & ImGuiHoveredFlags_AnyWindow) == 0) { IM_ASSERT(cur_window); // Not inside a Begin()/End() @@ -7744,6 +8797,7 @@ bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) const bool dock_hierarchy = (flags & ImGuiHoveredFlags_DockHierarchy) != 0; if (flags & ImGuiHoveredFlags_RootWindow) cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy, dock_hierarchy); + bool result; if (flags & ImGuiHoveredFlags_ChildWindows) result = IsWindowChildOf(ref_window, cur_window, popup_hierarchy, dock_hierarchy); @@ -7752,11 +8806,13 @@ bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) if (!result) return false; } + if (!IsWindowContentHoverable(ref_window, flags)) return false; if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != ref_window->MoveId) return false; + // When changing hovered window we requires a bit of stationary delay before activating hover timer. // FIXME: We don't support delay other than stationary one for now, other delay would need a way // to fulfill the possibility that multiple IsWindowHovered() with varying flag could return true @@ -7766,42 +8822,51 @@ bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipMouse); if ((flags & ImGuiHoveredFlags_Stationary) != 0 && g.HoverWindowUnlockedStationaryId != ref_window->ID) return false; + return true; } + ImGuiID ImGui::GetWindowDockID() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DockId; } + bool ImGui::IsWindowDocked() { ImGuiContext& g = *GImGui; return g.CurrentWindow->DockIsActive; } + float ImGui::GetWindowWidth() { ImGuiWindow* window = GImGui->CurrentWindow; return window->Size.x; } + float ImGui::GetWindowHeight() { ImGuiWindow* window = GImGui->CurrentWindow; return window->Size.y; } + ImVec2 ImGui::GetWindowPos() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; return window->Pos; } + void ImGui::SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond) { // Test condition (NB: bit 0 is always true) and clear flags for next time if (cond && (window->SetWindowPosAllowFlags & cond) == 0) return; + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX); + // Set const ImVec2 old_pos = window->Pos; window->Pos = ImTrunc(pos); @@ -7815,33 +8880,40 @@ void ImGui::SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond) window->DC.IdealMaxPos += offset; window->DC.CursorStartPos += offset; } + void ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond) { ImGuiWindow* window = GetCurrentWindowRead(); SetWindowPos(window, pos, cond); } + void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond) { if (ImGuiWindow* window = FindWindowByName(name)) SetWindowPos(window, pos, cond); } + ImVec2 ImGui::GetWindowSize() { ImGuiWindow* window = GetCurrentWindowRead(); return window->Size; } + void ImGui::SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond) { // Test condition (NB: bit 0 is always true) and clear flags for next time if (cond && (window->SetWindowSizeAllowFlags & cond) == 0) return; + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + // Enable auto-fit (not done in BeginChild() path unless appearing or combined with ImGuiChildFlags_AlwaysAutoResize) if ((window->Flags & ImGuiWindowFlags_ChildWindow) == 0 || window->Appearing || (window->ChildFlags & ImGuiChildFlags_AlwaysAutoResize) != 0) window->AutoFitFramesX = (size.x <= 0.0f) ? 2 : 0; if ((window->Flags & ImGuiWindowFlags_ChildWindow) == 0 || window->Appearing || (window->ChildFlags & ImGuiChildFlags_AlwaysAutoResize) != 0) window->AutoFitFramesY = (size.y <= 0.0f) ? 2 : 0; + // Set ImVec2 old_size = window->SizeFull; if (size.x <= 0.0f) @@ -7855,54 +8927,67 @@ void ImGui::SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond con if (old_size.x != window->SizeFull.x || old_size.y != window->SizeFull.y) MarkIniSettingsDirty(window); } + void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond) { SetWindowSize(GImGui->CurrentWindow, size, cond); } + void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond) { if (ImGuiWindow* window = FindWindowByName(name)) SetWindowSize(window, size, cond); } + void ImGui::SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond) { // Test condition (NB: bit 0 is always true) and clear flags for next time if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0) return; window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); - // Set - window->Collapsed = collapsed; + + // Queue applying in Begin() + if (window->WantCollapseToggle) + window->Collapsed ^= 1; + window->WantCollapseToggle = (window->Collapsed != collapsed); } + void ImGui::SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size) { IM_ASSERT(window->HitTestHoleSize.x == 0); // We don't support multiple holes/hit test filters window->HitTestHoleSize = ImVec2ih(size); window->HitTestHoleOffset = ImVec2ih(pos - window->Pos); } + void ImGui::SetWindowHiddenAndSkipItemsForCurrentFrame(ImGuiWindow* window) { window->Hidden = window->SkipItems = true; window->HiddenFramesCanSkipItems = 1; } + void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond) { SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond); } + bool ImGui::IsWindowCollapsed() { ImGuiWindow* window = GetCurrentWindowRead(); return window->Collapsed; } + bool ImGui::IsWindowAppearing() { ImGuiWindow* window = GetCurrentWindowRead(); return window->Appearing; } + void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond) { if (ImGuiWindow* window = FindWindowByName(name)) SetWindowCollapsed(window, collapsed, cond); } + void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot) { ImGuiContext& g = *GImGui; @@ -7913,6 +8998,7 @@ void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pi g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always; g.NextWindowData.PosUndock = true; } + void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond) { ImGuiContext& g = *GImGui; @@ -7921,6 +9007,7 @@ void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond) g.NextWindowData.SizeVal = size; g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always; } + // For each axis: // - Use 0.0f as min or FLT_MAX as max if you don't want limits, e.g. size_min = (500.0f, 0.0f), size_max = (FLT_MAX, FLT_MAX) sets a minimum width. // - Use -1 for both min and max of same axis to preserve current size which itself is a constraint. @@ -7933,6 +9020,7 @@ void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& s g.NextWindowData.SizeCallback = custom_callback; g.NextWindowData.SizeCallbackUserData = custom_callback_user_data; } + // Content size = inner scrollable rectangle, padded with WindowPadding. // SetNextWindowContentSize(ImVec2(100,100) + ImGuiWindowFlags_AlwaysAutoResize will always allow submitting a 100x100 item. void ImGui::SetNextWindowContentSize(const ImVec2& size) @@ -7941,12 +9029,14 @@ void ImGui::SetNextWindowContentSize(const ImVec2& size) g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasContentSize; g.NextWindowData.ContentSizeVal = ImTrunc(size); } + void ImGui::SetNextWindowScroll(const ImVec2& scroll) { ImGuiContext& g = *GImGui; g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasScroll; g.NextWindowData.ScrollVal = scroll; } + void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond) { ImGuiContext& g = *GImGui; @@ -7955,18 +9045,21 @@ void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond) g.NextWindowData.CollapsedVal = collapsed; g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always; } + void ImGui::SetNextWindowBgAlpha(float alpha) { ImGuiContext& g = *GImGui; g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasBgAlpha; g.NextWindowData.BgAlphaVal = alpha; } + void ImGui::SetNextWindowViewport(ImGuiID id) { ImGuiContext& g = *GImGui; g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasViewport; g.NextWindowData.ViewportId = id; } + void ImGui::SetNextWindowDockID(ImGuiID id, ImGuiCond cond) { ImGuiContext& g = *GImGui; @@ -7974,6 +9067,7 @@ void ImGui::SetNextWindowDockID(ImGuiID id, ImGuiCond cond) g.NextWindowData.DockCond = cond ? cond : ImGuiCond_Always; g.NextWindowData.DockId = id; } + void ImGui::SetNextWindowClass(const ImGuiWindowClass* window_class) { ImGuiContext& g = *GImGui; @@ -7981,6 +9075,7 @@ void ImGui::SetNextWindowClass(const ImGuiWindowClass* window_class) g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasWindowClass; g.NextWindowData.WindowClass = *window_class; } + // This is experimental and meant to be a toy for exploring a future/wider range of features. void ImGui::SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags) { @@ -7988,43 +9083,60 @@ void ImGui::SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags) g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasRefreshPolicy; g.NextWindowData.RefreshFlagsVal = flags; } + ImDrawList* ImGui::GetWindowDrawList() { ImGuiWindow* window = GetCurrentWindow(); return window->DrawList; } + float ImGui::GetWindowDpiScale() { ImGuiContext& g = *GImGui; return g.CurrentDpiScale; } + ImGuiViewport* ImGui::GetWindowViewport() { ImGuiContext& g = *GImGui; IM_ASSERT(g.CurrentViewport != NULL && g.CurrentViewport == g.CurrentWindow->Viewport); return g.CurrentViewport; } + ImFont* ImGui::GetFont() { return GImGui->Font; } + +ImFontBaked* ImGui::GetFontBaked() +{ + return GImGui->FontBaked; +} + +// Get current font size (= height in pixels) of current font, with global scale factors applied. +// - Use style.FontSizeBase to get value before global scale factors. +// - recap: ImGui::GetFontSize() == style.FontSizeBase * (style.FontScaleMain * style.FontScaleDpi * other_scaling_factors) float ImGui::GetFontSize() { return GImGui->FontSize; } + ImVec2 ImGui::GetFontTexUvWhitePixel() { return GImGui->DrawListSharedData.TexUvWhitePixel; } + +// Prefer using PushFont(NULL, style.FontSizeBase * factor), or use style.FontScaleMain to scale all windows. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS void ImGui::SetWindowFontScale(float scale) { IM_ASSERT(scale > 0.0f); - ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); window->FontWindowScale = scale; - g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); - g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize; + UpdateCurrentFontSize(0.0f); } +#endif + void ImGui::PushFocusScope(ImGuiID id) { ImGuiContext& g = *GImGui; @@ -8034,6 +9146,7 @@ void ImGui::PushFocusScope(ImGuiID id) g.FocusScopeStack.push_back(data); g.CurrentFocusScopeId = id; } + void ImGui::PopFocusScope() { ImGuiContext& g = *GImGui; @@ -8045,6 +9158,7 @@ void ImGui::PopFocusScope() g.FocusScopeStack.pop_back(); g.CurrentFocusScopeId = g.FocusScopeStack.Size ? g.FocusScopeStack.back().ID : 0; } + void ImGui::SetNavFocusScope(ImGuiID focus_scope_id) { ImGuiContext& g = *GImGui; @@ -8053,6 +9167,7 @@ void ImGui::SetNavFocusScope(ImGuiID focus_scope_id) if (focus_scope_id == 0) return; IM_ASSERT(g.NavWindow != NULL); + // Store current path (in reverse order) if (focus_scope_id == g.CurrentFocusScopeId) { @@ -8064,11 +9179,13 @@ void ImGui::SetNavFocusScope(ImGuiID focus_scope_id) g.NavFocusRoute.push_back({ focus_scope_id, g.NavWindow->ID }); else return; + // Then follow on manually set ParentWindowForFocusRoute field (#6798) for (ImGuiWindow* window = g.NavWindow->ParentWindowForFocusRoute; window != NULL; window = window->ParentWindowForFocusRoute) g.NavFocusRoute.push_back({ window->NavRootFocusScopeId, window->ID }); IM_ASSERT(g.NavFocusRoute.Size < 100); // Maximum depth is technically 251 as per CalcRoutingScore(): 254 - 3 } + // Focus = move navigation cursor, set scrolling, set focus window. void ImGui::FocusItem() { @@ -8080,18 +9197,21 @@ void ImGui::FocusItem() IMGUI_DEBUG_LOG_FOCUS("FocusItem() ignored while DragDropActive!\n"); return; } + ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_FocusApi | ImGuiNavMoveFlags_NoSetNavCursorVisible | ImGuiNavMoveFlags_NoSelect; ImGuiScrollFlags scroll_flags = window->Appearing ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY; SetNavWindow(window); NavMoveRequestSubmit(ImGuiDir_None, ImGuiDir_Up, move_flags, scroll_flags); NavMoveRequestResolveWithLastItem(&g.NavMoveResultLocal); } + void ImGui::ActivateItemByID(ImGuiID id) { ImGuiContext& g = *GImGui; g.NavNextActivateId = id; g.NavNextActivateFlags = ImGuiActivateFlags_None; } + // Note: this will likely be called ActivateItem() once we rework our Focus/Activation system! // But ActivateItem() should function without altering scroll/focus? void ImGui::SetKeyboardFocusHere(int offset) @@ -8100,6 +9220,7 @@ void ImGui::SetKeyboardFocusHere(int offset) ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(offset >= -1); // -1 is allowed but not below IMGUI_DEBUG_LOG_FOCUS("SetKeyboardFocusHere(%d) in window \"%s\"\n", offset, window->Name); + // It makes sense in the vast majority of cases to never interrupt a drag and drop. // When we refactor this function into ActivateItem() we may want to make this an option. // MovingWindow is protected from most user inputs using SetActiveIdUsingNavAndKeys(), but @@ -8109,7 +9230,9 @@ void ImGui::SetKeyboardFocusHere(int offset) IMGUI_DEBUG_LOG_FOCUS("SetKeyboardFocusHere() ignored while DragDropActive!\n"); return; } + SetNavWindow(window); + ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_Activate | ImGuiNavMoveFlags_FocusApi | ImGuiNavMoveFlags_NoSetNavCursorVisible; ImGuiScrollFlags scroll_flags = window->Appearing ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY; NavMoveRequestSubmit(ImGuiDir_None, offset < 0 ? ImGuiDir_Up : ImGuiDir_Down, move_flags, scroll_flags); // FIXME-NAV: Once we refactor tabbing, add LegacyApi flag to not activate non-inputable. @@ -8123,6 +9246,7 @@ void ImGui::SetKeyboardFocusHere(int offset) g.NavTabbingCounter = offset + 1; } } + void ImGui::SetItemDefaultFocus() { ImGuiContext& g = *GImGui; @@ -8131,36 +9255,306 @@ void ImGui::SetItemDefaultFocus() return; if (g.NavWindow != window->RootWindowForNav || (!g.NavInitRequest && g.NavInitResult.ID == 0) || g.NavLayer != window->DC.NavLayerCurrent) return; + g.NavInitRequest = false; NavApplyItemToResult(&g.NavInitResult); NavUpdateAnyRequestFlag(); + // Scroll could be done in NavInitRequestApplyResult() via an opt-in flag (we however don't want regular init requests to scroll) if (!window->ClipRect.Contains(g.LastItemData.Rect)) ScrollToRectEx(window, g.LastItemData.Rect, ImGuiScrollFlags_None); } + void ImGui::SetStateStorage(ImGuiStorage* tree) { ImGuiWindow* window = GImGui->CurrentWindow; window->DC.StateStorage = tree ? tree : &window->StateStorage; } + ImGuiStorage* ImGui::GetStateStorage() { ImGuiWindow* window = GImGui->CurrentWindow; return window->DC.StateStorage; } + bool ImGui::IsRectVisible(const ImVec2& size) { ImGuiWindow* window = GImGui->CurrentWindow; return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size)); } + bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) { ImGuiWindow* window = GImGui->CurrentWindow; return window->ClipRect.Overlaps(ImRect(rect_min, rect_max)); } + +//----------------------------------------------------------------------------- +// [SECTION] FONTS, TEXTURES +//----------------------------------------------------------------------------- +// Most of the relevant font logic is in imgui_draw.cpp. +// Those are high-level support functions. +//----------------------------------------------------------------------------- +// - UpdateTexturesNewFrame() [Internal] +// - UpdateTexturesEndFrame() [Internal] +// - UpdateFontsNewFrame() [Internal] +// - UpdateFontsEndFrame() [Internal] +// - GetDefaultFont() [Internal] +// - RegisterUserTexture() [Internal] +// - UnregisterUserTexture() [Internal] +// - RegisterFontAtlas() [Internal] +// - UnregisterFontAtlas() [Internal] +// - SetCurrentFont() [Internal] +// - UpdateCurrentFontSize() [Internal] +// - SetFontRasterizerDensity() [Internal] +// - PushFont() +// - PopFont() +//----------------------------------------------------------------------------- + +static void ImGui::UpdateTexturesNewFrame() +{ + // Cannot update every atlases based on atlas's FrameCount < g.FrameCount, because an atlas may be shared by multiple contexts with different frame count. + ImGuiContext& g = *GImGui; + const bool has_textures = (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; + for (ImFontAtlas* atlas : g.FontAtlases) + { + if (atlas->OwnerContext == &g) + { + ImFontAtlasUpdateNewFrame(atlas, g.FrameCount, has_textures); + } + else + { + // (1) If you manage font atlases yourself, e.g. create a ImFontAtlas yourself you need to call ImFontAtlasUpdateNewFrame() on it. + // Otherwise, calling ImGui::CreateContext() without parameter will create an atlas owned by the context. + // (2) If you have multiple font atlases, make sure the 'atlas->RendererHasTextures' as specified in the ImFontAtlasUpdateNewFrame() call matches for that. + // (3) If you have multiple imgui contexts, they also need to have a matching value for ImGuiBackendFlags_RendererHasTextures. + IM_ASSERT(atlas->Builder != NULL && atlas->Builder->FrameCount != -1); + IM_ASSERT(atlas->RendererHasTextures == has_textures); + } + } +} + +// Build a single texture list +static void ImGui::UpdateTexturesEndFrame() +{ + ImGuiContext& g = *GImGui; + g.PlatformIO.Textures.resize(0); + for (ImFontAtlas* atlas : g.FontAtlases) + for (ImTextureData* tex : atlas->TexList) + { + // We provide this information so backends can decide whether to destroy textures. + // This means in practice that if N imgui contexts are created with a shared atlas, we assume all of them have a backend initialized. + tex->RefCount = (unsigned short)atlas->RefCount; + g.PlatformIO.Textures.push_back(tex); + } + for (ImTextureData* tex : g.UserTextures) + g.PlatformIO.Textures.push_back(tex); +} + +void ImGui::UpdateFontsNewFrame() +{ + ImGuiContext& g = *GImGui; + if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + for (ImFontAtlas* atlas : g.FontAtlases) + atlas->Locked = true; + + if (g.Style._NextFrameFontSizeBase != 0.0f) + { + g.Style.FontSizeBase = g.Style._NextFrameFontSizeBase; + g.Style._NextFrameFontSizeBase = 0.0f; + } + + // Apply default font size the first time + ImFont* font = ImGui::GetDefaultFont(); + if (g.Style.FontSizeBase <= 0.0f) + g.Style.FontSizeBase = (font->LegacySize > 0.0f ? font->LegacySize : FONT_DEFAULT_SIZE); + + // Set initial font + g.Font = font; + g.FontSizeBase = g.Style.FontSizeBase; + g.FontSize = 0.0f; + ImFontStackData font_stack_data = { font, g.Style.FontSizeBase, g.Style.FontSizeBase }; // <--- Will restore FontSize + SetCurrentFont(font_stack_data.Font, font_stack_data.FontSizeBeforeScaling, 0.0f); // <--- but use 0.0f to enable scale + g.FontStack.push_back(font_stack_data); + IM_ASSERT(g.Font->IsLoaded()); +} + +void ImGui::UpdateFontsEndFrame() +{ + PopFont(); +} + +ImFont* ImGui::GetDefaultFont() +{ + ImGuiContext& g = *GImGui; + ImFontAtlas* atlas = g.IO.Fonts; + if (atlas->Builder == NULL || atlas->Fonts.Size == 0) + ImFontAtlasBuildMain(atlas); + return g.IO.FontDefault ? g.IO.FontDefault : atlas->Fonts[0]; +} + +// EXPERIMENTAL: DO NOT USE YET. +void ImGui::RegisterUserTexture(ImTextureData* tex) +{ + ImGuiContext& g = *GImGui; + tex->RefCount++; + g.UserTextures.push_back(tex); +} + +void ImGui::UnregisterUserTexture(ImTextureData* tex) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(tex->RefCount > 0); + tex->RefCount--; + g.UserTextures.find_erase(tex); +} + +void ImGui::RegisterFontAtlas(ImFontAtlas* atlas) +{ + ImGuiContext& g = *GImGui; + if (g.FontAtlases.Size == 0) + IM_ASSERT(atlas == g.IO.Fonts); + atlas->RefCount++; + g.FontAtlases.push_back(atlas); + ImFontAtlasAddDrawListSharedData(atlas, &g.DrawListSharedData); +} + +void ImGui::UnregisterFontAtlas(ImFontAtlas* atlas) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(atlas->RefCount > 0); + ImFontAtlasRemoveDrawListSharedData(atlas, &g.DrawListSharedData); + g.FontAtlases.find_erase(atlas); + atlas->RefCount--; +} + +// Use ImDrawList::_SetTexture(), making our shared g.FontStack[] authoritative against window-local ImDrawList. +// - Whereas ImDrawList::PushTexture()/PopTexture() is not to be used across Begin() calls. +// - Note that we don't propagate current texture id when e.g. Begin()-ing into a new window, we never really did... +// - Some code paths never really fully worked with multiple atlas textures. +// - The right-ish solution may be to remove _SetTexture() and make AddText/RenderText lazily call PushTexture()/PopTexture() +// the same way AddImage() does, but then all other primitives would also need to? I don't think we should tackle this problem +// because we have a concrete need and a test bed for multiple atlas textures. +// FIXME-NEWATLAS-V2: perhaps we can now leverage ImFontAtlasUpdateDrawListsTextures() ? +void ImGui::SetCurrentFont(ImFont* font, float font_size_before_scaling, float font_size_after_scaling) +{ + ImGuiContext& g = *GImGui; + g.Font = font; + g.FontSizeBase = font_size_before_scaling; + UpdateCurrentFontSize(font_size_after_scaling); + + if (font != NULL) + { + IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + IM_ASSERT(font->Scale > 0.0f); +#endif + ImFontAtlas* atlas = font->ContainerAtlas; + g.DrawListSharedData.FontAtlas = atlas; + g.DrawListSharedData.Font = font; + ImFontAtlasUpdateDrawListsSharedData(atlas); + if (g.CurrentWindow != NULL) + g.CurrentWindow->DrawList->_SetTexture(atlas->TexRef); + } +} + +void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + g.Style.FontSizeBase = g.FontSizeBase; + + // Early out to avoid hidden window keeping bakes referenced and out of GC reach. + // However this would leave a pretty subtle and damning error surface area if g.FontBaked was mismatching. + // FIXME: perhaps g.FontSize should be updated? + if (window != NULL && window->SkipItems) + { + ImGuiTable* table = g.CurrentTable; + if (table == NULL || (table->CurrentColumn != -1 && table->Columns[table->CurrentColumn].IsSkipItems == false)) // See 8465#issuecomment-2951509561 and #8865. Ideally the SkipItems=true in tables would be amended with extra data. + return; + } + + // Restoring is pretty much only used by PopFont() + float final_size = (restore_font_size_after_scaling > 0.0f) ? restore_font_size_after_scaling : 0.0f; + if (final_size == 0.0f) + { + final_size = g.FontSizeBase; + + // Global scale factors + final_size *= g.Style.FontScaleMain; // Main global scale factor + final_size *= g.Style.FontScaleDpi; // Per-monitor/viewport DPI scale factor, automatically updated when io.ConfigDpiScaleFonts is enabled. + + // Window scale (mostly obsolete now) + if (window != NULL) + final_size *= window->FontWindowScale; + + // Legacy scale factors +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + final_size *= g.IO.FontGlobalScale; // Use style.FontScaleMain instead! + if (g.Font != NULL) + final_size *= g.Font->Scale; // Was never really useful. +#endif + } + + // Round font size + // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet. + // - We may support it better later and remove this rounding. + final_size = GetRoundedFontSize(final_size); + final_size = ImClamp(final_size, 1.0f, IMGUI_FONT_SIZE_MAX); + if (g.Font != NULL && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures)) + g.Font->CurrentRasterizerDensity = g.FontRasterizerDensity; + g.FontSize = final_size; + g.FontBaked = (g.Font != NULL && window != NULL) ? g.Font->GetFontBaked(final_size) : NULL; + g.FontBakedScale = (g.Font != NULL && window != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f; + g.DrawListSharedData.FontSize = g.FontSize; + g.DrawListSharedData.FontScale = g.FontBakedScale; +} + +// Exposed in case user may want to override setting density. +// IMPORTANT: Begin()/End() is overriding density. Be considerate of this you change it. +void ImGui::SetFontRasterizerDensity(float rasterizer_density) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures); + if (g.FontRasterizerDensity == rasterizer_density) + return; + g.FontRasterizerDensity = rasterizer_density; + UpdateCurrentFontSize(0.0f); +} + +// If you want to scale an existing font size! Read comments in imgui.h! +void ImGui::PushFont(ImFont* font, float font_size_base) +{ + ImGuiContext& g = *GImGui; + if (font == NULL) // Before 1.92 (June 2025), PushFont(NULL) == PushFont(GetDefaultFont()) + font = g.Font; + IM_ASSERT(font != NULL); + IM_ASSERT(font_size_base >= 0.0f); + + g.FontStack.push_back({ g.Font, g.FontSizeBase, g.FontSize }); + if (font_size_base == 0.0f) + font_size_base = g.FontSizeBase; // Keep current font size + SetCurrentFont(font, font_size_base, 0.0f); +} + +void ImGui::PopFont() +{ + ImGuiContext& g = *GImGui; + if (g.FontStack.Size <= 0) + { + IM_ASSERT_USER_ERROR(0, "Calling PopFont() too many times!"); + return; + } + ImFontStackData* font_stack_data = &g.FontStack.back(); + SetCurrentFont(font_stack_data->Font, font_stack_data->FontSizeBeforeScaling, font_stack_data->FontSizeAfterScaling); + g.FontStack.pop_back(); +} + //----------------------------------------------------------------------------- // [SECTION] ID STACK //----------------------------------------------------------------------------- + // This is one of the very rare legacy case where we use ImGuiWindow methods, // it should ideally be flattened at some point but it's been used a lots by widgets. IM_MSVC_RUNTIME_CHECKS_OFF @@ -8175,6 +9569,7 @@ ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) #endif return id; } + ImGuiID ImGuiWindow::GetID(const void* ptr) { ImGuiID seed = IDStack.back(); @@ -8186,6 +9581,7 @@ ImGuiID ImGuiWindow::GetID(const void* ptr) #endif return id; } + ImGuiID ImGuiWindow::GetID(int n) { ImGuiID seed = IDStack.back(); @@ -8197,6 +9593,7 @@ ImGuiID ImGuiWindow::GetID(int n) #endif return id; } + // This is only used in rare/specific situations to manufacture an ID out of nowhere. // FIXME: Consider instead storing last non-zero ID + count of successive zero-ID, and combine those? ImGuiID ImGuiWindow::GetIDFromPos(const ImVec2& p_abs) @@ -8206,6 +9603,7 @@ ImGuiID ImGuiWindow::GetIDFromPos(const ImVec2& p_abs) ImGuiID id = ImHashData(&p_rel, sizeof(p_rel), seed); return id; } + // " ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs) { @@ -8214,6 +9612,7 @@ ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs) ImGuiID id = ImHashData(&r_rel, sizeof(r_rel), seed); return id; } + void ImGui::PushID(const char* str_id) { ImGuiContext& g = *GImGui; @@ -8221,6 +9620,7 @@ void ImGui::PushID(const char* str_id) ImGuiID id = window->GetID(str_id); window->IDStack.push_back(id); } + void ImGui::PushID(const char* str_id_begin, const char* str_id_end) { ImGuiContext& g = *GImGui; @@ -8228,6 +9628,7 @@ void ImGui::PushID(const char* str_id_begin, const char* str_id_end) ImGuiID id = window->GetID(str_id_begin, str_id_end); window->IDStack.push_back(id); } + void ImGui::PushID(const void* ptr_id) { ImGuiContext& g = *GImGui; @@ -8235,6 +9636,7 @@ void ImGui::PushID(const void* ptr_id) ImGuiID id = window->GetID(ptr_id); window->IDStack.push_back(id); } + void ImGui::PushID(int int_id) { ImGuiContext& g = *GImGui; @@ -8242,6 +9644,7 @@ void ImGui::PushID(int int_id) ImGuiID id = window->GetID(int_id); window->IDStack.push_back(id); } + // Push a given id value ignoring the ID stack as a seed. void ImGui::PushOverrideID(ImGuiID id) { @@ -8253,6 +9656,7 @@ void ImGui::PushOverrideID(ImGuiID id) #endif window->IDStack.push_back(id); } + // Helper to avoid a common series of PushOverrideID -> GetID() -> PopID() call // (note that when using this pattern, ID Stack Tool will tend to not display the intermediate stack level. // for that to work we would need to do PushOverrideID() -> ItemAdd() -> PopID() which would alter widget code a little more) @@ -8266,6 +9670,7 @@ ImGuiID ImGui::GetIDWithSeed(const char* str, const char* str_end, ImGuiID seed) #endif return id; } + ImGuiID ImGui::GetIDWithSeed(int n, ImGuiID seed) { ImGuiID id = ImHashData(&n, sizeof(n), seed); @@ -8276,6 +9681,7 @@ ImGuiID ImGui::GetIDWithSeed(int n, ImGuiID seed) #endif return id; } + void ImGui::PopID() { ImGuiWindow* window = GImGui->CurrentWindow; @@ -8286,27 +9692,32 @@ void ImGui::PopID() } window->IDStack.pop_back(); } + ImGuiID ImGui::GetID(const char* str_id) { ImGuiWindow* window = GImGui->CurrentWindow; return window->GetID(str_id); } + ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end) { ImGuiWindow* window = GImGui->CurrentWindow; return window->GetID(str_id_begin, str_id_end); } + ImGuiID ImGui::GetID(const void* ptr_id) { ImGuiWindow* window = GImGui->CurrentWindow; return window->GetID(ptr_id); } + ImGuiID ImGui::GetID(int int_id) { ImGuiWindow* window = GImGui->CurrentWindow; return window->GetID(int_id); } IM_MSVC_RUNTIME_CHECKS_RESTORE + //----------------------------------------------------------------------------- // [SECTION] INPUTS //----------------------------------------------------------------------------- @@ -8372,6 +9783,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE // - SetItemKeyOwner() [Internal] // - Shortcut() [Internal] //----------------------------------------------------------------------------- + static ImGuiKeyChord GetModForLRModKey(ImGuiKey key) { if (key == ImGuiKey_LeftCtrl || key == ImGuiKey_RightCtrl) @@ -8384,6 +9796,7 @@ static ImGuiKeyChord GetModForLRModKey(ImGuiKey key) return ImGuiMod_Super; return ImGuiMod_None; } + ImGuiKeyChord ImGui::FixupKeyChord(ImGuiKeyChord key_chord) { // Add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified. @@ -8392,15 +9805,19 @@ ImGuiKeyChord ImGui::FixupKeyChord(ImGuiKeyChord key_chord) key_chord |= GetModForLRModKey(key); return key_chord; } + ImGuiKeyData* ImGui::GetKeyData(ImGuiContext* ctx, ImGuiKey key) { ImGuiContext& g = *ctx; + // Special storage location for mods if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); + IM_ASSERT(IsNamedKey(key) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend & user code."); return &g.IO.KeysData[key - ImGuiKey_NamedKey_BEGIN]; } + // Those names are provided for debugging purpose and are not meant to be saved persistently nor compared. static const char* const GKeyNames[] = { @@ -8427,6 +9844,7 @@ static const char* const GKeyNames[] = "ModCtrl", "ModShift", "ModAlt", "ModSuper", // ReservedForModXXX are showing the ModXXX names. }; IM_STATIC_ASSERT(ImGuiKey_NamedKey_COUNT == IM_ARRAYSIZE(GKeyNames)); + const char* ImGui::GetKeyName(ImGuiKey key) { if (key == ImGuiKey_None) @@ -8436,13 +9854,16 @@ const char* ImGui::GetKeyName(ImGuiKey key) key = ConvertSingleModFlagToKey(key); if (!IsNamedKey(key)) return "Unknown"; + return GKeyNames[key - ImGuiKey_NamedKey_BEGIN]; } + // Return untranslated names: on macOS, Cmd key will show as Ctrl, Ctrl key will show as super. // Lifetime of return value: valid until next call to same function. const char* ImGui::GetKeyChordName(ImGuiKeyChord key_chord) { ImGuiContext& g = *GImGui; + const ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); if (IsLRModKey(key)) key_chord &= ~GetModForLRModKey(key); // Return "Ctrl+LeftShift" instead of "Ctrl+Shift+LeftShift" @@ -8458,6 +9879,7 @@ const char* ImGui::GetKeyChordName(ImGuiKeyChord key_chord) g.TempKeychordName[len - 1] = 0; return g.TempKeychordName; } + // t0 = previous time (e.g.: g.Time - g.IO.DeltaTime) // t1 = current time (e.g.: g.Time) // An event is triggered at: @@ -8475,6 +9897,7 @@ int ImGui::CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, flo const int count = count_t1 - count_t0; return count; } + void ImGui::GetTypematicRepeatRate(ImGuiInputFlags flags, float* repeat_delay, float* repeat_rate) { ImGuiContext& g = *GImGui; @@ -8485,6 +9908,7 @@ void ImGui::GetTypematicRepeatRate(ImGuiInputFlags flags, float* repeat_delay, f case ImGuiInputFlags_RepeatRateDefault: default: *repeat_delay = g.IO.KeyRepeatDelay * 1.00f; *repeat_rate = g.IO.KeyRepeatRate * 1.00f; return; } } + // Return value representing the number of presses in the last time period, for the given repeat rate // (most often returns 0 or 1. The result is generally only >1 when RepeatRate is smaller than DeltaTime, aka large DeltaTime or fast RepeatRate) int ImGui::GetKeyPressedAmount(ImGuiKey key, float repeat_delay, float repeat_rate) @@ -8496,6 +9920,7 @@ int ImGui::GetKeyPressedAmount(ImGuiKey key, float repeat_delay, float repeat_ra const float t = key_data->DownDuration; return CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, repeat_delay, repeat_rate); } + // Return 2D vector representing the combination of four cardinal direction, with analog value support (for e.g. ImGuiKey_GamepadLStick* values). ImVec2 ImGui::GetKeyMagnitude2d(ImGuiKey key_left, ImGuiKey key_right, ImGuiKey key_up, ImGuiKey key_down) { @@ -8503,6 +9928,7 @@ ImVec2 ImGui::GetKeyMagnitude2d(ImGuiKey key_left, ImGuiKey key_right, ImGuiKey GetKeyData(key_right)->AnalogValue - GetKeyData(key_left)->AnalogValue, GetKeyData(key_down)->AnalogValue - GetKeyData(key_up)->AnalogValue); } + // Rewrite routing data buffers to strip old entries + sort by key to make queries not touch scattered data. // Entries D,A,B,B,A,C,B --> A,A,B,B,B,C,D // Index A:1 B:2 C:5 D:0 --> A:0 B:2 C:5 D:6 @@ -8525,6 +9951,7 @@ static void ImGui::UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt) if (routing_entry->RoutingCurr == ImGuiKeyOwner_NoOwner) continue; rt->EntriesNext.push_back(*routing_entry); // Write alive ones into new buffer + // Apply routing to owner if there's no owner already (RoutingCurr == None at this point) // This is the result of previous frame's SetShortcutRouting() call. if (routing_entry->Mods == g.IO.KeyMods) @@ -8537,6 +9964,7 @@ static void ImGui::UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt) } } } + // Rewrite linked-list rt->Index[key - ImGuiKey_NamedKey_BEGIN] = (ImGuiKeyRoutingIndex)(new_routing_start_idx < rt->EntriesNext.Size ? new_routing_start_idx : -1); for (int n = new_routing_start_idx; n < rt->EntriesNext.Size; n++) @@ -8544,12 +9972,14 @@ static void ImGui::UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt) } rt->Entries.swap(rt->EntriesNext); // Swap new and old indexes } + // owner_id may be None/Any, but routing_id needs to be always be set, so we default to GetCurrentFocusScope(). static inline ImGuiID GetRoutingIdFromOwnerId(ImGuiID owner_id) { ImGuiContext& g = *GImGui; return (owner_id != ImGuiKeyOwner_NoOwner && owner_id != ImGuiKeyOwner_Any) ? owner_id : g.CurrentFocusScopeId; } + ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord) { // Majority of shortcuts will be Key + any number of Mods @@ -8566,6 +9996,7 @@ ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord) if (key == ImGuiKey_None) key = ConvertSingleModFlagToKey(mods); IM_ASSERT(IsNamedKey(key)); + // Get (in the majority of case, the linked list will have one element so this should be 2 reads. // Subsequent elements will be contiguous in memory as list is sorted/rebuilt in NewFrame). for (ImGuiKeyRoutingIndex idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; idx != -1; idx = routing_data->NextEntryIndex) @@ -8574,6 +10005,7 @@ ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord) if (routing_data->Mods == mods) return routing_data; } + // Add to linked-list ImGuiKeyRoutingIndex routing_data_idx = (ImGuiKeyRoutingIndex)rt->Entries.Size; rt->Entries.push_back(ImGuiKeyRoutingData()); @@ -8583,6 +10015,7 @@ ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord) rt->Index[key - ImGuiKey_NamedKey_BEGIN] = routing_data_idx; return routing_data; } + // Current score encoding (lower is highest priority): // - 0: ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverActive // - 1: ImGuiInputFlags_ActiveItem or ImGuiInputFlags_RouteFocused (if item active) @@ -8600,6 +10033,7 @@ static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInput // (we don't check g.ActiveIdUsingAllKeys here. Routing is applied but if input ownership is tested later it may discard it) if (owner_id != 0 && g.ActiveId == owner_id) return 1; + // Score based on distance to focused window (lower is better) // Assuming both windows are submitting a routing request, // - When Window....... is focused -> Window scores 3 (best), Window/ChildB scores 255 (no match) @@ -8631,6 +10065,7 @@ static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInput IM_ASSERT(0); return 0; } + // - We need this to filter some Shortcut() routes when an item e.g. an InputText() is active // e.g. ImGuiKey_G won't be considered a shortcut when item is active, but ImGuiMod|ImGuiKey_G can be. // - This is also used by UpdateInputEvents() to avoid trickling in the most common case of e.g. pressing ImGuiKey_G also emitting a G character. @@ -8638,17 +10073,20 @@ static bool IsKeyChordPotentiallyCharInput(ImGuiKeyChord key_chord) { // Mimic 'ignore_char_inputs' logic in InputText() ImGuiContext& g = *GImGui; + // When the right mods are pressed it cannot be a char input so we won't filter the shortcut out. ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); const bool ignore_char_inputs = ((mods & ImGuiMod_Ctrl) && !(mods & ImGuiMod_Alt)) || (g.IO.ConfigMacOSXBehaviors && (mods & ImGuiMod_Ctrl)); if (ignore_char_inputs) return false; + // Return true for A-Z, 0-9 and other keys associated to char inputs. Other keys such as F1-F12 won't be filtered. ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); if (key == ImGuiKey_None) return false; return g.KeysMayBeCharInput.TestBit(key); } + // Request a desired route for an input chord (key + mods). // Return true if the route is available this frame. // - Routes and key ownership are attributed at the beginning of next frame based on best score and mod state. @@ -8664,25 +10102,31 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiInputFlags flags, I IM_ASSERT(owner_id != ImGuiKeyOwner_Any && owner_id != ImGuiKeyOwner_NoOwner); if (flags & (ImGuiInputFlags_RouteOverFocused | ImGuiInputFlags_RouteOverActive | ImGuiInputFlags_RouteUnlessBgFocused)) IM_ASSERT(flags & ImGuiInputFlags_RouteGlobal); + // Add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified. key_chord = FixupKeyChord(key_chord); + // [DEBUG] Debug break requested by user if (g.DebugBreakInShortcutRouting == key_chord) IM_DEBUG_BREAK(); + if (flags & ImGuiInputFlags_RouteUnlessBgFocused) if (g.NavWindow == NULL) return false; + // Note how ImGuiInputFlags_RouteAlways won't set routing and thus won't set owner. May want to rework this? if (flags & ImGuiInputFlags_RouteAlways) { IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> always, no register\n", GetKeyChordName(key_chord), flags, owner_id); return true; } + // Specific culling when there's an active item. if (g.ActiveId != 0 && g.ActiveId != owner_id) { if (flags & ImGuiInputFlags_RouteActive) return false; + // Cull shortcuts with no modifiers when it could generate a character. // e.g. Shortcut(ImGuiKey_G) also generates 'g' character, should not trigger when InputText() is active. // but Shortcut(Ctrl+G) should generally trigger when InputText() is active. @@ -8693,6 +10137,7 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiInputFlags flags, I IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> filtered as potential char input\n", GetKeyChordName(key_chord), flags, owner_id); return false; } + // ActiveIdUsingAllKeyboardKeys trumps all for ActiveId if ((flags & ImGuiInputFlags_RouteOverActive) == 0 && g.ActiveIdUsingAllKeyboardKeys) { @@ -8703,14 +10148,17 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiInputFlags flags, I return false; } } + // Where do we evaluate route for? ImGuiID focus_scope_id = g.CurrentFocusScopeId; if (flags & ImGuiInputFlags_RouteFromRootWindow) focus_scope_id = g.CurrentWindow->RootWindow->ID; // See PushFocusScope() call in Begin() + const int score = CalcRoutingScore(focus_scope_id, owner_id, flags); IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> score %d\n", GetKeyChordName(key_chord), flags, owner_id, score); if (score == 255) return false; + // Submit routing for NEXT frame (assuming score is sufficient) // FIXME: Could expose a way to use a "serve last" policy for same score resolution (using <= instead of <). ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); @@ -8720,11 +10168,13 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiInputFlags flags, I routing_data->RoutingNext = owner_id; routing_data->RoutingNextScore = (ImU8)score; } + // Return routing state for CURRENT frame if (routing_data->RoutingCurr == owner_id) IMGUI_DEBUG_LOG_INPUTROUTING("--> granting current route\n"); return routing_data->RoutingCurr == owner_id; } + // Currently unused by core (but used by tests) // Note: this cannot be turned into GetShortcutRouting() because we do the owner_id->routing_id translation, name would be more misleading. bool ImGui::TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id) @@ -8734,12 +10184,14 @@ bool ImGui::TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id) ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); // FIXME: Could avoid creating entry. return routing_data->RoutingCurr == routing_id; } + // Note that Dear ImGui doesn't know the meaning/semantic of ImGuiKey from 0..511: they are legacy native keycodes. // Consider transitioning from 'IsKeyDown(MY_ENGINE_KEY_A)' (<1.87) to IsKeyDown(ImGuiKey_A) (>= 1.87) bool ImGui::IsKeyDown(ImGuiKey key) { return IsKeyDown(key, ImGuiKeyOwner_Any); } + bool ImGui::IsKeyDown(ImGuiKey key, ImGuiID owner_id) { const ImGuiKeyData* key_data = GetKeyData(key); @@ -8749,10 +10201,12 @@ bool ImGui::IsKeyDown(ImGuiKey key, ImGuiID owner_id) return false; return true; } + bool ImGui::IsKeyPressed(ImGuiKey key, bool repeat) { return IsKeyPressed(key, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None, ImGuiKeyOwner_Any); } + // Important: unlike legacy IsKeyPressed(ImGuiKey, bool repeat=true) which DEFAULT to repeat, this requires EXPLICIT repeat. bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id) { @@ -8765,6 +10219,7 @@ bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id) IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsKeyPressed) == 0); // Passing flags not supported by this function! if (flags & (ImGuiInputFlags_RepeatRateMask_ | ImGuiInputFlags_RepeatUntilMask_)) // Setting any _RepeatXXX option enables _Repeat flags |= ImGuiInputFlags_Repeat; + bool pressed = (t == 0.0f); if (!pressed && (flags & ImGuiInputFlags_Repeat) != 0) { @@ -8791,10 +10246,12 @@ bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id) return false; return true; } + bool ImGui::IsKeyReleased(ImGuiKey key) { return IsKeyReleased(key, ImGuiKeyOwner_Any); } + bool ImGui::IsKeyReleased(ImGuiKey key, ImGuiID owner_id) { const ImGuiKeyData* key_data = GetKeyData(key); @@ -8804,22 +10261,26 @@ bool ImGui::IsKeyReleased(ImGuiKey key, ImGuiID owner_id) return false; return true; } + bool ImGui::IsMouseDown(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); return g.IO.MouseDown[button] && TestKeyOwner(MouseButtonToKey(button), ImGuiKeyOwner_Any); // should be same as IsKeyDown(MouseButtonToKey(button), ImGuiKeyOwner_Any), but this allows legacy code hijacking the io.Mousedown[] array. } + bool ImGui::IsMouseDown(ImGuiMouseButton button, ImGuiID owner_id) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); return g.IO.MouseDown[button] && TestKeyOwner(MouseButtonToKey(button), owner_id); // Should be same as IsKeyDown(MouseButtonToKey(button), owner_id), but this allows legacy code hijacking the io.Mousedown[] array. } + bool ImGui::IsMouseClicked(ImGuiMouseButton button, bool repeat) { return IsMouseClicked(button, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None, ImGuiKeyOwner_Any); } + bool ImGui::IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGuiID owner_id) { ImGuiContext& g = *GImGui; @@ -8830,26 +10291,32 @@ bool ImGui::IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGui if (t < 0.0f) return false; IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsMouseClicked) == 0); // Passing flags not supported by this function! // FIXME: Could support RepeatRate and RepeatUntil flags here. + const bool repeat = (flags & ImGuiInputFlags_Repeat) != 0; const bool pressed = (t == 0.0f) || (repeat && t > g.IO.KeyRepeatDelay && CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0); if (!pressed) return false; + if (!TestKeyOwner(MouseButtonToKey(button), owner_id)) return false; + return true; } + bool ImGui::IsMouseReleased(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); return g.IO.MouseReleased[button] && TestKeyOwner(MouseButtonToKey(button), ImGuiKeyOwner_Any); // Should be same as IsKeyReleased(MouseButtonToKey(button), ImGuiKeyOwner_Any) } + bool ImGui::IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); return g.IO.MouseReleased[button] && TestKeyOwner(MouseButtonToKey(button), owner_id); // Should be same as IsKeyReleased(MouseButtonToKey(button), owner_id) } + // Use if you absolutely need to distinguish single-click from double-click by introducing a delay. // Generally use with 'delay >= io.MouseDoubleClickTime' + combined with a 'io.MouseClickedLastCount == 1' test. // This is a very rarely used UI idiom, but some apps use this: e.g. MS Explorer single click on an icon to rename. @@ -8860,34 +10327,40 @@ bool ImGui::IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay) const float time_since_release = (float)(g.Time - g.IO.MouseReleasedTime[button]); return !IsMouseDown(button) && (time_since_release - g.IO.DeltaTime < delay) && (time_since_release >= delay); } + bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); return g.IO.MouseClickedCount[button] == 2 && TestKeyOwner(MouseButtonToKey(button), ImGuiKeyOwner_Any); } + bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button, ImGuiID owner_id) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); return g.IO.MouseClickedCount[button] == 2 && TestKeyOwner(MouseButtonToKey(button), owner_id); } + int ImGui::GetMouseClickedCount(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); return g.IO.MouseClickedCount[button]; } + // Test if mouse cursor is hovering given rectangle // NB- Rectangle is clipped by our current clip setting // NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding) bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip) { ImGuiContext& g = *GImGui; + // Clip ImRect rect_clipped(r_min, r_max); if (clip) rect_clipped.ClipWith(g.CurrentWindow->ClipRect); + // Hit testing, expanded for touch input if (!rect_clipped.ContainsWithPad(g.IO.MousePos, g.Style.TouchExtraPadding)) return false; @@ -8895,6 +10368,7 @@ bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool c return false; return true; } + // Return if a mouse click/drag went past the given threshold. Valid to call during the MouseReleased frame. // [Internal] This doesn't test if the button is pressed bool ImGui::IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_threshold) @@ -8905,6 +10379,7 @@ bool ImGui::IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_thresho lock_threshold = g.IO.MouseDragThreshold; return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold; } + bool ImGui::IsMouseDragging(ImGuiMouseButton button, float lock_threshold) { ImGuiContext& g = *GImGui; @@ -8913,11 +10388,13 @@ bool ImGui::IsMouseDragging(ImGuiMouseButton button, float lock_threshold) return false; return IsMouseDragPastThreshold(button, lock_threshold); } + ImVec2 ImGui::GetMousePos() { ImGuiContext& g = *GImGui; return g.IO.MousePos; } + // This is called TeleportMousePos() and not SetMousePos() to emphasis that setting MousePosPrev will effectively clear mouse delta as well. // It is expected you only call this if (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos) is set and supported by backend. void ImGui::TeleportMousePos(const ImVec2& pos) @@ -8928,6 +10405,7 @@ void ImGui::TeleportMousePos(const ImVec2& pos) g.IO.WantSetMousePos = true; //IMGUI_DEBUG_LOG_IO("TeleportMousePos: (%.1f,%.1f)\n", io.MousePos.x, io.MousePos.y); } + // NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed! ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup() { @@ -8936,6 +10414,7 @@ ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup() return g.OpenPopupStack[g.BeginPopupStack.Size - 1].OpenMousePos; return g.IO.MousePos; } + // We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position. bool ImGui::IsMousePosValid(const ImVec2* mouse_pos) { @@ -8946,6 +10425,7 @@ bool ImGui::IsMousePosValid(const ImVec2* mouse_pos) ImVec2 p = mouse_pos ? *mouse_pos : GImGui->IO.MousePos; return p.x >= MOUSE_INVALID && p.y >= MOUSE_INVALID; } + // [WILL OBSOLETE] This was designed for backends, but prefer having backend maintain a mask of held mouse buttons, because upcoming input queue system will make this invalid. bool ImGui::IsAnyMouseDown() { @@ -8955,6 +10435,7 @@ bool ImGui::IsAnyMouseDown() return true; return false; } + // Return the delta from the initial clicking position while the mouse button is clicked or was just released. // This is locked and return 0.0f until the mouse moves past a distance threshold at least once. // NB: This is only valid if IsMousePosValid(). backends in theory should always keep mouse position valid when dragging even outside the client window. @@ -8970,6 +10451,7 @@ ImVec2 ImGui::GetMouseDragDelta(ImGuiMouseButton button, float lock_threshold) return g.IO.MousePos - g.IO.MouseClickedPos[button]; return ImVec2(0.0f, 0.0f); } + void ImGui::ResetMouseDragDelta(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; @@ -8977,6 +10459,7 @@ void ImGui::ResetMouseDragDelta(ImGuiMouseButton button) // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr g.IO.MouseClickedPos[button] = g.IO.MousePos; } + // Get desired mouse cursor shape. // Important: this is meant to be used by a platform backend, it is reset in ImGui::NewFrame(), // updated during the frame, and locked in EndFrame()/Render(). @@ -8986,6 +10469,7 @@ ImGuiMouseCursor ImGui::GetMouseCursor() ImGuiContext& g = *GImGui; return g.MouseCursor; } + // We intentionally accept values of ImGuiMouseCursor that are outside our bounds, in case users needs to hack-in a custom cursor value. // Custom cursors may be handled by custom backends. If you are using a standard backend and want to hack in a custom cursor, you may // handle it before the backend _NewFrame() call and temporarily set ImGuiConfigFlags_NoMouseCursorChange during the backend _NewFrame() call. @@ -8994,6 +10478,7 @@ void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type) ImGuiContext& g = *GImGui; g.MouseCursor = cursor_type; } + static void UpdateAliasKey(ImGuiKey key, bool v, float analog_value) { IM_ASSERT(ImGui::IsAliasKey(key)); @@ -9001,6 +10486,7 @@ static void UpdateAliasKey(ImGuiKey key, bool v, float analog_value) key_data->Down = v; key_data->AnalogValue = analog_value; } + // [Internal] Do not use directly static ImGuiKeyChord GetMergedModsFromKeys() { @@ -9011,17 +10497,21 @@ static ImGuiKeyChord GetMergedModsFromKeys() if (ImGui::IsKeyDown(ImGuiMod_Super)) { mods |= ImGuiMod_Super; } return mods; } + static void ImGui::UpdateKeyboardInputs() { ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; + if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) io.ClearInputKeys(); + // Update aliases for (int n = 0; n < ImGuiMouseButton_COUNT; n++) UpdateAliasKey(MouseButtonToKey(n), io.MouseDown[n], io.MouseDown[n] ? 1.0f : 0.0f); UpdateAliasKey(ImGuiKey_MouseWheelX, io.MouseWheelH != 0.0f, io.MouseWheelH); UpdateAliasKey(ImGuiKey_MouseWheelY, io.MouseWheel != 0.0f, io.MouseWheel); + // Synchronize io.KeyMods and io.KeyCtrl/io.KeyShift/etc. values. // - New backends (1.87+): send io.AddKeyEvent(ImGuiMod_XXX) -> -> (here) deriving io.KeyMods + io.KeyXXX from key array. // - Legacy backends: set io.KeyXXX bools -> (above) set key array from io.KeyXXX -> (here) deriving io.KeyMods + io.KeyXXX from key array. @@ -9036,6 +10526,7 @@ static void ImGui::UpdateKeyboardInputs() g.LastKeyModsChangeTime = g.Time; if (prev_key_mods != io.KeyMods && prev_key_mods == 0) g.LastKeyModsChangeFromNoneTime = g.Time; + // Clear gamepad data if disabled if ((io.BackendFlags & ImGuiBackendFlags_HasGamepad) == 0) for (int key = ImGuiKey_Gamepad_BEGIN; key < ImGuiKey_Gamepad_END; key++) @@ -9043,6 +10534,7 @@ static void ImGui::UpdateKeyboardInputs() io.KeysData[key - ImGuiKey_NamedKey_BEGIN].Down = false; io.KeysData[key - ImGuiKey_NamedKey_BEGIN].AnalogValue = 0.0f; } + // Update keys for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key++) { @@ -9057,6 +10549,7 @@ static void ImGui::UpdateKeyboardInputs() g.LastKeyboardKeyPressTime = g.Time; } } + // Update keys/input owner (named keys only): one entry per key for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { @@ -9067,36 +10560,44 @@ static void ImGui::UpdateKeyboardInputs() owner_data->OwnerNext = ImGuiKeyOwner_NoOwner; owner_data->LockThisFrame = owner_data->LockUntilRelease = owner_data->LockUntilRelease && key_data->Down; // Clear LockUntilRelease when key is not Down anymore } + // Update key routing (for e.g. shortcuts) UpdateKeyRoutingTable(&g.KeysRoutingTable); } + static void ImGui::UpdateMouseInputs() { ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; + // Mouse Wheel swapping flag // As a standard behavior holding SHIFT while using Vertical Mouse Wheel triggers Horizontal scroll instead // - We avoid doing it on OSX as it the OS input layer handles this already. // - FIXME: However this means when running on OSX over Emscripten, Shift+WheelY will incur two swapping (1 in OS, 1 here), canceling the feature. // - FIXME: When we can distinguish e.g. touchpad scroll events from mouse ones, we'll set this accordingly based on input source. io.MouseWheelRequestAxisSwap = io.KeyShift && !io.ConfigMacOSXBehaviors; + // Round mouse position to avoid spreading non-rounded position (e.g. UpdateManualResize doesn't support them well) if (IsMousePosValid(&io.MousePos)) io.MousePos = g.MouseLastValidPos = ImFloor(io.MousePos); + // If mouse just appeared or disappeared (usually denoted by -FLT_MAX components) we cancel out movement in MouseDelta if (IsMousePosValid(&io.MousePos) && IsMousePosValid(&io.MousePosPrev)) io.MouseDelta = io.MousePos - io.MousePosPrev; else io.MouseDelta = ImVec2(0.0f, 0.0f); + // Update stationary timer. // FIXME: May need to rework again to have some tolerance for occasional small movement, while being functional on high-framerates. const float mouse_stationary_threshold = (io.MouseSource == ImGuiMouseSource_Mouse) ? 2.0f : 3.0f; // Slightly higher threshold for ImGuiMouseSource_TouchScreen/ImGuiMouseSource_Pen, may need rework. const bool mouse_stationary = (ImLengthSqr(io.MouseDelta) <= mouse_stationary_threshold * mouse_stationary_threshold); g.MouseStationaryTimer = mouse_stationary ? (g.MouseStationaryTimer + io.DeltaTime) : 0.0f; //IMGUI_DEBUG_LOG("%.4f\n", g.MouseStationaryTimer); + // If mouse moved we re-enable mouse hovering in case it was disabled by keyboard/gamepad. In theory should use a >0.0f threshold but would need to reset in everywhere we set this to true. if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) g.NavHighlightItemUnderNav = false; + for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) { io.MouseClicked[i] = io.MouseDown[i] && io.MouseDownDuration[i] < 0.0f; @@ -9133,13 +10634,16 @@ static void ImGui::UpdateMouseInputs() io.MouseDragMaxDistanceAbs[i].x = ImMax(io.MouseDragMaxDistanceAbs[i].x, delta_from_click_pos.x < 0.0f ? -delta_from_click_pos.x : delta_from_click_pos.x); io.MouseDragMaxDistanceAbs[i].y = ImMax(io.MouseDragMaxDistanceAbs[i].y, delta_from_click_pos.y < 0.0f ? -delta_from_click_pos.y : delta_from_click_pos.y); } + // We provide io.MouseDoubleClicked[] as a legacy service io.MouseDoubleClicked[i] = (io.MouseClickedCount[i] == 2); + // Clicking any mouse button reactivate mouse hovering which may have been deactivated by keyboard/gamepad navigation if (io.MouseClicked[i]) g.NavHighlightItemUnderNav = false; } } + static void LockWheelingWindow(ImGuiWindow* window, float wheel_amount) { ImGuiContext& g = *GImGui; @@ -9158,6 +10662,7 @@ static void LockWheelingWindow(ImGuiWindow* window, float wheel_amount) g.WheelingAxisAvg = ImVec2(0.0f, 0.0f); } } + static ImGuiWindow* FindBestWheelingWindow(const ImVec2& wheel) { // For each axis, find window in the hierarchy that may want to use scrolling @@ -9179,9 +10684,11 @@ static ImGuiWindow* FindBestWheelingWindow(const ImVec2& wheel) } if (windows[0] == NULL && windows[1] == NULL) return NULL; + // If there's only one window or only one axis then there's no ambiguity if (windows[0] == windows[1] || windows[0] == NULL || windows[1] == NULL) return windows[1] ? windows[1] : windows[0]; + // If candidate are different windows we need to decide which one to prioritize // - First frame: only find a winner if one axis is zero. // - Subsequent frames: only find a winner when one is more than the other. @@ -9194,6 +10701,7 @@ static ImGuiWindow* FindBestWheelingWindow(const ImVec2& wheel) } return (g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? windows[0] : windows[1]; } + // Called by NewFrame() void ImGui::UpdateMouseWheel() { @@ -9208,13 +10716,16 @@ void ImGui::UpdateMouseWheel() if (g.WheelingWindowReleaseTimer <= 0.0f) LockWheelingWindow(NULL, 0.0f); } + ImVec2 wheel; wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheelH : 0.0f; wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheel : 0.0f; + //IMGUI_DEBUG_LOG("MouseWheel X:%.3f Y:%.3f\n", wheel_x, wheel_y); ImGuiWindow* mouse_window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow; if (!mouse_window || mouse_window->Collapsed) return; + // Zoom / Scale window // FIXME-OBSOLETE: This is an old feature, it still works but pretty much nobody is using it and may be best redesigned. if (wheel.y != 0.0f && g.IO.KeyCtrl && g.IO.FontAllowUserScaling) @@ -9235,19 +10746,23 @@ void ImGui::UpdateMouseWheel() } if (g.IO.KeyCtrl) return; + // Mouse wheel scrolling // Read about io.MouseWheelRequestAxisSwap and its issue on Mac+Emscripten in UpdateMouseInputs() if (g.IO.MouseWheelRequestAxisSwap) wheel = ImVec2(wheel.y, 0.0f); + // Maintain a rough average of moving magnitude on both axes // FIXME: should by based on wall clock time rather than frame-counter g.WheelingAxisAvg.x = ImExponentialMovingAverage(g.WheelingAxisAvg.x, ImAbs(wheel.x), 30); g.WheelingAxisAvg.y = ImExponentialMovingAverage(g.WheelingAxisAvg.y, ImAbs(wheel.y), 30); + // In the rare situation where FindBestWheelingWindow() had to defer first frame of wheeling due to ambiguous main axis, reinject it now. wheel += g.WheelingWindowWheelRemainder; g.WheelingWindowWheelRemainder = ImVec2(0.0f, 0.0f); if (wheel.x == 0.0f && wheel.y == 0.0f) return; + // Mouse wheel scrolling: find target and apply // - don't renew lock if axis doesn't apply on the window. // - select a main axis when both axes are being moved. @@ -9275,27 +10790,34 @@ void ImGui::UpdateMouseWheel() } } } + void ImGui::SetNextFrameWantCaptureKeyboard(bool want_capture_keyboard) { ImGuiContext& g = *GImGui; g.WantCaptureKeyboardNextFrame = want_capture_keyboard ? 1 : 0; } + void ImGui::SetNextFrameWantCaptureMouse(bool want_capture_mouse) { ImGuiContext& g = *GImGui; g.WantCaptureMouseNextFrame = want_capture_mouse ? 1 : 0; } + #ifndef IMGUI_DISABLE_DEBUG_TOOLS static const char* GetInputSourceName(ImGuiInputSource source) { const char* input_source_names[] = { "None", "Mouse", "Keyboard", "Gamepad" }; - IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT && source >= 0 && source < ImGuiInputSource_COUNT); + IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT); + if (source < 0 || source >= ImGuiInputSource_COUNT) + return "Unknown"; return input_source_names[source]; } static const char* GetMouseSourceName(ImGuiMouseSource source) { const char* mouse_source_names[] = { "Mouse", "TouchScreen", "Pen" }; - IM_ASSERT(IM_ARRAYSIZE(mouse_source_names) == ImGuiMouseSource_COUNT && source >= 0 && source < ImGuiMouseSource_COUNT); + IM_ASSERT(IM_ARRAYSIZE(mouse_source_names) == ImGuiMouseSource_COUNT); + if (source < 0 || source >= ImGuiMouseSource_COUNT) + return "Unknown"; return mouse_source_names[source]; } static void DebugPrintInputEvent(const char* prefix, const ImGuiInputEvent* e) @@ -9310,6 +10832,7 @@ static void DebugPrintInputEvent(const char* prefix, const ImGuiInputEvent* e) if (e->Type == ImGuiInputEventType_Focus) { IMGUI_DEBUG_LOG_IO("[io] %s: AppFocused %d\n", prefix, e->AppFocused.Focused); return; } } #endif + // Process input queue // We always call this with the value of 'bool g.IO.ConfigInputTrickleEventQueue'. // - trickle_fast_inputs = false : process all events, turn into flattened input state (e.g. successive down/up/down/up will be lost) @@ -9318,13 +10841,16 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) { ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; + // Only trickle chars<>key when working with InputText() // FIXME: InputText() could parse event trail? // FIXME: Could specialize chars<>keys trickling rules for control keys (those not typically associated to characters) const bool trickle_interleaved_nonchar_keys_and_text = (trickle_fast_inputs && g.WantTextInputNextFrame == 1); + bool mouse_moved = false, mouse_wheeled = false, key_changed = false, key_changed_nonchar = false, text_inputted = false; int mouse_button_changed = 0x00; ImBitArray key_changed_mask; + int event_n = 0; for (; event_n < g.InputEventsQueue.Size; event_n++) { @@ -9379,15 +10905,21 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) const int key_data_index = (int)(key_data - g.IO.KeysData); if (trickle_fast_inputs && key_data->Down != e->Key.Down && (key_changed_mask.TestBit(key_data_index) || mouse_button_changed != 0)) break; + const bool key_is_potentially_for_char_input = IsKeyChordPotentiallyCharInput(GetMergedModsFromKeys() | key); if (trickle_interleaved_nonchar_keys_and_text && (text_inputted && !key_is_potentially_for_char_input)) break; + + if (key_data->Down != e->Key.Down) // Analog change only do not trigger this, so it won't block e.g. further mouse pos events testing key_changed. + { + key_changed = true; + key_changed_mask.SetBit(key_data_index); + if (trickle_interleaved_nonchar_keys_and_text && !key_is_potentially_for_char_input) + key_changed_nonchar = true; + } + key_data->Down = e->Key.Down; key_data->AnalogValue = e->Key.AnalogValue; - key_changed = true; - key_changed_mask.SetBit(key_data_index); - if (trickle_interleaved_nonchar_keys_and_text && !key_is_potentially_for_char_input) - key_changed_nonchar = true; } else if (e->Type == ImGuiInputEventType_Text) { @@ -9415,21 +10947,25 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) IM_ASSERT(0 && "Unknown event!"); } } + // Record trail (for domain-specific applications wanting to access a precise trail) //if (event_n != 0) IMGUI_DEBUG_LOG_IO("Processed: %d / Remaining: %d\n", event_n, g.InputEventsQueue.Size - event_n); for (int n = 0; n < event_n; n++) g.InputEventsTrail.push_back(g.InputEventsQueue[n]); + // [DEBUG] #ifndef IMGUI_DISABLE_DEBUG_TOOLS if (event_n != 0 && (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO)) for (int n = 0; n < g.InputEventsQueue.Size; n++) DebugPrintInputEvent(n < event_n ? "Processed" : "Remaining", &g.InputEventsQueue[n]); #endif + // Remaining events will be processed on the next frame if (event_n == g.InputEventsQueue.Size) g.InputEventsQueue.resize(0); else g.InputEventsQueue.erase(g.InputEventsQueue.Data, g.InputEventsQueue.Data + event_n); + // Clear buttons state when focus is lost // - this is useful so e.g. releasing Alt after focus loss on Alt-Tab doesn't trigger the Alt menu toggle. // - we clear in EndFrame() and not now in order allow application/user code polling this flag @@ -9440,18 +10976,23 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) g.IO.ClearInputMouse(); } } + ImGuiID ImGui::GetKeyOwner(ImGuiKey key) { if (!IsNamedKeyOrMod(key)) return ImGuiKeyOwner_NoOwner; + ImGuiContext& g = *GImGui; ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key); ImGuiID owner_id = owner_data->OwnerCurr; + if (g.ActiveIdUsingAllKeyboardKeys && owner_id != g.ActiveId && owner_id != ImGuiKeyOwner_Any) if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END) return ImGuiKeyOwner_NoOwner; + return owner_id; } + // TestKeyOwner(..., ID) : (owner == None || owner == ID) // TestKeyOwner(..., None) : (owner == None) // TestKeyOwner(..., Any) : no owner test @@ -9460,13 +11001,16 @@ bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id) { if (!IsNamedKeyOrMod(key)) return true; + ImGuiContext& g = *GImGui; if (g.ActiveIdUsingAllKeyboardKeys && owner_id != g.ActiveId && owner_id != ImGuiKeyOwner_Any) if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END) return false; + ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key); if (owner_id == ImGuiKeyOwner_Any) return (owner_data->LockThisFrame == false); + // Note: SetKeyOwner() sets OwnerCurr. It is not strictly required for most mouse routing overlap (because of ActiveId/HoveredId // are acting as filter before this has a chance to filter), but sane as soon as user tries to look into things. // Setting OwnerCurr in SetKeyOwner() is more consistent than testing OwnerNext here: would be inconsistent with getter and other functions. @@ -9477,8 +11021,10 @@ bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id) if (owner_data->OwnerCurr != ImGuiKeyOwner_NoOwner) return false; } + return true; } + // _LockXXX flags are useful to lock keys away from code which is not input-owner aware. // When using _LockXXX flags, you can use ImGuiKeyOwner_Any to lock keys from everyone. // - SetKeyOwner(..., None) : clears owner @@ -9490,13 +11036,16 @@ void ImGui::SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags) IM_ASSERT(IsNamedKeyOrMod(key) && (owner_id != ImGuiKeyOwner_Any || (flags & (ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease)))); // Can only use _Any with _LockXXX flags (to eat a key away without an ID to retrieve it) IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetKeyOwner) == 0); // Passing flags not supported by this function! //IMGUI_DEBUG_LOG("SetKeyOwner(%s, owner_id=0x%08X, flags=%08X)\n", GetKeyName(key), owner_id, flags); + ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key); owner_data->OwnerCurr = owner_data->OwnerNext = owner_id; + // We cannot lock by default as it would likely break lots of legacy code. // In the case of using LockUntilRelease while key is not down we still lock during the frame (no key_data->Down test) owner_data->LockUntilRelease = (flags & ImGuiInputFlags_LockUntilRelease) != 0; owner_data->LockThisFrame = (flags & ImGuiInputFlags_LockThisFrame) != 0 || (owner_data->LockUntilRelease); } + // Rarely used helper void ImGui::SetKeyOwnersForKeyChord(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags) { @@ -9506,6 +11055,7 @@ void ImGui::SetKeyOwnersForKeyChord(ImGuiKeyChord key_chord, ImGuiID owner_id, I if (key_chord & ImGuiMod_Super) { SetKeyOwner(ImGuiMod_Super, owner_id, flags); } if (key_chord & ~ImGuiMod_Mask_) { SetKeyOwner((ImGuiKey)(key_chord & ~ImGuiMod_Mask_), owner_id, flags); } } + // This is more or less equivalent to: // if (IsItemHovered() || IsItemActive()) // SetKeyOwner(key, GetItemID()); @@ -9526,15 +11076,18 @@ void ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags) SetKeyOwner(key, id, flags & ~ImGuiInputFlags_CondMask_); } } + void ImGui::SetItemKeyOwner(ImGuiKey key) { SetItemKeyOwner(key, ImGuiInputFlags_None); } + // This is the only public API until we expose owner_id versions of the API as replacements. bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord) { return IsKeyChordPressed(key_chord, ImGuiInputFlags_None, ImGuiKeyOwner_Any); } + // This is equivalent to comparing KeyMods + doing a IsKeyPressed() bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id) { @@ -9543,6 +11096,7 @@ bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiInputFlags flags, Im ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); if (g.IO.KeyMods != mods) return false; + // Special storage location for mods ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); if (key == ImGuiKey_None) @@ -9551,6 +11105,7 @@ bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiInputFlags flags, Im return false; return true; } + void ImGui::SetNextItemShortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags) { ImGuiContext& g = *GImGui; @@ -9558,12 +11113,14 @@ void ImGui::SetNextItemShortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags) g.NextItemData.Shortcut = key_chord; g.NextItemData.ShortcutFlags = flags; } + // Called from within ItemAdd: at this point we can read from NextItemData and write to LastItemData void ImGui::ItemHandleShortcut(ImGuiID id) { ImGuiContext& g = *GImGui; ImGuiInputFlags flags = g.NextItemData.ShortcutFlags; IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetNextItemShortcut) == 0); // Passing flags not supported by SetNextItemShortcut()! + if (g.LastItemData.ItemFlags & ImGuiItemFlags_Disabled) return; if (flags & ImGuiInputFlags_Tooltip) @@ -9573,6 +11130,7 @@ void ImGui::ItemHandleShortcut(ImGuiID id) } if (!Shortcut(g.NextItemData.Shortcut, flags & ImGuiInputFlags_SupportedByShortcut, id) || g.NavActivateId != 0) return; + // FIXME: Generalize Activation queue? g.NavActivateId = id; // Will effectively disable clipping. g.NavActivateFlags = ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_FromShortcut; @@ -9580,37 +11138,48 @@ void ImGui::ItemHandleShortcut(ImGuiID id) g.NavActivateDownId = g.NavActivatePressedId = id; NavHighlightActivated(id); } + bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags) { return Shortcut(key_chord, flags, ImGuiKeyOwner_Any); } + bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id) { ImGuiContext& g = *GImGui; //IMGUI_DEBUG_LOG("Shortcut(%s, flags=%X, owner_id=0x%08X)\n", GetKeyChordName(key_chord, g.TempBuffer.Data, g.TempBuffer.Size), flags, owner_id); + // When using (owner_id == 0/Any): SetShortcutRouting() will use CurrentFocusScopeId and filter with this, so IsKeyPressed() is fine with he 0/Any. if ((flags & ImGuiInputFlags_RouteTypeMask_) == 0) flags |= ImGuiInputFlags_RouteFocused; + // Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its focus scope by default) // Effectively makes Shortcut() always input-owner aware. if (owner_id == ImGuiKeyOwner_Any || owner_id == ImGuiKeyOwner_NoOwner) owner_id = GetRoutingIdFromOwnerId(owner_id); + if (g.CurrentItemFlags & ImGuiItemFlags_Disabled) return false; + // Submit route if (!SetShortcutRouting(key_chord, flags, owner_id)) return false; + // Default repeat behavior for Shortcut() // So e.g. pressing Ctrl+W and releasing Ctrl while holding W will not trigger the W shortcut. if ((flags & ImGuiInputFlags_Repeat) != 0 && (flags & ImGuiInputFlags_RepeatUntilMask_) == 0) flags |= ImGuiInputFlags_RepeatUntilKeyModsChange; + if (!IsKeyChordPressed(key_chord, flags, owner_id)) return false; + // Claim mods during the press SetKeyOwnersForKeyChord(key_chord & ImGuiMod_Mask_, owner_id); + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByShortcut) == 0); // Passing flags not supported by this function! return true; } + //----------------------------------------------------------------------------- // [SECTION] ERROR CHECKING, STATE RECOVERY //----------------------------------------------------------------------------- @@ -9623,6 +11192,7 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID own // - ErrorRecoveryTryToRecoverWindowState() // - ErrorLog() //----------------------------------------------------------------------------- + // Verify ABI compatibility between caller code and compiled version of Dear ImGui. This helps detects some build issues. // Called by IMGUI_CHECKVERSION(). // Verify that the type sizes are matching between the calling file's compilation unit and imgui.cpp's compilation unit @@ -9645,40 +11215,51 @@ bool ImGui::DebugCheckVersionAndDataLayout(const char* version, size_t sz_io, si if (sz_idx != sizeof(ImDrawIdx)) { error = true; IM_ASSERT(sz_idx == sizeof(ImDrawIdx) && "Mismatched struct layout!"); } return !error; } -// Until 1.89 (IMGUI_VERSION_NUM < 18814) it was legal to use SetCursorPos() to extend the boundary of a parent (e.g. window or table cell) -// This is causing issues and ambiguity and we need to retire that. -// See https://github.com/ocornut/imgui/issues/5548 for more details. -// [Scenario 1] + +// Until 1.89 (August 2022, IMGUI_VERSION_NUM < 18814) it was legal to use SetCursorPos()/SetCursorScreenPos() +// to extend contents size of our parent container (e.g. window contents size, which is used for auto-resizing +// windows, table column contents size used for auto-resizing columns, group size). +// This was causing issues and ambiguities and we needed to retire that. +// From 1.89, extending contents size boundaries REQUIRES AN ITEM TO BE SUBMITTED. +// // Previously this would make the window content size ~200x200: -// Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); // NOT OK +// Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); // NOT OK ANYMORE // Instead, please submit an item: // Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End(); // OK // Alternative: // Begin(...) + Dummy(ImVec2(200,200)) + End(); // OK -// [Scenario 2] -// For reference this is one of the issue what we aim to fix with this change: -// BeginGroup() + SomeItem("foobar") + SetCursorScreenPos(GetCursorScreenPos()) + EndGroup() -// The previous logic made SetCursorScreenPos(GetCursorScreenPos()) have a side-effect! It would erroneously incorporate ItemSpacing.y after the item into content size, making the group taller! -// While this code is a little twisted, no-one would expect SetXXX(GetXXX()) to have a side-effect. Using vertical alignment patterns could trigger this issue. +// +// The assert below detects when the _last_ call in a window was a SetCursorPos() not followed by an Item, +// and with a position that would grow the parent contents size. +// +// Advanced: +// - For reference, old logic was causing issues because it meant that SetCursorScreenPos(GetCursorScreenPos()) +// had a side-effect on layout! In particular this caused problem to compute group boundaries. +// e.g. BeginGroup() + SomeItem() + SetCursorScreenPos(GetCursorScreenPos()) + EndGroup() would cause the +// group to be taller because auto-sizing generally adds padding on bottom and right side. +// - While this code is a little twisted, no-one would expect SetXXX(GetXXX()) to have a side-effect. +// Using vertical alignment patterns would frequently trigger this sorts of issue. +// - See https://github.com/ocornut/imgui/issues/5548 for more details. void ImGui::ErrorCheckUsingSetCursorPosToExtendParentBoundaries() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(window->DC.IsSetPos); window->DC.IsSetPos = false; -#ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS if (window->DC.CursorPos.x <= window->DC.CursorMaxPos.x && window->DC.CursorPos.y <= window->DC.CursorMaxPos.y) return; if (window->SkipItems) return; - IM_ASSERT(0 && "Code uses SetCursorPos()/SetCursorScreenPos() to extend window/parent boundaries. Please submit an item e.g. Dummy() to validate extent."); -#else - window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); -#endif + IM_ASSERT_USER_ERROR(0, "Code uses SetCursorPos()/SetCursorScreenPos() to extend window/parent boundaries.\nPlease submit an item e.g. Dummy() afterwards in order to grow window/parent boundaries."); + + // For reference, the old behavior was essentially: + //window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); } + static void ImGui::ErrorCheckNewFrameSanityChecks() { ImGuiContext& g = *GImGui; + // Check user IM_ASSERT macro // (IF YOU GET A WARNING OR COMPILE ERROR HERE: it means your assert macro is incorrectly defined! // If your macro uses multiple statements, it NEEDS to be surrounded by a 'do { ... } while (0)' block. @@ -9686,19 +11267,20 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() // #define IM_ASSERT(EXPR) if (SomeCode(EXPR)) SomeMoreCode(); // Wrong! // #define IM_ASSERT(EXPR) do { if (SomeCode(EXPR)) SomeMoreCode(); } while (0) // Correct! if (true) IM_ASSERT(1); else IM_ASSERT(0); + // Emscripten backends are often imprecise in their submission of DeltaTime. (#6114, #3644) // Ideally the Emscripten app/backend should aim to fix or smooth this value and avoid feeding zero, but we tolerate it. #ifdef __EMSCRIPTEN__ if (g.IO.DeltaTime <= 0.0f && g.FrameCount > 0) g.IO.DeltaTime = 0.00001f; #endif + // Check user data // (We pass an error message in the assert expression to make it visible to programmers who are not using a debugger, as most assert handlers display their argument) IM_ASSERT(g.Initialized); IM_ASSERT((g.IO.DeltaTime > 0.0f || g.FrameCount == 0) && "Need a positive DeltaTime!"); IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value!"); - IM_ASSERT(g.IO.Fonts->IsBuilt() && "Font Atlas not built! Make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()"); IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.CircleTessellationMaxError > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting!"); // Allows us to avoid a few clamps in color computations @@ -9706,10 +11288,16 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT(g.Style.WindowBorderHoverPadding > 0.0f && "Invalid style setting!"); // Required otherwise cannot resize from borders. IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right); IM_ASSERT(g.Style.ColorButtonPosition == ImGuiDir_Left || g.Style.ColorButtonPosition == ImGuiDir_Right); + IM_ASSERT(g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesNone || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesFull || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesToNodes); + // Error handling: we do not accept 100% silent recovery! Please contact me if you feel this is getting in your way. if (g.IO.ConfigErrorRecovery) IM_ASSERT(g.IO.ConfigErrorRecoveryEnableAssert || g.IO.ConfigErrorRecoveryEnableDebugLog || g.IO.ConfigErrorRecoveryEnableTooltip || g.ErrorCallback != NULL); + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (g.IO.FontGlobalScale > 1.0f) + IM_ASSERT(g.Style.FontScaleMain == 1.0f && "Since 1.92: use style.FontScaleMain instead of g.IO.FontGlobalScale!"); + // Remap legacy names if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) { @@ -9721,17 +11309,30 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() g.IO.ConfigNavCaptureKeyboard = false; g.IO.ConfigFlags &= ~ImGuiConfigFlags_NavNoCaptureKeyboard; } + if (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) + { + g.IO.ConfigDpiScaleFonts = false; + g.IO.ConfigFlags &= ~ImGuiConfigFlags_DpiEnableScaleFonts; + } + if (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleViewports) + { + g.IO.ConfigDpiScaleViewports = false; + g.IO.ConfigFlags &= ~ImGuiConfigFlags_DpiEnableScaleViewports; + } + // Remap legacy clipboard handlers (OBSOLETED in 1.91.1, August 2024) if (g.IO.GetClipboardTextFn != NULL && (g.PlatformIO.Platform_GetClipboardTextFn == NULL || g.PlatformIO.Platform_GetClipboardTextFn == Platform_GetClipboardTextFn_DefaultImpl)) g.PlatformIO.Platform_GetClipboardTextFn = [](ImGuiContext* ctx) { return ctx->IO.GetClipboardTextFn(ctx->IO.ClipboardUserData); }; if (g.IO.SetClipboardTextFn != NULL && (g.PlatformIO.Platform_SetClipboardTextFn == NULL || g.PlatformIO.Platform_SetClipboardTextFn == Platform_SetClipboardTextFn_DefaultImpl)) g.PlatformIO.Platform_SetClipboardTextFn = [](ImGuiContext* ctx, const char* text) { return ctx->IO.SetClipboardTextFn(ctx->IO.ClipboardUserData, text); }; #endif + // Perform simple check: error if Docking or Viewport are enabled _exactly_ on frame 1 (instead of frame 0 or later), which is a common error leading to loss of .ini data. if (g.FrameCount == 1 && (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable) && (g.ConfigFlagsLastFrame & ImGuiConfigFlags_DockingEnable) == 0) IM_ASSERT(0 && "Please set DockingEnable before the first call to NewFrame()! Otherwise you will lose your .ini settings!"); if (g.FrameCount == 1 && (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) && (g.ConfigFlagsLastFrame & ImGuiConfigFlags_ViewportsEnable) == 0) IM_ASSERT(0 && "Please set ViewportsEnable before the first call to NewFrame()! Otherwise you will lose your .ini settings!"); + // Perform simple checks: multi-viewport and platform windows support if (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { @@ -9754,6 +11355,7 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() // Disable feature, our backends do not support it g.IO.ConfigFlags &= ~ImGuiConfigFlags_ViewportsEnable; } + // Perform simple checks on platform monitor data + compute a total bounding box for quick early outs for (ImGuiPlatformMonitor& mon : g.PlatformIO.Monitors) { @@ -9764,6 +11366,7 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() } } } + static void ImGui::ErrorCheckEndFrameSanityChecks() { // Verify that io.KeyXXX fields haven't been tampered with. Key mods should not be modified between NewFrame() and EndFrame() @@ -9778,9 +11381,11 @@ static void ImGui::ErrorCheckEndFrameSanityChecks() IM_UNUSED(key_mods); IM_ASSERT((key_mods == 0 || g.IO.KeyMods == key_mods) && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); IM_UNUSED(key_mods); + IM_ASSERT(g.CurrentWindowStack.Size == 1); IM_ASSERT(g.CurrentWindowStack[0].Window->IsFallbackWindow); } + // Save current stack sizes. Called e.g. by NewFrame() and by Begin() but may be called for manual recovery. void ImGui::ErrorRecoveryStoreState(ImGuiErrorRecoveryState* state_out) { @@ -9797,6 +11402,7 @@ void ImGui::ErrorRecoveryStoreState(ImGuiErrorRecoveryState* state_out) state_out->SizeOfBeginPopupStack = (short)g.BeginPopupStack.Size; state_out->SizeOfDisabledStack = (short)g.DisabledStackSize; } + // Chosen name "Try to recover" over e.g. "Restore" to suggest this is not a 100% guaranteed recovery. // Called by e.g. EndFrame() but may be called for manual recovery. // Attempt to recover full window stack. @@ -9834,18 +11440,22 @@ void ImGui::ErrorRecoveryTryToRecoverState(const ImGuiErrorRecoveryState* state_ if (g.CurrentWindowStack.Size == state_in->SizeOfWindowStack) ErrorRecoveryTryToRecoverWindowState(state_in); } + // Called by e.g. End() but may be called for manual recovery. // Read '// Error Handling [BETA]' block in imgui_internal.h for details. // Attempt to recover from incorrect usage of BeginXXX/EndXXX/PushXXX/PopXXX calls. void ImGui::ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryState* state_in) { ImGuiContext& g = *GImGui; + while (g.CurrentTable != NULL && g.CurrentTable->InnerWindow == g.CurrentWindow) //-V1044 { IM_ASSERT_USER_ERROR(0, "Missing EndTable()"); EndTable(); } + ImGuiWindow* window = g.CurrentWindow; + // FIXME: Can't recover from inside BeginTabItem/EndTabItem yet. while (g.CurrentTabBar != NULL && g.CurrentTabBar->Window == window) //-V1044 { @@ -9917,12 +11527,15 @@ void ImGui::ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryStat } //IM_ASSERT(g.FocusScopeStack.Size == state_in->SizeOfFocusScopeStack); } + bool ImGui::ErrorLog(const char* msg) { ImGuiContext& g = *GImGui; + // Output to debug log #ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiWindow* window = g.CurrentWindow; + if (g.IO.ConfigErrorRecoveryEnableDebugLog) { if (g.ErrorFirst) @@ -9931,6 +11544,7 @@ bool ImGui::ErrorLog(const char* msg) IMGUI_DEBUG_LOG_ERROR("[imgui-error] In window '%s': %s\n", window ? window->Name : "NULL", msg); } g.ErrorFirst = false; + // Output to tooltip if (g.IO.ConfigErrorRecoveryEnableTooltip) { @@ -9950,19 +11564,22 @@ bool ImGui::ErrorLog(const char* msg) g.ErrorCountCurrentFrame++; } #endif + // Output to callback if (g.ErrorCallback != NULL) g.ErrorCallback(&g, g.ErrorCallbackUserData, msg); + // Return whether we should assert return g.IO.ConfigErrorRecoveryEnableAssert; } + void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip() { #ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiContext& g = *GImGui; - if (g.DebugDrawIdConflicts != 0 && g.IO.KeyCtrl == false) + if (g.DebugDrawIdConflictsId != 0 && g.IO.KeyCtrl == false) g.DebugDrawIdConflictsCount = g.HoveredIdPreviousFrameItemCount; - if (g.DebugDrawIdConflicts != 0 && g.DebugItemPickerActive == false && BeginErrorTooltip()) + if (g.DebugDrawIdConflictsId != 0 && g.DebugItemPickerActive == false && BeginErrorTooltip()) { Text("Programmer error: %d visible items with conflicting ID!", g.DebugDrawIdConflictsCount); BulletText("Code should use PushID()/PopID() in loops, or append \"##xx\" to same-label identifiers!"); @@ -9990,6 +11607,7 @@ void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip() Text(")"); EndErrorTooltip(); } + if (g.ErrorCountCurrentFrame > 0 && BeginErrorTooltip()) // Amend at end of frame { Separator(); @@ -10006,6 +11624,7 @@ void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip() } #endif } + // Pseudo-tooltip. Follow mouse until CTRL is held. When CTRL is held we lock position, allowing to click it. bool ImGui::BeginErrorTooltip() { @@ -10030,16 +11649,19 @@ bool ImGui::BeginErrorTooltip() } return is_visible; } + void ImGui::EndErrorTooltip() { End(); } + //----------------------------------------------------------------------------- // [SECTION] ITEM SUBMISSION //----------------------------------------------------------------------------- // - KeepAliveID() // - ItemAdd() //----------------------------------------------------------------------------- + // Code not using ItemAdd() may need to call this manually otherwise ActiveId will be cleared. In IMGUI_VERSION_NUM < 18717 this was called by GetID(). void ImGui::KeepAliveID(ImGuiID id) { @@ -10049,6 +11671,7 @@ void ImGui::KeepAliveID(ImGuiID id) if (g.DeactivatedItemData.ID == id) g.DeactivatedItemData.IsAlive = true; } + // Declare item bounding box for clipping and interaction. // Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface // declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which is used drawing/interaction. @@ -10058,6 +11681,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + // Set item data // (DisplayRect is left untouched, made valid when ImGuiItemStatusFlags_HasDisplayRect is set) g.LastItemData.ID = id; @@ -10066,9 +11690,11 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu g.LastItemData.ItemFlags = g.CurrentItemFlags | g.NextItemData.ItemFlags | extra_flags; g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None; // Note: we don't copy 'g.NextItemData.SelectionUserData' to an hypothetical g.LastItemData.SelectionUserData: since the former is not cleared. + if (id != 0) { KeepAliveID(id); + // Directional navigation processing // Runs prior to clipping early-out // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget @@ -10088,16 +11714,20 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu if (window == g.NavWindow || ((window->ChildFlags | g.NavWindow->ChildFlags) & ImGuiChildFlags_NavFlattened)) NavProcessItem(); } + if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasShortcut) ItemHandleShortcut(id); } + // Lightweight clear of SetNextItemXXX data. g.NextItemData.HasFlags = ImGuiNextItemDataFlags_None; g.NextItemData.ItemFlags = ImGuiItemFlags_None; + #ifdef IMGUI_ENABLE_TEST_ENGINE if (id != 0) IMGUI_TEST_ENGINE_ITEM_ADD(id, g.LastItemData.NavRect, &g.LastItemData); #endif + // Clipping test // (this is an inline copy of IsClippedEx() so we can reuse the is_rect_visible value, otherwise we'd do 'if (IsClippedEx(bb, id)) return false') // g.NavActivateId is not necessarily == g.NavId, in the case of remote activation (e.g. shortcuts) @@ -10106,23 +11736,42 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu if (id == 0 || (id != g.ActiveId && id != g.ActiveIdPreviousFrame && id != g.NavId && id != g.NavActivateId)) if (!g.ItemUnclipByLog) return false; + // [DEBUG] #ifndef IMGUI_DISABLE_DEBUG_TOOLS if (id != 0) { if (id == g.DebugLocateId) DebugLocateItemResolveWithLastItem(); + // [DEBUG] People keep stumbling on this problem and using "" as identifier in the root of a window instead of "##something". // Empty identifier are valid and useful in a small amount of cases, but 99.9% of the time you want to use "##something". // READ THE FAQ: https://dearimgui.com/faq IM_ASSERT(id != window->ID && "Cannot have an empty ID at the root of a window. If you need an empty label, use ## and read the FAQ about how the ID Stack works!"); + + // [DEBUG] Highlight all conflicts WITHOUT needing to hover. THIS WILL SLOW DOWN DEAR IMGUI. DON'T KEEP ACTIVATED. + // This will only work for items submitted with ItemAdd(). Some very rare/odd/unrecommended code patterns are calling ButtonBehavior() without ItemAdd(). +#ifdef IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + if ((g.LastItemData.ItemFlags & ImGuiItemFlags_AllowDuplicateId) == 0) + { + int* p_alive = g.DebugDrawIdConflictsAliveCount.GetIntRef(id, -1); // Could halve lookups if we knew ImGuiStorage can store 64-bit, or by storing FrameCount as 30-bits + highlight as 2-bits. But the point is that we should not pretend that this is fast. + int* p_highlight = g.DebugDrawIdConflictsHighlightSet.GetIntRef(id, -1); + if (*p_alive == g.FrameCount) + *p_highlight = g.FrameCount; + *p_alive = g.FrameCount; + if (*p_highlight >= g.FrameCount - 1) + window->DrawList->AddRect(bb.Min - ImVec2(1, 1), bb.Max + ImVec2(1, 1), IM_COL32(255, 0, 0, 255), 0.0f, ImDrawFlags_None, 2.0f); + } +#endif } //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] //if ((g.LastItemData.ItemFlags & ImGuiItemFlags_NoNav) == 0) // window->DrawList->AddRect(g.LastItemData.NavRect.Min, g.LastItemData.NavRect.Max, IM_COL32(255,255,0,255)); // [DEBUG] #endif + if (id != 0 && g.DeactivatedItemData.ID == id) g.DeactivatedItemData.ElapseFrame = g.FrameCount; + // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) if (is_rect_visible) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Visible; @@ -10131,6 +11780,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu return true; } IM_MSVC_RUNTIME_CHECKS_RESTORE + //----------------------------------------------------------------------------- // [SECTION] LAYOUT //----------------------------------------------------------------------------- @@ -10159,6 +11809,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE // - EndGroup() // Also see in imgui_widgets: tab bars, and in imgui_tables: tables, columns. //----------------------------------------------------------------------------- + // Advance cursor given item size for layout. // Register minimum needed size so it can extend the bounding box used for auto-fit calculation. // See comments in ItemAdd() about how/why the size provided to ItemSize() vs ItemAdd() may often different. @@ -10170,12 +11821,15 @@ void ImGui::ItemSize(const ImVec2& size, float text_baseline_y) ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return; + // We increase the height in this function to accommodate for baseline offset. // In theory we should be offsetting the starting position (window->DC.CursorPos), that will be the topic of a larger refactor, // but since ItemSize() is not yet an API that moves the cursor (to handle e.g. wrapping) enlarging the height has the same effect. const float offset_to_match_baseline_y = (text_baseline_y >= 0) ? ImMax(0.0f, window->DC.CurrLineTextBaseOffset - text_baseline_y) : 0.0f; + const float line_y1 = window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y; const float line_height = ImMax(window->DC.CurrLineSize.y, /*ImMax(*/window->DC.CursorPos.y - line_y1/*, 0.0f)*/ + size.y + offset_to_match_baseline_y); + // Always align ourselves on pixel boundaries //if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG] window->DC.CursorPosPrevLine.x = window->DC.CursorPos.x + size.x; @@ -10185,16 +11839,19 @@ void ImGui::ItemSize(const ImVec2& size, float text_baseline_y) window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x); window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y); //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG] + window->DC.PrevLineSize.y = line_height; window->DC.CurrLineSize.y = 0.0f; window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y); window->DC.CurrLineTextBaseOffset = 0.0f; window->DC.IsSameLine = window->DC.IsSetPos = false; + // Horizontal layout mode if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) SameLine(); } IM_MSVC_RUNTIME_CHECKS_RESTORE + // Gets back to previous line and continue with horizontal layout // offset_from_start_x == 0 : follow right after previous item // offset_from_start_x != 0 : align to specified x position (relative to window/group left) @@ -10206,6 +11863,7 @@ void ImGui::SameLine(float offset_from_start_x, float spacing_w) ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return; + if (offset_from_start_x != 0.0f) { if (spacing_w < 0.0f) @@ -10224,11 +11882,13 @@ void ImGui::SameLine(float offset_from_start_x, float spacing_w) window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; window->DC.IsSameLine = true; } + ImVec2 ImGui::GetCursorScreenPos() { ImGuiWindow* window = GetCurrentWindowRead(); return window->DC.CursorPos; } + void ImGui::SetCursorScreenPos(const ImVec2& pos) { ImGuiWindow* window = GetCurrentWindow(); @@ -10236,6 +11896,7 @@ void ImGui::SetCursorScreenPos(const ImVec2& pos) //window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); window->DC.IsSetPos = true; } + // User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient. // Conversion happens as we pass the value to user, but it makes our naming convention confusing because GetCursorPos() == (DC.CursorPos - window.Pos). May want to rename 'DC.CursorPos'. ImVec2 ImGui::GetCursorPos() @@ -10243,16 +11904,19 @@ ImVec2 ImGui::GetCursorPos() ImGuiWindow* window = GetCurrentWindowRead(); return window->DC.CursorPos - window->Pos + window->Scroll; } + float ImGui::GetCursorPosX() { ImGuiWindow* window = GetCurrentWindowRead(); return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x; } + float ImGui::GetCursorPosY() { ImGuiWindow* window = GetCurrentWindowRead(); return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y; } + void ImGui::SetCursorPos(const ImVec2& local_pos) { ImGuiWindow* window = GetCurrentWindow(); @@ -10260,6 +11924,7 @@ void ImGui::SetCursorPos(const ImVec2& local_pos) //window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); window->DC.IsSetPos = true; } + void ImGui::SetCursorPosX(float x) { ImGuiWindow* window = GetCurrentWindow(); @@ -10267,6 +11932,7 @@ void ImGui::SetCursorPosX(float x) //window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x); window->DC.IsSetPos = true; } + void ImGui::SetCursorPosY(float y) { ImGuiWindow* window = GetCurrentWindow(); @@ -10274,11 +11940,13 @@ void ImGui::SetCursorPosY(float y) //window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y); window->DC.IsSetPos = true; } + ImVec2 ImGui::GetCursorStartPos() { ImGuiWindow* window = GetCurrentWindowRead(); return window->DC.CursorStartPos - window->Pos; } + void ImGui::Indent(float indent_w) { ImGuiContext& g = *GImGui; @@ -10286,6 +11954,7 @@ void ImGui::Indent(float indent_w) window->DC.Indent.x += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x; } + void ImGui::Unindent(float indent_w) { ImGuiContext& g = *GImGui; @@ -10293,6 +11962,7 @@ void ImGui::Unindent(float indent_w) window->DC.Indent.x -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x; } + // Affect large frame+labels widgets only. void ImGui::SetNextItemWidth(float item_width) { @@ -10300,6 +11970,7 @@ void ImGui::SetNextItemWidth(float item_width) g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasWidth; g.NextItemData.Width = item_width; } + // FIXME: Remove the == 0.0f behavior? void ImGui::PushItemWidth(float item_width) { @@ -10309,6 +11980,7 @@ void ImGui::PushItemWidth(float item_width) window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width); g.NextItemData.HasFlags &= ~ImGuiNextItemDataFlags_HasWidth; } + void ImGui::PushMultiItemsWidths(int components, float w_full) { ImGuiContext& g = *GImGui; @@ -10327,6 +11999,7 @@ void ImGui::PushMultiItemsWidths(int components, float w_full) window->DC.ItemWidth = ImMax(prev_split, 1.0f); g.NextItemData.HasFlags &= ~ImGuiNextItemDataFlags_HasWidth; } + void ImGui::PopItemWidth() { ImGuiContext& g = *GImGui; @@ -10339,6 +12012,7 @@ void ImGui::PopItemWidth() window->DC.ItemWidth = window->DC.ItemWidthStack.back(); window->DC.ItemWidthStack.pop_back(); } + // Calculate default item width given value passed to PushItemWidth() or SetNextItemWidth(). // The SetNextItemWidth() data is generally cleared/consumed by ItemAdd() or NextItemData.ClearFlags() float ImGui::CalcItemWidth() @@ -10358,6 +12032,7 @@ float ImGui::CalcItemWidth() w = IM_TRUNC(w); return w; } + // [Internal] Calculate full item size given user provided 'size' parameter and default width/height. Default width is often == CalcItemWidth(). // Those two functions CalcItemWidth vs CalcItemSize are awkwardly named because they are not fully symmetrical. // Note that only CalcItemWidth() is publicly exposed. @@ -10367,36 +12042,44 @@ ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_w, float default_h) ImVec2 avail; if (size.x < 0.0f || size.y < 0.0f) avail = GetContentRegionAvail(); + if (size.x == 0.0f) size.x = default_w; else if (size.x < 0.0f) size.x = ImMax(4.0f, avail.x + size.x); // <-- size.x is negative here so we are subtracting + if (size.y == 0.0f) size.y = default_h; else if (size.y < 0.0f) size.y = ImMax(4.0f, avail.y + size.y); // <-- size.y is negative here so we are subtracting + return size; } + float ImGui::GetTextLineHeight() { ImGuiContext& g = *GImGui; return g.FontSize; } + float ImGui::GetTextLineHeightWithSpacing() { ImGuiContext& g = *GImGui; return g.FontSize + g.Style.ItemSpacing.y; } + float ImGui::GetFrameHeight() { ImGuiContext& g = *GImGui; return g.FontSize + g.Style.FramePadding.y * 2.0f; } + float ImGui::GetFrameHeightWithSpacing() { ImGuiContext& g = *GImGui; return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y; } + ImVec2 ImGui::GetContentRegionAvail() { ImGuiContext& g = *GImGui; @@ -10404,24 +12087,29 @@ ImVec2 ImGui::GetContentRegionAvail() ImVec2 mx = (window->DC.CurrentColumns || g.CurrentTable) ? window->WorkRect.Max : window->ContentRegionRect.Max; return mx - window->DC.CursorPos; } + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // You should never need those functions. Always use GetCursorScreenPos() and GetContentRegionAvail()! // They are bizarre local-coordinates which don't play well with scrolling. ImVec2 ImGui::GetContentRegionMax() { return GetContentRegionAvail() + GetCursorScreenPos() - GetWindowPos(); } + ImVec2 ImGui::GetWindowContentRegionMin() { ImGuiWindow* window = GImGui->CurrentWindow; return window->ContentRegionRect.Min - window->Pos; } + ImVec2 ImGui::GetWindowContentRegionMax() { ImGuiWindow* window = GImGui->CurrentWindow; return window->ContentRegionRect.Max - window->Pos; } #endif + // Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) // Groups are currently a mishmash of functionalities which should perhaps be clarified and separated. // FIXME-OPT: Could we safely early out on ->SkipItems? @@ -10429,6 +12117,7 @@ void ImGui::BeginGroup() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + g.GroupStack.resize(g.GroupStack.Size + 1); ImGuiGroupData& group_data = g.GroupStack.back(); group_data.WindowID = window->ID; @@ -10444,6 +12133,7 @@ void ImGui::BeginGroup() group_data.BackupIsSameLine = window->DC.IsSameLine; group_data.BackupDeactivatedIdIsAlive = g.DeactivatedItemData.IsAlive; group_data.EmitItem = true; + window->DC.GroupOffset.x = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffset.x; window->DC.Indent = window->DC.GroupOffset; window->DC.CursorMaxPos = window->DC.CursorPos; @@ -10451,15 +12141,19 @@ void ImGui::BeginGroup() if (g.LogEnabled) g.LogLinePosY = -FLT_MAX; // To enforce a carriage return } + void ImGui::EndGroup() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(g.GroupStack.Size > 0); // Mismatched BeginGroup()/EndGroup() calls + ImGuiGroupData& group_data = g.GroupStack.back(); IM_ASSERT(group_data.WindowID == window->ID); // EndGroup() in wrong window? + if (window->DC.IsSetPos) ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); + // Include LastItemData.Rect.Max as a workaround for e.g. EndTable() undershooting with CursorMaxPos report. (#7543) ImRect group_bb(group_data.BackupCursorPos, ImMax(ImMax(window->DC.CursorMaxPos, g.LastItemData.Rect.Max), group_data.BackupCursorPos)); window->DC.CursorPos = group_data.BackupCursorPos; @@ -10472,14 +12166,17 @@ void ImGui::EndGroup() window->DC.IsSameLine = group_data.BackupIsSameLine; if (g.LogEnabled) g.LogLinePosY = -FLT_MAX; // To enforce a carriage return + if (!group_data.EmitItem) { g.GroupStack.pop_back(); return; } + window->DC.CurrLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now. ItemSize(group_bb.GetSize()); ItemAdd(group_bb, 0, NULL, ImGuiItemFlags_NoTabStop); + // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive(), IsItemDeactivated() etc. will be functional on the entire group. // It would be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual widgets. // Also if you grep for LastItemId you'll notice it is only used in that context. @@ -10491,24 +12188,31 @@ void ImGui::EndGroup() else if (group_contains_deactivated_id) g.LastItemData.ID = g.DeactivatedItemData.ID; g.LastItemData.Rect = group_bb; + // Forward Hovered flag const bool group_contains_curr_hovered_id = (group_data.BackupHoveredIdIsAlive == false) && g.HoveredId != 0; if (group_contains_curr_hovered_id) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + // Forward Edited flag if (group_contains_curr_active_id && g.ActiveIdHasBeenEditedThisFrame) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; + // Forward Deactivated flag g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDeactivated; if (group_contains_deactivated_id) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Deactivated; + g.GroupStack.pop_back(); if (g.DebugShowGroupRects) window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug] } + + //----------------------------------------------------------------------------- // [SECTION] SCROLLING //----------------------------------------------------------------------------- + // Helper to snap on edges when aiming at an item very close to the edge, // So the difference between WindowPadding and ItemSpacing will be in the visible area after scrolling. // When we refactor the scrolling API this may be configurable with a flag? @@ -10521,6 +12225,7 @@ static float CalcScrollEdgeSnap(float target, float snap_min, float snap_max, fl return ImLerp(target, snap_max, center_ratio); return target; } + static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window) { ImVec2 scroll = window->Scroll; @@ -10539,22 +12244,25 @@ static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window) } scroll[axis] = scroll_target - center_ratio * (window->SizeFull[axis] - decoration_size[axis]); } - scroll[axis] = IM_ROUND(ImMax(scroll[axis], 0.0f)); + scroll[axis] = ImRound64(ImMax(scroll[axis], 0.0f)); if (!window->Collapsed && !window->SkipItems) scroll[axis] = ImMin(scroll[axis], window->ScrollMax[axis]); } return scroll; } + void ImGui::ScrollToItem(ImGuiScrollFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ScrollToRectEx(window, g.LastItemData.NavRect, flags); } + void ImGui::ScrollToRect(ImGuiWindow* window, const ImRect& item_rect, ImGuiScrollFlags flags) { ScrollToRectEx(window, item_rect, flags); } + // Scroll to keep newly navigated item fully into view ImVec2 ImGui::ScrollToRectEx(ImGuiWindow* window, const ImRect& item_rect, ImGuiScrollFlags flags) { @@ -10564,19 +12272,23 @@ ImVec2 ImGui::ScrollToRectEx(ImGuiWindow* window, const ImRect& item_rect, ImGui scroll_rect.Min.y = ImMin(scroll_rect.Min.y + window->DecoInnerSizeY1, scroll_rect.Max.y); //GetForegroundDrawList(window)->AddRect(item_rect.Min, item_rect.Max, IM_COL32(255,0,0,255), 0.0f, 0, 5.0f); // [DEBUG] //GetForegroundDrawList(window)->AddRect(scroll_rect.Min, scroll_rect.Max, IM_COL32_WHITE); // [DEBUG] + // Check that only one behavior is selected per axis IM_ASSERT((flags & ImGuiScrollFlags_MaskX_) == 0 || ImIsPowerOfTwo(flags & ImGuiScrollFlags_MaskX_)); IM_ASSERT((flags & ImGuiScrollFlags_MaskY_) == 0 || ImIsPowerOfTwo(flags & ImGuiScrollFlags_MaskY_)); + // Defaults ImGuiScrollFlags in_flags = flags; if ((flags & ImGuiScrollFlags_MaskX_) == 0 && window->ScrollbarX) flags |= ImGuiScrollFlags_KeepVisibleEdgeX; if ((flags & ImGuiScrollFlags_MaskY_) == 0) flags |= window->Appearing ? ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeY; + const bool fully_visible_x = item_rect.Min.x >= scroll_rect.Min.x && item_rect.Max.x <= scroll_rect.Max.x; const bool fully_visible_y = item_rect.Min.y >= scroll_rect.Min.y && item_rect.Max.y <= scroll_rect.Max.y; const bool can_be_fully_visible_x = (item_rect.GetWidth() + g.Style.ItemSpacing.x * 2.0f) <= scroll_rect.GetWidth() || (window->AutoFitFramesX > 0) || (window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0; const bool can_be_fully_visible_y = (item_rect.GetHeight() + g.Style.ItemSpacing.y * 2.0f) <= scroll_rect.GetHeight() || (window->AutoFitFramesY > 0) || (window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0; + if ((flags & ImGuiScrollFlags_KeepVisibleEdgeX) && !fully_visible_x) { if (item_rect.Min.x < scroll_rect.Min.x || !can_be_fully_visible_x) @@ -10591,6 +12303,7 @@ ImVec2 ImGui::ScrollToRectEx(ImGuiWindow* window, const ImRect& item_rect, ImGui else SetScrollFromPosX(window, item_rect.Min.x - window->Pos.x, 0.0f); } + if ((flags & ImGuiScrollFlags_KeepVisibleEdgeY) && !fully_visible_y) { if (item_rect.Min.y < scroll_rect.Min.y || !can_be_fully_visible_y) @@ -10605,8 +12318,10 @@ ImVec2 ImGui::ScrollToRectEx(ImGuiWindow* window, const ImRect& item_rect, ImGui else SetScrollFromPosY(window, item_rect.Min.y - window->Pos.y, 0.0f); } + ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window); ImVec2 delta_scroll = next_scroll - window->Scroll; + // Also scroll parent window to keep us into view if necessary if (!(flags & ImGuiScrollFlags_NoScrollParent) && (window->Flags & ImGuiWindowFlags_ChildWindow)) { @@ -10617,50 +12332,60 @@ ImVec2 ImGui::ScrollToRectEx(ImGuiWindow* window, const ImRect& item_rect, ImGui in_flags = (in_flags & ~ImGuiScrollFlags_MaskY_) | ImGuiScrollFlags_KeepVisibleEdgeY; delta_scroll += ScrollToRectEx(window->ParentWindow, ImRect(item_rect.Min - delta_scroll, item_rect.Max - delta_scroll), in_flags); } + return delta_scroll; } + float ImGui::GetScrollX() { ImGuiWindow* window = GImGui->CurrentWindow; return window->Scroll.x; } + float ImGui::GetScrollY() { ImGuiWindow* window = GImGui->CurrentWindow; return window->Scroll.y; } + float ImGui::GetScrollMaxX() { ImGuiWindow* window = GImGui->CurrentWindow; return window->ScrollMax.x; } + float ImGui::GetScrollMaxY() { ImGuiWindow* window = GImGui->CurrentWindow; return window->ScrollMax.y; } + void ImGui::SetScrollX(ImGuiWindow* window, float scroll_x) { window->ScrollTarget.x = scroll_x; window->ScrollTargetCenterRatio.x = 0.0f; window->ScrollTargetEdgeSnapDist.x = 0.0f; } + void ImGui::SetScrollY(ImGuiWindow* window, float scroll_y) { window->ScrollTarget.y = scroll_y; window->ScrollTargetCenterRatio.y = 0.0f; window->ScrollTargetEdgeSnapDist.y = 0.0f; } + void ImGui::SetScrollX(float scroll_x) { ImGuiContext& g = *GImGui; SetScrollX(g.CurrentWindow, scroll_x); } + void ImGui::SetScrollY(float scroll_y) { ImGuiContext& g = *GImGui; SetScrollY(g.CurrentWindow, scroll_y); } + // Note that a local position will vary depending on initial scroll value, // This is a little bit confusing so bear with us: // - local_pos = (absolution_pos - window->Pos) @@ -10678,6 +12403,7 @@ void ImGui::SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x window->ScrollTargetCenterRatio.x = center_x_ratio; window->ScrollTargetEdgeSnapDist.x = 0.0f; } + void ImGui::SetScrollFromPosY(ImGuiWindow* window, float local_y, float center_y_ratio) { IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f); @@ -10685,16 +12411,19 @@ void ImGui::SetScrollFromPosY(ImGuiWindow* window, float local_y, float center_y window->ScrollTargetCenterRatio.y = center_y_ratio; window->ScrollTargetEdgeSnapDist.y = 0.0f; } + void ImGui::SetScrollFromPosX(float local_x, float center_x_ratio) { ImGuiContext& g = *GImGui; SetScrollFromPosX(g.CurrentWindow, local_x, center_x_ratio); } + void ImGui::SetScrollFromPosY(float local_y, float center_y_ratio) { ImGuiContext& g = *GImGui; SetScrollFromPosY(g.CurrentWindow, local_y, center_y_ratio); } + // center_x_ratio: 0.0f left of last item, 0.5f horizontal center of last item, 1.0f right of last item. void ImGui::SetScrollHereX(float center_x_ratio) { @@ -10703,9 +12432,11 @@ void ImGui::SetScrollHereX(float center_x_ratio) float spacing_x = ImMax(window->WindowPadding.x, g.Style.ItemSpacing.x); float target_pos_x = ImLerp(g.LastItemData.Rect.Min.x - spacing_x, g.LastItemData.Rect.Max.x + spacing_x, center_x_ratio); SetScrollFromPosX(window, target_pos_x - window->Pos.x, center_x_ratio); // Convert from absolute to local pos + // Tweak: snap on edges when aiming at an item very close to the edge window->ScrollTargetEdgeSnapDist.x = ImMax(0.0f, window->WindowPadding.x - spacing_x); } + // center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item. void ImGui::SetScrollHereY(float center_y_ratio) { @@ -10714,25 +12445,31 @@ void ImGui::SetScrollHereY(float center_y_ratio) float spacing_y = ImMax(window->WindowPadding.y, g.Style.ItemSpacing.y); float target_pos_y = ImLerp(window->DC.CursorPosPrevLine.y - spacing_y, window->DC.CursorPosPrevLine.y + window->DC.PrevLineSize.y + spacing_y, center_y_ratio); SetScrollFromPosY(window, target_pos_y - window->Pos.y, center_y_ratio); // Convert from absolute to local pos + // Tweak: snap on edges when aiming at an item very close to the edge window->ScrollTargetEdgeSnapDist.y = ImMax(0.0f, window->WindowPadding.y - spacing_y); } + //----------------------------------------------------------------------------- // [SECTION] TOOLTIPS //----------------------------------------------------------------------------- + bool ImGui::BeginTooltip() { return BeginTooltipEx(ImGuiTooltipFlags_None, ImGuiWindowFlags_None); } + bool ImGui::BeginItemTooltip() { if (!IsItemHovered(ImGuiHoveredFlags_ForTooltip)) return false; return BeginTooltipEx(ImGuiTooltipFlags_None, ImGuiWindowFlags_None); } + bool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags extra_window_flags) { ImGuiContext& g = *GImGui; + const bool is_dragdrop_tooltip = g.DragDropWithinSource || g.DragDropWithinTarget; if (is_dragdrop_tooltip) { @@ -10740,7 +12477,7 @@ bool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags ext // - offset visibility to increase visibility around mouse. // - never clamp within outer viewport boundary. // We call SetNextWindowPos() to enforce position and disable clamping. - // See FindBestWindowPosForPopup() for positionning logic of other tooltips (not drag and drop ones). + // See FindBestWindowPosForPopup() for positioning logic of other tooltips (not drag and drop ones). //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding; const bool is_touchscreen = (g.IO.MouseSource == ImGuiMouseSource_TouchScreen); if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasPos) == 0) @@ -10749,10 +12486,12 @@ bool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags ext ImVec2 tooltip_pivot = is_touchscreen ? TOOLTIP_DEFAULT_PIVOT_TOUCH : ImVec2(0.0f, 0.0f); SetNextWindowPos(tooltip_pos, ImGuiCond_None, tooltip_pivot); } + SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f); //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :( tooltip_flags |= ImGuiTooltipFlags_OverridePrevious; } + const char* window_name_template = is_dragdrop_tooltip ? "##Tooltip_DragDrop_%02d" : "##Tooltip_%02d"; char window_name[32]; ImFormatString(window_name, IM_ARRAYSIZE(window_name), window_name_template, g.TooltipOverrideCount); @@ -10763,6 +12502,7 @@ bool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags ext SetWindowHiddenAndSkipItemsForCurrentFrame(g.TooltipPreviousWindow); ImFormatString(window_name, IM_ARRAYSIZE(window_name), window_name_template, ++g.TooltipOverrideCount); } + ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking; Begin(window_name, NULL, flags | extra_window_flags); // 2023-03-09: Added bool return value to the API, but currently always returning true. @@ -10772,11 +12512,13 @@ bool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags ext //return ret; return true; } + void ImGui::EndTooltip() { IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls End(); } + void ImGui::SetTooltip(const char* fmt, ...) { va_list args; @@ -10784,6 +12526,7 @@ void ImGui::SetTooltip(const char* fmt, ...) SetTooltipV(fmt, args); va_end(args); } + void ImGui::SetTooltipV(const char* fmt, va_list args) { if (!BeginTooltipEx(ImGuiTooltipFlags_OverridePrevious, ImGuiWindowFlags_None)) @@ -10791,6 +12534,7 @@ void ImGui::SetTooltipV(const char* fmt, va_list args) TextV(fmt, args); EndTooltip(); } + // Shortcut to use 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav'. // Defaults to == ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort when using the mouse. void ImGui::SetItemTooltip(const char* fmt, ...) @@ -10801,14 +12545,18 @@ void ImGui::SetItemTooltip(const char* fmt, ...) SetTooltipV(fmt, args); va_end(args); } + void ImGui::SetItemTooltipV(const char* fmt, va_list args) { if (IsItemHovered(ImGuiHoveredFlags_ForTooltip)) SetTooltipV(fmt, args); } + + //----------------------------------------------------------------------------- // [SECTION] POPUPS //----------------------------------------------------------------------------- + // Supported flags: ImGuiPopupFlags_AnyPopupId, ImGuiPopupFlags_AnyPopupLevel bool ImGui::IsPopupOpen(ImGuiID id, ImGuiPopupFlags popup_flags) { @@ -10840,6 +12588,7 @@ bool ImGui::IsPopupOpen(ImGuiID id, ImGuiPopupFlags popup_flags) } } } + bool ImGui::IsPopupOpen(const char* str_id, ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; @@ -10848,6 +12597,7 @@ bool ImGui::IsPopupOpen(const char* str_id, ImGuiPopupFlags popup_flags) IM_ASSERT(0 && "Cannot use IsPopupOpen() with a string id and ImGuiPopupFlags_AnyPopupLevel."); // But non-string version is legal and used internally return IsPopupOpen(id, popup_flags); } + // Also see FindBlockingModal(NULL) ImGuiWindow* ImGui::GetTopMostPopupModal() { @@ -10858,6 +12608,7 @@ ImGuiWindow* ImGui::GetTopMostPopupModal() return popup; return NULL; } + // See Demo->Stacked Modal to confirm what this is for. ImGuiWindow* ImGui::GetTopMostAndVisiblePopupModal() { @@ -10868,6 +12619,8 @@ ImGuiWindow* ImGui::GetTopMostAndVisiblePopupModal() return popup; return NULL; } + + // When a modal popup is open, newly created windows that want focus (i.e. are not popups and do not specify ImGuiWindowFlags_NoFocusOnAppearing) // should be positioned behind that modal window, unless the window was created inside the modal begin-stack. // In case of multiple stacked modals newly created window honors begin stack order and does not go below its own modal parent. @@ -10886,6 +12639,7 @@ ImGuiWindow* ImGui::FindBlockingModal(ImGuiWindow* window) ImGuiContext& g = *GImGui; if (g.OpenPopupStack.Size <= 0) return NULL; + // Find a modal that has common parent with specified window. Specified window should be positioned behind that modal. for (ImGuiPopupData& popup_data : g.OpenPopupStack) { @@ -10902,6 +12656,7 @@ ImGuiWindow* ImGui::FindBlockingModal(ImGuiWindow* window) } return NULL; } + void ImGui::OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; @@ -10909,10 +12664,12 @@ void ImGui::OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags) IMGUI_DEBUG_LOG_POPUP("[popup] OpenPopup(\"%s\" -> 0x%08X)\n", str_id, id); OpenPopupEx(id, popup_flags); } + void ImGui::OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags) { OpenPopupEx(id, popup_flags); } + // Mark popup as open (toggle toward open state). // Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. // Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). @@ -10922,9 +12679,11 @@ void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) ImGuiContext& g = *GImGui; ImGuiWindow* parent_window = g.CurrentWindow; const int current_stack_size = g.BeginPopupStack.Size; + if (popup_flags & ImGuiPopupFlags_NoOpenOverExistingPopup) if (IsPopupOpen((ImGuiID)0, ImGuiPopupFlags_AnyPopupId)) return; + ImGuiPopupData popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack. popup_ref.PopupId = id; popup_ref.Window = NULL; @@ -10933,6 +12692,7 @@ void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) popup_ref.OpenParentId = parent_window->IDStack.back(); popup_ref.OpenPopupPos = NavCalcPreferredRefPos(); popup_ref.OpenMousePos = IsMousePosValid(&g.IO.MousePos) ? g.IO.MousePos : popup_ref.OpenPopupPos; + IMGUI_DEBUG_LOG_POPUP("[popup] OpenPopupEx(0x%08X)\n", id); if (g.OpenPopupStack.Size < current_stack_size + 1) { @@ -10959,12 +12719,14 @@ void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) ClosePopupToLevel(current_stack_size, true); g.OpenPopupStack.push_back(popup_ref); } + // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow(). // This is equivalent to what ClosePopupToLevel() does. //if (g.OpenPopupStack[current_stack_size].PopupId == id) // FocusWindow(parent_window); } } + // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it. // This function closes any popups that are over 'ref_window'. void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to_window_under_popup) @@ -10972,6 +12734,7 @@ void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to ImGuiContext& g = *GImGui; if (g.OpenPopupStack.Size == 0) return; + // Don't close our own child popup windows. //IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupsOverWindow(\"%s\") restore_under=%d\n", ref_window ? ref_window->Name : "", restore_focus_to_window_under_popup); int popup_count_to_keep = 0; @@ -10984,6 +12747,7 @@ void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to if (!popup.Window) continue; IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0); + // Trim the stack unless the popup is a direct parent of the reference window (the reference window is often the NavWindow) // - Clicking/Focusing Window2 won't close Popup1: // Window -> Popup1 -> Window2(Ref) @@ -11011,9 +12775,11 @@ void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to ClosePopupToLevel(popup_count_to_keep, restore_focus_to_window_under_popup); } } + void ImGui::ClosePopupsExceptModals() { ImGuiContext& g = *GImGui; + int popup_count_to_keep; for (popup_count_to_keep = g.OpenPopupStack.Size; popup_count_to_keep > 0; popup_count_to_keep--) { @@ -11024,6 +12790,7 @@ void ImGui::ClosePopupsExceptModals() if (popup_count_to_keep < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the statement below ClosePopupToLevel(popup_count_to_keep, true); } + void ImGui::ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup) { ImGuiContext& g = *GImGui; @@ -11032,9 +12799,11 @@ void ImGui::ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_ if (g.DebugLogFlags & ImGuiDebugLogFlags_EventPopup) for (int n = remaining; n < g.OpenPopupStack.Size; n++) IMGUI_DEBUG_LOG_POPUP("[popup] - Closing PopupID 0x%08X Window \"%s\"\n", g.OpenPopupStack[n].PopupId, g.OpenPopupStack[n].Window ? g.OpenPopupStack[n].Window->Name : NULL); + // Trim open popup stack ImGuiPopupData prev_popup = g.OpenPopupStack[remaining]; g.OpenPopupStack.resize(remaining); + // Restore focus (unless popup window was not yet submitted, and didn't have a chance to take focus anyhow. See #7325 for an edge case) if (restore_focus_to_window_under_popup && prev_popup.Window) { @@ -11046,6 +12815,7 @@ void ImGui::ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_ FocusWindow(focus_window, (g.NavLayer == ImGuiNavLayer_Main) ? ImGuiFocusRequestFlags_RestoreFocusedChild : ImGuiFocusRequestFlags_None); } } + // Close the popup we have begin-ed into. void ImGui::CloseCurrentPopup() { @@ -11053,6 +12823,7 @@ void ImGui::CloseCurrentPopup() int popup_idx = g.BeginPopupStack.Size - 1; if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.BeginPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId) return; + // Closing a menu closes its top-most parent popup (unless a modal) while (popup_idx > 0) { @@ -11068,12 +12839,14 @@ void ImGui::CloseCurrentPopup() } IMGUI_DEBUG_LOG_POPUP("[popup] CloseCurrentPopup %d -> %d\n", g.BeginPopupStack.Size - 1, popup_idx); ClosePopupToLevel(popup_idx, true); + // A common pattern is to close a popup when selecting a menu item/selectable that will open another window. // To improve this usage pattern, we avoid nav highlight for a single frame in the parent window. // Similarly, we could avoid mouse hover highlight in this window but it is less visually problematic. if (ImGuiWindow* window = g.NavWindow) window->DC.NavHideHighlightOneFrame = true; } + // Attention! BeginPopup() adds default flags when calling BeginPopupEx()! bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags) { @@ -11083,15 +12856,18 @@ bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags) g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values return false; } + char name[20]; IM_ASSERT((extra_window_flags & ImGuiWindowFlags_ChildMenu) == 0); // Use BeginPopupMenuEx() ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // No recycling, so we can close/open during the same frame + bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoDocking); if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) EndPopup(); //g.CurrentWindow->FocusRouteParentWindow = g.CurrentWindow->ParentWindowInBeginStack; return is_open; } + bool ImGui::BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags extra_window_flags) { ImGuiContext& g = *GImGui; @@ -11100,6 +12876,7 @@ bool ImGui::BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags ext g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values return false; } + char name[128]; IM_ASSERT(extra_window_flags & ImGuiWindowFlags_ChildMenu); ImFormatString(name, IM_ARRAYSIZE(name), "%s###Menu_%02d", label, g.BeginMenuDepth); // Recycle windows based on depth @@ -11109,6 +12886,7 @@ bool ImGui::BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags ext //g.CurrentWindow->FocusRouteParentWindow = g.CurrentWindow->ParentWindowInBeginStack; return is_open; } + bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags) { ImGuiContext& g = *GImGui; @@ -11121,6 +12899,7 @@ bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags) ImGuiID id = g.CurrentWindow->GetID(str_id); return BeginPopupEx(id, flags); } + // If 'p_open' is specified for a modal popup window, the popup will have a regular close button which will close the popup. // Note that popup visibility status is owned by Dear ImGui (and manipulated with e.g. OpenPopup). // - *p_open set back to false in BeginPopupModal() when popup is not open. @@ -11137,6 +12916,7 @@ bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags fla *p_open = false; return false; } + // Center modal windows by default for increased visibility // (this won't really last as settings will kick in, and is mostly for backward compatibility. user may do the same themselves) // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window. @@ -11145,6 +12925,7 @@ bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags fla const ImGuiViewport* viewport = window->WasActive ? window->Viewport : GetMainViewport(); // FIXME-VIEWPORT: What may be our reference viewport? SetNextWindowPos(viewport->GetCenter(), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f)); } + flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking; const bool is_open = Begin(name, p_open, flags); if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display) @@ -11156,15 +12937,21 @@ bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags fla } return is_open; } + void ImGui::EndPopup() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls - IM_ASSERT(g.BeginPopupStack.Size > 0); + if ((window->Flags & ImGuiWindowFlags_Popup) == 0 || g.BeginPopupStack.Size == 0) + { + IM_ASSERT_USER_ERROR(0, "Calling EndPopup() too many times or in wrong window!"); + return; + } + // Make all menus and popups wrap around for now, may need to expose that policy (e.g. focus scope could include wrap/loop policy flags used by new move requests) if (g.NavWindow == window) NavMoveRequestTryWrapping(window, ImGuiNavMoveFlags_LoopY); + // Child-popups don't need to be laid out const ImGuiID backup_within_end_child_id = g.WithinEndChildID; if (window->Flags & ImGuiWindowFlags_ChildWindow) @@ -11172,6 +12959,7 @@ void ImGui::EndPopup() End(); g.WithinEndChildID = backup_within_end_child_id; } + // Helper to open a popup if mouse button is released over the item // - This is essentially the same as BeginPopupContextItem() but without the trailing BeginPopup() void ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags) @@ -11186,6 +12974,7 @@ void ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags OpenPopupEx(id, popup_flags); } } + // This is a helper to handle the simplest case of associating one named popup to one given widget. // - To create a popup associated to the last item, you generally want to pass a NULL value to str_id. // - To create a popup with a specific identifier, pass it in str_id. @@ -11215,6 +13004,7 @@ bool ImGui::BeginPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flag OpenPopupEx(id, popup_flags); return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); } + bool ImGui::BeginPopupContextWindow(const char* str_id, ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; @@ -11228,6 +13018,7 @@ bool ImGui::BeginPopupContextWindow(const char* str_id, ImGuiPopupFlags popup_fl OpenPopupEx(id, popup_flags); return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); } + bool ImGui::BeginPopupContextVoid(const char* str_id, ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; @@ -11241,6 +13032,7 @@ bool ImGui::BeginPopupContextVoid(const char* str_id, ImGuiPopupFlags popup_flag OpenPopupEx(id, popup_flags); return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); } + // r_avoid = the rectangle to avoid (e.g. for tooltip it is a rectangle around the mouse cursor which we want to avoid. for popups it's a small point around the cursor.) // r_outer = the visible area rectangle, minus safe area padding. If our popup size won't fit because of safe area padding we ignore it. // (r_outer is usually equivalent to the viewport rectangle minus padding, but when multi-viewports are enabled and monitor @@ -11251,6 +13043,7 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size); //GetForegroundDrawList()->AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255)); //GetForegroundDrawList()->AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255)); + // Combo Box policy (we want a connecting edge) if (policy == ImGuiPopupPositionPolicy_ComboBox) { @@ -11271,6 +13064,7 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s return pos; } } + // Tooltip and Default popup policy // (Always first try the direction we used on the last frame, if any) if (policy == ImGuiPopupPositionPolicy_Tooltip || policy == ImGuiPopupPositionPolicy_Default) @@ -11281,34 +13075,43 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; if (n != -1 && dir == *last_dir) // Already tried this direction? continue; + const float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x); const float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y); + // If there's not enough room on one axis, there's no point in positioning on a side on this axis (e.g. when not enough width, use a top/bottom position to maximize available width) if (avail_w < size.x && (dir == ImGuiDir_Left || dir == ImGuiDir_Right)) continue; if (avail_h < size.y && (dir == ImGuiDir_Up || dir == ImGuiDir_Down)) continue; + ImVec2 pos; pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x; pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y; + // Clamp top-left corner of popup pos.x = ImMax(pos.x, r_outer.Min.x); pos.y = ImMax(pos.y, r_outer.Min.y); + *last_dir = dir; return pos; } } + // Fallback when not enough room: *last_dir = ImGuiDir_None; + // For tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible. if (policy == ImGuiPopupPositionPolicy_Tooltip) return ref_pos + ImVec2(2, 2); + // Otherwise try to keep within display ImVec2 pos = ref_pos; pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x); pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y); return pos; } + // Note that this is used for popups, which can overlap the non work-area of individual viewports. ImRect ImGui::GetPopupAllowedExtentRect(ImGuiWindow* window) { @@ -11330,9 +13133,11 @@ ImRect ImGui::GetPopupAllowedExtentRect(ImGuiWindow* window) r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f)); return r_screen; } + ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) { ImGuiContext& g = *GImGui; + ImRect r_outer = GetPopupAllowedExtentRect(window); if (window->Flags & ImGuiWindowFlags_ChildMenu) { @@ -11362,12 +13167,14 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) IM_ASSERT(g.CurrentWindow == window); const float scale = g.Style.MouseCursorScale; const ImVec2 ref_pos = NavCalcPreferredRefPos(); + if (g.IO.MouseSource == ImGuiMouseSource_TouchScreen && NavCalcPreferredRefPosSource() == ImGuiInputSource_Mouse) { ImVec2 tooltip_pos = ref_pos + TOOLTIP_DEFAULT_OFFSET_TOUCH * scale - (TOOLTIP_DEFAULT_PIVOT_TOUCH * window->Size); if (r_outer.Contains(ImRect(tooltip_pos, tooltip_pos + window->Size))) return tooltip_pos; } + ImVec2 tooltip_pos = ref_pos + TOOLTIP_DEFAULT_OFFSET_MOUSE * scale; ImRect r_avoid; if (g.NavCursorVisible && g.NavHighlightItemUnderNav && !g.IO.ConfigNavMoveSetMousePos) @@ -11375,11 +13182,13 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) else r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * scale, ref_pos.y + 24 * scale); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important. //GetForegroundDrawList()->AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255, 0, 255, 255)); + return FindBestWindowPosForPopupEx(tooltip_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Tooltip); } IM_ASSERT(0); return window->Pos; } + //----------------------------------------------------------------------------- // [SECTION] WINDOW FOCUS //---------------------------------------------------------------------------- @@ -11395,10 +13204,12 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) // - FocusWindow() [Internal] // - FocusTopMostWindowUnderOne() [Internal] //----------------------------------------------------------------------------- + void ImGui::SetWindowFocus() { FocusWindow(GImGui->CurrentWindow); } + void ImGui::SetWindowFocus(const char* name) { if (name) @@ -11411,31 +13222,37 @@ void ImGui::SetWindowFocus(const char* name) FocusWindow(NULL); } } + void ImGui::SetNextWindowFocus() { ImGuiContext& g = *GImGui; g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasFocus; } + // Similar to IsWindowHovered() bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* ref_window = g.NavWindow; ImGuiWindow* cur_window = g.CurrentWindow; + if (ref_window == NULL) return false; if (flags & ImGuiFocusedFlags_AnyWindow) return true; + IM_ASSERT(cur_window); // Not inside a Begin()/End() const bool popup_hierarchy = (flags & ImGuiFocusedFlags_NoPopupHierarchy) == 0; const bool dock_hierarchy = (flags & ImGuiFocusedFlags_DockHierarchy) != 0; - if (flags & ImGuiHoveredFlags_RootWindow) + if (flags & ImGuiFocusedFlags_RootWindow) cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy, dock_hierarchy); - if (flags & ImGuiHoveredFlags_ChildWindows) + + if (flags & ImGuiFocusedFlags_ChildWindows) return IsWindowChildOf(ref_window, cur_window, popup_hierarchy, dock_hierarchy); else return (ref_window == cur_window); } + static int ImGui::FindWindowFocusIndex(ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -11445,9 +13262,11 @@ static int ImGui::FindWindowFocusIndex(ImGuiWindow* window) IM_ASSERT(g.WindowsFocusOrder[order] == window); return order; } + static void ImGui::UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, ImGuiWindowFlags new_flags) { ImGuiContext& g = *GImGui; + const bool new_is_explicit_child = (new_flags & ImGuiWindowFlags_ChildWindow) != 0 && ((new_flags & ImGuiWindowFlags_Popup) == 0 || (new_flags & ImGuiWindowFlags_ChildMenu) != 0); const bool child_flag_changed = new_is_explicit_child != window->IsExplicitChild; if ((just_created || child_flag_changed) && !new_is_explicit_child) @@ -11466,14 +13285,17 @@ static void ImGui::UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_c } window->IsExplicitChild = new_is_explicit_child; } + void ImGui::BringWindowToFocusFront(ImGuiWindow* window) { ImGuiContext& g = *GImGui; IM_ASSERT(window == window->RootWindow); + const int cur_order = window->FocusOrder; IM_ASSERT(g.WindowsFocusOrder[cur_order] == window); if (g.WindowsFocusOrder.back() == window) return; + const int new_order = g.WindowsFocusOrder.Size - 1; for (int n = cur_order; n < new_order; n++) { @@ -11484,6 +13306,7 @@ void ImGui::BringWindowToFocusFront(ImGuiWindow* window) g.WindowsFocusOrder[new_order] = window; window->FocusOrder = (short)new_order; } + // Note technically focus related but rather adjacent and close to BringWindowToFocusFront() void ImGui::BringWindowToDisplayFront(ImGuiWindow* window) { @@ -11499,6 +13322,7 @@ void ImGui::BringWindowToDisplayFront(ImGuiWindow* window) break; } } + void ImGui::BringWindowToDisplayBack(ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -11512,6 +13336,7 @@ void ImGui::BringWindowToDisplayBack(ImGuiWindow* window) break; } } + void ImGui::BringWindowToDisplayBehind(ImGuiWindow* window, ImGuiWindow* behind_window) { IM_ASSERT(window != NULL && behind_window != NULL); @@ -11533,15 +13358,18 @@ void ImGui::BringWindowToDisplayBehind(ImGuiWindow* window, ImGuiWindow* behind_ g.Windows[pos_beh] = window; } } + int ImGui::FindWindowDisplayIndex(ImGuiWindow* window) { ImGuiContext& g = *GImGui; return g.Windows.index_from_ptr(g.Windows.find(window)); } + // Moving window to front of display and set focus (which happens to be back of our sorted list) void ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags) { ImGuiContext& g = *GImGui; + // Modal check? if ((flags & ImGuiFocusRequestFlags_UnlessBelowModal) && (g.NavWindow != window)) // Early out in common case. if (ImGuiWindow* blocking_modal = FindBlockingModal(window)) @@ -11555,9 +13383,11 @@ void ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags) ClosePopupsOverWindow(GetTopMostPopupModal(), false); // Note how we need to use GetTopMostPopupModal() aad NOT blocking_modal, to handle nested modals return; } + // Find last focused child (if any) and focus it instead. if ((flags & ImGuiFocusRequestFlags_RestoreFocusedChild) && window != NULL) window = NavRestoreLastChildNavWindow(window); + // Apply focus if (g.NavWindow != window) { @@ -11569,15 +13399,18 @@ void ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags) SetNavFocusScope(window ? window->NavRootFocusScopeId : 0); g.NavIdIsAlive = false; g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; + // Close popups if any ClosePopupsOverWindow(window, false); } + // Move the root window to the top of the pile IM_ASSERT(window == NULL || window->RootWindowDockTree != NULL); ImGuiWindow* focus_front_window = window ? window->RootWindow : NULL; ImGuiWindow* display_front_window = window ? window->RootWindowDockTree : NULL; ImGuiDockNode* dock_node = window ? window->DockNode : NULL; bool active_id_window_is_dock_node_host = (g.ActiveIdWindow && dock_node && dock_node->HostWindow == g.ActiveIdWindow); + // Steal active widgets. Some of the cases it triggers includes: // - Focus a window while an InputText in another window is active, if focus happens before the old InputText can run. // - When using Nav to activate menu items (due to timing of activating on press->new window appears->losing ActiveId) @@ -11585,19 +13418,23 @@ void ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags) if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != focus_front_window) if (!g.ActiveIdNoClearOnFocusLoss && !active_id_window_is_dock_node_host) ClearActiveID(); + // Passing NULL allow to disable keyboard focus if (!window) return; window->LastFrameJustFocused = g.FrameCount; + // Select in dock node // For #2304 we avoid applying focus immediately before the tabbar is visible. //if (dock_node && dock_node->TabBar) // dock_node->TabBar->SelectedTabId = dock_node->TabBar->NextSelectedTabId = window->TabId; + // Bring to front BringWindowToFocusFront(focus_front_window); if (((window->Flags | focus_front_window->Flags | display_front_window->Flags) & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0) BringWindowToDisplayFront(display_front_window); } + void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window, ImGuiViewport* filter_viewport, ImGuiFocusRequestFlags flags) { ImGuiContext& g = *GImGui; @@ -11634,12 +13471,15 @@ void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWind } FocusWindow(NULL, flags); } + //----------------------------------------------------------------------------- // [SECTION] KEYBOARD/GAMEPAD NAVIGATION //----------------------------------------------------------------------------- + // FIXME-NAV: The existence of SetNavID vs SetFocusID vs FocusWindow() needs to be clarified/reworked. // In our terminology those should be interchangeable, yet right now this is super confusing. // Those two functions are merely a legacy artifact, so at minimum naming should be clarified. + void ImGui::SetNavCursorVisible(bool visible) { ImGuiContext& g = *GImGui; @@ -11647,6 +13487,7 @@ void ImGui::SetNavCursorVisible(bool visible) visible = true; g.NavCursorVisible = visible; } + // (was called NavRestoreHighlightAfterMove() before 1.91.4) void ImGui::SetNavCursorVisibleAfterMove() { @@ -11655,6 +13496,7 @@ void ImGui::SetNavCursorVisibleAfterMove() g.NavCursorVisible = true; g.NavHighlightItemUnderNav = g.NavMousePosDirty = true; } + void ImGui::SetNavWindow(ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -11667,17 +13509,20 @@ void ImGui::SetNavWindow(ImGuiWindow* window) g.NavInitRequest = g.NavMoveSubmitted = g.NavMoveScoringItems = false; NavUpdateAnyRequestFlag(); } + void ImGui::NavHighlightActivated(ImGuiID id) { ImGuiContext& g = *GImGui; g.NavHighlightActivatedId = id; g.NavHighlightActivatedTimer = NAV_ACTIVATE_HIGHLIGHT_TIMER; } + void ImGui::NavClearPreferredPosForAxis(ImGuiAxis axis) { ImGuiContext& g = *GImGui; g.NavWindow->RootWindowForNav->NavPreferredScoringPosRel[g.NavLayer][axis] = FLT_MAX; } + void ImGui::SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel) { ImGuiContext& g = *GImGui; @@ -11688,16 +13533,20 @@ void ImGui::SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id SetNavFocusScope(focus_scope_id); g.NavWindow->NavLastIds[nav_layer] = id; g.NavWindow->NavRectRel[nav_layer] = rect_rel; + // Clear preferred scoring position (NavMoveRequestApplyResult() will tend to restore it) NavClearPreferredPosForAxis(ImGuiAxis_X); NavClearPreferredPosForAxis(ImGuiAxis_Y); } + void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) { ImGuiContext& g = *GImGui; IM_ASSERT(id != 0); + if (g.NavWindow != window) SetNavWindow(window); + // Assume that SetFocusID() is called in the context where its window->DC.NavLayerCurrent and g.CurrentFocusScopeId are valid. // Note that window may be != g.CurrentWindow (e.g. SetFocusID call in InputTextEx for multi-line text) const ImGuiNavLayer nav_layer = window->DC.NavLayerCurrent; @@ -11707,20 +13556,24 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) window->NavLastIds[nav_layer] = id; if (g.LastItemData.ID == id) window->NavRectRel[nav_layer] = WindowRectAbsToRel(window, g.LastItemData.NavRect); + if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) g.NavHighlightItemUnderNav = true; else if (g.IO.ConfigNavCursorVisibleAuto) g.NavCursorVisible = false; + // Clear preferred scoring position (NavMoveRequestApplyResult() will tend to restore it) NavClearPreferredPosForAxis(ImGuiAxis_X); NavClearPreferredPosForAxis(ImGuiAxis_Y); } + static ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy) { if (ImFabs(dx) > ImFabs(dy)) return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left; return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up; } + static float inline NavScoreItemDistInterval(float cand_min, float cand_max, float curr_min, float curr_max) { if (cand_max < curr_min) @@ -11729,17 +13582,20 @@ static float inline NavScoreItemDistInterval(float cand_min, float cand_max, flo return cand_min - curr_max; return 0.0f; } + // Scoring function for keyboard/gamepad directional navigation. Based on https://gist.github.com/rygorous/6981057 -static bool ImGui::NavScoreItem(ImGuiNavItemData* result) +static bool ImGui::NavScoreItem(ImGuiNavItemData* result, const ImRect& nav_bb) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (g.NavLayer != window->DC.NavLayerCurrent) return false; + // FIXME: Those are not good variables names - ImRect cand = g.LastItemData.NavRect; // Current item nav rectangle + ImRect cand = nav_bb; // Current item nav rectangle const ImRect curr = g.NavScoringRect; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width) g.NavScoringDebugCount++; + // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring if (window->ParentWindow == g.NavWindow) { @@ -11748,6 +13604,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) return false; cand.ClipWithFull(window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window } + // Compute distance between boxes // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed. float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x); @@ -11755,10 +13612,12 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) if (dby != 0.0f && dbx != 0.0f) dbx = (dbx / 1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f); float dist_box = ImFabs(dbx) + ImFabs(dby); + // Compute distance between centers (this is off by a factor of 2, but we only compare center distances with each other so it doesn't matter) float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x); float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y); float dist_center = ImFabs(dcx) + ImFabs(dcy); // L1 metric (need this for our connectedness guarantee) + // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance ImGuiDir quadrant; float dax = 0.0f, day = 0.0f, dist_axial = 0.0f; @@ -11786,6 +13645,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter) quadrant = (g.LastItemData.ID < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right; } + const ImGuiDir move_dir = g.NavMoveDir; #if IMGUI_DEBUG_NAV_SCORING char buf[200]; @@ -11818,6 +13678,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) if (debug_tty) { IMGUI_DEBUG_LOG_NAV("id 0x%08X\n%s\n", g.LastItemData.ID, buf); } } #endif + // Is it in the quadrant we're interested in moving to? bool new_best = false; if (quadrant == move_dir) @@ -11847,6 +13708,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) } } } + // Axial check: if 'curr' has no link at all in some direction and 'cand' lies roughly in that direction, add a tentative link. This will only be kept if no "real" matches // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness) // This is just to avoid buttons having no links in a particular direction when there's a suitable neighbor. you get good graphs without this too. @@ -11859,8 +13721,10 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) result->DistAxial = dist_axial; new_best = true; } + return new_best; } + static void ImGui::NavApplyItemToResult(ImGuiNavItemData* result) { ImGuiContext& g = *GImGui; @@ -11876,6 +13740,7 @@ static void ImGui::NavApplyItemToResult(ImGuiNavItemData* result) result->SelectionUserData = g.NextItemData.SelectionUserData; // INTENTIONAL: At this point this field is not cleared in NextItemData. Avoid unnecessary copy to LastItemData. } } + // True when current work location may be scrolled horizontally when moving left / right. // This is generally always true UNLESS within a column. We don't have a vertical equivalent. void ImGui::NavUpdateCurrentWindowIsScrollPushableX() @@ -11884,6 +13749,7 @@ void ImGui::NavUpdateCurrentWindowIsScrollPushableX() ImGuiWindow* window = g.CurrentWindow; window->DC.NavIsScrollPushableX = (g.CurrentTable == NULL && window->DC.CurrentColumns == NULL); } + // We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above) // This is called after LastItemData is set, but NextItemData is also still valid. static void ImGui::NavProcessItem() @@ -11892,13 +13758,15 @@ static void ImGui::NavProcessItem() ImGuiWindow* window = g.CurrentWindow; const ImGuiID id = g.LastItemData.ID; const ImGuiItemFlags item_flags = g.LastItemData.ItemFlags; - // When inside a container that isn't scrollable with Left<>Right, clip NavRect accordingly (#2221) + + // When inside a container that isn't scrollable with Left<>Right, clip NavRect accordingly (#2221, #8816) + ImRect nav_bb = g.LastItemData.NavRect; if (window->DC.NavIsScrollPushableX == false) { - g.LastItemData.NavRect.Min.x = ImClamp(g.LastItemData.NavRect.Min.x, window->ClipRect.Min.x, window->ClipRect.Max.x); - g.LastItemData.NavRect.Max.x = ImClamp(g.LastItemData.NavRect.Max.x, window->ClipRect.Min.x, window->ClipRect.Max.x); + nav_bb.Min.x = ImClamp(nav_bb.Min.x, window->ClipRect.Min.x, window->ClipRect.Max.x); + nav_bb.Max.x = ImClamp(nav_bb.Max.x, window->ClipRect.Min.x, window->ClipRect.Max.x); } - const ImRect nav_bb = g.LastItemData.NavRect; + // Process Init Request if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent && (item_flags & ImGuiItemFlags_Disabled) == 0) { @@ -11914,6 +13782,7 @@ static void ImGui::NavProcessItem() NavUpdateAnyRequestFlag(); } } + // Process Move Request (scoring for navigation) // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRect + scoring from a rect wrapped according to current wrapping policy) if (g.NavMoveScoringItems && (item_flags & ImGuiItemFlags_Disabled) == 0) @@ -11928,17 +13797,19 @@ static void ImGui::NavProcessItem() else if (g.NavId != id || (g.NavMoveFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) { ImGuiNavItemData* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; - if (NavScoreItem(result)) + if (NavScoreItem(result, nav_bb)) NavApplyItemToResult(result); + // Features like PageUp/PageDown need to maintain a separate score for the visible set of items. const float VISIBLE_RATIO = 0.70f; if ((g.NavMoveFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb)) if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) - ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO) - if (NavScoreItem(&g.NavMoveResultLocalVisible)) + if (NavScoreItem(&g.NavMoveResultLocalVisible, nav_bb)) NavApplyItemToResult(&g.NavMoveResultLocalVisible); } } } + // Update information for currently focused/navigated item if (g.NavId == id) { @@ -11956,6 +13827,7 @@ static void ImGui::NavProcessItem() window->NavRectRel[window->DC.NavLayerCurrent] = WindowRectAbsToRel(window, nav_bb); // Store item bounding box (relative to window position) } } + // Handle "scoring" of an item for a tabbing/focusing request initiated by NavUpdateCreateTabbingRequest(). // Note that SetKeyboardFocusHere() API calls are considered tabbing requests! // - Case 1: no nav/active id: set result to first eligible item, stop storing. @@ -11966,6 +13838,7 @@ static void ImGui::NavProcessItem() void ImGui::NavProcessItemForTabbingRequest(ImGuiID id, ImGuiItemFlags item_flags, ImGuiNavMoveFlags move_flags) { ImGuiContext& g = *GImGui; + if ((move_flags & ImGuiNavMoveFlags_FocusApi) == 0) { if (g.NavLayer != g.CurrentWindow->DC.NavLayerCurrent) @@ -11973,6 +13846,7 @@ void ImGui::NavProcessItemForTabbingRequest(ImGuiID id, ImGuiItemFlags item_flag if (g.NavFocusScopeId != g.CurrentFocusScopeId) return; } + // - Can always land on an item when using API call. // - Tabbing with _NavEnableKeyboard (space/enter/arrows): goes through every item. // - Tabbing without _NavEnableKeyboard: goes through inputable items only. @@ -11981,6 +13855,7 @@ void ImGui::NavProcessItemForTabbingRequest(ImGuiID id, ImGuiItemFlags item_flag can_stop = true; else can_stop = (item_flags & ImGuiItemFlags_NoTabStop) == 0 && ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) || (item_flags & ImGuiItemFlags_Inputable)); + // Always store in NavMoveResultLocal (unlike directional request which uses NavMoveResultOther on sibling/flattened windows) ImGuiNavItemData* result = &g.NavMoveResultLocal; if (g.NavTabbingDir == +1) @@ -12018,19 +13893,23 @@ void ImGui::NavProcessItemForTabbingRequest(ImGuiID id, ImGuiItemFlags item_flag NavApplyItemToResult(&g.NavTabbingResultFirst); } } + bool ImGui::NavMoveRequestButNoResultYet() { ImGuiContext& g = *GImGui; return g.NavMoveScoringItems && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0; } + // FIXME: ScoringRect is not set void ImGui::NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags) { ImGuiContext& g = *GImGui; IM_ASSERT(g.NavWindow != NULL); //IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequestSubmit: dir %c, window \"%s\"\n", "-WENS"[move_dir + 1], g.NavWindow->Name); + if (move_flags & ImGuiNavMoveFlags_IsTabbing) move_flags |= ImGuiNavMoveFlags_AllowCurrentNavId; + g.NavMoveSubmitted = g.NavMoveScoringItems = true; g.NavMoveDir = move_dir; g.NavMoveDirForDebug = move_dir; @@ -12046,6 +13925,7 @@ void ImGui::NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavM g.NavTabbingResultFirst.Clear(); NavUpdateAnyRequestFlag(); } + void ImGui::NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result) { ImGuiContext& g = *GImGui; @@ -12053,8 +13933,9 @@ void ImGui::NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result) NavApplyItemToResult(result); NavUpdateAnyRequestFlag(); } -// Called by TreePop() to implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere -void ImGui::NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiTreeNodeStackData* tree_node_data) + +// Called by TreePop() to implement ImGuiTreeNodeFlags_NavLeftJumpsToParent +void ImGui::NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, const ImGuiTreeNodeStackData* tree_node_data) { ImGuiContext& g = *GImGui; g.NavMoveScoringItems = false; @@ -12065,12 +13946,14 @@ void ImGui::NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGu NavClearPreferredPosForAxis(ImGuiAxis_Y); NavUpdateAnyRequestFlag(); } + void ImGui::NavMoveRequestCancel() { ImGuiContext& g = *GImGui; g.NavMoveSubmitted = g.NavMoveScoringItems = false; NavUpdateAnyRequestFlag(); } + // Forward will reuse the move request again on the next frame (generally with modifications done to it) void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags) { @@ -12083,17 +13966,20 @@ void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNav g.NavMoveFlags = move_flags | ImGuiNavMoveFlags_Forwarded; g.NavMoveScrollFlags = scroll_flags; } + // Navigation wrap-around logic is delayed to the end of the frame because this operation is only valid after entire // popup is assembled and in case of appended popups it is not clear which EndPopup() call is final. void ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags wrap_flags) { ImGuiContext& g = *GImGui; IM_ASSERT((wrap_flags & ImGuiNavMoveFlags_WrapMask_ ) != 0 && (wrap_flags & ~ImGuiNavMoveFlags_WrapMask_) == 0); // Call with _WrapX, _WrapY, _LoopX, _LoopY + // In theory we should test for NavMoveRequestButNoResultYet() but there's no point doing it: // as NavEndFrame() will do the same test. It will end up calling NavUpdateCreateWrappingRequest(). if (g.NavWindow == window && g.NavMoveScoringItems && g.NavLayer == ImGuiNavLayer_Main) g.NavMoveFlags = (g.NavMoveFlags & ~ImGuiNavMoveFlags_WrapMask_) | wrap_flags; } + // FIXME: This could be replaced by updating a frame number in each window when (window == NavWindow) and (NavLayer == 0). // This way we could find the last focused window among our children. It would be much less confusing this way? static void ImGui::NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window) @@ -12104,6 +13990,7 @@ static void ImGui::NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window) if (parent && parent != nav_window) parent->NavLastChildNavWindow = nav_window; } + // Restore the last focused child. // Call when we are expected to land on the Main Layer (0) after FocusWindow() static ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window) @@ -12115,6 +14002,7 @@ static ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window) return tab->Window; return window; } + void ImGui::NavRestoreLayer(ImGuiNavLayer layer) { ImGuiContext& g = *GImGui; @@ -12137,6 +14025,7 @@ void ImGui::NavRestoreLayer(ImGuiNavLayer layer) NavInitWindow(window, true); } } + static inline void ImGui::NavUpdateAnyRequestFlag() { ImGuiContext& g = *GImGui; @@ -12144,18 +14033,21 @@ static inline void ImGui::NavUpdateAnyRequestFlag() if (g.NavAnyRequest) IM_ASSERT(g.NavWindow != NULL); } + // This needs to be called before we submit any widget (aka in or before Begin) void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) { // FIXME: ChildWindow test here is wrong for docking ImGuiContext& g = *GImGui; IM_ASSERT(window == g.NavWindow); + if (window->Flags & ImGuiWindowFlags_NoNavInputs) { g.NavId = 0; SetNavFocusScope(window->NavRootFocusScopeId); return; } + bool init_for_nav = false; if (window == window->RootWindow || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit) init_for_nav = true; @@ -12174,23 +14066,28 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) SetNavFocusScope(window->NavRootFocusScopeId); } } + static ImGuiInputSource ImGui::NavCalcPreferredRefPosSource() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.NavWindow; const bool activated_shortcut = g.ActiveId != 0 && g.ActiveIdFromShortcut && g.ActiveId == g.LastItemData.ID; + // Testing for !activated_shortcut here could in theory be removed if we decided that activating a remote shortcut altered one of the g.NavDisableXXX flag. if ((!g.NavCursorVisible || !g.NavHighlightItemUnderNav || !window) && !activated_shortcut) return ImGuiInputSource_Mouse; else return ImGuiInputSource_Keyboard; // or Nav in general } + static ImVec2 ImGui::NavCalcPreferredRefPos() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.NavWindow; ImGuiInputSource source = NavCalcPreferredRefPosSource(); + const bool activated_shortcut = g.ActiveId != 0 && g.ActiveIdFromShortcut && g.ActiveId == g.LastItemData.ID; + // Testing for !activated_shortcut here could in theory be removed if we decided that activating a remote shortcut altered one of the g.NavDisableXXX flag. if (source == ImGuiInputSource_Mouse) { @@ -12208,6 +14105,7 @@ static ImVec2 ImGui::NavCalcPreferredRefPos() ref_rect = g.LastItemData.NavRect; else ref_rect = WindowRectRelToAbs(window, window->NavRectRel[g.NavLayer]); + // Take account of upcoming scrolling (maybe set mouse pos should be done in EndFrame?) if (window->LastFrameActive != g.FrameCount && (window->ScrollTarget.x != FLT_MAX || window->ScrollTarget.y != FLT_MAX)) { @@ -12219,11 +14117,13 @@ static ImVec2 ImGui::NavCalcPreferredRefPos() return ImTrunc(ImClamp(pos, viewport->Pos, viewport->Pos + viewport->Size)); // ImTrunc() is important because non-integer mouse position application in backend might be lossy and result in undesirable non-zero delta. } } + float ImGui::GetNavTweakPressedAmount(ImGuiAxis axis) { ImGuiContext& g = *GImGui; float repeat_delay, repeat_rate; GetTypematicRepeatRate(ImGuiInputFlags_RepeatRateNavTweak, &repeat_delay, &repeat_rate); + ImGuiKey key_less, key_more; if (g.NavInputSource == ImGuiInputSource_Gamepad) { @@ -12240,12 +14140,15 @@ float ImGui::GetNavTweakPressedAmount(ImGuiAxis axis) amount = 0.0f; return amount; } + static void ImGui::NavUpdate() { ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; + io.WantSetMousePos = false; //if (g.NavScoringDebugCount > 0) IMGUI_DEBUG_LOG_NAV("[nav] NavScoringDebugCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.NavScoringDebugCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest); + // Set input source based on which keys are last pressed (as some features differs when used with Gamepad vs Keyboard) // FIXME-NAV: Now that keys are separated maybe we can get rid of NavInputSource? const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; @@ -12260,6 +14163,7 @@ static void ImGui::NavUpdate() for (ImGuiKey key : nav_keyboard_keys_to_change_source) if (IsKeyDown(key)) g.NavInputSource = ImGuiInputSource_Keyboard; + // Process navigation init request (select first/default focus) g.NavJustMovedToId = 0; g.NavJustMovedToFocusScopeId = g.NavJustMovedFromFocusScopeId = 0; @@ -12268,6 +14172,7 @@ static void ImGui::NavUpdate() g.NavInitRequest = false; g.NavInitRequestFromMove = false; g.NavInitResult.ID = 0; + // Process navigation move request if (g.NavMoveSubmitted) NavMoveRequestApplyResult(); @@ -12276,6 +14181,7 @@ static void ImGui::NavUpdate() if (g.NavCursorHideFrames > 0) if (--g.NavCursorHideFrames == 0) g.NavCursorVisible = true; + // Schedule mouse position update (will be done at the bottom of this function, after 1) processing all move requests and 2) updating scrolling) bool set_mouse_pos = false; if (g.NavMousePosDirty && g.NavIdIsAlive) @@ -12283,18 +14189,23 @@ static void ImGui::NavUpdate() set_mouse_pos = true; g.NavMousePosDirty = false; IM_ASSERT(g.NavLayer == ImGuiNavLayer_Main || g.NavLayer == ImGuiNavLayer_Menu); + // Store our return window (for returning from Menu Layer to Main Layer) and clear it as soon as we step back in our own Layer 0 if (g.NavWindow) NavSaveLastChildNavWindowIntoParent(g.NavWindow); if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == ImGuiNavLayer_Main) g.NavWindow->NavLastChildNavWindow = NULL; + // Update CTRL+TAB and Windowing features (hold Square to move/resize/etc.) NavUpdateWindowing(); + // Set output flags for user application io.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs); io.NavVisible = (io.NavActive && g.NavId != 0 && g.NavCursorVisible) || (g.NavWindowingTarget != NULL); + // Process NavCancel input (to close a popup, get back to parent, clear focus) NavUpdateCancelRequest(); + // Process manual activation request g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = 0; g.NavActivateFlags = ImGuiActivateFlags_None; @@ -12328,11 +14239,13 @@ static void ImGui::NavUpdate() g.NavCursorVisible = true; if (g.NavActivateId != 0) IM_ASSERT(g.NavActivateDownId == g.NavActivateId); + // Highlight if (g.NavHighlightActivatedTimer > 0.0f) g.NavHighlightActivatedTimer = ImMax(0.0f, g.NavHighlightActivatedTimer - io.DeltaTime); if (g.NavHighlightActivatedTimer == 0.0f) g.NavHighlightActivatedId = 0; + // Process programmatic activation request // FIXME-NAV: Those should eventually be queued (unlike focus they don't cancel each others) if (g.NavNextActivateId != 0) @@ -12341,12 +14254,14 @@ static void ImGui::NavUpdate() g.NavActivateFlags = g.NavNextActivateFlags; } g.NavNextActivateId = 0; + // Process move requests NavUpdateCreateMoveRequest(); if (g.NavMoveDir == ImGuiDir_None) NavUpdateCreateTabbingRequest(); NavUpdateAnyRequestFlag(); g.NavIdIsAlive = false; + // Scrolling if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget) { @@ -12361,6 +14276,7 @@ static void ImGui::NavUpdate() if (move_dir == ImGuiDir_Up || move_dir == ImGuiDir_Down) SetScrollY(window, ImTrunc(window->Scroll.y + ((move_dir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed)); } + // *Normal* Manual scroll with LStick // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds. if (nav_gamepad_active) @@ -12373,16 +14289,19 @@ static void ImGui::NavUpdate() SetScrollY(window, ImTrunc(window->Scroll.y + scroll_dir.y * scroll_speed * tweak_factor)); } } + // Always prioritize mouse highlight if navigation is disabled if (!nav_keyboard_active && !nav_gamepad_active) { g.NavCursorVisible = false; g.NavHighlightItemUnderNav = set_mouse_pos = false; } + // Update mouse position if requested // (This will take into account the possibility that a Scroll was queued in the window to offset our absolute mouse position before scroll has been applied) if (set_mouse_pos && io.ConfigNavMoveSetMousePos && (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos)) TeleportMousePos(NavCalcPreferredRefPos()); + // [DEBUG] g.NavScoringDebugCount = 0; #if IMGUI_DEBUG_NAV_RECTS @@ -12394,12 +14313,14 @@ static void ImGui::NavUpdate() } #endif } + void ImGui::NavInitRequestApplyResult() { // In very rare cases g.NavWindow may be null (e.g. clearing focus after requesting an init request, which does happen when releasing Alt while clicking on void) ImGuiContext& g = *GImGui; if (!g.NavWindow) return; + ImGuiNavItemData* result = &g.NavInitResult; if (g.NavId != result->ID) { @@ -12410,6 +14331,7 @@ void ImGui::NavInitRequestApplyResult() g.NavJustMovedToIsTabbing = false; g.NavJustMovedToHasSelectionData = (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) != 0; } + // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called) // FIXME-NAV: On _NavFlattened windows, g.NavWindow will only be updated during subsequent frame. Not a problem currently. IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: ApplyResult: NavID 0x%08X in Layer %d Window \"%s\"\n", result->ID, g.NavLayer, g.NavWindow->Name); @@ -12420,12 +14342,14 @@ void ImGui::NavInitRequestApplyResult() if (g.NavInitRequestFromMove) SetNavCursorVisibleAfterMove(); } + // Bias scoring rect ahead of scoring + update preferred pos (if missing) using source position static void NavBiasScoringRect(ImRect& r, ImVec2& preferred_pos_rel, ImGuiDir move_dir, ImGuiNavMoveFlags move_flags) { // Bias initial rect ImGuiContext& g = *GImGui; const ImVec2 rel_to_abs_offset = g.NavWindow->DC.CursorStartPos; + // Initialize bias on departure if we don't have any. So mouse-click + arrow will record bias. // - We default to L/U bias, so moving down from a large source item into several columns will land on left-most column. // - But each successful move sets new bias on one axis, only cleared when using mouse. @@ -12436,12 +14360,14 @@ static void NavBiasScoringRect(ImRect& r, ImVec2& preferred_pos_rel, ImGuiDir mo if (preferred_pos_rel.y == FLT_MAX) preferred_pos_rel.y = r.GetCenter().y - rel_to_abs_offset.y; } + // Apply general bias on the other axis if ((move_dir == ImGuiDir_Up || move_dir == ImGuiDir_Down) && preferred_pos_rel.x != FLT_MAX) r.Min.x = r.Max.x = preferred_pos_rel.x + rel_to_abs_offset.x; else if ((move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right) && preferred_pos_rel.y != FLT_MAX) r.Min.y = r.Max.y = preferred_pos_rel.y + rel_to_abs_offset.y; } + void ImGui::NavUpdateCreateMoveRequest() { ImGuiContext& g = *GImGui; @@ -12449,6 +14375,7 @@ void ImGui::NavUpdateCreateMoveRequest() ImGuiWindow* window = g.NavWindow; const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; + if (g.NavMoveForwardToNextFrame && window != NULL) { // Forwarding previous request (which has been modified, e.g. wrap around menus rewrite the requests with a starting rectangle at the other side of the window) @@ -12474,6 +14401,7 @@ void ImGui::NavUpdateCreateMoveRequest() g.NavMoveClipDir = g.NavMoveDir; g.NavScoringNoClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); } + // Update PageUp/PageDown/Home/End scroll // FIXME-NAV: Consider enabling those keys even without the master ImGuiConfigFlags_NavEnableKeyboard flag? float scoring_rect_offset_y = 0.0f; @@ -12484,6 +14412,7 @@ void ImGui::NavUpdateCreateMoveRequest() g.NavScoringNoClipRect = window->InnerRect; g.NavScoringNoClipRect.TranslateY(scoring_rect_offset_y); } + // [DEBUG] Always send a request when holding CTRL. Hold CTRL + Arrow change the direction. #if IMGUI_DEBUG_NAV_SCORING //if (io.KeyCtrl && IsKeyPressed(ImGuiKey_C)) @@ -12496,10 +14425,12 @@ void ImGui::NavUpdateCreateMoveRequest() g.NavMoveFlags |= ImGuiNavMoveFlags_DebugNoResult; } #endif + // Submit g.NavMoveForwardToNextFrame = false; if (g.NavMoveDir != ImGuiDir_None) NavMoveRequestSubmit(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); + // Moving with no reference triggers an init request (will be used as a fallback if the direction fails to find a match) if (g.NavMoveSubmitted && g.NavId == 0) { @@ -12509,6 +14440,7 @@ void ImGui::NavUpdateCreateMoveRequest() if (g.IO.ConfigNavCursorVisibleAuto) g.NavCursorVisible = true; } + // When using gamepad, we project the reference nav bounding box into window visible area. // This is to allow resuming navigation inside the visible area after doing a large amount of scrolling, // since with gamepad all movements are relative (can't focus a visible object like we can with the mouse). @@ -12517,9 +14449,11 @@ void ImGui::NavUpdateCreateMoveRequest() bool clamp_x = (g.NavMoveFlags & (ImGuiNavMoveFlags_LoopX | ImGuiNavMoveFlags_WrapX)) == 0; bool clamp_y = (g.NavMoveFlags & (ImGuiNavMoveFlags_LoopY | ImGuiNavMoveFlags_WrapY)) == 0; ImRect inner_rect_rel = WindowRectAbsToRel(window, ImRect(window->InnerRect.Min - ImVec2(1, 1), window->InnerRect.Max + ImVec2(1, 1))); + // Take account of changing scroll to handle triggering a new move request on a scrolling frame. (#6171) // Otherwise 'inner_rect_rel' would be off on the move result frame. inner_rect_rel.Translate(CalcNextScrollFromScrollTargetAndClamp(window) - window->Scroll); + if ((clamp_x || clamp_y) && !inner_rect_rel.Contains(window->NavRectRel[g.NavLayer])) { IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: clamp NavRectRel for gamepad move\n"); @@ -12533,6 +14467,8 @@ void ImGui::NavUpdateCreateMoveRequest() g.NavId = 0; } } + + // Prepare scoring rectangle. // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items) ImRect scoring_rect; if (window != NULL) @@ -12547,8 +14483,9 @@ void ImGui::NavUpdateCreateMoveRequest() //if (!g.NavScoringNoClipRect.IsInverted()) { GetForegroundDrawList()->AddRect(g.NavScoringNoClipRect.Min, g.NavScoringNoClipRect.Max, IM_COL32(255, 200, 0, 255)); } // [DEBUG] } g.NavScoringRect = scoring_rect; - g.NavScoringNoClipRect.Add(scoring_rect); + //g.NavScoringNoClipRect.Add(scoring_rect); } + void ImGui::NavUpdateCreateTabbingRequest() { ImGuiContext& g = *GImGui; @@ -12556,9 +14493,11 @@ void ImGui::NavUpdateCreateTabbingRequest() IM_ASSERT(g.NavMoveDir == ImGuiDir_None); if (window == NULL || g.NavWindowingTarget != NULL || (window->Flags & ImGuiWindowFlags_NoNavInputs)) return; + const bool tab_pressed = IsKeyPressed(ImGuiKey_Tab, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner) && !g.IO.KeyCtrl && !g.IO.KeyAlt; if (!tab_pressed) return; + // Initiate tabbing request // (this is ALWAYS ENABLED, regardless of ImGuiConfigFlags_NavEnableKeyboard flag!) // See NavProcessItemForTabbingRequest() for a description of the various forward/backward tabbing cases with and without wrapping. @@ -12573,6 +14512,7 @@ void ImGui::NavUpdateCreateTabbingRequest() NavMoveRequestSubmit(ImGuiDir_None, clip_dir, move_flags, scroll_flags); // FIXME-NAV: Once we refactor tabbing, add LegacyApi flag to not activate non-inputable. g.NavTabbingCounter = -1; } + // Apply result from previous frame navigation directional move request. Always called from NavUpdate() void ImGui::NavMoveRequestApplyResult() { @@ -12581,12 +14521,15 @@ void ImGui::NavMoveRequestApplyResult() if (g.NavMoveFlags & ImGuiNavMoveFlags_DebugNoResult) // [DEBUG] Scoring all items in NavWindow at all times return; #endif + // Select which result to use ImGuiNavItemData* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : (g.NavMoveResultOther.ID != 0) ? &g.NavMoveResultOther : NULL; + // Tabbing forward wrap if ((g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && result == NULL) if ((g.NavTabbingCounter == 1 || g.NavTabbingDir == 0) && g.NavTabbingResultFirst.ID) result = &g.NavTabbingResultFirst; + // In a situation when there are no results but NavId != 0, re-enable the Navigation highlight (because g.NavId is not considered as a possible result) const ImGuiAxis axis = (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X; if (result == NULL) @@ -12599,20 +14542,24 @@ void ImGui::NavMoveRequestApplyResult() IMGUI_DEBUG_LOG_NAV("[nav] NavMoveSubmitted but not led to a result!\n"); return; } + // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page. if (g.NavMoveFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) if (g.NavMoveResultLocalVisible.ID != 0 && g.NavMoveResultLocalVisible.ID != g.NavId) result = &g.NavMoveResultLocalVisible; + // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules. if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow) if ((g.NavMoveResultOther.DistBox < result->DistBox) || (g.NavMoveResultOther.DistBox == result->DistBox && g.NavMoveResultOther.DistCenter < result->DistCenter)) result = &g.NavMoveResultOther; IM_ASSERT(g.NavWindow && result->Window); + // Scroll to keep newly navigated item fully into view. if (g.NavLayer == ImGuiNavLayer_Main) { ImRect rect_abs = WindowRectRelToAbs(result->Window, result->RectRel); ScrollToRectEx(result->Window, rect_abs, g.NavMoveScrollFlags); + if (g.NavMoveFlags & ImGuiNavMoveFlags_ScrollToEdgeY) { // FIXME: Should remove this? Or make more precise: use ScrollToRectEx() with edge? @@ -12620,17 +14567,20 @@ void ImGui::NavMoveRequestApplyResult() SetScrollY(result->Window, scroll_target); } } + if (g.NavWindow != result->Window) { IMGUI_DEBUG_LOG_FOCUS("[focus] NavMoveRequest: SetNavWindow(\"%s\")\n", result->Window->Name); g.NavWindow = result->Window; g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; } + // Clear active id unless requested not to // FIXME: ImGuiNavMoveFlags_NoClearActiveId is currently unused as we don't have a clear strategy to preserve active id after interaction, // so this is mostly provided as a gateway for further experiments (see #1418, #2890) if (g.ActiveId != result->ID && (g.NavMoveFlags & ImGuiNavMoveFlags_NoClearActiveId) == 0) ClearActiveID(); + // Don't set NavJustMovedToId if just landed on the same spot (which may happen with ImGuiNavMoveFlags_AllowCurrentNavId) // PageUp/PageDown however sets always set NavJustMovedTo (vs Home/End which doesn't) mimicking Windows behavior. if ((g.NavId != result->ID || (g.NavMoveFlags & ImGuiNavMoveFlags_IsPageMove)) && (g.NavMoveFlags & ImGuiNavMoveFlags_NoSelect) == 0) @@ -12643,12 +14593,14 @@ void ImGui::NavMoveRequestApplyResult() g.NavJustMovedToHasSelectionData = (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) != 0; //IMGUI_DEBUG_LOG_NAV("[nav] NavJustMovedFromFocusScopeId = 0x%08X, NavJustMovedToFocusScopeId = 0x%08X\n", g.NavJustMovedFromFocusScopeId, g.NavJustMovedToFocusScopeId); } + // Apply new NavID/Focus IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: result NavID 0x%08X in Layer %d Window \"%s\"\n", result->ID, g.NavLayer, g.NavWindow->Name); ImVec2 preferred_scoring_pos_rel = g.NavWindow->RootWindowForNav->NavPreferredScoringPosRel[g.NavLayer]; SetNavID(result->ID, g.NavLayer, result->FocusScopeId, result->RectRel); if (result->SelectionUserData != ImGuiSelectionUserData_Invalid) g.NavLastValidSelectionUserData = result->SelectionUserData; + // Restore last preferred position for current axis // (storing in RootWindowForNav-> as the info is desirable at the beginning of a Move Request. In theory all storage should use RootWindowForNav..) if ((g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) == 0) @@ -12656,9 +14608,11 @@ void ImGui::NavMoveRequestApplyResult() preferred_scoring_pos_rel[axis] = result->RectRel.GetCenter()[axis]; g.NavWindow->RootWindowForNav->NavPreferredScoringPosRel[g.NavLayer] = preferred_scoring_pos_rel; } + // Tabbing: Activates Inputable, otherwise only Focus if ((g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && (result->ItemFlags & ImGuiItemFlags_Inputable) == 0) g.NavMoveFlags &= ~ImGuiNavMoveFlags_Activate; + // Activate if (g.NavMoveFlags & ImGuiNavMoveFlags_Activate) { @@ -12667,10 +14621,12 @@ void ImGui::NavMoveRequestApplyResult() if (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) g.NavNextActivateFlags |= ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_TryToPreserveState | ImGuiActivateFlags_FromTabbing; } + // Make nav cursor visible if ((g.NavMoveFlags & ImGuiNavMoveFlags_NoSetNavCursorVisible) == 0) SetNavCursorVisibleAfterMove(); } + // Process Escape/NavCancel input (to close a popup, get back to parent, clear focus) // FIXME: In order to support e.g. Escape to clear a selection we'll need: // - either to store the equivalent of ActiveIdUsingKeyInputMask for a FocusScope and test for it. @@ -12682,6 +14638,7 @@ static void ImGui::NavUpdateCancelRequest() const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; if (!(nav_keyboard_active && IsKeyPressed(ImGuiKey_Escape, 0, ImGuiKeyOwner_NoOwner)) && !(nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, 0, ImGuiKeyOwner_NoOwner))) return; + IMGUI_DEBUG_LOG_NAV("[nav] NavUpdateCancelRequest()\n"); if (g.ActiveId != 0) { @@ -12715,6 +14672,7 @@ static void ImGui::NavUpdateCancelRequest() if (g.IO.ConfigNavEscapeClearFocusItem || g.IO.ConfigNavEscapeClearFocusWindow) if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup)))// || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) g.NavWindow->NavLastIds[0] = 0; + // Clear nav focus if (g.IO.ConfigNavEscapeClearFocusItem || g.IO.ConfigNavEscapeClearFocusWindow) g.NavId = 0; @@ -12722,6 +14680,7 @@ static void ImGui::NavUpdateCancelRequest() FocusWindow(NULL); } } + // Handle PageUp/PageDown/Home/End keys // Called from NavUpdateCreateMoveRequest() which will use our output to create a move request // FIXME-NAV: This doesn't work properly with NavFlattened siblings as we use NavWindow rectangle for reference @@ -12732,15 +14691,18 @@ static float ImGui::NavUpdatePageUpPageDown() ImGuiWindow* window = g.NavWindow; if ((window->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL) return 0.0f; + const bool page_up_held = IsKeyDown(ImGuiKey_PageUp, ImGuiKeyOwner_NoOwner); const bool page_down_held = IsKeyDown(ImGuiKey_PageDown, ImGuiKeyOwner_NoOwner); const bool home_pressed = IsKeyPressed(ImGuiKey_Home, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner); const bool end_pressed = IsKeyPressed(ImGuiKey_End, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner); if (page_up_held == page_down_held && home_pressed == end_pressed) // Proceed if either (not both) are pressed, otherwise early out return 0.0f; + if (g.NavLayer != ImGuiNavLayer_Main) NavRestoreLayer(ImGuiNavLayer_Main); - if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavWindowHasScrollY) + + if ((window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Main)) == 0 && window->DC.NavWindowHasScrollY) { // Fallback manual-scroll when window has no navigable item if (IsKeyPressed(ImGuiKey_PageUp, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner)) @@ -12796,25 +14758,31 @@ static float ImGui::NavUpdatePageUpPageDown() } return 0.0f; } + static void ImGui::NavEndFrame() { ImGuiContext& g = *GImGui; + // Show CTRL+TAB list window if (g.NavWindowingTarget != NULL) NavUpdateWindowingOverlay(); + // Perform wrap-around in menus // FIXME-NAV: Wrap may need to apply a weight bias on the other axis. e.g. 4x4 grid with 2 last items missing on last item won't handle LoopY/WrapY correctly. // FIXME-NAV: Wrap (not Loop) support could be handled by the scoring function and then WrapX would function without an extra frame. if (g.NavWindow && NavMoveRequestButNoResultYet() && (g.NavMoveFlags & ImGuiNavMoveFlags_WrapMask_) && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0) NavUpdateCreateWrappingRequest(); } + static void ImGui::NavUpdateCreateWrappingRequest() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.NavWindow; + bool do_forward = false; ImRect bb_rel = window->NavRectRel[g.NavLayer]; ImGuiDir clip_dir = g.NavMoveDir; + const ImGuiNavMoveFlags move_flags = g.NavMoveFlags; //const ImGuiAxis move_axis = (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X; if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) @@ -12864,6 +14832,7 @@ static void ImGui::NavUpdateCreateWrappingRequest() NavClearPreferredPosForAxis(ImGuiAxis_Y); NavMoveRequestForward(g.NavMoveDir, clip_dir, move_flags, g.NavMoveScrollFlags); } + // Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext) // Note that NoNavFocus makes the window not reachable with CTRL+TAB but it can still be focused with mouse or programmatically. // If you want a window to never be focused, you may use the e.g. NoInputs flag. @@ -12871,6 +14840,7 @@ bool ImGui::IsWindowNavFocusable(ImGuiWindow* window) { return window->WasActive && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus); } + static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N) { ImGuiContext& g = *GImGui; @@ -12879,12 +14849,14 @@ static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // return g.WindowsFocusOrder[i]; return NULL; } + static void NavUpdateWindowingTarget(int focus_change_dir) { ImGuiContext& g = *GImGui; IM_ASSERT(g.NavWindowingTarget); if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal) return; + const int i_current = ImGui::FindWindowFocusIndex(g.NavWindowingTarget); ImGuiWindow* window_target = FindWindowNavFocusable(i_current + focus_change_dir, -INT_MAX, focus_change_dir); if (!window_target) @@ -12896,6 +14868,7 @@ static void NavUpdateWindowingTarget(int focus_change_dir) } g.NavWindowingToggleLayer = false; } + // Apply focus and close overlay static void ImGui::NavUpdateWindowingApplyFocus(ImGuiWindow* apply_focus_window) { @@ -12909,9 +14882,11 @@ static void ImGui::NavUpdateWindowingApplyFocus(ImGuiWindow* apply_focus_window) SetNavCursorVisibleAfterMove(); ClosePopupsOverWindow(apply_focus_window, false); FocusWindow(apply_focus_window, ImGuiFocusRequestFlags_RestoreFocusedChild); + IM_ASSERT(g.NavWindow != NULL); apply_focus_window = g.NavWindow; if (apply_focus_window->NavLastIds[0] == 0) NavInitWindow(apply_focus_window, false); + // If the window has ONLY a menu layer (no main layer), select it directly // Use NavLayersActiveMaskNext since windows didn't have a chance to be Begin()-ed on this frame, // so CTRL+Tab where the keys are only held for 1 frame will be able to use correct layers mask since @@ -12921,12 +14896,14 @@ static void ImGui::NavUpdateWindowingApplyFocus(ImGuiWindow* apply_focus_window) // won't be valid. if (apply_focus_window->DC.NavLayersActiveMaskNext == (1 << ImGuiNavLayer_Menu)) g.NavLayer = ImGuiNavLayer_Menu; + // Request OS level focus if (apply_focus_window->Viewport != previous_viewport && g.PlatformIO.Platform_SetWindowFocus) g.PlatformIO.Platform_SetWindowFocus(apply_focus_window->Viewport); } g.NavWindowingTarget = NULL; } + // Windowing management mode // Keyboard: CTRL+Tab (change focus/move/resize), Alt (toggle menu layer) // Gamepad: Hold Menu/Square (change focus/move/resize), Tap Menu/Square (toggle menu layer) @@ -12934,12 +14911,15 @@ static void ImGui::NavUpdateWindowing() { ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; + ImGuiWindow* apply_focus_window = NULL; bool apply_toggle_layer = false; + ImGuiWindow* modal_window = GetTopMostPopupModal(); bool allow_windowing = (modal_window == NULL); // FIXME: This prevent CTRL+TAB from being usable with windows that are inside the Begin-stack of that modal. if (!allow_windowing) g.NavWindowingTarget = NULL; + // Fade out if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL) { @@ -12947,6 +14927,7 @@ static void ImGui::NavUpdateWindowing() if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f) g.NavWindowingTargetAnim = NULL; } + // Start CTRL+Tab or Square+L/R window selection // (g.ConfigNavWindowingKeyNext/g.ConfigNavWindowingKeyPrev defaults are ImGuiMod_Ctrl|ImGuiKey_Tab and ImGuiMod_Ctrl|ImGuiMod_Shift|ImGuiKey_Tab) const ImGuiID owner_id = ImHashStr("##NavUpdateWindowing"); @@ -12954,36 +14935,51 @@ static void ImGui::NavUpdateWindowing() const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; const bool keyboard_next_window = allow_windowing && g.ConfigNavWindowingKeyNext && Shortcut(g.ConfigNavWindowingKeyNext, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways, owner_id); const bool keyboard_prev_window = allow_windowing && g.ConfigNavWindowingKeyPrev && Shortcut(g.ConfigNavWindowingKeyPrev, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways, owner_id); - const bool start_windowing_with_gamepad = allow_windowing && nav_gamepad_active && !g.NavWindowingTarget && IsKeyPressed(ImGuiKey_NavGamepadMenu, ImGuiInputFlags_None); + const bool start_toggling_with_gamepad = nav_gamepad_active && !g.NavWindowingTarget && Shortcut(ImGuiKey_NavGamepadMenu, ImGuiInputFlags_RouteAlways, owner_id); + const bool start_windowing_with_gamepad = allow_windowing && start_toggling_with_gamepad; const bool start_windowing_with_keyboard = allow_windowing && !g.NavWindowingTarget && (keyboard_next_window || keyboard_prev_window); // Note: enabled even without NavEnableKeyboard! bool just_started_windowing_from_null_focus = false; + if (start_toggling_with_gamepad) + { + g.NavWindowingToggleLayer = true; // Gamepad starts toggling layer + g.NavWindowingToggleKey = ImGuiKey_NavGamepadMenu; + g.NavWindowingInputSource = g.NavInputSource = ImGuiInputSource_Gamepad; + } if (start_windowing_with_gamepad || start_windowing_with_keyboard) if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.WindowsFocusOrder.Size - 1, -INT_MAX, -1)) { - g.NavWindowingTarget = g.NavWindowingTargetAnim = window->RootWindow; // Current location + if (start_windowing_with_keyboard || g.ConfigNavWindowingWithGamepad) + g.NavWindowingTarget = g.NavWindowingTargetAnim = window->RootWindow; // Current location g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f; g.NavWindowingAccumDeltaPos = g.NavWindowingAccumDeltaSize = ImVec2(0.0f, 0.0f); - g.NavWindowingToggleLayer = start_windowing_with_gamepad ? true : false; // Gamepad starts toggling layer - g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_Keyboard : ImGuiInputSource_Gamepad; + g.NavWindowingInputSource = g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_Keyboard : ImGuiInputSource_Gamepad; if (g.NavWindow == NULL) just_started_windowing_from_null_focus = true; + // Manually register ownership of our mods. Using a global route in the Shortcut() calls instead would probably be correct but may have more side-effects. if (keyboard_next_window || keyboard_prev_window) SetKeyOwnersForKeyChord((g.ConfigNavWindowingKeyNext | g.ConfigNavWindowingKeyPrev) & ImGuiMod_Mask_, owner_id); } + // Gamepad update - g.NavWindowingTimer += io.DeltaTime; - if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Gamepad) + if ((g.NavWindowingTarget || g.NavWindowingToggleLayer) && g.NavWindowingInputSource == ImGuiInputSource_Gamepad) { - // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise - g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); - // Select window to focus - const int focus_change_dir = (int)IsKeyPressed(ImGuiKey_GamepadL1) - (int)IsKeyPressed(ImGuiKey_GamepadR1); - if (focus_change_dir != 0 && !just_started_windowing_from_null_focus) + if (g.NavWindowingTarget != NULL) { - NavUpdateWindowingTarget(focus_change_dir); - g.NavWindowingHighlightAlpha = 1.0f; + // Highlight only appears after a brief time holding the button, so that a fast tap on ImGuiKey_NavGamepadMenu (to toggle NavLayer) doesn't add visual noise + // However inputs are accepted immediately, so you press ImGuiKey_NavGamepadMenu + L1/R1 fast. + g.NavWindowingTimer += io.DeltaTime; + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); + + // Select window to focus + const int focus_change_dir = (int)IsKeyPressed(ImGuiKey_GamepadL1) - (int)IsKeyPressed(ImGuiKey_GamepadR1); + if (focus_change_dir != 0 && !just_started_windowing_from_null_focus) + { + NavUpdateWindowingTarget(focus_change_dir); + g.NavWindowingHighlightAlpha = 1.0f; + } } + // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered top-most) if (!IsKeyDown(ImGuiKey_NavGamepadMenu)) { @@ -12993,20 +14989,24 @@ static void ImGui::NavUpdateWindowing() else if (!g.NavWindowingToggleLayer) apply_focus_window = g.NavWindowingTarget; g.NavWindowingTarget = NULL; + g.NavWindowingToggleLayer = false; } } + // Keyboard: Focus - if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Keyboard) + if (g.NavWindowingTarget && g.NavWindowingInputSource == ImGuiInputSource_Keyboard) { // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise ImGuiKeyChord shared_mods = ((g.ConfigNavWindowingKeyNext ? g.ConfigNavWindowingKeyNext : ImGuiMod_Mask_) & (g.ConfigNavWindowingKeyPrev ? g.ConfigNavWindowingKeyPrev : ImGuiMod_Mask_)) & ImGuiMod_Mask_; IM_ASSERT(shared_mods != 0); // Next/Prev shortcut currently needs a shared modifier to "hold", otherwise Prev actions would keep cycling between two windows. + g.NavWindowingTimer += io.DeltaTime; g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f if ((keyboard_next_window || keyboard_prev_window) && !just_started_windowing_from_null_focus) NavUpdateWindowingTarget(keyboard_next_window ? -1 : +1); else if ((io.KeyMods & shared_mods) != shared_mods) apply_focus_window = g.NavWindowingTarget; } + // Keyboard: Press and Release ALT to toggle menu layer const ImGuiKey windowing_toggle_keys[] = { ImGuiKey_LeftAlt, ImGuiKey_RightAlt }; bool windowing_toggle_layer_start = false; @@ -13017,10 +15017,10 @@ static void ImGui::NavUpdateWindowing() windowing_toggle_layer_start = true; g.NavWindowingToggleLayer = true; g.NavWindowingToggleKey = windowing_toggle_key; - g.NavInputSource = ImGuiInputSource_Keyboard; + g.NavWindowingInputSource = g.NavInputSource = ImGuiInputSource_Keyboard; break; } - if (g.NavWindowingToggleLayer && g.NavInputSource == ImGuiInputSource_Keyboard) + if (g.NavWindowingToggleLayer && g.NavWindowingInputSource == ImGuiInputSource_Keyboard) { // We cancel toggling nav layer when any text has been typed (generally while holding Alt). (See #370) // We cancel toggling nav layer when other modifiers are pressed. (See #4439) @@ -13032,6 +15032,7 @@ static void ImGui::NavUpdateWindowing() g.NavWindowingToggleLayer = false; else if (TestKeyOwner(g.NavWindowingToggleKey, ImGuiKeyOwner_NoOwner) == false || TestKeyOwner(ImGuiMod_Alt, ImGuiKeyOwner_NoOwner) == false) g.NavWindowingToggleLayer = false; + // Apply layer toggle on Alt release // Important: as before version <18314 we lacked an explicit IO event for focus gain/loss, we also compare mouse validity to detect old backends clearing mouse pos on focus loss. if (IsKeyReleased(g.NavWindowingToggleKey) && g.NavWindowingToggleLayer) @@ -13041,6 +15042,7 @@ static void ImGui::NavUpdateWindowing() if (!IsKeyDown(g.NavWindowingToggleKey)) g.NavWindowingToggleLayer = false; } + // Move window if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove)) { @@ -13064,13 +15066,16 @@ static void ImGui::NavUpdateWindowing() } } } + // Apply final focus if (apply_focus_window) NavUpdateWindowingApplyFocus(apply_focus_window); + // Apply menu/layer toggle if (apply_toggle_layer && g.NavWindow) { ClearActiveID(); + // Move to parent menu if necessary ImGuiWindow* new_nav_window = g.NavWindow; while (new_nav_window->ParentWindow @@ -13084,6 +15089,7 @@ static void ImGui::NavUpdateWindowing() FocusWindow(new_nav_window); new_nav_window->NavLastChildNavWindow = old_nav_window; } + // Toggle layer const ImGuiNavLayer new_nav_layer = (g.NavWindow->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) ? (ImGuiNavLayer)((int)g.NavLayer ^ 1) : ImGuiNavLayer_Main; if (new_nav_layer != g.NavLayer) @@ -13097,6 +15103,7 @@ static void ImGui::NavUpdateWindowing() } } } + // Window has already passed the IsWindowNavFocusable() static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window) { @@ -13108,13 +15115,16 @@ static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window) return "(Dock node)"; // Not normally shown to user. return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingUntitled); } + // Overlay displayed when using CTRL+TAB. Called by EndFrame(). void ImGui::NavUpdateWindowingOverlay() { ImGuiContext& g = *GImGui; IM_ASSERT(g.NavWindowingTarget != NULL); + if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY) return; + if (g.NavWindowingListWindow == NULL) g.NavWindowingListWindow = FindWindowByName("##NavWindowingOverlay"); const ImGuiViewport* viewport = /*g.NavWindow ? g.NavWindow->Viewport :*/ GetMainViewport(); @@ -13138,14 +15148,17 @@ void ImGui::NavUpdateWindowingOverlay() End(); PopStyleVar(); } + //----------------------------------------------------------------------------- // [SECTION] DRAG AND DROP //----------------------------------------------------------------------------- + bool ImGui::IsDragDropActive() { ImGuiContext& g = *GImGui; return g.DragDropActive; } + void ImGui::ClearDragDrop() { ImGuiContext& g = *GImGui; @@ -13157,9 +15170,11 @@ void ImGui::ClearDragDrop() g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0; g.DragDropAcceptIdCurrRectSurface = FLT_MAX; g.DragDropAcceptFrameCount = -1; + g.DragDropPayloadBufHeap.clear(); memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); } + bool ImGui::BeginTooltipHidden() { ImGuiContext& g = *GImGui; @@ -13167,6 +15182,7 @@ bool ImGui::BeginTooltipHidden() SetWindowHiddenAndSkipItemsForCurrentFrame(g.CurrentWindow); return ret; } + // When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() // If the item has an identifier: // - This assume/require the item to be activated (typically via ButtonBehavior). @@ -13178,9 +15194,11 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + // FIXME-DRAGDROP: While in the common-most "drag from non-zero active id" case we can tell the mouse button, // in both SourceExtern and id==0 cases we may requires something else (explicit flags or some heuristic). ImGuiMouseButton mouse_button = ImGuiMouseButton_Left; + bool source_drag_active = false; ImGuiID source_id = 0; ImGuiID source_parent_id = 0; @@ -13205,6 +15223,7 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) return false; if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) == 0 && (g.ActiveId == 0 || g.ActiveIdWindow != window)) return false; + // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to: // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag. if (!(flags & ImGuiDragDropFlags_SourceAllowNullID)) @@ -13212,6 +15231,7 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) IM_ASSERT(0); return false; } + // Magic fallback to handle items with no assigned ID, e.g. Text(), Image() // We build a throwaway ID based on current ID stack + relative AABB of items in window. // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING/RESIZINGG OF THE WIDGET, so if your widget moves your dragging operation will be canceled. @@ -13232,6 +15252,7 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) return false; source_parent_id = window->IDStack.back(); source_drag_active = IsMouseDragging(mouse_button); + // Disable navigation and key inputs while dragging + cancel existing request if any SetActiveIdUsingAllKeyboardKeys(); } @@ -13245,9 +15266,11 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) KeepAliveID(source_id); SetActiveID(source_id, NULL); } + IM_ASSERT(g.DragDropWithinTarget == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget() if (!source_drag_active) return false; + // Activate drag and drop if (!g.DragDropActive) { @@ -13266,6 +15289,7 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) } g.DragDropSourceFrameCount = g.FrameCount; g.DragDropWithinSource = true; + if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) { // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request explicit) @@ -13278,22 +15302,28 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) IM_ASSERT(ret); // FIXME-NEWBEGIN: If this ever becomes false, we need to Begin("##Hidden", NULL, ImGuiWindowFlags_NoSavedSettings) + SetWindowHiddendAndSkipItemsForCurrentFrame(). IM_UNUSED(ret); } + if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern)) g.LastItemData.StatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; + return true; } + void ImGui::EndDragDropSource() { ImGuiContext& g = *GImGui; IM_ASSERT(g.DragDropActive); IM_ASSERT(g.DragDropWithinSource && "Not after a BeginDragDropSource()?"); + if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) EndTooltip(); + // Discard the drag if have not called SetDragDropPayload() if (g.DragDropPayload.DataFrameCount == -1) ClearDragDrop(); g.DragDropWithinSource = false; } + // Use 'cond' to choose to submit payload on drag start or every frame bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond) { @@ -13301,11 +15331,13 @@ bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_s ImGuiPayload& payload = g.DragDropPayload; if (cond == 0) cond = ImGuiCond_Always; + IM_ASSERT(type != NULL); IM_ASSERT(ImStrlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long"); IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0)); IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once); IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() + if (cond == ImGuiCond_Always || payload.DataFrameCount == -1) { // Copy payload @@ -13332,14 +15364,17 @@ bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_s payload.DataSize = (int)data_size; } payload.DataFrameCount = g.FrameCount; + // Return whether the payload has been accepted return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1); } + bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) { ImGuiContext& g = *GImGui; if (!g.DragDropActive) return false; + ImGuiWindow* window = g.CurrentWindow; ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow; if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree) @@ -13349,6 +15384,7 @@ bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) return false; if (window->SkipItems) return false; + IM_ASSERT(g.DragDropWithinTarget == false && g.DragDropWithinSource == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget() g.DragDropTargetRect = bb; g.DragDropTargetClipRect = window->ClipRect; // May want to be overridden by user depending on use case? @@ -13356,6 +15392,7 @@ bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) g.DragDropWithinTarget = true; return true; } + // We don't use BeginDragDropTargetCustom() and duplicate its code because: // 1) we use LastItemData's ImGuiItemStatusFlags_HoveredRect which handles items that push a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them. // 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can. @@ -13365,12 +15402,14 @@ bool ImGui::BeginDragDropTarget() ImGuiContext& g = *GImGui; if (!g.DragDropActive) return false; + ImGuiWindow* window = g.CurrentWindow; if (!(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect)) return false; ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow; if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree || window->SkipItems) return false; + const ImRect& display_rect = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? g.LastItemData.DisplayRect : g.LastItemData.Rect; ImGuiID id = g.LastItemData.ID; if (id == 0) @@ -13380,6 +15419,7 @@ bool ImGui::BeginDragDropTarget() } if (g.DragDropPayload.SourceId == id) return false; + IM_ASSERT(g.DragDropWithinTarget == false && g.DragDropWithinSource == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget() g.DragDropTargetRect = display_rect; g.DragDropTargetClipRect = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasClipRect) ? g.LastItemData.ClipRect : window->ClipRect; @@ -13387,11 +15427,13 @@ bool ImGui::BeginDragDropTarget() g.DragDropWithinTarget = true; return true; } + bool ImGui::IsDragDropPayloadBeingAccepted() { ImGuiContext& g = *GImGui; return g.DragDropActive && g.DragDropAcceptIdPrev != 0; } + const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags) { ImGuiContext& g = *GImGui; @@ -13400,6 +15442,7 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ? if (type != NULL && !payload.IsDataType(type)) return NULL; + // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints. // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function! const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId); @@ -13407,15 +15450,18 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop float r_surface = r.GetWidth() * r.GetHeight(); if (r_surface > g.DragDropAcceptIdCurrRectSurface) return NULL; + g.DragDropAcceptFlags = flags; g.DragDropAcceptIdCurr = g.DragDropTargetId; g.DragDropAcceptIdCurrRectSurface = r_surface; //IMGUI_DEBUG_LOG("AcceptDragDropPayload(): %08X: accept\n", g.DragDropTargetId); + // Render default drop visuals payload.Preview = was_accepted_previously; flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that live for 1 frame) if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview) RenderDragDropTargetRect(r, g.DragDropTargetClipRect); + g.DragDropAcceptFrameCount = g.FrameCount; if ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) && g.DragDropMouseButton == -1) payload.Delivery = was_accepted_previously && (g.DragDropSourceFrameCount < g.FrameCount); @@ -13423,10 +15469,12 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting OS window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery)) return NULL; + if (payload.Delivery) IMGUI_DEBUG_LOG_ACTIVEID("[dragdrop] AcceptDragDropPayload(): 0x%08X: payload delivery\n", g.DragDropTargetId); return &payload; } + // FIXME-STYLE FIXME-DRAGDROP: Settle on a proper default visuals for drop target. void ImGui::RenderDragDropTargetRect(const ImRect& bb, const ImRect& item_clip_rect) { @@ -13438,31 +15486,36 @@ void ImGui::RenderDragDropTargetRect(const ImRect& bb, const ImRect& item_clip_r bool push_clip_rect = !window->ClipRect.Contains(bb_display); if (push_clip_rect) window->DrawList->PushClipRectFullScreen(); - window->DrawList->AddRect(bb_display.Min, bb_display.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f); + window->DrawList->AddRect(bb_display.Min, bb_display.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f); // FIXME-DPI if (push_clip_rect) window->DrawList->PopClipRect(); } + const ImGuiPayload* ImGui::GetDragDropPayload() { ImGuiContext& g = *GImGui; return (g.DragDropActive && g.DragDropPayload.DataFrameCount != -1) ? &g.DragDropPayload : NULL; } + void ImGui::EndDragDropTarget() { ImGuiContext& g = *GImGui; IM_ASSERT(g.DragDropActive); IM_ASSERT(g.DragDropWithinTarget); g.DragDropWithinTarget = false; + // Clear drag and drop state payload right after delivery if (g.DragDropPayload.Delivery) ClearDragDrop(); } + //----------------------------------------------------------------------------- // [SECTION] LOGGING/CAPTURING //----------------------------------------------------------------------------- // All text output from the interface can be captured into tty/file/clipboard. // By default, tree nodes are automatically opened during logging. //----------------------------------------------------------------------------- + // Pass text data straight to log (without being displayed) static inline void LogTextV(ImGuiContext& g, const char* fmt, va_list args) { @@ -13477,23 +15530,28 @@ static inline void LogTextV(ImGuiContext& g, const char* fmt, va_list args) g.LogBuffer.appendfv(fmt, args); } } + void ImGui::LogText(const char* fmt, ...) { ImGuiContext& g = *GImGui; if (!g.LogEnabled) return; + va_list args; va_start(args, fmt); LogTextV(g, fmt, args); va_end(args); } + void ImGui::LogTextV(const char* fmt, va_list args) { ImGuiContext& g = *GImGui; if (!g.LogEnabled) return; + LogTextV(g, fmt, args); } + // Internal version that takes a position to decide on newline placement and pad items according to their depth. // We split text into individual lines to add current tree level padding // FIXME: This code is a little complicated perhaps, considering simplifying the whole system. @@ -13501,11 +15559,14 @@ void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + const char* prefix = g.LogNextPrefix; const char* suffix = g.LogNextSuffix; g.LogNextPrefix = g.LogNextSuffix = NULL; + if (!text_end) text_end = FindRenderedTextEnd(text, text_end); + const bool log_new_line = ref_pos && (ref_pos->y > g.LogLinePosY + g.Style.FramePadding.y + 1); if (ref_pos) g.LogLinePosY = ref_pos->y; @@ -13514,12 +15575,15 @@ void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* LogText(IM_NEWLINE); g.LogLineFirstItem = true; } + if (prefix) LogRenderedText(ref_pos, prefix, prefix + ImStrlen(prefix)); // Calculate end ourself to ensure "##" are included here. + // Re-adjust padding if we have popped out of our starting depth if (g.LogDepthRef > window->DC.TreeDepth) g.LogDepthRef = window->DC.TreeDepth; const int tree_depth = (window->DC.TreeDepth - g.LogDepthRef); + const char* text_remaining = text; for (;;) { @@ -13544,9 +15608,11 @@ void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* break; text_remaining = line_end + 1; } + if (suffix) LogRenderedText(ref_pos, suffix, suffix + ImStrlen(suffix)); } + // Start logging/capturing text output void ImGui::LogBegin(ImGuiLogFlags flags, int auto_open_depth) { @@ -13555,6 +15621,7 @@ void ImGui::LogBegin(ImGuiLogFlags flags, int auto_open_depth) IM_ASSERT(g.LogEnabled == false); IM_ASSERT(g.LogFile == NULL && g.LogBuffer.empty()); IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiLogFlags_OutputMask_)); // Check that only 1 type flag is used + g.LogEnabled = g.ItemUnclipByLog = true; g.LogFlags = flags; g.LogWindow = window; @@ -13564,6 +15631,7 @@ void ImGui::LogBegin(ImGuiLogFlags flags, int auto_open_depth) g.LogLinePosY = FLT_MAX; g.LogLineFirstItem = true; } + // Important: doesn't copy underlying data, use carefully (prefix/suffix must be in scope at the time of the next LogRenderedText) void ImGui::LogSetNextTextDecoration(const char* prefix, const char* suffix) { @@ -13571,6 +15639,7 @@ void ImGui::LogSetNextTextDecoration(const char* prefix, const char* suffix) g.LogNextPrefix = prefix; g.LogNextSuffix = suffix; } + void ImGui::LogToTTY(int auto_open_depth) { ImGuiContext& g = *GImGui; @@ -13582,12 +15651,14 @@ void ImGui::LogToTTY(int auto_open_depth) g.LogFile = stdout; #endif } + // Start logging/capturing text output to given file void ImGui::LogToFile(int auto_open_depth, const char* filename) { ImGuiContext& g = *GImGui; if (g.LogEnabled) return; + // FIXME: We could probably open the file in text mode "at", however note that clipboard/buffer logging will still // be subject to outputting OS-incompatible carriage return if within strings the user doesn't use IM_NEWLINE. // By opening the file in binary mode "ab" we have consistent output everywhere. @@ -13601,9 +15672,11 @@ void ImGui::LogToFile(int auto_open_depth, const char* filename) IM_ASSERT(0); return; } + LogBegin(ImGuiLogFlags_OutputFile, auto_open_depth); g.LogFile = f; } + // Start logging/capturing text output to clipboard void ImGui::LogToClipboard(int auto_open_depth) { @@ -13612,6 +15685,7 @@ void ImGui::LogToClipboard(int auto_open_depth) return; LogBegin(ImGuiLogFlags_OutputClipboard, auto_open_depth); } + void ImGui::LogToBuffer(int auto_open_depth) { ImGuiContext& g = *GImGui; @@ -13619,11 +15693,13 @@ void ImGui::LogToBuffer(int auto_open_depth) return; LogBegin(ImGuiLogFlags_OutputBuffer, auto_open_depth); } + void ImGui::LogFinish() { ImGuiContext& g = *GImGui; if (!g.LogEnabled) return; + LogText(IM_NEWLINE); switch (g.LogFlags & ImGuiLogFlags_OutputMask_) { @@ -13645,16 +15721,19 @@ void ImGui::LogFinish() IM_ASSERT(0); break; } + g.LogEnabled = g.ItemUnclipByLog = false; g.LogFlags = ImGuiLogFlags_None; g.LogFile = NULL; g.LogBuffer.clear(); } + // Helper to display logging buttons // FIXME-OBSOLETE: We should probably obsolete this and let the user have their own helper (this is one of the oldest function alive!) void ImGui::LogButtons() { ImGuiContext& g = *GImGui; + PushID("LogButtons"); #ifndef IMGUI_DISABLE_TTY_FUNCTIONS const bool log_to_tty = Button("Log To TTY"); SameLine(); @@ -13668,6 +15747,7 @@ void ImGui::LogButtons() SliderInt("Default Depth", &g.LogDepthToExpandDefault, 0, 9, NULL); PopItemFlag(); PopID(); + // Start logging at the end of the function so that the buttons don't appear in the log if (log_to_tty) LogToTTY(); @@ -13676,6 +15756,7 @@ void ImGui::LogButtons() if (log_to_clipboard) LogToClipboard(); } + //----------------------------------------------------------------------------- // [SECTION] SETTINGS //----------------------------------------------------------------------------- @@ -13694,6 +15775,7 @@ void ImGui::LogButtons() // - ClearWindowSettings() [Internal] // - WindowSettingsHandler_***() [Internal] //----------------------------------------------------------------------------- + // Called by NewFrame() void ImGui::UpdateSettings() { @@ -13706,6 +15788,7 @@ void ImGui::UpdateSettings() LoadIniSettingsFromDisk(g.IO.IniFilename); g.SettingsLoaded = true; } + // Save settings (with a delay after the last modification, so we don't spam disk too much) if (g.SettingsDirtyTimer > 0.0f) { @@ -13720,12 +15803,14 @@ void ImGui::UpdateSettings() } } } + void ImGui::MarkIniSettingsDirty() { ImGuiContext& g = *GImGui; if (g.SettingsDirtyTimer <= 0.0f) g.SettingsDirtyTimer = g.IO.IniSavingRate; } + void ImGui::MarkIniSettingsDirty(ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -13733,18 +15818,21 @@ void ImGui::MarkIniSettingsDirty(ImGuiWindow* window) if (g.SettingsDirtyTimer <= 0.0f) g.SettingsDirtyTimer = g.IO.IniSavingRate; } + void ImGui::AddSettingsHandler(const ImGuiSettingsHandler* handler) { ImGuiContext& g = *GImGui; IM_ASSERT(FindSettingsHandler(handler->TypeName) == NULL); g.SettingsHandlers.push_back(*handler); } + void ImGui::RemoveSettingsHandler(const char* type_name) { ImGuiContext& g = *GImGui; if (ImGuiSettingsHandler* handler = FindSettingsHandler(type_name)) g.SettingsHandlers.erase(handler); } + ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name) { ImGuiContext& g = *GImGui; @@ -13754,6 +15842,7 @@ ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name) return &handler; return NULL; } + // Clear all settings (windows, tables, docking etc.) void ImGui::ClearIniSettings() { @@ -13763,6 +15852,7 @@ void ImGui::ClearIniSettings() if (handler.ClearAllFn != NULL) handler.ClearAllFn(&g, &handler); } + void ImGui::LoadIniSettingsFromDisk(const char* ini_filename) { size_t file_data_size = 0; @@ -13773,6 +15863,7 @@ void ImGui::LoadIniSettingsFromDisk(const char* ini_filename) LoadIniSettingsFromMemory(file_data, (size_t)file_data_size); IM_FREE(file_data); } + // Zero-tolerance, no error reporting, cheap .ini parsing // Set ini_size==0 to let us use strlen(ini_data). Do not call this function with a 0 if your buffer is actually empty! void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size) @@ -13781,6 +15872,7 @@ void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size) IM_ASSERT(g.Initialized); //IM_ASSERT(!g.WithinFrameScope && "Cannot be called between NewFrame() and EndFrame()"); //IM_ASSERT(g.SettingsLoaded == false && g.FrameCount == 0); + // For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter). // For our convenience and to make the code simpler, we'll also write zero-terminators within the buffer. So let's create a writable copy.. if (ini_size == 0) @@ -13790,13 +15882,16 @@ void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size) char* const buf_end = buf + ini_size; memcpy(buf, ini_data, ini_size); buf_end[0] = 0; + // Call pre-read handlers // Some types will clear their data (e.g. dock information) some types will allow merge/override (window) for (ImGuiSettingsHandler& handler : g.SettingsHandlers) if (handler.ReadInitFn != NULL) handler.ReadInitFn(&g, &handler); + void* entry_data = NULL; ImGuiSettingsHandler* entry_handler = NULL; + char* line_end = NULL; for (char* line = buf; line < buf_end; line = line_end + 1) { @@ -13831,19 +15926,23 @@ void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size) } } g.SettingsLoaded = true; + // [DEBUG] Restore untouched copy so it can be browsed in Metrics (not strictly necessary) memcpy(buf, ini_data, ini_size); + // Call post-read handlers for (ImGuiSettingsHandler& handler : g.SettingsHandlers) if (handler.ApplyAllFn != NULL) handler.ApplyAllFn(&g, &handler); } + void ImGui::SaveIniSettingsToDisk(const char* ini_filename) { ImGuiContext& g = *GImGui; g.SettingsDirtyTimer = 0.0f; if (!ini_filename) return; + size_t ini_data_size = 0; const char* ini_data = SaveIniSettingsToMemory(&ini_data_size); ImFileHandle f = ImFileOpen(ini_filename, "wt"); @@ -13852,6 +15951,7 @@ void ImGui::SaveIniSettingsToDisk(const char* ini_filename) ImFileWrite(ini_data, sizeof(char), ini_data_size, f); ImFileClose(f); } + // Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text buffer const char* ImGui::SaveIniSettingsToMemory(size_t* out_size) { @@ -13865,9 +15965,11 @@ const char* ImGui::SaveIniSettingsToMemory(size_t* out_size) *out_size = (size_t)g.SettingsIniData.size(); return g.SettingsIniData.c_str(); } + ImGuiWindowSettings* ImGui::CreateNewWindowSettings(const char* name) { ImGuiContext& g = *GImGui; + if (g.IO.ConfigDebugIniSettings == false) { // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() @@ -13876,14 +15978,17 @@ ImGuiWindowSettings* ImGui::CreateNewWindowSettings(const char* name) name = p; } const size_t name_len = ImStrlen(name); + // Allocate chunk const size_t chunk_size = sizeof(ImGuiWindowSettings) + name_len + 1; ImGuiWindowSettings* settings = g.SettingsWindows.alloc_chunk(chunk_size); IM_PLACEMENT_NEW(settings) ImGuiWindowSettings(); settings->ID = ImHashStr(name, name_len); memcpy(settings->GetName(), name, name_len + 1); // Store with zero terminator + return settings; } + // We don't provide a FindWindowSettingsByName() because Docking system doesn't always hold on names. // This is called once per window .ini entry + once per newly instantiated window. ImGuiWindowSettings* ImGui::FindWindowSettingsByID(ImGuiID id) @@ -13894,6 +15999,7 @@ ImGuiWindowSettings* ImGui::FindWindowSettingsByID(ImGuiID id) return settings; return NULL; } + // This is faster if you are holding on a Window already as we don't need to perform a search. ImGuiWindowSettings* ImGui::FindWindowSettingsByWindow(ImGuiWindow* window) { @@ -13902,6 +16008,7 @@ ImGuiWindowSettings* ImGui::FindWindowSettingsByWindow(ImGuiWindow* window) return g.SettingsWindows.ptr_from_offset(window->SettingsOffset); return FindWindowSettingsByID(window->ID); } + // This will revert window to its initial state, including enabling the ImGuiCond_FirstUseEver/ImGuiCond_Once conditions once more. void ImGui::ClearWindowSettings(const char* name) { @@ -13918,6 +16025,7 @@ void ImGui::ClearWindowSettings(const char* name) if (ImGuiWindowSettings* settings = window ? FindWindowSettingsByWindow(window) : FindWindowSettingsByID(ImHashStr(name))) settings->WantDelete = true; } + static void WindowSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*) { ImGuiContext& g = *ctx; @@ -13925,6 +16033,7 @@ static void WindowSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandl window->SettingsOffset = -1; g.SettingsWindows.clear(); } + static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) { ImGuiID id = ImHashStr(name); @@ -13937,6 +16046,7 @@ static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler* settings->WantApply = true; return (void*)settings; } + static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line) { ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry; @@ -13953,6 +16063,7 @@ static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, else if (sscanf(line, "DockId=0x%X", &u1) == 1) { settings->DockId = u1; settings->DockOrder = -1; } else if (sscanf(line, "ClassId=0x%X", &u1) == 1) { settings->ClassId = u1; } } + // Apply to existing windows (if any) static void WindowSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*) { @@ -13965,6 +16076,7 @@ static void WindowSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandl settings->WantApply = false; } } + static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) { // Gather data from windows that were active during this session @@ -13974,6 +16086,7 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl { if (window->Flags & ImGuiWindowFlags_NoSavedSettings) continue; + ImGuiWindowSettings* settings = ImGui::FindWindowSettingsByWindow(window); if (!settings) { @@ -13993,6 +16106,7 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl settings->IsChild = (window->RootWindow != window); // Cannot rely on ImGuiWindowFlags_ChildWindow here as docked windows have this set. settings->WantDelete = false; } + // Write to text buffer buf->reserve(buf->size() + g.SettingsWindows.size() * 6); // ballpark reserve for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) @@ -14032,15 +16146,18 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl buf->append("\n"); } } + //----------------------------------------------------------------------------- // [SECTION] LOCALIZATION //----------------------------------------------------------------------------- + void ImGui::LocalizeRegisterEntries(const ImGuiLocEntry* entries, int count) { ImGuiContext& g = *GImGui; for (int n = 0; n < count; n++) g.LocalizationTable[entries[n].Key] = entries[n].Text; } + //----------------------------------------------------------------------------- // [SECTION] VIEWPORTS, PLATFORM WINDOWS //----------------------------------------------------------------------------- @@ -14068,11 +16185,13 @@ void ImGui::LocalizeRegisterEntries(const ImGuiLocEntry* entries, int count) // - DestroyPlatformWindow() [Internal] // - DestroyPlatformWindows() //----------------------------------------------------------------------------- + ImGuiViewport* ImGui::GetMainViewport() { ImGuiContext& g = *GImGui; return g.Viewports[0]; } + // FIXME: This leaks access to viewports not listed in PlatformIO.Viewports[]. Problematic? (#4236) ImGuiViewport* ImGui::FindViewportByID(ImGuiID id) { @@ -14082,6 +16201,7 @@ ImGuiViewport* ImGui::FindViewportByID(ImGuiID id) return viewport; return NULL; } + ImGuiViewport* ImGui::FindViewportByPlatformHandle(void* platform_handle) { ImGuiContext& g = *GImGui; @@ -14090,10 +16210,12 @@ ImGuiViewport* ImGui::FindViewportByPlatformHandle(void* platform_handle) return viewport; return NULL; } + void ImGui::SetCurrentViewport(ImGuiWindow* current_window, ImGuiViewportP* viewport) { ImGuiContext& g = *GImGui; (void)current_window; + if (viewport) viewport->LastFrameActive = g.FrameCount; if (g.CurrentViewport == viewport) @@ -14102,20 +16224,24 @@ void ImGui::SetCurrentViewport(ImGuiWindow* current_window, ImGuiViewportP* view g.CurrentViewport = viewport; IM_ASSERT(g.CurrentDpiScale > 0.0f && g.CurrentDpiScale < 99.0f); // Typical correct values would be between 1.0f and 4.0f //IMGUI_DEBUG_LOG_VIEWPORT("[viewport] SetCurrentViewport changed '%s' 0x%08X\n", current_window ? current_window->Name : NULL, viewport ? viewport->ID : 0); + // Notify platform layer of viewport changes // FIXME-DPI: This is only currently used for experimenting with handling of multiple DPI if (g.CurrentViewport && g.PlatformIO.Platform_OnChangedViewport) g.PlatformIO.Platform_OnChangedViewport(g.CurrentViewport); } + void ImGui::SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport) { // Abandon viewport if (window->ViewportOwned && window->Viewport->Window == window) window->Viewport->Size = ImVec2(0.0f, 0.0f); + window->Viewport = viewport; window->ViewportId = viewport->ID; window->ViewportOwned = (viewport->Window == window); } + static bool ImGui::GetWindowAlwaysWantOwnViewport(ImGuiWindow* window) { // Tooltips and menus are not automatically forced into their own viewport when the NoMerge flag is set, however the multiplication of viewports makes them more likely to protrude and create their own. @@ -14128,6 +16254,7 @@ static bool ImGui::GetWindowAlwaysWantOwnViewport(ImGuiWindow* window) return true; return false; } + static bool ImGui::UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* viewport) { ImGuiContext& g = *GImGui; @@ -14141,6 +16268,7 @@ static bool ImGui::UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImG return false; if (GetWindowAlwaysWantOwnViewport(window)) return false; + // FIXME: Can't use g.WindowsFocusOrder[] for root windows only as we care about Z order. If we maintained a DisplayOrder along with FocusOrder we could.. for (ImGuiWindow* window_behind : g.Windows) { @@ -14150,6 +16278,7 @@ static bool ImGui::UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImG if (window_behind->Viewport->GetMainRect().Overlaps(window->Rect())) return false; } + // Move to the existing viewport, Move child/hosted windows as well (FIXME-OPT: iterate child) ImGuiViewportP* old_viewport = window->Viewport; if (window->ViewportOwned) @@ -14158,36 +16287,43 @@ static bool ImGui::UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImG SetWindowViewport(g.Windows[n], viewport); SetWindowViewport(window, viewport); BringWindowToDisplayFront(window); + return true; } + // FIXME: handle 0 to N host viewports static bool ImGui::UpdateTryMergeWindowIntoHostViewports(ImGuiWindow* window) { ImGuiContext& g = *GImGui; return UpdateTryMergeWindowIntoHostViewport(window, g.Viewports[0]); } + // Translate Dear ImGui windows when a Host Viewport has been moved // (This additionally keeps windows at the same place when ImGuiConfigFlags_ViewportsEnable is toggled!) void ImGui::TranslateWindowsInViewport(ImGuiViewportP* viewport, const ImVec2& old_pos, const ImVec2& new_pos, const ImVec2& old_size, const ImVec2& new_size) { ImGuiContext& g = *GImGui; + //IMGUI_DEBUG_LOG_VIEWPORT("[viewport] TranslateWindowsInViewport 0x%08X\n", viewport->ID); IM_ASSERT(viewport->Window == NULL && (viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows)); + // 1) We test if ImGuiConfigFlags_ViewportsEnable was just toggled, which allows us to conveniently // translate imgui windows from OS-window-local to absolute coordinates or vice-versa. // 2) If it's not going to fit into the new size, keep it at same absolute position. // One problem with this is that most Win32 applications doesn't update their render while dragging, // and so the window will appear to teleport when releasing the mouse. const bool translate_all_windows = (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) != (g.ConfigFlagsLastFrame & ImGuiConfigFlags_ViewportsEnable); - ImRect test_still_fit_rect(old_pos, old_pos + viewport->Size); + ImRect test_still_fit_rect(old_pos, old_pos + old_size); ImVec2 delta_pos = new_pos - old_pos; for (ImGuiWindow* window : g.Windows) // FIXME-OPT if (translate_all_windows || (window->Viewport == viewport && (old_size == new_size || test_still_fit_rect.Contains(window->Rect())))) TranslateWindow(window, delta_pos); } + // Scale all windows (position, size). Use when e.g. changing DPI. (This is a lossy operation!) void ImGui::ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale) { ImGuiContext& g = *GImGui; + //IMGUI_DEBUG_LOG_VIEWPORT("[viewport] ScaleWindowsInViewport 0x%08X\n", viewport->ID); if (viewport->Window) { ScaleWindow(viewport->Window, scale); @@ -14199,6 +16335,7 @@ void ImGui::ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale) ScaleWindow(window, scale); } } + // If the backend doesn't set MouseLastHoveredViewport or doesn't honor ImGuiViewportFlags_NoInputs, we do a search ourselves. // A) It won't take account of the possibility that non-imgui windows may be in-between our dragged window and our target window. // B) It requires Platform_GetWindowFocus to be implemented by backend. @@ -14212,12 +16349,14 @@ ImGuiViewportP* ImGui::FindHoveredViewportFromPlatformWindowStack(const ImVec2& best_candidate = viewport; return best_candidate; } + // Update viewports and monitor infos // Note that this is running even if 'ImGuiConfigFlags_ViewportsEnable' is not set, in order to clear unused viewports (if any) and update monitor info. static void ImGui::UpdateViewportsNewFrame() { ImGuiContext& g = *GImGui; IM_ASSERT(g.PlatformIO.Viewports.Size <= g.Viewports.Size); + // Update Minimized status (we need it first in order to decide if we'll apply Pos/Size of the main viewport) // Update Focused status const bool viewports_enabled = (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) != 0; @@ -14235,6 +16374,7 @@ static void ImGui::UpdateViewportsNewFrame() else viewport->Flags &= ~ImGuiViewportFlags_IsMinimized; } + // Update our implicit z-order knowledge of platform windows, which is used when the backend cannot provide io.MouseHoveredViewport. // When setting Platform_GetWindowFocus, it is expected that the platform backend can handle calls without crashing if it doesn't have data stored. if (g.PlatformIO.Platform_GetWindowFocus && platform_funcs_available) @@ -14248,24 +16388,27 @@ static void ImGui::UpdateViewportsNewFrame() focused_viewport = viewport; } } + // Focused viewport has changed? if (focused_viewport && g.PlatformLastFocusedViewportId != focused_viewport->ID) { - IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Focused viewport changed %08X -> %08X, attempting to apply our focus.\n", g.PlatformLastFocusedViewportId, focused_viewport->ID); + IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Focused viewport changed %08X -> %08X '%s', attempting to apply our focus.\n", g.PlatformLastFocusedViewportId, focused_viewport->ID, focused_viewport->Window ? focused_viewport->Window->Name : "n/a"); const ImGuiViewport* prev_focused_viewport = FindViewportByID(g.PlatformLastFocusedViewportId); const bool prev_focused_has_been_destroyed = (prev_focused_viewport == NULL) || (prev_focused_viewport->PlatformWindowCreated == false); + // Store a tag so we can infer z-order easily from all our windows // We compare PlatformLastFocusedViewportId so newly created viewports with _NoFocusOnAppearing flag // will keep the front most stamp instead of losing it back to their parent viewport. if (focused_viewport->LastFocusedStampCount != g.ViewportFocusedStampCount) focused_viewport->LastFocusedStampCount = ++g.ViewportFocusedStampCount; g.PlatformLastFocusedViewportId = focused_viewport->ID; + // Focus associated dear imgui window // - if focus didn't happen with a click within imgui boundaries, e.g. Clicking platform title bar. (#6299) // - if focus didn't happen because we destroyed another window (#6462) // FIXME: perhaps 'FocusTopMostWindowUnderOne()' can handle the 'focused_window->Window != NULL' case as well. const bool apply_imgui_focus_on_focused_viewport = !IsAnyMouseDown() && !prev_focused_has_been_destroyed; - if (apply_imgui_focus_on_focused_viewport) + if (apply_imgui_focus_on_focused_viewport && g.IO.ConfigViewportPlatformFocusSetsImGuiFocus) { focused_viewport->LastFocusedHadNavWindow |= (g.NavWindow != NULL) && (g.NavWindow->Viewport == focused_viewport); // Update so a window changing viewport won't lose focus. ImGuiFocusRequestFlags focus_request_flags = ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild; @@ -14280,6 +16423,7 @@ static void ImGui::UpdateViewportsNewFrame() if (focused_viewport) focused_viewport->LastFocusedHadNavWindow = (g.NavWindow != NULL) && (g.NavWindow->Viewport == focused_viewport); } + // Create/update main viewport with current platform position. // FIXME-VIEWPORT: Size is driven by backend/user code for backward-compatibility but we should aim to make this more consistent. ImGuiViewportP* main_viewport = g.Viewports[0]; @@ -14287,12 +16431,15 @@ static void ImGui::UpdateViewportsNewFrame() IM_ASSERT(main_viewport->Window == NULL); ImVec2 main_viewport_pos = viewports_enabled ? g.PlatformIO.Platform_GetWindowPos(main_viewport) : ImVec2(0.0f, 0.0f); ImVec2 main_viewport_size = g.IO.DisplaySize; + ImVec2 main_viewport_framebuffer_scale = g.IO.DisplayFramebufferScale; if (viewports_enabled && (main_viewport->Flags & ImGuiViewportFlags_IsMinimized)) { - main_viewport_pos = main_viewport->Pos; // Preserve last pos/size when minimized (FIXME: We don't do the same for Size outside of the viewport path) + main_viewport_pos = main_viewport->Pos; // Preserve last pos/size when minimized (FIXME: We don't do the same for Size outside of the viewport path) main_viewport_size = main_viewport->Size; + main_viewport_framebuffer_scale = main_viewport->FramebufferScale; } AddUpdateViewport(NULL, IMGUI_VIEWPORT_DEFAULT_ID, main_viewport_pos, main_viewport_size, ImGuiViewportFlags_OwnedByApp | ImGuiViewportFlags_CanHostOtherWindows); + g.CurrentDpiScale = 0.0f; g.CurrentViewport = NULL; g.MouseViewport = NULL; @@ -14300,6 +16447,7 @@ static void ImGui::UpdateViewportsNewFrame() { ImGuiViewportP* viewport = g.Viewports[n]; viewport->Idx = n; + // Erase unused viewports if (n > 0 && viewport->LastFrameActive < g.FrameCount - 2) { @@ -14307,6 +16455,7 @@ static void ImGui::UpdateViewportsNewFrame() n--; continue; } + const bool platform_funcs_available = viewport->PlatformWindowCreated; if (viewports_enabled) { @@ -14319,10 +16468,14 @@ static void ImGui::UpdateViewportsNewFrame() viewport->Pos = viewport->LastPlatformPos = g.PlatformIO.Platform_GetWindowPos(viewport); if (viewport->PlatformRequestResize) viewport->Size = viewport->LastPlatformSize = g.PlatformIO.Platform_GetWindowSize(viewport); + if (g.PlatformIO.Platform_GetWindowFramebufferScale != NULL) + viewport->FramebufferScale = g.PlatformIO.Platform_GetWindowFramebufferScale(viewport); } } + // Update/copy monitor info UpdateViewportPlatformMonitor(viewport); + // Lock down space taken by menu bars and status bars + query initial insets from backend // Setup initial value for functions like BeginMainMenuBar(), DockSpaceOverViewport() etc. viewport->WorkInsetMin = viewport->BuildWorkInsetMin; @@ -14336,13 +16489,16 @@ static void ImGui::UpdateViewportsNewFrame() viewport->BuildWorkInsetMax = ImVec2(insets.z, insets.w); } viewport->UpdateWorkRect(); + // Reset alpha every frame. Users of transparency (docking) needs to request a lower alpha back. viewport->Alpha = 1.0f; + // Translate Dear ImGui windows when a Host Viewport has been moved // (This additionally keeps windows at the same place when ImGuiConfigFlags_ViewportsEnable is toggled!) const ImVec2 viewport_delta_pos = viewport->Pos - viewport->LastPos; if ((viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) && (viewport_delta_pos.x != 0.0f || viewport_delta_pos.y != 0.0f)) TranslateWindowsInViewport(viewport, viewport->LastPos, viewport->Pos, viewport->LastSize, viewport->Size); + // Update DPI scale float new_dpi_scale; if (g.PlatformIO.Platform_GetWindowDpiScale && platform_funcs_available) @@ -14355,10 +16511,11 @@ static void ImGui::UpdateViewportsNewFrame() if (viewport->DpiScale != 0.0f && new_dpi_scale != viewport->DpiScale) { float scale_factor = new_dpi_scale / viewport->DpiScale; - if (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleViewports) + if (g.IO.ConfigDpiScaleViewports) ScaleWindowsInViewport(viewport, scale_factor); //if (viewport == GetMainViewport()) // g.PlatformInterface.SetWindowSize(viewport, viewport->Size * scale_factor); + // Scale our window moving pivot so that the window will rescale roughly around the mouse position. // FIXME-VIEWPORT: This currently creates a resizing feedback loop when a window is straddling a DPI transition border. // (Minor: since our sizes do not perfectly linearly scale, deferring the click offset scale until we know the actual window scale ratio may get us slightly more precise mouse positioning.) @@ -14367,6 +16524,7 @@ static void ImGui::UpdateViewportsNewFrame() } viewport->DpiScale = new_dpi_scale; } + // Update fallback monitor g.PlatformMonitorsFullWorkRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); if (g.PlatformIO.Monitors.Size == 0) @@ -14389,11 +16547,13 @@ static void ImGui::UpdateViewportsNewFrame() g.PlatformMonitorsFullWorkRect.Add(monitor.WorkPos); g.PlatformMonitorsFullWorkRect.Add(monitor.WorkPos + monitor.WorkSize); } + if (!viewports_enabled) { g.MouseViewport = main_viewport; return; } + // Mouse handling: decide on the actual mouse viewport for this frame between the active/focused viewport and the hovered viewport. // Note that 'viewport_hovered' should skip over any viewport that has the ImGuiViewportFlags_NoInputs flags set. ImGuiViewportP* viewport_hovered = NULL; @@ -14415,6 +16575,7 @@ static void ImGui::UpdateViewportsNewFrame() g.MouseLastHoveredViewport = viewport_hovered; else if (g.MouseLastHoveredViewport == NULL) g.MouseLastHoveredViewport = g.Viewports[0]; + // Update mouse reference viewport // (when moving a window we aim at its viewport, but this will be overwritten below if we go in drag and drop mode) // (MovingViewport->Viewport will be NULL in the rare situation where the window disappared while moving, set UpdateMouseMovingWindowNewFrame() for details) @@ -14422,6 +16583,7 @@ static void ImGui::UpdateViewportsNewFrame() g.MouseViewport = g.MovingWindow->Viewport; else g.MouseViewport = g.MouseLastHoveredViewport; + // When dragging something, always refer to the last hovered viewport. // - when releasing a moving window we will revert to aiming behind (at viewport_hovered) // - when we are between viewports, our dragged preview will tend to show in the last viewport _even_ if we don't have tooltips in their viewports (when lacking monitor info) @@ -14433,8 +16595,10 @@ static void ImGui::UpdateViewportsNewFrame() if (is_mouse_dragging_with_an_expected_destination || g.ActiveId == 0 || !IsAnyMouseDown()) if (viewport_hovered != NULL && viewport_hovered != g.MouseViewport && !(viewport_hovered->Flags & ImGuiViewportFlags_NoInputs)) g.MouseViewport = viewport_hovered; + IM_ASSERT(g.MouseViewport != NULL); } + // Update user-facing viewport list (g.Viewports -> g.PlatformIO.Viewports after filtering out some) static void ImGui::UpdateViewportsEndFrame() { @@ -14456,21 +16620,25 @@ static void ImGui::UpdateViewportsEndFrame() } g.Viewports[0]->ClearRequestFlags(); // Clear main viewport flags because UpdatePlatformWindows() won't do it and may not even be called } + // FIXME: We should ideally refactor the system to call this every frame (we currently don't) ImGuiViewportP* ImGui::AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const ImVec2& pos, const ImVec2& size, ImGuiViewportFlags flags) { ImGuiContext& g = *GImGui; IM_ASSERT(id != 0); + flags |= ImGuiViewportFlags_IsPlatformWindow; if (window != NULL) { + const bool window_can_use_inputs = ((window->Flags & ImGuiWindowFlags_NoMouseInputs) && (window->Flags & ImGuiWindowFlags_NoNavInputs)) == false; if (g.MovingWindow && g.MovingWindow->RootWindowDockTree == window) flags |= ImGuiViewportFlags_NoInputs | ImGuiViewportFlags_NoFocusOnAppearing; - if ((window->Flags & ImGuiWindowFlags_NoMouseInputs) && (window->Flags & ImGuiWindowFlags_NoNavInputs)) + if (!window_can_use_inputs) flags |= ImGuiViewportFlags_NoInputs; if (window->Flags & ImGuiWindowFlags_NoFocusOnAppearing) flags |= ImGuiViewportFlags_NoFocusOnAppearing; } + ImGuiViewportP* viewport = (ImGuiViewportP*)FindViewportByID(id); if (viewport) { @@ -14498,27 +16666,30 @@ ImGuiViewportP* ImGui::AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const g.Viewports.push_back(viewport); g.ViewportCreatedCount++; IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Add Viewport %08X '%s'\n", id, window ? window->Name : ""); + // We normally setup for all viewports in NewFrame() but here need to handle the mid-frame creation of a new viewport. // We need to extend the fullscreen clip rect so the OverlayDrawList clip is correct for that the first frame g.DrawListSharedData.ClipRectFullscreen.x = ImMin(g.DrawListSharedData.ClipRectFullscreen.x, viewport->Pos.x); g.DrawListSharedData.ClipRectFullscreen.y = ImMin(g.DrawListSharedData.ClipRectFullscreen.y, viewport->Pos.y); g.DrawListSharedData.ClipRectFullscreen.z = ImMax(g.DrawListSharedData.ClipRectFullscreen.z, viewport->Pos.x + viewport->Size.x); g.DrawListSharedData.ClipRectFullscreen.w = ImMax(g.DrawListSharedData.ClipRectFullscreen.w, viewport->Pos.y + viewport->Size.y); + // Store initial DpiScale before the OS platform window creation, based on expected monitor data. // This is so we can select an appropriate font size on the first frame of our window lifetime - if (viewport->PlatformMonitor != -1) - viewport->DpiScale = g.PlatformIO.Monitors[viewport->PlatformMonitor].DpiScale; - else - viewport->DpiScale = 1.0f; + viewport->DpiScale = GetViewportPlatformMonitor(viewport)->DpiScale; } + viewport->Window = window; viewport->LastFrameActive = g.FrameCount; viewport->UpdateWorkRect(); IM_ASSERT(window == NULL || viewport->ID == window->ID); + if (window != NULL) window->ViewportOwned = true; + return viewport; } + static void ImGui::DestroyViewport(ImGuiViewportP* viewport) { // Clear references to this viewport in windows (window->ViewportId becomes the master data) @@ -14532,6 +16703,7 @@ static void ImGui::DestroyViewport(ImGuiViewportP* viewport) } if (viewport == g.MouseLastHoveredViewport) g.MouseLastHoveredViewport = NULL; + // Destroy IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Delete Viewport %08X '%s'\n", viewport->ID, viewport->Window ? viewport->Window->Name : "n/a"); DestroyPlatformWindow(viewport); // In most circumstances the platform window will already be destroyed here. @@ -14540,12 +16712,14 @@ static void ImGui::DestroyViewport(ImGuiViewportP* viewport) g.Viewports.erase(g.Viewports.Data + viewport->Idx); IM_DELETE(viewport); } + // FIXME-VIEWPORT: This is all super messy and ought to be clarified or rewritten. static void ImGui::WindowSelectViewport(ImGuiWindow* window) { ImGuiContext& g = *GImGui; ImGuiWindowFlags flags = window->Flags; window->ViewportAllowPlatformMonitorExtend = -1; + // Restore main viewport if multi-viewport is not supported by the backend ImGuiViewportP* main_viewport = (ImGuiViewportP*)(void*)GetMainViewport(); if (!(g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)) @@ -14554,17 +16728,20 @@ static void ImGui::WindowSelectViewport(ImGuiWindow* window) return; } window->ViewportOwned = false; + // Appearing popups reset their viewport so they can inherit again if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && window->Appearing) { window->Viewport = NULL; window->ViewportId = 0; } + if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasViewport) == 0) { // By default inherit from parent window if (window->Viewport == NULL && window->ParentWindow && (!window->ParentWindow->IsFallbackWindow || window->ParentWindow->WasActive)) window->Viewport = window->ParentWindow->Viewport; + // Attempt to restore saved viewport id (= window that hasn't been activated yet), try to restore the viewport based on saved 'window->ViewportPos' restored from .ini file if (window->Viewport == NULL && window->ViewportId != 0) { @@ -14573,6 +16750,7 @@ static void ImGui::WindowSelectViewport(ImGuiWindow* window) window->Viewport = AddUpdateViewport(window, window->ID, window->ViewportPos, window->Size, ImGuiViewportFlags_None); } } + bool lock_viewport = false; if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasViewport) { @@ -14620,10 +16798,12 @@ static void ImGui::WindowSelectViewport(ImGuiWindow* window) if (try_to_merge_into_host_viewport) UpdateTryMergeWindowIntoHostViewports(window); } + // Fallback: merge in default viewport if z-order matches, otherwise create a new viewport if (window->Viewport == NULL) if (!UpdateTryMergeWindowIntoHostViewport(window, main_viewport)) window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None); + // Mark window as allowed to protrude outside of its viewport and into the current monitor if (!lock_viewport) { @@ -14664,17 +16844,22 @@ static void ImGui::WindowSelectViewport(ImGuiWindow* window) window->ViewportAllowPlatformMonitorExtend = window->Viewport->PlatformMonitor; } } + // Update flags window->ViewportOwned = (window == window->Viewport->Window); window->ViewportId = window->Viewport->ID; + // If the OS window has a title bar, hide our imgui title bar //if (window->ViewportOwned && !(window->Viewport->Flags & ImGuiViewportFlags_NoDecoration)) // window->Flags |= ImGuiWindowFlags_NoTitleBar; } + void ImGui::WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_window_in_stack) { ImGuiContext& g = *GImGui; + bool viewport_rect_changed = false; + // Synchronize window --> viewport in most situations // Synchronize viewport -> window in case the platform window has been moved or resized from the OS/WM if (window->Viewport->PlatformRequestMove) @@ -14687,6 +16872,7 @@ void ImGui::WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_win viewport_rect_changed = true; window->Viewport->Pos = window->Pos; } + if (window->Viewport->PlatformRequestResize) { window->Size = window->SizeFull = window->Viewport->Size; @@ -14698,10 +16884,12 @@ void ImGui::WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_win window->Viewport->Size = window->Size; } window->Viewport->UpdateWorkRect(); + // The viewport may have changed monitor since the global update in UpdateViewportsNewFrame() // Either a SetNextWindowPos() call in the current frame or a SetWindowPos() call in the previous frame may have this effect. if (viewport_rect_changed) UpdateViewportPlatformMonitor(window->Viewport); + // Update common viewport flags const ImGuiViewportFlags viewport_flags_to_clear = ImGuiViewportFlags_TopMost | ImGuiViewportFlags_NoTaskBarIcon | ImGuiViewportFlags_NoDecoration | ImGuiViewportFlags_NoRendererClear; ImGuiViewportFlags viewport_flags = window->Viewport->Flags & ~viewport_flags_to_clear; @@ -14714,28 +16902,34 @@ void ImGui::WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_win viewport_flags |= ImGuiViewportFlags_NoTaskBarIcon; if (g.IO.ConfigViewportsNoDecoration || is_short_lived_floating_window) viewport_flags |= ImGuiViewportFlags_NoDecoration; + // Not correct to set modal as topmost because: // - Because other popups can be stacked above a modal (e.g. combo box in a modal) // - ImGuiViewportFlags_TopMost is currently handled different in backends: in Win32 it is "appear top most" whereas in GLFW and SDL it is "stay topmost" //if (flags & ImGuiWindowFlags_Modal) // viewport_flags |= ImGuiViewportFlags_TopMost; + // For popups and menus that may be protruding out of their parent viewport, we enable _NoFocusOnClick so that clicking on them // won't steal the OS focus away from their parent window (which may be reflected in OS the title bar decoration). // Setting _NoFocusOnClick would technically prevent us from bringing back to front in case they are being covered by an OS window from a different app, // but it shouldn't be much of a problem considering those are already popups that are closed when clicking elsewhere. if (is_short_lived_floating_window && !is_modal) viewport_flags |= ImGuiViewportFlags_NoFocusOnAppearing | ImGuiViewportFlags_NoFocusOnClick; + // We can overwrite viewport flags using ImGuiWindowClass (advanced users) if (window->WindowClass.ViewportFlagsOverrideSet) viewport_flags |= window->WindowClass.ViewportFlagsOverrideSet; if (window->WindowClass.ViewportFlagsOverrideClear) viewport_flags &= ~window->WindowClass.ViewportFlagsOverrideClear; + // We can also tell the backend that clearing the platform window won't be necessary, // as our window background is filling the viewport and we have disabled BgAlpha. // FIXME: Work on support for per-viewport transparency (#2766) if (!(window_flags & ImGuiWindowFlags_NoBackground)) viewport_flags |= ImGuiViewportFlags_NoRendererClear; + window->Viewport->Flags = viewport_flags; + // Update parent viewport ID // (the !IsFallbackWindow test mimic the one done in WindowSelectViewport()) if (window->WindowClass.ParentViewportId != (ImGuiID)-1) @@ -14745,6 +16939,7 @@ void ImGui::WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_win else window->Viewport->ParentViewportId = g.IO.ConfigViewportsNoDefaultParent ? 0 : IMGUI_VIEWPORT_DEFAULT_ID; } + // Called by user at the end of the main loop, after EndFrame() // This will handle the creation/update of all OS windows via function defined in the ImGuiPlatformIO api. void ImGui::UpdatePlatformWindows() @@ -14755,11 +16950,13 @@ void ImGui::UpdatePlatformWindows() g.FrameCountPlatformEnded = g.FrameCount; if (!(g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)) return; + // Create/resize/destroy platform windows to match each active viewport. // Skip the main viewport (index 0), which is always fully handled by the application! for (int i = 1; i < g.Viewports.Size; i++) { ImGuiViewportP* viewport = g.Viewports[i]; + // Destroy platform window if the viewport hasn't been submitted or if it is hosting a hidden window // (the implicit/fallback Debug##Default window will be registering its viewport then be disabled, causing a dummy DestroyPlatformWindow to be made each frame) bool destroy_platform_window = false; @@ -14770,9 +16967,11 @@ void ImGui::UpdatePlatformWindows() DestroyPlatformWindow(viewport); continue; } + // New windows that appears directly in a new viewport won't always have a size on their first frame if (viewport->LastFrameActive < g.FrameCount || viewport->Size.x <= 0 || viewport->Size.y <= 0) continue; + // Create window const bool is_new_platform_window = (viewport->PlatformWindowCreated == false); if (is_new_platform_window) @@ -14787,6 +16986,7 @@ void ImGui::UpdatePlatformWindows() viewport->LastRendererSize = viewport->Size; // We don't need to call Renderer_SetWindowSize() as it is expected Renderer_CreateWindow() already did it. viewport->PlatformWindowCreated = true; } + // Apply Position and Size (from ImGui to Platform/Renderer backends) if ((viewport->LastPlatformPos.x != viewport->Pos.x || viewport->LastPlatformPos.y != viewport->Pos.y) && !viewport->PlatformRequestMove) g.PlatformIO.Platform_SetWindowPos(viewport, viewport->Pos); @@ -14796,6 +16996,7 @@ void ImGui::UpdatePlatformWindows() g.PlatformIO.Renderer_SetWindowSize(viewport, viewport->Size); viewport->LastPlatformPos = viewport->Pos; viewport->LastPlatformSize = viewport->LastRendererSize = viewport->Size; + // Update title bar (if it changed) if (ImGuiWindow* window_for_title = GetWindowForTitleDisplay(viewport->Window)) { @@ -14811,29 +17012,36 @@ void ImGui::UpdatePlatformWindows() viewport->LastNameHash = title_hash; } } + // Update alpha (if it changed) if (viewport->LastAlpha != viewport->Alpha && g.PlatformIO.Platform_SetWindowAlpha) g.PlatformIO.Platform_SetWindowAlpha(viewport, viewport->Alpha); viewport->LastAlpha = viewport->Alpha; + // Optional, general purpose call to allow the backend to perform general book-keeping even if things haven't changed. if (g.PlatformIO.Platform_UpdateWindow) g.PlatformIO.Platform_UpdateWindow(viewport); + if (is_new_platform_window) { // On startup ensure new platform window don't steal focus (give it a few frames, as nested contents may lead to viewport being created a few frames late) if (g.FrameCount < 3) viewport->Flags |= ImGuiViewportFlags_NoFocusOnAppearing; + // Show window g.PlatformIO.Platform_ShowWindow(viewport); + // Even without focus, we assume the window becomes front-most. // This is useful for our platform z-order heuristic when io.MouseHoveredViewport is not available. if (viewport->LastFocusedStampCount != g.ViewportFocusedStampCount) viewport->LastFocusedStampCount = ++g.ViewportFocusedStampCount; } + // Clear request flags viewport->ClearRequestFlags(); } } + // This is a default/basic function for performing the rendering/swap of multiple Platform Windows. // Custom renderers may prefer to not call this function at all, and instead iterate the publicly exposed platform data and handle rendering/sync themselves. // The Render/Swap functions stored in ImGuiPlatformIO are merely here to allow for this helper to exist, but you can do it yourself: @@ -14867,6 +17075,7 @@ void ImGui::RenderPlatformWindowsDefault(void* platform_render_arg, void* render if (platform_io.Renderer_SwapBuffers) platform_io.Renderer_SwapBuffers(viewport, renderer_render_arg); } } + static int ImGui::FindPlatformMonitorForPos(const ImVec2& pos) { ImGuiContext& g = *GImGui; @@ -14878,20 +17087,24 @@ static int ImGui::FindPlatformMonitorForPos(const ImVec2& pos) } return -1; } + // Search for the monitor with the largest intersection area with the given rectangle // We generally try to avoid searching loops but the monitor count should be very small here // FIXME-OPT: We could test the last monitor used for that viewport first, and early static int ImGui::FindPlatformMonitorForRect(const ImRect& rect) { ImGuiContext& g = *GImGui; + const int monitor_count = g.PlatformIO.Monitors.Size; if (monitor_count <= 1) return monitor_count - 1; + // Use a minimum threshold of 1.0f so a zero-sized rect won't false positive, and will still find the correct monitor given its position. // This is necessary for tooltips which always resize down to zero at first. const float surface_threshold = ImMax(rect.GetWidth() * rect.GetHeight() * 0.5f, 1.0f); int best_monitor_n = 0; // Default to the first monitor as fallback float best_monitor_surface = 0.001f; + for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size && best_monitor_surface < surface_threshold; monitor_n++) { const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[monitor_n]; @@ -14908,11 +17121,13 @@ static int ImGui::FindPlatformMonitorForRect(const ImRect& rect) } return best_monitor_n; } + // Update monitor from viewport rectangle (we'll use this info to clamp windows and save windows lost in a removed monitor) static void ImGui::UpdateViewportPlatformMonitor(ImGuiViewportP* viewport) { viewport->PlatformMonitor = (short)FindPlatformMonitorForRect(viewport->GetMainRect()); } + // Return value is always != NULL, but don't hold on it across frames. const ImGuiPlatformMonitor* ImGui::GetViewportPlatformMonitor(ImGuiViewport* viewport_p) { @@ -14923,6 +17138,7 @@ const ImGuiPlatformMonitor* ImGui::GetViewportPlatformMonitor(ImGuiViewport* vie return &g.PlatformIO.Monitors[monitor_idx]; return &g.FallbackMonitor; } + void ImGui::DestroyPlatformWindow(ImGuiViewportP* viewport) { ImGuiContext& g = *GImGui; @@ -14934,6 +17150,7 @@ void ImGui::DestroyPlatformWindow(ImGuiViewportP* viewport) if (g.PlatformIO.Platform_DestroyWindow) g.PlatformIO.Platform_DestroyWindow(viewport); IM_ASSERT(viewport->RendererUserData == NULL && viewport->PlatformUserData == NULL); + // Don't clear PlatformWindowCreated for the main viewport, as we initially set that up to true in Initialize() // The righter way may be to leave it to the backend to set this flag all-together, and made the flag public. if (viewport->ID != IMGUI_VIEWPORT_DEFAULT_ID) @@ -14946,6 +17163,7 @@ void ImGui::DestroyPlatformWindow(ImGuiViewportP* viewport) viewport->RendererUserData = viewport->PlatformUserData = viewport->PlatformHandle = NULL; viewport->ClearRequestFlags(); } + void ImGui::DestroyPlatformWindows() { // We call the destroy window on every viewport (including the main viewport, index 0) to give a chance to the backend @@ -14958,6 +17176,8 @@ void ImGui::DestroyPlatformWindows() for (ImGuiViewportP* viewport : g.Viewports) DestroyPlatformWindow(viewport); } + + //----------------------------------------------------------------------------- // [SECTION] DOCKING //----------------------------------------------------------------------------- @@ -14972,6 +17192,7 @@ void ImGui::DestroyPlatformWindows() // Docking: Begin/End Support Functions (called from Begin/End) // Docking: Settings //----------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // Typical Docking call flow: (root level is generally public API): //----------------------------------------------------------------------------- @@ -15018,6 +17239,7 @@ void ImGui::DestroyPlatformWindows() // - EndFrame() // | DockContextEndFrame() //----------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // Docking: Internal Types //----------------------------------------------------------------------------- @@ -15027,6 +17249,7 @@ void ImGui::DestroyPlatformWindows() // - ImGuiDockNodeSettings // - ImGuiDockContext //----------------------------------------------------------------------------- + enum ImGuiDockRequestType { ImGuiDockRequestType_None = 0, @@ -15034,6 +17257,7 @@ enum ImGuiDockRequestType ImGuiDockRequestType_Undock, ImGuiDockRequestType_Split // Split is the same as Dock but without a DockPayload }; + struct ImGuiDockRequest { ImGuiDockRequestType Type; @@ -15045,6 +17269,7 @@ struct ImGuiDockRequest bool DockSplitOuter; ImGuiWindow* UndockTargetWindow; ImGuiDockNode* UndockTargetNode; + ImGuiDockRequest() { Type = ImGuiDockRequestType_None; @@ -15055,6 +17280,7 @@ struct ImGuiDockRequest DockSplitOuter = false; } }; + struct ImGuiDockPreviewData { ImGuiDockNode FutureNode; @@ -15066,8 +17292,10 @@ struct ImGuiDockPreviewData ImGuiDir SplitDir; float SplitRatio; ImRect DropRectsDraw[ImGuiDir_COUNT + 1]; // May be slightly different from hit-testing drop rects used in DockNodeCalcDropRects() + ImGuiDockPreviewData() : FutureNode(0) { IsDropAllowed = IsCenterAvailable = IsSidesAvailable = IsSplitDirExplicit = false; SplitNode = NULL; SplitDir = ImGuiDir_None; SplitRatio = 0.f; for (int n = 0; n < IM_ARRAYSIZE(DropRectsDraw); n++) DropRectsDraw[n] = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); } }; + // Persistent Settings data, stored contiguously in SettingsNodes (sizeof() ~32 bytes) struct ImGuiDockNodeSettings { @@ -15083,9 +17311,11 @@ struct ImGuiDockNodeSettings ImVec2ih SizeRef; ImGuiDockNodeSettings() { memset(this, 0, sizeof(*this)); SplitAxis = ImGuiAxis_None; } }; + //----------------------------------------------------------------------------- // Docking: Forward Declarations //----------------------------------------------------------------------------- + namespace ImGui { // ImGuiDockContext @@ -15097,6 +17327,7 @@ namespace ImGui static ImGuiDockNode* DockContextBindNodeToWindow(ImGuiContext* ctx, ImGuiWindow* window); static void DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDockNodeSettings* node_settings_array, int node_settings_count); static void DockContextBuildAddWindowsToNodes(ImGuiContext* ctx, ImGuiID root_id); // Use root_id==0 to add all + // ImGuiDockNode static void DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, bool add_to_tab_bar); static void DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* src_node); @@ -15123,6 +17354,7 @@ namespace ImGui static bool DockNodeCalcDropRectsAndTestMousePos(const ImRect& parent, ImGuiDir dir, ImRect& out_draw, bool outer_docking, ImVec2* test_mouse_pos); static const char* DockNodeGetHostWindowTitle(ImGuiDockNode* node, char* buf, int buf_size) { ImFormatString(buf, buf_size, "##DockNode_%02X", node->ID); return buf; } static int DockNodeGetTabOrder(ImGuiWindow* window); + // ImGuiDockNode tree manipulations static void DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiAxis split_axis, int split_first_child, float split_ratio, ImGuiDockNode* new_node); static void DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiDockNode* merge_lead_child); @@ -15130,6 +17362,7 @@ namespace ImGui static void DockNodeTreeUpdateSplitter(ImGuiDockNode* node); static ImGuiDockNode* DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode* node, ImVec2 pos); static ImGuiDockNode* DockNodeTreeFindFallbackLeafNode(ImGuiDockNode* node); + // Settings static void DockSettingsRenameNodeReferences(ImGuiID old_node_id, ImGuiID new_node_id); static void DockSettingsRemoveNodeReferences(ImGuiID* node_ids, int node_ids_count); @@ -15140,6 +17373,7 @@ namespace ImGui static void DockSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line); static void DockSettingsHandler_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf); } + //----------------------------------------------------------------------------- // Docking: ImGuiDockContext //----------------------------------------------------------------------------- @@ -15167,9 +17401,11 @@ namespace ImGui // - DockContextBuildNodesFromSettings() // - DockContextBuildAddWindowsToNodes() //----------------------------------------------------------------------------- + void ImGui::DockContextInitialize(ImGuiContext* ctx) { ImGuiContext& g = *ctx; + // Add .ini handle for persistent docking data ImGuiSettingsHandler ini_handler; ini_handler.TypeName = "Docking"; @@ -15181,8 +17417,10 @@ void ImGui::DockContextInitialize(ImGuiContext* ctx) ini_handler.ApplyAllFn = DockSettingsHandler_ApplyAll; ini_handler.WriteAllFn = DockSettingsHandler_WriteAll; g.SettingsHandlers.push_back(ini_handler); + g.DockNodeWindowMenuHandler = &DockNodeWindowMenuHandler_Default; } + void ImGui::DockContextShutdown(ImGuiContext* ctx) { ImGuiDockContext* dc = &ctx->DockContext; @@ -15190,6 +17428,7 @@ void ImGui::DockContextShutdown(ImGuiContext* ctx) if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p) IM_DELETE(node); } + void ImGui::DockContextClearNodes(ImGuiContext* ctx, ImGuiID root_id, bool clear_settings_refs) { IM_UNUSED(ctx); @@ -15197,6 +17436,7 @@ void ImGui::DockContextClearNodes(ImGuiContext* ctx, ImGuiID root_id, bool clear DockBuilderRemoveNodeDockedWindows(root_id, clear_settings_refs); DockBuilderRemoveNodeChildNodes(root_id); } + // [DEBUG] This function also acts as a defacto test to make sure we can rebuild from scratch without a glitch // (Different from DockSettingsHandler_ClearAll() + DockSettingsHandler_ApplyAll() because this reuses current settings!) void ImGui::DockContextRebuildNodes(ImGuiContext* ctx) @@ -15210,6 +17450,7 @@ void ImGui::DockContextRebuildNodes(ImGuiContext* ctx) DockContextBuildNodesFromSettings(ctx, dc->NodesSettings.Data, dc->NodesSettings.Size); DockContextBuildAddWindowsToNodes(ctx, root_id); } + // Docking context update function, called by NewFrame() void ImGui::DockContextNewFrameUpdateUndocking(ImGuiContext* ctx) { @@ -15221,6 +17462,7 @@ void ImGui::DockContextNewFrameUpdateUndocking(ImGuiContext* ctx) DockContextClearNodes(ctx, 0, true); return; } + // Setting NoSplit at runtime merges all nodes if (g.IO.ConfigDockingNoSplit) for (int n = 0; n < dc->Nodes.Data.Size; n++) @@ -15230,6 +17472,7 @@ void ImGui::DockContextNewFrameUpdateUndocking(ImGuiContext* ctx) DockBuilderRemoveNodeChildNodes(node->ID); //dc->WantFullRebuild = true; } + // Process full rebuild #if 0 if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C))) @@ -15240,6 +17483,7 @@ void ImGui::DockContextNewFrameUpdateUndocking(ImGuiContext* ctx) DockContextRebuildNodes(ctx); dc->WantFullRebuild = false; } + // Process Undocking requests (we need to process them _before_ the UpdateMouseMovingWindowNewFrame call in NewFrame) for (ImGuiDockRequest& req : dc->Requests) { @@ -15249,6 +17493,7 @@ void ImGui::DockContextNewFrameUpdateUndocking(ImGuiContext* ctx) DockContextProcessUndockNode(ctx, req.UndockTargetNode); } } + // Docking context update function, called by NewFrame() void ImGui::DockContextNewFrameUpdateDocking(ImGuiContext* ctx) { @@ -15256,6 +17501,7 @@ void ImGui::DockContextNewFrameUpdateDocking(ImGuiContext* ctx) ImGuiDockContext* dc = &ctx->DockContext; if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)) return; + // [DEBUG] Store hovered dock node. // We could in theory use DockNodeTreeFindVisibleNodeByPos() on the root host dock node, but using ->DockNode is a good shortcut. // Note this is mostly a debug thing and isn't actually used for docking target, because docking involve more detailed filtering. @@ -15267,11 +17513,13 @@ void ImGui::DockContextNewFrameUpdateDocking(ImGuiContext* ctx) else if (hovered_window->RootWindow->DockNode) g.DebugHoveredDockNode = hovered_window->RootWindow->DockNode; } + // Process Docking requests for (ImGuiDockRequest& req : dc->Requests) if (req.Type == ImGuiDockRequestType_Dock) DockContextProcessDock(ctx, &req); dc->Requests.resize(0); + // Create windows for each automatic docking nodes // We can have NULL pointers when we delete nodes, but because ID are recycled this should amortize nicely (and our node count will never be very high) for (int n = 0; n < dc->Nodes.Data.Size; n++) @@ -15279,6 +17527,7 @@ void ImGui::DockContextNewFrameUpdateDocking(ImGuiContext* ctx) if (node->IsFloatingNode()) DockNodeUpdate(node); } + void ImGui::DockContextEndFrame(ImGuiContext* ctx) { // Draw backgrounds of node missing their window @@ -15294,10 +17543,12 @@ void ImGui::DockContextEndFrame(ImGuiContext* ctx) node->HostWindow->DrawList->AddRectFilled(bg_rect.Min, bg_rect.Max, node->LastBgColor, node->HostWindow->WindowRounding, bg_rounding_flags); } } + ImGuiDockNode* ImGui::DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id) { return (ImGuiDockNode*)ctx->DockContext.Nodes.GetVoidPtr(id); } + ImGuiID ImGui::DockContextGenNodeID(ImGuiContext* ctx) { // Generate an ID for new node (the exact ID value doesn't matter as long as it is not already used) @@ -15308,6 +17559,7 @@ ImGuiID ImGui::DockContextGenNodeID(ImGuiContext* ctx) id++; return id; } + static ImGuiDockNode* ImGui::DockContextAddNode(ImGuiContext* ctx, ImGuiID id) { // Generate an ID for the new node (the exact ID value doesn't matter as long as it is not already used) and add the first window. @@ -15316,22 +17568,27 @@ static ImGuiDockNode* ImGui::DockContextAddNode(ImGuiContext* ctx, ImGuiID id) id = DockContextGenNodeID(ctx); else IM_ASSERT(DockContextFindNodeByID(ctx, id) == NULL); + // We don't set node->LastFrameAlive on construction. Nodes are always created at all time to reflect .ini settings! IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextAddNode 0x%08X\n", id); ImGuiDockNode* node = IM_NEW(ImGuiDockNode)(id); ctx->DockContext.Nodes.SetVoidPtr(node->ID, node); return node; } + static void ImGui::DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node) { ImGuiContext& g = *ctx; ImGuiDockContext* dc = &ctx->DockContext; + IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextRemoveNode 0x%08X\n", node->ID); IM_ASSERT(DockContextFindNodeByID(ctx, node->ID) == node); IM_ASSERT(node->ChildNodes[0] == NULL && node->ChildNodes[1] == NULL); IM_ASSERT(node->Windows.Size == 0); + if (node->HostWindow) node->HostWindow->DockNodeAsHost = NULL; + ImGuiDockNode* parent_node = node->ParentNode; const bool merge = (merge_sibling_into_parent_node && parent_node != NULL); if (merge) @@ -15349,12 +17606,14 @@ static void ImGui::DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, IM_DELETE(node); } } + static int IMGUI_CDECL DockNodeComparerDepthMostFirst(const void* lhs, const void* rhs) { const ImGuiDockNode* a = *(const ImGuiDockNode* const*)lhs; const ImGuiDockNode* b = *(const ImGuiDockNode* const*)rhs; return ImGui::DockNodeGetDepth(b) - ImGui::DockNodeGetDepth(a); } + // Pre C++0x doesn't allow us to use a function-local type (without linkage) as template parameter, so we moved this here. struct ImGuiDockContextPruneNodeData { @@ -15362,14 +17621,17 @@ struct ImGuiDockContextPruneNodeData ImGuiID RootId; ImGuiDockContextPruneNodeData() { CountWindows = CountChildWindows = CountChildNodes = 0; RootId = 0; } }; + // Garbage collect unused nodes (run once at init time) static void ImGui::DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx) { ImGuiContext& g = *ctx; ImGuiDockContext* dc = &ctx->DockContext; IM_ASSERT(g.Windows.Size == 0); + ImPool pool; pool.Reserve(dc->NodesSettings.Size); + // Count child nodes and compute RootID for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++) { @@ -15379,6 +17641,7 @@ static void ImGui::DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx) if (settings->ParentNodeId) pool.GetOrAddByKey(settings->ParentNodeId)->CountChildNodes++; } + // Count reference to dock ids from dockspaces // We track the 'auto-DockNode <- manual-Window <- manual-DockSpace' in order to avoid 'auto-DockNode' being ditched by DockContextPruneUnusedSettingsNodes() for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++) @@ -15390,6 +17653,7 @@ static void ImGui::DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx) if (ImGuiDockContextPruneNodeData* data = pool.GetByKey(window_settings->DockId)) data->CountChildNodes++; } + // Count reference to dock ids from window settings // We guard against the possibility of an invalid .ini file (RootID may point to a missing node) for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) @@ -15400,14 +17664,16 @@ static void ImGui::DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx) if (ImGuiDockContextPruneNodeData* data_root = (data->RootId == dock_id) ? data : pool.GetByKey(data->RootId)) data_root->CountChildWindows++; } + // Prune for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++) { ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n]; ImGuiDockContextPruneNodeData* data = pool.GetByKey(settings->ID); - if (data->CountWindows > 1) + if (data == NULL || data->CountWindows > 1) continue; ImGuiDockContextPruneNodeData* data_root = (data->RootId == settings->ID) ? data : pool.GetByKey(data->RootId); + bool remove = false; remove |= (data->CountWindows == 1 && settings->ParentNodeId == 0 && data->CountChildNodes == 0 && !(settings->Flags & ImGuiDockNodeFlags_CentralNode)); // Floating root node with only 1 window remove |= (data->CountWindows == 0 && settings->ParentNodeId == 0 && data->CountChildNodes == 0); // Leaf nodes with 0 window @@ -15420,6 +17686,7 @@ static void ImGui::DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx) } } } + static void ImGui::DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDockNodeSettings* node_settings_array, int node_settings_count) { // Build nodes @@ -15441,6 +17708,7 @@ static void ImGui::DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDoc node->SelectedTabId = settings->SelectedTabId; node->SplitAxis = (ImGuiAxis)settings->SplitAxis; node->SetLocalFlags(settings->Flags & ImGuiDockNodeFlags_SavedFlagsMask_); + // Bind host window immediately if it already exist (in case of a rebuild) // This is useful as the RootWindowForTitleBarHighlight links necessary to highlight the currently focused node requires node->HostWindow to be set. char host_window_title[20]; @@ -15448,6 +17716,7 @@ static void ImGui::DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDoc node->HostWindow = FindWindowByName(DockNodeGetHostWindowTitle(root_node, host_window_title, IM_ARRAYSIZE(host_window_title))); } } + void ImGui::DockContextBuildAddWindowsToNodes(ImGuiContext* ctx, ImGuiID root_id) { // Rebind all windows to nodes (they can also lazily rebind but we'll have a visible glitch during the first frame) @@ -15458,12 +17727,14 @@ void ImGui::DockContextBuildAddWindowsToNodes(ImGuiContext* ctx, ImGuiID root_id continue; if (window->DockNode != NULL) continue; + ImGuiDockNode* node = DockContextFindNodeByID(ctx, window->DockId); IM_ASSERT(node != NULL); // This should have been called after DockContextBuildNodesFromSettings() if (root_id == 0 || DockNodeGetRootNode(node)->ID == root_id) DockNodeAddWindow(node, window, true); } } + //----------------------------------------------------------------------------- // Docking: ImGuiDockContext Docking/Undocking functions //----------------------------------------------------------------------------- @@ -15476,6 +17747,7 @@ void ImGui::DockContextBuildAddWindowsToNodes(ImGuiContext* ctx, ImGuiID root_id // - DockContextProcessUndockNode() // - DockContextCalcDropPosForDocking() //----------------------------------------------------------------------------- + void ImGui::DockContextQueueDock(ImGuiContext* ctx, ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload, ImGuiDir split_dir, float split_ratio, bool split_outer) { IM_ASSERT(target != payload); @@ -15489,6 +17761,7 @@ void ImGui::DockContextQueueDock(ImGuiContext* ctx, ImGuiWindow* target, ImGuiDo req.DockSplitOuter = split_outer; ctx->DockContext.Requests.push_back(req); } + void ImGui::DockContextQueueUndockWindow(ImGuiContext* ctx, ImGuiWindow* window) { ImGuiDockRequest req; @@ -15496,6 +17769,7 @@ void ImGui::DockContextQueueUndockWindow(ImGuiContext* ctx, ImGuiWindow* window) req.UndockTargetWindow = window; ctx->DockContext.Requests.push_back(req); } + void ImGui::DockContextQueueUndockNode(ImGuiContext* ctx, ImGuiDockNode* node) { ImGuiDockRequest req; @@ -15503,6 +17777,7 @@ void ImGui::DockContextQueueUndockNode(ImGuiContext* ctx, ImGuiDockNode* node) req.UndockTargetNode = node; ctx->DockContext.Requests.push_back(req); } + void ImGui::DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* node) { ImGuiDockContext* dc = &ctx->DockContext; @@ -15510,12 +17785,15 @@ void ImGui::DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* if (req.DockTargetNode == node) req.Type = ImGuiDockRequestType_None; } + void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req) { IM_ASSERT((req->Type == ImGuiDockRequestType_Dock && req->DockPayload != NULL) || (req->Type == ImGuiDockRequestType_Split && req->DockPayload == NULL)); IM_ASSERT(req->DockTargetWindow != NULL || req->DockTargetNode != NULL); + ImGuiContext& g = *ctx; IM_UNUSED(g); + ImGuiWindow* payload_window = req->DockPayload; // Optional ImGuiWindow* target_window = req->DockTargetWindow; ImGuiDockNode* node = req->DockTargetNode; @@ -15523,6 +17801,7 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req) IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessDock node 0x%08X target '%s' dock window '%s', split_dir %d\n", node ? node->ID : 0, target_window ? target_window->Name : "NULL", payload_window->Name, req->DockSplitDir); else IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessDock node 0x%08X, split_dir %d\n", node ? node->ID : 0, req->DockSplitDir); + // Decide which Tab will be selected at the end of the operation ImGuiID next_selected_id = 0; ImGuiDockNode* payload_node = NULL; @@ -15535,12 +17814,14 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req) if (payload_node == NULL) next_selected_id = payload_window->TabId; } + // FIXME-DOCK: When we are trying to dock an existing single-window node into a loose window, transfer Node ID as well // When processing an interactive split, usually LastFrameAlive will be < g.FrameCount. But DockBuilder operations can make it ==. if (node) IM_ASSERT(node->LastFrameAlive <= g.FrameCount); if (node && target_window && node == target_window->DockNodeAsHost) IM_ASSERT(node->Windows.Size > 0 || node->IsSplitNode() || node->IsCentralNode()); + // Create new node and add existing window to it if (node == NULL) { @@ -15554,6 +17835,7 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req) target_window->DockIsActive = true; } } + ImGuiDir split_dir = req->DockSplitDir; if (split_dir != ImGuiDir_None) { @@ -15567,6 +17849,7 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req) node = new_node; } node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar); + if (node != payload_node) { // Create tab bar before we call DockNodeMoveWindows (which would attempt to move the old tab-bar, which would lead us to payload tabs wrongly appearing before target tabs!) @@ -15576,6 +17859,7 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req) for (int n = 0; n < node->Windows.Size; n++) TabBarAddTab(node->TabBar, ImGuiTabItemFlags_None, node->Windows[n]); } + if (payload_node != NULL) { // Transfer full payload node (with 1+ child windows or child nodes) @@ -15606,6 +17890,7 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req) node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_CentralNode); last_focused_root_node->CentralNode = last_focused_node; } + IM_ASSERT(node->Windows.Size == 0); DockNodeMoveChildNodes(node, payload_node); } @@ -15632,11 +17917,13 @@ void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req) // When docking a floating single window node we want to reevaluate auto-hiding of the tab bar node->WantHiddenTabBarUpdate = true; } + // Update selection immediately if (ImGuiTabBar* tab_bar = node->TabBar) tab_bar->NextSelectedTabId = next_selected_id; MarkIniSettingsDirty(); } + // Problem: // Undocking a large (~full screen) window would leave it so large that the bottom right sizing corner would more // than likely be off the screen and the window would be hard to resize to fit on screen. This can be particularly problematic @@ -15649,6 +17936,7 @@ static ImVec2 FixLargeWindowsWhenUndocking(const ImVec2& size, ImGuiViewport* re { if (ref_viewport == NULL) return size; + ImGuiContext& g = *GImGui; ImVec2 max_size = ImTrunc(ref_viewport->WorkSize * 0.90f); if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) @@ -15658,6 +17946,7 @@ static ImVec2 FixLargeWindowsWhenUndocking(const ImVec2& size, ImGuiViewport* re } return ImMin(size, max_size); } + void ImGui::DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* window, bool clear_persistent_docking_ref) { ImGuiContext& g = *ctx; @@ -15670,14 +17959,17 @@ void ImGui::DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* windo window->DockIsActive = false; window->DockNodeIsVisible = window->DockTabIsVisible = false; window->Size = window->SizeFull = FixLargeWindowsWhenUndocking(window->SizeFull, window->Viewport); + MarkIniSettingsDirty(); } + void ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node) { ImGuiContext& g = *ctx; IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessUndockNode node %08X\n", node->ID); IM_ASSERT(node->IsLeafNode()); IM_ASSERT(node->Windows.Size >= 1); + if (node->IsRootNode() || node->IsCentralNode()) { // In the case of a root node or central node, the node will have to stay in place. Create a new node to receive the payload. @@ -15711,11 +18003,13 @@ void ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node) node->WantMouseMove = true; MarkIniSettingsDirty(); } + // This is mostly used for automation. bool ImGui::DockContextCalcDropPosForDocking(ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload_window, ImGuiDockNode* payload_node, ImGuiDir split_dir, bool split_outer, ImVec2* out_pos) { if (target != NULL && target_node == NULL) target_node = target->DockNode; + // In DockNodePreviewDockSetup() for a root central node instead of showing both "inner" and "outer" drop rects // (which would be functionally identical) we only show the outer one. Reflect this here. if (target_node && target_node->ParentNode == NULL && target_node->IsCentralNode() && split_dir != ImGuiDir_None) @@ -15727,6 +18021,7 @@ bool ImGui::DockContextCalcDropPosForDocking(ImGuiWindow* target, ImGuiDockNode* *out_pos = split_data.DropRectsDraw[split_dir+1].GetCenter(); return true; } + //----------------------------------------------------------------------------- // Docking: ImGuiDockNode //----------------------------------------------------------------------------- @@ -15759,6 +18054,7 @@ bool ImGui::DockContextCalcDropPosForDocking(ImGuiWindow* target, ImGuiDockNode* // - DockNodePreviewDockSetup() // - DockNodePreviewDockRender() //----------------------------------------------------------------------------- + ImGuiDockNode::ImGuiDockNode(ImGuiID id) { ID = id; @@ -15766,6 +18062,7 @@ ImGuiDockNode::ImGuiDockNode(ImGuiID id) ParentNode = ChildNodes[0] = ChildNodes[1] = NULL; TabBar = NULL; SplitAxis = ImGuiAxis_None; + State = ImGuiDockNodeState_Unknown; LastBgColor = IM_COL32_WHITE; HostWindow = VisibleWindow = NULL; @@ -15783,12 +18080,14 @@ ImGuiDockNode::ImGuiDockNode(ImGuiID id) IsBgDrawnThisFrame = false; WantCloseAll = WantLockSizeOnce = WantMouseMove = WantHiddenTabBarUpdate = WantHiddenTabBarToggle = false; } + ImGuiDockNode::~ImGuiDockNode() { IM_DELETE(TabBar); TabBar = NULL; ChildNodes[0] = ChildNodes[1] = NULL; } + int ImGui::DockNodeGetTabOrder(ImGuiWindow* window) { ImGuiTabBar* tab_bar = window->DockNode->TabBar; @@ -15797,11 +18096,13 @@ int ImGui::DockNodeGetTabOrder(ImGuiWindow* window) ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, window->TabId); return tab ? TabBarGetTabOrder(tab_bar, tab) : -1; } + static void DockNodeHideWindowDuringHostWindowCreation(ImGuiWindow* window) { window->Hidden = true; window->HiddenFramesCanSkipItems = window->Active ? 1 : 2; } + static void ImGui::DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, bool add_to_tab_bar) { ImGuiContext& g = *GImGui; (void)g; @@ -15813,17 +18114,20 @@ static void ImGui::DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, b } IM_ASSERT(window->DockNode == NULL || window->DockNodeAsHost == NULL); IMGUI_DEBUG_LOG_DOCKING("[docking] DockNodeAddWindow node 0x%08X window '%s'\n", node->ID, window->Name); + // If more than 2 windows appeared on the same frame leading to the creation of a new hosting window, // we'll hide windows until the host window is ready. Hide the 1st window after its been output (so it is not visible for one frame). // We will call DockNodeHideWindowDuringHostWindowCreation() on ourselves in Begin() if (node->HostWindow == NULL && node->Windows.Size == 1 && node->Windows[0]->WasActive == false) DockNodeHideWindowDuringHostWindowCreation(node->Windows[0]); + node->Windows.push_back(window); node->WantHiddenTabBarUpdate = true; window->DockNode = node; window->DockId = node->ID; window->DockIsActive = (node->Windows.Size > 1); window->DockTabWantClose = false; + // When reactivating a node with one or two loose window, the window pos/size/viewport are authoritative over the node storage. // In particular it is important we init the viewport from the first window so we don't create two viewports and drop one. if (node->HostWindow == NULL && node->IsFloatingNode()) @@ -15835,6 +18139,7 @@ static void ImGui::DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, b if (node->AuthorityForViewport == ImGuiDataAuthority_Auto) node->AuthorityForViewport = ImGuiDataAuthority_Window; } + // Add to tab bar if requested if (add_to_tab_bar) { @@ -15842,17 +18147,21 @@ static void ImGui::DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, b { DockNodeAddTabBar(node); node->TabBar->SelectedTabId = node->TabBar->NextSelectedTabId = node->SelectedTabId; + // Add existing windows for (int n = 0; n < node->Windows.Size - 1; n++) TabBarAddTab(node->TabBar, ImGuiTabItemFlags_None, node->Windows[n]); } TabBarAddTab(node->TabBar, ImGuiTabItemFlags_Unsorted, window); } + DockNodeUpdateVisibleFlag(node); + // Update this without waiting for the next time we Begin() in the window, so our host window will have the proper title bar color on its first frame. if (node->HostWindow) UpdateWindowParentAndRootLinks(window, window->Flags | ImGuiWindowFlags_ChildWindow, node->HostWindow); } + static void ImGui::DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window, ImGuiID save_dock_id) { ImGuiContext& g = *GImGui; @@ -15861,6 +18170,7 @@ static void ImGui::DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window //IM_ASSERT(window->LastFrameActive < g.FrameCount); // We may call this from Begin() IM_ASSERT(save_dock_id == 0 || save_dock_id == node->ID); IMGUI_DEBUG_LOG_DOCKING("[docking] DockNodeRemoveWindow node 0x%08X window '%s'\n", node->ID, window->Name); + window->DockNode = NULL; window->DockIsActive = window->DockTabWantClose = false; window->DockId = save_dock_id; @@ -15868,6 +18178,7 @@ static void ImGui::DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window if (window->ParentWindow) window->ParentWindow->DC.ChildWindows.find_erase(window); UpdateWindowParentAndRootLinks(window, window->Flags, NULL); // Update immediately + if (node->HostWindow && node->HostWindow->ViewportOwned) { // When undocking from a user interaction this will always run in NewFrame() and have not much effect. @@ -15877,6 +18188,7 @@ static void ImGui::DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window window->ViewportOwned = false; window->Hidden = true; } + // Remove window bool erased = false; for (int n = 0; n < node->Windows.Size; n++) @@ -15890,6 +18202,7 @@ static void ImGui::DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window IM_ASSERT(erased); if (node->VisibleWindow == window) node->VisibleWindow = NULL; + // Remove tab and possibly tab bar node->WantHiddenTabBarUpdate = true; if (node->TabBar) @@ -15899,21 +18212,25 @@ static void ImGui::DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window if (node->Windows.Size < tab_count_threshold_for_tab_bar) DockNodeRemoveTabBar(node); } + if (node->Windows.Size == 0 && !node->IsCentralNode() && !node->IsDockSpace() && window->DockId != node->ID) { // Automatic dock node delete themselves if they are not holding at least one tab DockContextRemoveNode(&g, node, true); return; } + if (node->Windows.Size == 1 && !node->IsCentralNode() && node->HostWindow) { ImGuiWindow* remaining_window = node->Windows[0]; // Note: we used to transport viewport ownership here. remaining_window->Collapsed = node->HostWindow->Collapsed; } + // Update visibility immediately is required so the DockNodeUpdateRemoveInactiveChilds() processing can reflect changes up the tree DockNodeUpdateVisibleFlag(node); } + static void ImGui::DockNodeMoveChildNodes(ImGuiDockNode* dst_node, ImGuiDockNode* src_node) { IM_ASSERT(dst_node->Windows.Size == 0); @@ -15927,6 +18244,7 @@ static void ImGui::DockNodeMoveChildNodes(ImGuiDockNode* dst_node, ImGuiDockNode dst_node->SizeRef = src_node->SizeRef; src_node->ChildNodes[0] = src_node->ChildNodes[1] = NULL; } + static void ImGui::DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* src_node) { // Insert tabs in the same orders as currently ordered (node->Windows isn't ordered) @@ -15934,6 +18252,7 @@ static void ImGui::DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* s ImGuiTabBar* src_tab_bar = src_node->TabBar; if (src_tab_bar != NULL) IM_ASSERT(src_node->Windows.Size <= src_node->TabBar->Tabs.Size); + // If the dst_node is empty we can just move the entire tab bar (to preserve selection, scrolling, etc.) bool move_tab_bar = (src_tab_bar != NULL) && (dst_node->TabBar == NULL); if (move_tab_bar) @@ -15941,6 +18260,7 @@ static void ImGui::DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* s dst_node->TabBar = src_node->TabBar; src_node->TabBar = NULL; } + // Tab order is not important here, it is preserved by sorting in DockNodeUpdateTabBar(). for (ImGuiWindow* window : src_node->Windows) { @@ -15949,6 +18269,7 @@ static void ImGui::DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* s DockNodeAddWindow(dst_node, window, !move_tab_bar); } src_node->Windows.clear(); + if (!move_tab_bar && src_node->TabBar) { if (dst_node->TabBar) @@ -15956,6 +18277,7 @@ static void ImGui::DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* s DockNodeRemoveTabBar(src_node); } } + static void ImGui::DockNodeApplyPosSizeToWindows(ImGuiDockNode* node) { for (ImGuiWindow* window : node->Windows) @@ -15964,6 +18286,7 @@ static void ImGui::DockNodeApplyPosSizeToWindows(ImGuiDockNode* node) SetWindowSize(window, node->Size, ImGuiCond_Always); } } + static void ImGui::DockNodeHideHostWindow(ImGuiDockNode* node) { if (node->HostWindow) @@ -15972,14 +18295,17 @@ static void ImGui::DockNodeHideHostWindow(ImGuiDockNode* node) node->HostWindow->DockNodeAsHost = NULL; node->HostWindow = NULL; } + if (node->Windows.Size == 1) { node->VisibleWindow = node->Windows[0]; node->Windows[0]->DockIsActive = false; } + if (node->TabBar) DockNodeRemoveTabBar(node); } + // Search function called once by root node in DockNodeUpdate() struct ImGuiDockNodeTreeInfo { @@ -15987,8 +18313,10 @@ struct ImGuiDockNodeTreeInfo ImGuiDockNode* FirstNodeWithWindows; int CountNodesWithWindows; //ImGuiWindowClass WindowClassForMerges; + ImGuiDockNodeTreeInfo() { memset(this, 0, sizeof(*this)); } }; + static void DockNodeFindInfo(ImGuiDockNode* node, ImGuiDockNodeTreeInfo* info) { if (node->Windows.Size > 0) @@ -16010,6 +18338,7 @@ static void DockNodeFindInfo(ImGuiDockNode* node, ImGuiDockNodeTreeInfo* info) if (node->ChildNodes[1]) DockNodeFindInfo(node->ChildNodes[1], info); } + static ImGuiWindow* ImGui::DockNodeFindWindowByID(ImGuiDockNode* node, ImGuiID id) { IM_ASSERT(id != 0); @@ -16018,15 +18347,18 @@ static ImGuiWindow* ImGui::DockNodeFindWindowByID(ImGuiDockNode* node, ImGuiID i return window; return NULL; } + // - Remove inactive windows/nodes. // - Update visibility flag. static void ImGui::DockNodeUpdateFlagsAndCollapse(ImGuiDockNode* node) { ImGuiContext& g = *GImGui; IM_ASSERT(node->ParentNode == NULL || node->ParentNode->ChildNodes[0] == node || node->ParentNode->ChildNodes[1] == node); + // Inherit most flags if (node->ParentNode) node->SharedFlags = node->ParentNode->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_; + // Recurse into children // There is the possibility that one of our child becoming empty will delete itself and moving its sibling contents into 'node'. // If 'node->ChildNode[0]' delete itself, then 'node->ChildNode[1]->Windows' will be moved into 'node' @@ -16036,6 +18368,7 @@ static void ImGui::DockNodeUpdateFlagsAndCollapse(ImGuiDockNode* node) DockNodeUpdateFlagsAndCollapse(node->ChildNodes[0]); if (node->ChildNodes[1]) DockNodeUpdateFlagsAndCollapse(node->ChildNodes[1]); + // Remove inactive windows, collapse nodes // Merge node flags overrides stored in windows node->LocalFlagsInWindows = ImGuiDockNodeFlags_None; @@ -16043,6 +18376,7 @@ static void ImGui::DockNodeUpdateFlagsAndCollapse(ImGuiDockNode* node) { ImGuiWindow* window = node->Windows[window_n]; IM_ASSERT(window->DockNode == node); + bool node_was_active = (node->LastFrameActive + 1 == g.FrameCount); bool remove = false; remove |= node_was_active && (window->LastFrameActive + 1 < g.FrameCount); @@ -16062,27 +18396,33 @@ static void ImGui::DockNodeUpdateFlagsAndCollapse(ImGuiDockNode* node) window_n--; continue; } + // FIXME-DOCKING: Missing policies for conflict resolution, hence the "Experimental" tag on this. //node->LocalFlagsInWindow &= ~window->WindowClass.DockNodeFlagsOverrideClear; node->LocalFlagsInWindows |= window->WindowClass.DockNodeFlagsOverrideSet; } node->UpdateMergedFlags(); + // Auto-hide tab bar option ImGuiDockNodeFlags node_flags = node->MergedFlags; if (node->WantHiddenTabBarUpdate && node->Windows.Size == 1 && (node_flags & ImGuiDockNodeFlags_AutoHideTabBar) && !node->IsHiddenTabBar()) node->WantHiddenTabBarToggle = true; node->WantHiddenTabBarUpdate = false; + // Cancel toggling if we know our tab bar is enforced to be hidden at all times if (node->WantHiddenTabBarToggle && node->VisibleWindow && (node->VisibleWindow->WindowClass.DockNodeFlagsOverrideSet & ImGuiDockNodeFlags_HiddenTabBar)) node->WantHiddenTabBarToggle = false; + // Apply toggles at a single point of the frame (here!) if (node->Windows.Size > 1) node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar); else if (node->WantHiddenTabBarToggle) node->SetLocalFlags(node->LocalFlags ^ ImGuiDockNodeFlags_HiddenTabBar); node->WantHiddenTabBarToggle = false; + DockNodeUpdateVisibleFlag(node); } + // This is rarely called as DockNodeUpdateForRootNode() generally does it most frames. static void ImGui::DockNodeUpdateHasCentralNodeChild(ImGuiDockNode* node) { @@ -16101,6 +18441,7 @@ static void ImGui::DockNodeUpdateHasCentralNodeChild(ImGuiDockNode* node) } } } + static void ImGui::DockNodeUpdateVisibleFlag(ImGuiDockNode* node) { // Update visibility flag @@ -16110,6 +18451,7 @@ static void ImGui::DockNodeUpdateVisibleFlag(ImGuiDockNode* node) is_visible |= (node->ChildNodes[1] && node->ChildNodes[1]->IsVisible); node->IsVisible = is_visible; } + static void ImGui::DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -16119,10 +18461,12 @@ static void ImGui::DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWind g.MovingWindow = window; // If we are docked into a non moveable root window, StartMouseMovingWindow() won't set g.MovingWindow. Override that decision. node->WantMouseMove = false; } + // Update CentralNode, OnlyNodeWithWindows, LastFocusedNodeID. Copy window class. static void ImGui::DockNodeUpdateForRootNode(ImGuiDockNode* node) { DockNodeUpdateFlagsAndCollapse(node); + // - Setup central node pointers // - Find if there's only a single visible window in the hierarchy (in which case we need to display a regular title bar -> FIXME-DOCK: that last part is not done yet!) // Cannot merge this with DockNodeUpdateFlagsAndCollapse() because FirstNodeWithWindows is found after window removal and child collapsing @@ -16133,6 +18477,7 @@ static void ImGui::DockNodeUpdateForRootNode(ImGuiDockNode* node) node->CountNodeWithWindows = info.CountNodesWithWindows; if (node->LastFocusedNodeId == 0 && info.FirstNodeWithWindows != NULL) node->LastFocusedNodeId = info.FirstNodeWithWindows->ID; + // Copy the window class from of our first window so it can be used for proper dock filtering. // When node has mixed windows, prioritize the class with the most constraint (DockingAllowUnclassed = false) as the reference to copy. // FIXME-DOCK: We don't recurse properly, this code could be reworked to work from DockNodeUpdateScanRec. @@ -16146,6 +18491,7 @@ static void ImGui::DockNodeUpdateForRootNode(ImGuiDockNode* node) break; } } + ImGuiDockNode* mark_node = node->CentralNode; while (mark_node) { @@ -16153,6 +18499,7 @@ static void ImGui::DockNodeUpdateForRootNode(ImGuiDockNode* node) mark_node = mark_node->ParentNode; } } + static void DockNodeSetupHostWindow(ImGuiDockNode* node, ImGuiWindow* host_window) { // Remove ourselves from any previous different host window @@ -16162,21 +18509,26 @@ static void DockNodeSetupHostWindow(ImGuiDockNode* node, ImGuiWindow* host_windo // - N+1: DockSpace(id) // requalify node as dockspace, moving host window if (node->HostWindow && node->HostWindow != host_window && node->HostWindow->DockNodeAsHost == node) node->HostWindow->DockNodeAsHost = NULL; + host_window->DockNodeAsHost = node; node->HostWindow = host_window; } + static void ImGui::DockNodeUpdate(ImGuiDockNode* node) { ImGuiContext& g = *GImGui; IM_ASSERT(node->LastFrameActive != g.FrameCount); node->LastFrameAlive = g.FrameCount; node->IsBgDrawnThisFrame = false; + node->CentralNode = node->OnlyNodeWithWindows = NULL; if (node->IsRootNode()) DockNodeUpdateForRootNode(node); + // Remove tab bar if not needed if (node->TabBar && node->IsNoTabBar()) DockNodeRemoveTabBar(node); + // Early out for hidden root dock nodes (when all DockId references are in inactive windows, or there is only 1 floating window holding on the DockId) bool want_to_hide_host_window = false; if (node->IsFloatingNode()) @@ -16196,6 +18548,7 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) node->Pos = single_window->Pos; node->Size = single_window->SizeFull; node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Window; + // Transfer focus immediately so when we revert to a regular window it is immediately selected if (node->HostWindow && g.NavWindow == node->HostWindow) FocusWindow(single_window); @@ -16213,16 +18566,19 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) } node->RefViewportId = single_window->ViewportId; } + DockNodeHideHostWindow(node); node->State = ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow; node->WantCloseAll = false; node->WantCloseTabId = 0; node->HasCloseButton = node->HasWindowMenuButton = false; node->LastFrameActive = g.FrameCount; + if (node->WantMouseMove && node->Windows.Size == 1) DockNodeStartMouseMovingWindow(node, node->Windows[0]); return; } + // In some circumstance we will defer creating the host window (so everything will be kept hidden), // while the expected visible window is resizing itself. // This is important for first-time (no ini settings restored) single window when io.ConfigDockingAlwaysTabBar is enabled, @@ -16247,7 +18603,9 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) return; } } + const ImGuiDockNodeFlags node_flags = node->MergedFlags; + // Decide if the node will have a close button and a window menu button node->HasWindowMenuButton = (node->Windows.Size > 0) && (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0; node->HasCloseButton = false; @@ -16259,6 +18617,7 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) } if (node_flags & ImGuiDockNodeFlags_NoCloseButton) node->HasCloseButton = false; + // Bind or create host window ImGuiWindow* host_window = NULL; bool beginned_into_host_window = false; @@ -16274,25 +18633,31 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) if (node->IsRootNode() && node->IsVisible) { ImGuiWindow* ref_window = (node->Windows.Size > 0) ? node->Windows[0] : NULL; + // Sync Pos if (node->AuthorityForPos == ImGuiDataAuthority_Window && ref_window) SetNextWindowPos(ref_window->Pos); else if (node->AuthorityForPos == ImGuiDataAuthority_DockNode) SetNextWindowPos(node->Pos); + // Sync Size if (node->AuthorityForSize == ImGuiDataAuthority_Window && ref_window) SetNextWindowSize(ref_window->SizeFull); else if (node->AuthorityForSize == ImGuiDataAuthority_DockNode) SetNextWindowSize(node->Size); + // Sync Collapsed if (node->AuthorityForSize == ImGuiDataAuthority_Window && ref_window) SetNextWindowCollapsed(ref_window->Collapsed); + // Sync Viewport if (node->AuthorityForViewport == ImGuiDataAuthority_Window && ref_window) SetNextWindowViewport(ref_window->ViewportId); else if (node->AuthorityForViewport == ImGuiDataAuthority_Window && node->RefViewportId != 0) SetNextWindowViewport(node->RefViewportId); + SetNextWindowClass(&node->WindowClass); + // Begin into the host window char window_label[20]; DockNodeGetHostWindowTitle(node, window_label, IM_ARRAYSIZE(window_label)); @@ -16300,16 +18665,19 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) window_flags |= ImGuiWindowFlags_NoFocusOnAppearing; window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoCollapse; window_flags |= ImGuiWindowFlags_NoTitleBar; + SetNextWindowBgAlpha(0.0f); // Don't set ImGuiWindowFlags_NoBackground because it disables borders PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); Begin(window_label, NULL, window_flags); PopStyleVar(); beginned_into_host_window = true; + host_window = g.CurrentWindow; DockNodeSetupHostWindow(node, host_window); host_window->DC.CursorPos = host_window->Pos; node->Pos = host_window->Pos; node->Size = host_window->Size; + // We set ImGuiWindowFlags_NoFocusOnAppearing because we don't want the host window to take full focus (e.g. steal NavWindow) // But we still it bring it to the front of display. There's no way to choose this precise behavior via window flags. // One simple case to ponder if: window A has a toggle to create windows B/C/D. Dock B/C/D together, clear the toggle and enable it again. @@ -16318,6 +18686,7 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) // after the dock host window, losing their top-most status. if (node->HostWindow->Appearing) BringWindowToDisplayFront(node->HostWindow); + node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Auto; } else if (node->ParentNode) @@ -16329,6 +18698,7 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) DockNodeStartMouseMovingWindow(node, node->HostWindow); } node->RefViewportId = 0; // Clear when we have a host window + // Update focused node (the one whose title bar is highlight) within a node tree if (node->IsSplitNode()) IM_ASSERT(node->TabBar == NULL); @@ -16344,6 +18714,7 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) } p_window = p_node->HostWindow ? p_node->HostWindow->RootWindow : NULL; } + // Register a hit-test hole in the window unless we are currently dragging a window that is compatible with our dockspace ImGuiDockNode* central_node = node->CentralNode; const bool central_node_hole = node->IsRootNode() && host_window && (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0 && central_node != NULL && central_node->IsEmpty(); @@ -16373,6 +18744,7 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) SetWindowHitTestHole(host_window->ParentWindow, hole_rect.Min, hole_rect.Max - hole_rect.Min); } } + // Update position/size, process and draw resizing splitters if (node->IsRootNode() && host_window) { @@ -16383,6 +18755,7 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) DockNodeTreeUpdateSplitter(node); PopStyleColor(3); } + // Draw empty node background (currently can only be the Central Node) if (host_window && node->IsEmpty() && node->IsVisible) { @@ -16392,6 +18765,7 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, node->LastBgColor); node->IsBgDrawnThisFrame = true; } + // Draw whole dockspace background if ImGuiDockNodeFlags_PassthruCentralNode if set. // We need to draw a background at the root level if requested by ImGuiDockNodeFlags_PassthruCentralNode, but we will only know the correct pos/size // _after_ processing the resizing splitters. So we are using the DrawList channel splitting facility to submit drawing primitives out of order! @@ -16404,6 +18778,7 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) else host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, GetColorU32(ImGuiCol_WindowBg), 0.0f); } + // Draw and populate Tab Bar if (host_window) host_window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG); @@ -16421,12 +18796,15 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) node->SelectedTabId = node->TabBar->SelectedTabId; else if (node->Windows.Size > 0) node->SelectedTabId = node->Windows[0]->TabId; + // Draw payload drop target if (host_window && node->IsVisible) if (node->IsRootNode() && (g.MovingWindow == NULL || g.MovingWindow->RootWindowDockTree != host_window)) BeginDockableDragDropTarget(host_window); + // We update this after DockNodeUpdateTabBar() node->LastFrameActive = g.FrameCount; + // Recurse into children // FIXME-DOCK FIXME-OPT: Should not need to recurse into children if (host_window) @@ -16435,14 +18813,17 @@ static void ImGui::DockNodeUpdate(ImGuiDockNode* node) DockNodeUpdate(node->ChildNodes[0]); if (node->ChildNodes[1]) DockNodeUpdate(node->ChildNodes[1]); + // Render outer borders last (after the tab bar) if (node->IsRootNode()) RenderWindowOuterBorders(host_window); } + // End host window if (beginned_into_host_window) //-V1020 End(); } + // Compare TabItem nodes given the last known DockOrder (will persist in .ini file as hint), used to sort tabs when multiple tabs are added on the same frame. static int IMGUI_CDECL TabItemComparerByDockOrder(const void* lhs, const void* rhs) { @@ -16452,6 +18833,7 @@ static int IMGUI_CDECL TabItemComparerByDockOrder(const void* lhs, const void* r return d; return (a->BeginOrderWithinContext - b->BeginOrderWithinContext); } + // Default handler for g.DockNodeWindowMenuHandler(): display the list of windows for a given dock-node. // This is exceptionally stored in a function pointer to also user applications to tweak this menu (undocumented) // Custom overrides may want to decorate, group, sort entries. @@ -16481,6 +18863,7 @@ void ImGui::DockNodeWindowMenuHandler_Default(ImGuiContext* ctx, ImGuiDockNode* } } } + static void ImGui::DockNodeWindowMenuUpdate(ImGuiDockNode* node, ImGuiTabBar* tab_bar) { // Try to position the menu so it is more likely to stays within the same viewport @@ -16496,6 +18879,7 @@ static void ImGui::DockNodeWindowMenuUpdate(ImGuiDockNode* node, ImGuiTabBar* ta EndPopup(); } } + // User helper to append/amend into a dock node tab bar. Most commonly used to add e.g. a "+" button. bool ImGui::DockNodeBeginAmendTabBar(ImGuiDockNode* node) { @@ -16512,18 +18896,21 @@ bool ImGui::DockNodeBeginAmendTabBar(ImGuiDockNode* node) IM_ASSERT(ret); return true; } + void ImGui::DockNodeEndAmendTabBar() { EndTabBar(); PopID(); End(); } + static bool IsDockNodeTitleBarHighlighted(ImGuiDockNode* node, ImGuiDockNode* root_node) { // CTRL+Tab highlight (only highlighting leaf node, not whole hierarchy) ImGuiContext& g = *GImGui; if (g.NavWindowingTarget) return (g.NavWindowingTarget->DockNode == node); + // FIXME-DOCKING: May want alternative to treat central node void differently? e.g. if (g.NavWindow == host_window) if (g.NavWindow && root_node->LastFocusedNodeId == node->ID) { @@ -16538,21 +18925,23 @@ static bool IsDockNodeTitleBarHighlighted(ImGuiDockNode* node, ImGuiDockNode* ro } return false; } + // Submit the tab bar corresponding to a dock node and various housekeeping details. static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_window) { ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; + const bool node_was_active = (node->LastFrameActive + 1 == g.FrameCount); - const bool closed_all = node->WantCloseAll && node_was_active; - const ImGuiID closed_one = node->WantCloseTabId && node_was_active; node->WantCloseAll = false; node->WantCloseTabId = 0; + // Decide if we should use a focused title bar color bool is_focused = false; ImGuiDockNode* root_node = DockNodeGetRootNode(node); if (IsDockNodeTitleBarHighlighted(node, root_node)) is_focused = true; + // Hidden tab bar will show a triangle on the upper-left (in Begin) if (node->IsHiddenTabBar() || node->IsNoTabBar()) { @@ -16570,6 +18959,7 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w } return; } + // Move ourselves to the Menu layer (so we can be accessed by tapping Alt) + undo SkipItems flag in order to draw over the title bar even if the window is collapsed bool backup_skip_item = host_window->SkipItems; if (!node->IsDockSpace()) @@ -16577,6 +18967,7 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w host_window->SkipItems = false; host_window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; } + // Use PushOverrideID() instead of PushID() to use the node id _without_ the host window ID. // This is to facilitate computing those ID from the outside, and will affect more or less only the ID of the collapse button, popup and tabs, // as docked windows themselves will override the stack with their own root ID. @@ -16588,10 +18979,13 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w DockNodeAddTabBar(node); tab_bar = node->TabBar; } + ImGuiID focus_tab_id = 0; node->IsFocused = is_focused; + const ImGuiDockNodeFlags node_flags = node->MergedFlags; const bool has_window_menu_button = (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0 && (style.WindowMenuButtonPosition != ImGuiDir_None); + // In a dock node, the Collapse Button turns into the Window Menu button. // FIXME-DOCK FIXME-OPT: Could we recycle popups id across multiple dock nodes? if (has_window_menu_button && IsPopupOpen("#WindowMenu")) @@ -16602,11 +18996,13 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w focus_tab_id = tab_bar->NextSelectedTabId; is_focused |= node->IsFocused; } + // Layout ImRect title_bar_rect, tab_bar_rect; ImVec2 window_menu_button_pos; ImVec2 close_button_pos; DockNodeCalcTabBarLayout(node, &title_bar_rect, &tab_bar_rect, &window_menu_button_pos, &close_button_pos); + // Submit new tabs, they will be added as Unsorted and sorted below based on relative DockOrder value. const int tabs_count_old = tab_bar->Tabs.Size; for (int window_n = 0; window_n < node->Windows.Size; window_n++) @@ -16615,12 +19011,14 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w if (TabBarFindTabByID(tab_bar, window->TabId) == NULL) TabBarAddTab(tab_bar, ImGuiTabItemFlags_Unsorted, window); } + // Title bar if (is_focused) node->LastFrameFocused = g.FrameCount; ImU32 title_bar_col = GetColorU32(host_window->Collapsed ? ImGuiCol_TitleBgCollapsed : is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); ImDrawFlags rounding_flags = CalcRoundingFlagsForRectInRect(title_bar_rect, host_window->Rect(), g.Style.DockingSeparatorSize); host_window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, host_window->WindowRounding, rounding_flags); + // Docking/Collapse button if (has_window_menu_button) { @@ -16631,6 +19029,7 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w if (IsItemHovered(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_DelayNormal) && g.HoveredIdTimer > 0.5f) SetTooltip("%s", LocalizeGetMsg(ImGuiLocKey_DockingDragToUndockOrMoveNode)); } + // If multiple tabs are appearing on the same frame, sort them based on their persistent DockOrder value int tabs_unsorted_start = tab_bar->Tabs.Size; for (int tab_n = tab_bar->Tabs.Size - 1; tab_n >= 0 && (tab_bar->Tabs[tab_n].Flags & ImGuiTabItemFlags_Unsorted); tab_n--) @@ -16652,17 +19051,21 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w if (tab_bar->Tabs.Size > tabs_unsorted_start + 1) ImQsort(tab_bar->Tabs.Data + tabs_unsorted_start, tab_bar->Tabs.Size - tabs_unsorted_start, sizeof(ImGuiTabItem), TabItemComparerByDockOrder); } + // Apply NavWindow focus back to the tab bar if (g.NavWindow && g.NavWindow->RootWindow->DockNode == node) tab_bar->SelectedTabId = g.NavWindow->RootWindow->TabId; + // Selected newly added tabs, or persistent tab ID if the tab bar was just recreated if (tab_bar_is_recreated && TabBarFindTabByID(tab_bar, node->SelectedTabId) != NULL) tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = node->SelectedTabId; else if (tab_bar->Tabs.Size > tabs_count_old) tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = tab_bar->Tabs.back().Window->TabId; + // Begin tab bar ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_AutoSelectNewTabs; // | ImGuiTabBarFlags_NoTabListScrollingButtons); - tab_bar_flags |= ImGuiTabBarFlags_SaveSettings | ImGuiTabBarFlags_DockNode;// | ImGuiTabBarFlags_FittingPolicyScroll; + tab_bar_flags |= ImGuiTabBarFlags_SaveSettings | ImGuiTabBarFlags_DockNode; + tab_bar_flags |= ImGuiTabBarFlags_FittingPolicyMixed; // Enforce default policy. Since 1.92.2 this is now reasonable. May expose later if needed. (#8800, #3421) tab_bar_flags |= ImGuiTabBarFlags_DrawSelectedOverline; if (!host_window->Collapsed && is_focused) tab_bar_flags |= ImGuiTabBarFlags_IsFocused; @@ -16671,50 +19074,57 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w tab_bar->SeparatorMaxX = node->Pos.x + node->Size.x - host_window->WindowBorderSize; BeginTabBarEx(tab_bar, tab_bar_rect, tab_bar_flags); //host_window->DrawList->AddRect(tab_bar_rect.Min, tab_bar_rect.Max, IM_COL32(255,0,255,255)); + // Backup style colors ImVec4 backup_style_cols[ImGuiWindowDockStyleCol_COUNT]; for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++) backup_style_cols[color_n] = g.Style.Colors[GWindowDockStyleColors[color_n]]; + // Submit actual tabs node->VisibleWindow = NULL; for (int window_n = 0; window_n < node->Windows.Size; window_n++) { ImGuiWindow* window = node->Windows[window_n]; - if ((closed_all || closed_one == window->TabId) && window->HasCloseButton && !(window->Flags & ImGuiWindowFlags_UnsavedDocument)) - continue; - if (window->LastFrameActive + 1 >= g.FrameCount || !node_was_active) - { - ImGuiTabItemFlags tab_item_flags = 0; - tab_item_flags |= window->WindowClass.TabItemFlagsOverrideSet; - if (window->Flags & ImGuiWindowFlags_UnsavedDocument) - tab_item_flags |= ImGuiTabItemFlags_UnsavedDocument; - if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) - tab_item_flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; - // Apply stored style overrides for the window - for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++) - g.Style.Colors[GWindowDockStyleColors[color_n]] = ColorConvertU32ToFloat4(window->DockStyle.Colors[color_n]); - // Note that TabItemEx() calls TabBarCalcTabID() so our tab item ID will ignore the current ID stack (rightly so) - bool tab_open = true; - TabItemEx(tab_bar, window->Name, window->HasCloseButton ? &tab_open : NULL, tab_item_flags, window); - if (!tab_open) - node->WantCloseTabId = window->TabId; - if (tab_bar->VisibleTabId == window->TabId) - node->VisibleWindow = window; - // Store last item data so it can be queried with IsItemXXX functions after the user Begin() call - window->DC.DockTabItemStatusFlags = g.LastItemData.StatusFlags; - window->DC.DockTabItemRect = g.LastItemData.Rect; - // Update navigation ID on menu layer - if (g.NavWindow && g.NavWindow->RootWindow == window && (window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 0) - host_window->NavLastIds[1] = window->TabId; - } + if (window->LastFrameActive + 1 < g.FrameCount && node_was_active) + continue; // FIXME: Not sure if that's still taken/useful. + + ImGuiTabItemFlags tab_item_flags = 0; + tab_item_flags |= window->WindowClass.TabItemFlagsOverrideSet; + if (window->Flags & ImGuiWindowFlags_UnsavedDocument) + tab_item_flags |= ImGuiTabItemFlags_UnsavedDocument; + if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) + tab_item_flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; + + // Apply stored style overrides for the window + for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++) + g.Style.Colors[GWindowDockStyleColors[color_n]] = ColorConvertU32ToFloat4(window->DockStyle.Colors[color_n]); + + // Note that TabItemEx() calls TabBarCalcTabID() so our tab item ID will ignore the current ID stack (rightly so) + bool tab_open = true; + TabItemEx(tab_bar, window->Name, window->HasCloseButton ? &tab_open : NULL, tab_item_flags, window); + if (!tab_open) + node->WantCloseTabId = window->TabId; + if (tab_bar->VisibleTabId == window->TabId) + node->VisibleWindow = window; + + // Store last item data so it can be queried with IsItemXXX functions after the user Begin() call + window->DC.DockTabItemStatusFlags = g.LastItemData.StatusFlags; + window->DC.DockTabItemRect = g.LastItemData.Rect; + + // Update navigation ID on menu layer + if (g.NavWindow && g.NavWindow->RootWindow == window && (window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 0) + host_window->NavLastIds[1] = window->TabId; } + // Restore style colors for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++) g.Style.Colors[GWindowDockStyleColors[color_n]] = backup_style_cols[color_n]; + // Notify root of visible window (used to display title in OS task bar) if (node->VisibleWindow) if (is_focused || root_node->VisibleWindow == NULL) root_node->VisibleWindow = node->VisibleWindow; + // Close button (after VisibleWindow was updated) // Note that VisibleWindow may have been overrided by CTRL+Tabbing, so VisibleWindow->TabId may be != from tab_bar->SelectedTabId const bool close_button_is_enabled = node->HasCloseButton && node->VisibleWindow && node->VisibleWindow->HasCloseButton; @@ -16741,6 +19151,7 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w PopItemFlag(); } } + // When clicking on the title bar outside of tabs, we still focus the selected tab for that node // FIXME: TabItems submitted earlier use AllowItemOverlap so we manually perform a more specific test for now (hovered || held) in order to not cover them. ImGuiID title_bar_id = host_window->GetID("#TITLEBAR"); @@ -16759,18 +19170,22 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w { if (IsMouseClicked(0)) focus_tab_id = tab_bar->SelectedTabId; + // Forward moving request to selected window if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId)) StartMouseMovingWindowOrNode(tab->Window ? tab->Window : node->HostWindow, node, false); // Undock from tab bar empty space } } + // Forward focus from host node to selected window //if (is_focused && g.NavWindow == host_window && !g.NavWindowingTarget) // focus_tab_id = tab_bar->SelectedTabId; + // When clicked on a tab we requested focus to the docked child // This overrides the value set by "forward focus from host node to selected window". if (tab_bar->NextSelectedTabId) focus_tab_id = tab_bar->NextSelectedTabId; + // Apply navigation focus if (focus_tab_id != 0) if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, focus_tab_id)) @@ -16779,8 +19194,10 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w FocusWindow(tab->Window); NavInitWindow(tab->Window, false); } + EndTabBar(); PopID(); + // Restore SkipItems flag if (!node->IsDockSpace()) { @@ -16788,11 +19205,13 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w host_window->SkipItems = backup_skip_item; } } + static void ImGui::DockNodeAddTabBar(ImGuiDockNode* node) { IM_ASSERT(node->TabBar == NULL); node->TabBar = IM_NEW(ImGuiTabBar); } + static void ImGui::DockNodeRemoveTabBar(ImGuiDockNode* node) { if (node->TabBar == NULL) @@ -16800,10 +19219,12 @@ static void ImGui::DockNodeRemoveTabBar(ImGuiDockNode* node) IM_DELETE(node->TabBar); node->TabBar = NULL; } + static bool DockNodeIsDropAllowedOne(ImGuiWindow* payload, ImGuiWindow* host_window) { if (host_window->DockNodeAsHost && host_window->DockNodeAsHost->IsDockSpace() && payload->BeginOrderWithinContext < host_window->BeginOrderWithinContext) return false; + ImGuiWindowClass* host_class = host_window->DockNodeAsHost ? &host_window->DockNodeAsHost->WindowClass : &host_window->WindowClass; ImGuiWindowClass* payload_class = &payload->WindowClass; if (host_class->ClassId != payload_class->ClassId) @@ -16816,6 +19237,7 @@ static bool DockNodeIsDropAllowedOne(ImGuiWindow* payload, ImGuiWindow* host_win if (!pass) return false; } + // Prevent docking any window created above a popup // Technically we should support it (e.g. in the case of a long-lived modal window that had fancy docking features), // by e.g. adding a 'if (!ImGui::IsWindowWithinBeginStackOf(host_window, popup_window))' test. @@ -16826,12 +19248,15 @@ static bool DockNodeIsDropAllowedOne(ImGuiWindow* payload, ImGuiWindow* host_win if (ImGuiWindow* popup_window = g.OpenPopupStack[i].Window) if (ImGui::IsWindowWithinBeginStackOf(payload, popup_window)) // Payload is created from within a popup begin stack. return false; + return true; } + static bool ImGui::DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* root_payload) { if (root_payload->DockNodeAsHost && root_payload->DockNodeAsHost->IsSplitNode()) // FIXME-DOCK: Missing filtering return true; + const int payload_count = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->Windows.Size : 1; for (int payload_n = 0; payload_n < payload_count; payload_n++) { @@ -16841,16 +19266,20 @@ static bool ImGui::DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* } return false; } + // window menu button == collapse button when not in a dock node. // FIXME: This is similar to RenderWindowTitleBarContents(), may want to share code. static void ImGui::DockNodeCalcTabBarLayout(const ImGuiDockNode* node, ImRect* out_title_rect, ImRect* out_tab_bar_rect, ImVec2* out_window_menu_button_pos, ImVec2* out_close_button_pos) { ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; + ImRect r = ImRect(node->Pos.x, node->Pos.y, node->Pos.x + node->Size.x, node->Pos.y + g.FontSize + g.Style.FramePadding.y * 2.0f); if (out_title_rect) { *out_title_rect = r; } + r.Min.x += style.WindowBorderSize; r.Max.x -= style.WindowBorderSize; + float button_sz = g.FontSize; r.Min.x += style.FramePadding.x; r.Max.x -= style.FramePadding.x; @@ -16872,6 +19301,7 @@ static void ImGui::DockNodeCalcTabBarLayout(const ImGuiDockNode* node, ImRect* o if (out_tab_bar_rect) { *out_tab_bar_rect = r; } if (out_window_menu_button_pos) { *out_window_menu_button_pos = window_menu_button_pos; } } + void ImGui::DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& pos_new, ImVec2& size_new, ImGuiDir dir, ImVec2 size_new_desired) { ImGuiContext& g = *GImGui; @@ -16879,6 +19309,7 @@ void ImGui::DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& po const ImGuiAxis axis = (dir == ImGuiDir_Left || dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y; pos_new[axis ^ 1] = pos_old[axis ^ 1]; size_new[axis ^ 1] = size_old[axis ^ 1]; + // Distribute size on given axis (with a desired size or equally) const float w_avail = size_old[axis] - dock_spacing; if (size_new_desired[axis] > 0.0f && size_new_desired[axis] <= w_avail * 0.5f) @@ -16891,6 +19322,7 @@ void ImGui::DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& po size_new[axis] = IM_TRUNC(w_avail * 0.5f); size_old[axis] = IM_TRUNC(w_avail - size_new[axis]); } + // Position each node if (dir == ImGuiDir_Right || dir == ImGuiDir_Down) { @@ -16902,10 +19334,12 @@ void ImGui::DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& po pos_old[axis] = pos_new[axis] + size_new[axis] + dock_spacing; } } + // Retrieve the drop rectangles for a given direction or for the center + perform hit testing. bool ImGui::DockNodeCalcDropRectsAndTestMousePos(const ImRect& parent, ImGuiDir dir, ImRect& out_r, bool outer_docking, ImVec2* test_mouse_pos) { ImGuiContext& g = *GImGui; + const float parent_smaller_axis = ImMin(parent.GetWidth(), parent.GetHeight()); const float hs_for_central_nodes = ImMin(g.FontSize * 1.5f, ImMax(g.FontSize * 0.5f, parent_smaller_axis / 8.0f)); float hs_w; // Half-size, longer axis @@ -16926,14 +19360,17 @@ bool ImGui::DockNodeCalcDropRectsAndTestMousePos(const ImRect& parent, ImGuiDir hs_h = ImTrunc(hs_for_central_nodes * 0.90f); off = ImTrunc(ImVec2(hs_w * 2.40f, hs_w * 2.40f)); } + ImVec2 c = ImTrunc(parent.GetCenter()); if (dir == ImGuiDir_None) { out_r = ImRect(c.x - hs_w, c.y - hs_w, c.x + hs_w, c.y + hs_w); } else if (dir == ImGuiDir_Up) { out_r = ImRect(c.x - hs_w, c.y - off.y - hs_h, c.x + hs_w, c.y - off.y + hs_h); } else if (dir == ImGuiDir_Down) { out_r = ImRect(c.x - hs_w, c.y + off.y - hs_h, c.x + hs_w, c.y + off.y + hs_h); } else if (dir == ImGuiDir_Left) { out_r = ImRect(c.x - off.x - hs_h, c.y - hs_w, c.x - off.x + hs_h, c.y + hs_w); } else if (dir == ImGuiDir_Right) { out_r = ImRect(c.x + off.x - hs_h, c.y - hs_w, c.x + off.x + hs_h, c.y + hs_w); } + if (test_mouse_pos == NULL) return false; + ImRect hit_r = out_r; if (!outer_docking) { @@ -16950,11 +19387,13 @@ bool ImGui::DockNodeCalcDropRectsAndTestMousePos(const ImRect& parent, ImGuiDir } return hit_r.Contains(*test_mouse_pos); } + // host_node may be NULL if the window doesn't have a DockNode already. // FIXME-DOCK: This is misnamed since it's also doing the filtering. static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, ImGuiDockNode* payload_node, ImGuiDockPreviewData* data, bool is_explicit_target, bool is_outer_docking) { ImGuiContext& g = *GImGui; + // There is an edge case when docking into a dockspace which only has inactive nodes. // In this case DockNodeTreeFindNodeByPos() will have selected a leaf node which is inactive. // Because the inactive leaf node doesn't have proper pos/size yet, we'll use the root node as reference. @@ -16963,6 +19402,7 @@ static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockN ImGuiDockNode* ref_node_for_rect = (host_node && !host_node->IsVisible) ? DockNodeGetRootNode(host_node) : host_node; if (ref_node_for_rect) IM_ASSERT(ref_node_for_rect->IsVisible == true); + // Filter, figure out where we are allowed to dock ImGuiDockNodeFlags src_node_flags = payload_node ? payload_node->MergedFlags : payload_window->WindowClass.DockNodeFlagsOverrideSet; ImGuiDockNodeFlags dst_node_flags = host_node ? host_node->MergedFlags : host_window->WindowClass.DockNodeFlagsOverrideSet; @@ -16979,6 +19419,7 @@ static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockN data->IsCenterAvailable = false; else if ((src_node_flags & ImGuiDockNodeFlags_NoDockingOverEmpty) && host_node && host_node->IsEmpty()) data->IsCenterAvailable = false; + data->IsSidesAvailable = true; if ((dst_node_flags & ImGuiDockNodeFlags_NoDockingSplit) || g.IO.ConfigDockingNoSplit) data->IsSidesAvailable = false; @@ -16986,11 +19427,13 @@ static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockN data->IsSidesAvailable = false; else if (src_node_flags & ImGuiDockNodeFlags_NoDockingSplitOther) data->IsSidesAvailable = false; + // Build a tentative future node (reuse same structure because it is practical. Shape will be readjusted when previewing a split) data->FutureNode.HasCloseButton = (host_node ? host_node->HasCloseButton : host_window->HasCloseButton) || (payload_window->HasCloseButton); data->FutureNode.HasWindowMenuButton = host_node ? true : ((host_window->Flags & ImGuiWindowFlags_NoCollapse) == 0); data->FutureNode.Pos = ref_node_for_rect ? ref_node_for_rect->Pos : host_window->Pos; data->FutureNode.Size = ref_node_for_rect ? ref_node_for_rect->Size : host_window->Size; + // Calculate drop shapes geometry for allowed splitting directions IM_ASSERT(ImGuiDir_None == -1); data->SplitNode = host_node; @@ -17009,10 +19452,12 @@ static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockN data->IsSplitDirExplicit = true; } } + // When docking without holding Shift, we only allow and preview docking when hovering over a drop rect or over the title bar data->IsDropAllowed = (data->SplitDir != ImGuiDir_None) || (data->IsCenterAvailable); if (!is_explicit_target && !data->IsSplitDirExplicit && !g.IO.ConfigDockingWithShift) data->IsDropAllowed = false; + // Calculate split area data->SplitRatio = 0.0f; if (data->SplitDir != ImGuiDir_None) @@ -17022,6 +19467,7 @@ static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockN ImVec2 pos_new, pos_old = data->FutureNode.Pos; ImVec2 size_new, size_old = data->FutureNode.Size; DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, split_dir, payload_window->Size); + // Calculate split ratio so we can pass it down the docking request float split_ratio = ImSaturate(size_new[split_axis] / data->FutureNode.Size[split_axis]); data->FutureNode.Pos = pos_new; @@ -17029,24 +19475,29 @@ static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockN data->SplitRatio = (split_dir == ImGuiDir_Right || split_dir == ImGuiDir_Down) ? (1.0f - split_ratio) : (split_ratio); } } + static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* root_payload, const ImGuiDockPreviewData* data) { ImGuiContext& g = *GImGui; IM_ASSERT(g.CurrentWindow == host_window); // Because we rely on font size to calculate tab sizes + // With this option, we only display the preview on the target viewport, and the payload viewport is made transparent. // To compensate for the single layer obstructed by the payload, we'll increase the alpha of the preview nodes. const bool is_transparent_payload = g.IO.ConfigDockingTransparentPayload; + // In case the two windows involved are on different viewports, we will draw the overlay on each of them. int overlay_draw_lists_count = 0; ImDrawList* overlay_draw_lists[2]; overlay_draw_lists[overlay_draw_lists_count++] = GetForegroundDrawList(host_window->Viewport); if (host_window->Viewport != root_payload->Viewport && !is_transparent_payload) overlay_draw_lists[overlay_draw_lists_count++] = GetForegroundDrawList(root_payload->Viewport); + // Draw main preview rectangle const ImU32 overlay_col_main = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 0.60f : 0.40f); const ImU32 overlay_col_drop = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 0.90f : 0.70f); const ImU32 overlay_col_drop_hovered = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 1.20f : 1.00f); const ImU32 overlay_col_lines = GetColorU32(ImGuiCol_NavWindowingHighlight, is_transparent_payload ? 0.80f : 0.60f); + // Display area preview const bool can_preview_tabs = (root_payload->DockNodeAsHost == NULL || root_payload->DockNodeAsHost->Windows.Size > 0); if (data->IsDropAllowed) @@ -17058,6 +19509,7 @@ static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDock for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++) overlay_draw_lists[overlay_n]->AddRectFilled(overlay_rect.Min, overlay_rect.Max, overlay_col_main, host_window->WindowRounding, CalcRoundingFlagsForRectInRect(overlay_rect, host_window->Rect(), g.Style.DockingSeparatorSize)); } + // Display tab shape/label preview unless we are splitting node (it generally makes the situation harder to read) if (data->IsDropAllowed && can_preview_tabs && data->SplitDir == ImGuiDir_None && data->IsCenterAvailable) { @@ -17076,6 +19528,7 @@ static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDock { tab_pos.x += g.Style.ItemInnerSpacing.x + TabItemCalcSize(host_window).x; // Account for slight offset which will be added when changing from title bar to tab bar } + // Draw tab shape/label preview (payload may be a loose window or a host window carrying multiple tabbed windows) if (root_payload->DockNodeAsHost) IM_ASSERT(root_payload->DockNodeAsHost->Windows.Size <= root_payload->DockNodeAsHost->TabBar->Tabs.Size); @@ -17089,6 +19542,7 @@ static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDock continue; if (!DockNodeIsDropAllowedOne(payload_window, host_window)) continue; + // Calculate the tab bounding box for each payload window ImVec2 tab_size = TabItemCalcSize(payload_window); ImRect tab_bb(tab_pos.x, tab_pos.y, tab_pos.x + tab_size.x, tab_pos.y + tab_size.y); @@ -17109,6 +19563,7 @@ static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDock PopStyleColor(); } } + // Display drop boxes const float overlay_rounding = ImMax(3.0f, g.Style.FrameRounding); for (int dir = ImGuiDir_None; dir < ImGuiDir_COUNT; dir++) @@ -17130,11 +19585,13 @@ static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDock overlay_draw_lists[overlay_n]->AddLine(ImVec2(draw_r_in.Min.x, center.y), ImVec2(draw_r_in.Max.x, center.y), overlay_col_lines); } } + // Stop after ImGuiDir_None if ((host_node && (host_node->MergedFlags & ImGuiDockNodeFlags_NoDockingSplit)) || g.IO.ConfigDockingNoSplit) return; } } + //----------------------------------------------------------------------------- // Docking: ImGuiDockNode Tree manipulation functions //----------------------------------------------------------------------------- @@ -17146,14 +19603,18 @@ static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDock // - DockNodeTreeFindFallbackLeafNode() // - DockNodeTreeFindNodeByPos() //----------------------------------------------------------------------------- + void ImGui::DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiAxis split_axis, int split_inheritor_child_idx, float split_ratio, ImGuiDockNode* new_node) { ImGuiContext& g = *GImGui; IM_ASSERT(split_axis != ImGuiAxis_None); + ImGuiDockNode* child_0 = (new_node && split_inheritor_child_idx != 0) ? new_node : DockContextAddNode(ctx, 0); child_0->ParentNode = parent_node; + ImGuiDockNode* child_1 = (new_node && split_inheritor_child_idx != 1) ? new_node : DockContextAddNode(ctx, 0); child_1->ParentNode = parent_node; + ImGuiDockNode* child_inheritor = (split_inheritor_child_idx == 0) ? child_0 : child_1; DockNodeMoveChildNodes(child_inheritor, parent_node); parent_node->ChildNodes[0] = child_0; @@ -17162,16 +19623,19 @@ void ImGui::DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImG parent_node->SplitAxis = split_axis; parent_node->VisibleWindow = NULL; parent_node->AuthorityForPos = parent_node->AuthorityForSize = ImGuiDataAuthority_DockNode; + float size_avail = (parent_node->Size[split_axis] - g.Style.DockingSeparatorSize); size_avail = ImMax(size_avail, g.Style.WindowMinSize[split_axis] * 2.0f); IM_ASSERT(size_avail > 0.0f); // If you created a node manually with DockBuilderAddNode(), you need to also call DockBuilderSetNodeSize() before splitting. child_0->SizeRef = child_1->SizeRef = parent_node->Size; child_0->SizeRef[split_axis] = ImTrunc(size_avail * split_ratio); child_1->SizeRef[split_axis] = ImTrunc(size_avail - child_0->SizeRef[split_axis]); + DockNodeMoveWindows(parent_node->ChildNodes[split_inheritor_child_idx], parent_node); DockSettingsRenameNodeReferences(parent_node->ID, parent_node->ChildNodes[split_inheritor_child_idx]->ID); DockNodeUpdateHasCentralNodeChild(DockNodeGetRootNode(parent_node)); DockNodeTreeUpdatePosSize(parent_node, parent_node->Pos, parent_node->Size); + // Flags transfer (e.g. this is where we transfer the ImGuiDockNodeFlags_CentralNode property) child_0->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_; child_1->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_; @@ -17183,6 +19647,7 @@ void ImGui::DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImG if (child_inheritor->IsCentralNode()) DockNodeGetRootNode(parent_node)->CentralNode = child_inheritor; } + void ImGui::DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiDockNode* merge_lead_child) { // When called from DockContextProcessUndockNode() it is possible that one of the child is NULL. @@ -17197,6 +19662,7 @@ void ImGui::DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImG IM_ASSERT(parent_node->Windows.Size == 0); } IMGUI_DEBUG_LOG_DOCKING("[docking] DockNodeTreeMerge: 0x%08X + 0x%08X back into parent 0x%08X\n", child_0 ? child_0->ID : 0, child_1 ? child_1->ID : 0, parent_node->ID); + ImVec2 backup_last_explicit_size = parent_node->SizeRef; DockNodeMoveChildNodes(parent_node, merge_lead_child); if (child_0) @@ -17213,12 +19679,14 @@ void ImGui::DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImG parent_node->AuthorityForPos = parent_node->AuthorityForSize = parent_node->AuthorityForViewport = ImGuiDataAuthority_Auto; parent_node->VisibleWindow = merge_lead_child->VisibleWindow; parent_node->SizeRef = backup_last_explicit_size; + // Flags transfer parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_; // Preserve Dockspace flag parent_node->LocalFlags |= (child_0 ? child_0->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_; parent_node->LocalFlags |= (child_1 ? child_1->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_; parent_node->LocalFlagsInWindows = (child_0 ? child_0->LocalFlagsInWindows : 0) | (child_1 ? child_1->LocalFlagsInWindows : 0); // FIXME: Would be more consistent to update from actual windows parent_node->UpdateMergedFlags(); + if (child_0) { ctx->DockContext.Nodes.SetVoidPtr(child_0->ID, NULL); @@ -17230,6 +19698,7 @@ void ImGui::DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImG IM_DELETE(child_1); } } + // Update Pos/Size for a node hierarchy (don't affect child Windows yet) // (Depth-first, Pre-Order) void ImGui::DockNodeTreeUpdatePosSize(ImGuiDockNode* node, ImVec2 pos, ImVec2 size, ImGuiDockNode* only_write_to_single_node) @@ -17243,27 +19712,34 @@ void ImGui::DockNodeTreeUpdatePosSize(ImGuiDockNode* node, ImVec2 pos, ImVec2 si node->Pos = pos; node->Size = size; } + if (node->IsLeafNode()) return; + ImGuiDockNode* child_0 = node->ChildNodes[0]; ImGuiDockNode* child_1 = node->ChildNodes[1]; ImVec2 child_0_pos = pos, child_1_pos = pos; ImVec2 child_0_size = size, child_1_size = size; + const bool child_0_is_toward_single_node = (only_write_to_single_node != NULL && DockNodeIsInHierarchyOf(only_write_to_single_node, child_0)); const bool child_1_is_toward_single_node = (only_write_to_single_node != NULL && DockNodeIsInHierarchyOf(only_write_to_single_node, child_1)); const bool child_0_is_or_will_be_visible = child_0->IsVisible || child_0_is_toward_single_node; const bool child_1_is_or_will_be_visible = child_1->IsVisible || child_1_is_toward_single_node; + if (child_0_is_or_will_be_visible && child_1_is_or_will_be_visible) { const float spacing = g.Style.DockingSeparatorSize; const ImGuiAxis axis = (ImGuiAxis)node->SplitAxis; const float size_avail = ImMax(size[axis] - spacing, 0.0f); + // Size allocation policy // 1) The first 0..WindowMinSize[axis]*2 are allocated evenly to both windows. const float size_min_each = ImTrunc(ImMin(size_avail, g.Style.WindowMinSize[axis] * 2.0f) * 0.5f); + // FIXME: Blocks 2) and 3) are essentially doing nearly the same thing. // Difference are: write-back to SizeRef; application of a minimum size; rounding before ImTrunc() // Clarify and rework differences between Size & SizeRef and purpose of WantLockSizeOnce + // 2) Process locked absolute size (during a splitter resize we preserve the child of nodes not touching the splitter edge) if (child_0->WantLockSizeOnce && !child_1->WantLockSizeOnce) { @@ -17286,6 +19762,7 @@ void ImGui::DockNodeTreeUpdatePosSize(ImGuiDockNode* node, ImVec2 pos, ImVec2 si child_1_size[axis] = child_1->SizeRef[axis] = (size_avail - child_0_size[axis]); IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f); } + // 3) If one window is the central node (~ use remaining space, should be made explicit!), use explicit size from the other, and remainder for the central node else if (child_0->SizeRef[axis] != 0.0f && child_1->HasCentralNodeChild) { @@ -17304,10 +19781,13 @@ void ImGui::DockNodeTreeUpdatePosSize(ImGuiDockNode* node, ImVec2 pos, ImVec2 si child_0_size[axis] = ImMax(size_min_each, ImTrunc(size_avail * split_ratio + 0.5f)); child_1_size[axis] = (size_avail - child_0_size[axis]); } + child_1_pos[axis] += spacing + child_0_size[axis]; } + if (only_write_to_single_node == NULL) child_0->WantLockSizeOnce = child_1->WantLockSizeOnce = false; + const bool child_0_recurse = only_write_to_single_node ? child_0_is_toward_single_node : child_0->IsVisible; const bool child_1_recurse = only_write_to_single_node ? child_1_is_toward_single_node : child_1->IsVisible; if (child_0_recurse) @@ -17315,6 +19795,7 @@ void ImGui::DockNodeTreeUpdatePosSize(ImGuiDockNode* node, ImVec2 pos, ImVec2 si if (child_1_recurse) DockNodeTreeUpdatePosSize(child_1, child_1_pos, child_1_size); } + static void DockNodeTreeUpdateSplitterFindTouchingNode(ImGuiDockNode* node, ImGuiAxis axis, int side, ImVector* touching_nodes) { if (node->IsLeafNode()) @@ -17329,12 +19810,15 @@ static void DockNodeTreeUpdateSplitterFindTouchingNode(ImGuiDockNode* node, ImGu if (node->SplitAxis != axis || side == 1 || !node->ChildNodes[0]->IsVisible) DockNodeTreeUpdateSplitterFindTouchingNode(node->ChildNodes[1], axis, side, touching_nodes); } + // (Depth-First, Pre-Order) void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node) { if (node->IsLeafNode()) return; + ImGuiContext& g = *GImGui; + ImGuiDockNode* child_0 = node->ChildNodes[0]; ImGuiDockNode* child_1 = node->ChildNodes[1]; if (child_0->IsVisible && child_1->IsVisible) @@ -17348,6 +19832,7 @@ void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node) bb.Min[axis] += child_0->Size[axis]; bb.Max[axis ^ 1] += child_1->Size[axis ^ 1]; //if (g.IO.KeyCtrl) GetForegroundDrawList(g.CurrentWindow->Viewport)->AddRect(bb.Min, bb.Max, IM_COL32(255,0,255,255)); + const ImGuiDockNodeFlags merged_flags = child_0->MergedFlags | child_1->MergedFlags; // Merged flags for BOTH childs const ImGuiDockNodeFlags no_resize_axis_flag = (axis == ImGuiAxis_X) ? ImGuiDockNodeFlags_NoResizeX : ImGuiDockNodeFlags_NoResizeY; if ((merged_flags & ImGuiDockNodeFlags_NoResize) || (merged_flags & no_resize_axis_flag)) @@ -17360,12 +19845,14 @@ void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node) //bb.Min[axis] += 1; // Display a little inward so highlight doesn't connect with nearby tabs on the neighbor node. //bb.Max[axis] -= 1; PushID(node->ID); + // Find resizing limits by gathering list of nodes that are touching the splitter line. ImVector touching_nodes[2]; float min_size = g.Style.WindowMinSize[axis]; float resize_limits[2]; resize_limits[0] = node->ChildNodes[0]->Pos[axis] + min_size; resize_limits[1] = node->ChildNodes[1]->Pos[axis] + node->ChildNodes[1]->Size[axis] - min_size; + ImGuiID splitter_id = GetID("##Splitter"); if (g.ActiveId == splitter_id) // Only process when splitter is active { @@ -17375,6 +19862,7 @@ void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node) resize_limits[0] = ImMax(resize_limits[0], touching_nodes[0][touching_node_n]->Rect().Min[axis] + min_size); for (int touching_node_n = 0; touching_node_n < touching_nodes[1].Size; touching_node_n++) resize_limits[1] = ImMin(resize_limits[1], touching_nodes[1][touching_node_n]->Rect().Max[axis] - min_size); + // [DEBUG] Render touching nodes & limits /* ImDrawList* draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) : GetForegroundDrawList(GetMainViewport()); @@ -17389,6 +19877,7 @@ void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node) } */ } + // Use a short delay before highlighting the splitter (and changing the mouse cursor) in order for regular mouse movement to not highlight many splitters float cur_size_0 = child_0->Size[axis]; float cur_size_1 = child_1->Size[axis]; @@ -17402,6 +19891,7 @@ void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node) child_0->Size[axis] = child_0->SizeRef[axis] = cur_size_0; child_1->Pos[axis] -= cur_size_1 - child_1->Size[axis]; child_1->Size[axis] = child_1->SizeRef[axis] = cur_size_1; + // Lock the size of every node that is a sibling of the node we are touching // This might be less desirable if we can merge sibling of a same axis into the same parental level. for (int side_n = 0; side_n < 2; side_n++) @@ -17423,6 +19913,7 @@ void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node) touching_node = touching_node->ParentNode; } } + DockNodeTreeUpdatePosSize(child_0, child_0->Pos, child_0->Size); DockNodeTreeUpdatePosSize(child_1, child_1->Pos, child_1->Size); MarkIniSettingsDirty(); @@ -17431,11 +19922,13 @@ void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node) PopID(); } } + if (child_0->IsVisible) DockNodeTreeUpdateSplitter(child_0); if (child_1->IsVisible) DockNodeTreeUpdateSplitter(child_1); } + ImGuiDockNode* ImGui::DockNodeTreeFindFallbackLeafNode(ImGuiDockNode* node) { if (node->IsLeafNode()) @@ -17446,25 +19939,30 @@ ImGuiDockNode* ImGui::DockNodeTreeFindFallbackLeafNode(ImGuiDockNode* node) return leaf_node; return NULL; } + ImGuiDockNode* ImGui::DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode* node, ImVec2 pos) { if (!node->IsVisible) return NULL; + const float dock_spacing = 0.0f;// g.Style.ItemInnerSpacing.x; // FIXME: Relation to DOCKING_SPLITTER_SIZE? ImRect r(node->Pos, node->Pos + node->Size); r.Expand(dock_spacing * 0.5f); bool inside = r.Contains(pos); if (!inside) return NULL; + if (node->IsLeafNode()) return node; if (ImGuiDockNode* hovered_node = DockNodeTreeFindVisibleNodeByPos(node->ChildNodes[0], pos)) return hovered_node; if (ImGuiDockNode* hovered_node = DockNodeTreeFindVisibleNodeByPos(node->ChildNodes[1], pos)) return hovered_node; + // This means we are hovering over the splitter/spacing of a parent node return node; } + //----------------------------------------------------------------------------- // Docking: Public Functions (SetWindowDock, DockSpace, DockSpaceOverViewport) //----------------------------------------------------------------------------- @@ -17472,6 +19970,7 @@ ImGuiDockNode* ImGui::DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode* node, ImVe // - DockSpace() // - DockSpaceOverViewport() //----------------------------------------------------------------------------- + // [Internal] Called via SetNextWindowDockID() void ImGui::SetWindowDock(ImGuiWindow* window, ImGuiID dock_id, ImGuiCond cond) { @@ -17479,8 +19978,10 @@ void ImGui::SetWindowDock(ImGuiWindow* window, ImGuiID dock_id, ImGuiCond cond) if (cond && (window->SetWindowDockAllowFlags & cond) == 0) return; window->SetWindowDockAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + if (window->DockId == dock_id) return; + // If the user attempt to set a dock id that is a split node, we'll dig within to find a suitable docking spot ImGuiContext& g = *GImGui; if (ImGuiDockNode* new_node = DockContextFindNodeByID(&g, dock_id)) @@ -17498,12 +19999,15 @@ void ImGui::SetWindowDock(ImGuiWindow* window, ImGuiID dock_id, ImGuiCond cond) dock_id = new_node->LastFocusedNodeId; } } + if (window->DockId == dock_id) return; + if (window->DockNode) DockNodeRemoveWindow(window->DockNode, window, 0); window->DockId = dock_id; } + // Create an explicit dockspace node within an existing window. Also expose dock node flags and creates a CentralNode by default. // The Central Node is always displayed even when empty and shrink/extend according to the requested size of its neighbors. // DockSpace() needs to be submitted _before_ any window they can host. If you use a dockspace, submit it early in your app. @@ -17514,6 +20018,7 @@ ImGuiID ImGui::DockSpace(ImGuiID dockspace_id, const ImVec2& size_arg, ImGuiDock ImGuiWindow* window = GetCurrentWindowRead(); if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)) return 0; + // Early out if parent window is hidden/collapsed // This is faster but also DockNodeUpdateTabBar() relies on TabBarLayout() running (which won't if SkipItems=true) to set NextSelectedTabId = 0). See #2960. // If for whichever reason this is causing problem we would need to ensure that DockNodeUpdateTabBar() ends up clearing NextSelectedTabId even if SkipItems=true. @@ -17521,8 +20026,10 @@ ImGuiID ImGui::DockSpace(ImGuiID dockspace_id, const ImVec2& size_arg, ImGuiDock flags |= ImGuiDockNodeFlags_KeepAliveOnly; if ((flags & ImGuiDockNodeFlags_KeepAliveOnly) == 0) window = GetCurrentWindow(); // call to set window->WriteAccessed = true; + IM_ASSERT((flags & ImGuiDockNodeFlags_DockSpace) == 0); // Flag is automatically set by DockSpace() as LocalFlags, not SharedFlags! IM_ASSERT((flags & ImGuiDockNodeFlags_CentralNode) == 0); // Flag is automatically set by DockSpace() as LocalFlags, not SharedFlags! (#8145) + IM_ASSERT(dockspace_id != 0); ImGuiDockNode* node = DockContextFindNodeByID(&g, dockspace_id); if (node == NULL) @@ -17535,6 +20042,7 @@ ImGuiID ImGui::DockSpace(ImGuiID dockspace_id, const ImVec2& size_arg, ImGuiDock IMGUI_DEBUG_LOG_DOCKING("[docking] DockSpace: dockspace node 0x%08X: setup WindowClass 0x%08X -> 0x%08X\n", dockspace_id, node->WindowClass.ClassId, window_class->ClassId); node->SharedFlags = flags; node->WindowClass = window_class ? *window_class : ImGuiWindowClass(); + // When a DockSpace transitioned form implicit to explicit this may be called a second time // It is possible that the node has already been claimed by a docked window which appeared before the DockSpace() node, so we overwrite IsDockSpace again. if (node->LastFrameActive == g.FrameCount && !(flags & ImGuiDockNodeFlags_KeepAliveOnly)) @@ -17544,12 +20052,14 @@ ImGuiID ImGui::DockSpace(ImGuiID dockspace_id, const ImVec2& size_arg, ImGuiDock return dockspace_id; } node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace); + // Keep alive mode, this is allow windows docked into this node so stay docked even if they are not visible if (flags & ImGuiDockNodeFlags_KeepAliveOnly) { node->LastFrameAlive = g.FrameCount; return dockspace_id; } + const ImVec2 content_avail = GetContentRegionAvail(); ImVec2 size = ImTrunc(size_arg); if (size.x <= 0.0f) @@ -17557,27 +20067,34 @@ ImGuiID ImGui::DockSpace(ImGuiID dockspace_id, const ImVec2& size_arg, ImGuiDock if (size.y <= 0.0f) size.y = ImMax(content_avail.y + size.y, 4.0f); IM_ASSERT(size.x > 0.0f && size.y > 0.0f); + node->Pos = window->DC.CursorPos; node->Size = node->SizeRef = size; SetNextWindowPos(node->Pos); SetNextWindowSize(node->Size); g.NextWindowData.PosUndock = false; + // FIXME-DOCK: Why do we need a child window to host a dockspace, could we host it in the existing window? // FIXME-DOCK: What is the reason for not simply calling BeginChild()? (OK to have a reason but should be commented) ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_DockNodeHost; window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar; window_flags |= ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; window_flags |= ImGuiWindowFlags_NoBackground; + char title[256]; ImFormatString(title, IM_ARRAYSIZE(title), "%s/DockSpace_%08X", window->Name, dockspace_id); + PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.0f); Begin(title, NULL, window_flags); PopStyleVar(); + ImGuiWindow* host_window = g.CurrentWindow; DockNodeSetupHostWindow(node, host_window); host_window->ChildId = window->GetID(title); node->OnlyNodeWithWindows = NULL; + IM_ASSERT(node->IsRootNode()); + // We need to handle the rare case were a central node is missing. // This can happen if the node was first created manually with DockBuilderAddNode() but _without_ the ImGuiDockNodeFlags_Dockspace. // Doing it correctly would set the _CentralNode flags, which would then propagate according to subsequent split. @@ -17586,16 +20103,21 @@ ImGuiID ImGui::DockSpace(ImGuiID dockspace_id, const ImVec2& size_arg, ImGuiDock // as it doesn't make sense for an empty dockspace to not have this property. if (node->IsLeafNode() && !node->IsCentralNode()) node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_CentralNode); + // Update the node DockNodeUpdate(node); + End(); + ImRect bb(node->Pos, node->Pos + size); ItemSize(size); ItemAdd(bb, dockspace_id, NULL, ImGuiItemFlags_NoNav); // Not a nav point (could be, would need to draw the nav rect and replicate/refactor activation from BeginChild(), but seems like CTRL+Tab works better here?) if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && IsWindowChildOf(g.HoveredWindow, host_window, false, true)) // To fullfill IsItemHovered(), similar to EndChild() g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + return dockspace_id; } + // Tips: Use with ImGuiDockNodeFlags_PassthruCentralNode! // The limitation with this call is that your window won't have a local menu bar, but you can also use BeginMainMenuBar(). // Even though we could pass window flags, it would also require the user to be able to call BeginMenuBar() somehow meaning we can't Begin/End in a single function. @@ -17604,33 +20126,42 @@ ImGuiID ImGui::DockSpaceOverViewport(ImGuiID dockspace_id, const ImGuiViewport* { if (viewport == NULL) viewport = GetMainViewport(); + // Submit a window filling the entire viewport SetNextWindowPos(viewport->WorkPos); SetNextWindowSize(viewport->WorkSize); SetNextWindowViewport(viewport->ID); + ImGuiWindowFlags host_window_flags = 0; host_window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking; host_window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) host_window_flags |= ImGuiWindowFlags_NoBackground; + // FIXME-OPT: When using ImGuiDockNodeFlags_KeepAliveOnly with DockSpaceOverViewport() we might be able to spare submitting the window, // since DockSpace() with that flag doesn't need a window. We'd only need to compute the default ID accordingly. if (dockspace_flags & ImGuiDockNodeFlags_KeepAliveOnly) host_window_flags |= ImGuiWindowFlags_NoMouseInputs; + char label[32]; ImFormatString(label, IM_ARRAYSIZE(label), "WindowOverViewport_%08X", viewport->ID); + PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); Begin(label, NULL, host_window_flags); PopStyleVar(3); + // Submit the dockspace if (dockspace_id == 0) dockspace_id = GetID("DockSpace"); DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags, window_class); + End(); + return dockspace_id; } + //----------------------------------------------------------------------------- // Docking: Builder Functions //----------------------------------------------------------------------------- @@ -17653,6 +20184,7 @@ ImGuiID ImGui::DockSpaceOverViewport(ImGuiID dockspace_id, const ImGuiViewport* // - DockBuilderCopyDockSpace() // - DockBuilderFinish() //----------------------------------------------------------------------------- + void ImGui::DockBuilderDockWindow(const char* window_name, ImGuiID node_id) { // We don't preserve relative order of multiple docked windows (by clearing DockOrder back to -1) @@ -17678,11 +20210,13 @@ void ImGui::DockBuilderDockWindow(const char* window_name, ImGuiID node_id) settings->DockId = node_id; } } + ImGuiDockNode* ImGui::DockBuilderGetNode(ImGuiID node_id) { ImGuiContext& g = *GImGui; return DockContextFindNodeByID(&g, node_id); } + void ImGui::DockBuilderSetNodePos(ImGuiID node_id, ImVec2 pos) { ImGuiContext& g = *GImGui; @@ -17692,6 +20226,7 @@ void ImGui::DockBuilderSetNodePos(ImGuiID node_id, ImVec2 pos) node->Pos = pos; node->AuthorityForPos = ImGuiDataAuthority_DockNode; } + void ImGui::DockBuilderSetNodeSize(ImGuiID node_id, ImVec2 size) { ImGuiContext& g = *GImGui; @@ -17702,6 +20237,7 @@ void ImGui::DockBuilderSetNodeSize(ImGuiID node_id, ImVec2 size) node->Size = node->SizeRef = size; node->AuthorityForSize = ImGuiDataAuthority_DockNode; } + // Make sure to use the ImGuiDockNodeFlags_DockSpace flag to create a dockspace node! Otherwise this will create a floating node! // - Floating node: you can then call DockBuilderSetNodePos()/DockBuilderSetNodeSize() to position and size the floating node. // - Dockspace node: calling DockBuilderSetNodePos() is unnecessary. @@ -17713,8 +20249,10 @@ ImGuiID ImGui::DockBuilderAddNode(ImGuiID node_id, ImGuiDockNodeFlags flags) { ImGuiContext& g = *GImGui; IM_UNUSED(g); IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderAddNode 0x%08X flags=%08X\n", node_id, flags); + if (node_id != 0) DockBuilderRemoveNode(node_id); + ImGuiDockNode* node = NULL; if (flags & ImGuiDockNodeFlags_DockSpace) { @@ -17729,10 +20267,12 @@ ImGuiID ImGui::DockBuilderAddNode(ImGuiID node_id, ImGuiDockNodeFlags flags) node->LastFrameAlive = g.FrameCount; // Set this otherwise BeginDocked will undock during the same frame. return node->ID; } + void ImGui::DockBuilderRemoveNode(ImGuiID node_id) { ImGuiContext& g = *GImGui; IM_UNUSED(g); IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderRemoveNode 0x%08X\n", node_id); + ImGuiDockNode* node = DockContextFindNodeByID(&g, node_id); if (node == NULL) return; @@ -17746,17 +20286,21 @@ void ImGui::DockBuilderRemoveNode(ImGuiID node_id) node->ParentNode->SetLocalFlags(node->ParentNode->LocalFlags | ImGuiDockNodeFlags_CentralNode); DockContextRemoveNode(&g, node, true); } + // root_id = 0 to remove all, root_id != 0 to remove child of given node. void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiID root_id) { ImGuiContext& g = *GImGui; ImGuiDockContext* dc = &g.DockContext; + ImGuiDockNode* root_node = root_id ? DockContextFindNodeByID(&g, root_id) : NULL; if (root_id && root_node == NULL) return; bool has_central_node = false; + ImGuiDataAuthority backup_root_node_authority_for_pos = root_node ? root_node->AuthorityForPos : ImGuiDataAuthority_Auto; ImGuiDataAuthority backup_root_node_authority_for_size = root_node ? root_node->AuthorityForSize : ImGuiDataAuthority_Auto; + // Process active windows ImVector nodes_to_remove; for (int n = 0; n < dc->Nodes.Data.Size; n++) @@ -17777,6 +20321,7 @@ void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiID root_id) nodes_to_remove.push_back(node); } } + // DockNodeMoveWindows->DockNodeAddWindow will normally set those when reaching two windows (which is only adequate during interactive merge) // Make sure we don't lose our current pos/size. (FIXME-DOCK: Consider tidying up that code in DockNodeAddWindow instead) if (root_node) @@ -17784,6 +20329,7 @@ void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiID root_id) root_node->AuthorityForPos = backup_root_node_authority_for_pos; root_node->AuthorityForSize = backup_root_node_authority_for_size; } + // Apply to settings for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) if (ImGuiID window_settings_dock_id = settings->DockId) @@ -17793,11 +20339,13 @@ void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiID root_id) settings->DockId = root_id; break; } + // Not really efficient, but easier to destroy a whole hierarchy considering DockContextRemoveNode is attempting to merge nodes if (nodes_to_remove.Size > 1) ImQsort(nodes_to_remove.Data, nodes_to_remove.Size, sizeof(ImGuiDockNode*), DockNodeComparerDepthMostFirst); for (int n = 0; n < nodes_to_remove.Size; n++) DockContextRemoveNode(&g, nodes_to_remove[n], false); + if (root_id == 0) { dc->Nodes.Clear(); @@ -17809,6 +20357,7 @@ void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiID root_id) root_node->SetLocalFlags(root_node->LocalFlags | ImGuiDockNodeFlags_CentralNode); } } + void ImGui::DockBuilderRemoveNodeDockedWindows(ImGuiID root_id, bool clear_settings_refs) { // Clear references in settings @@ -17826,6 +20375,7 @@ void ImGui::DockBuilderRemoveNodeDockedWindows(ImGuiID root_id, bool clear_setti settings->DockId = 0; } } + // Clear references in windows for (int n = 0; n < g.Windows.Size; n++) { @@ -17841,6 +20391,7 @@ void ImGui::DockBuilderRemoveNodeDockedWindows(ImGuiID root_id, bool clear_setti } } } + // If 'out_id_at_dir' or 'out_id_at_opposite_dir' are non NULL, the function will write out the ID of the two new nodes created. // Return value is ID of the node at the specified direction, so same as (*out_id_at_dir) if that pointer is set. // FIXME-DOCK: We are not exposing nor using split_outer. @@ -17849,12 +20400,14 @@ ImGuiID ImGui::DockBuilderSplitNode(ImGuiID id, ImGuiDir split_dir, float size_r ImGuiContext& g = *GImGui; IM_ASSERT(split_dir != ImGuiDir_None); IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderSplitNode: node 0x%08X, split_dir %d\n", id, split_dir); + ImGuiDockNode* node = DockContextFindNodeByID(&g, id); if (node == NULL) { IM_ASSERT(node != NULL); return 0; } + ImGuiDockRequest req; req.Type = ImGuiDockRequestType_Split; req.DockTargetWindow = NULL; @@ -17864,6 +20417,7 @@ ImGuiID ImGui::DockBuilderSplitNode(ImGuiID id, ImGuiDir split_dir, float size_r req.DockSplitRatio = ImSaturate((split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? size_ratio_for_node_at_dir : 1.0f - size_ratio_for_node_at_dir); req.DockSplitOuter = false; DockContextProcessDock(&g, &req); + ImGuiID id_at_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 0 : 1]->ID; ImGuiID id_at_opposite_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 1 : 0]->ID; if (out_id_at_dir) @@ -17872,6 +20426,7 @@ ImGuiID ImGui::DockBuilderSplitNode(ImGuiID id, ImGuiDir split_dir, float size_r *out_id_at_opposite_dir = id_at_opposite_dir; return id_at_dir; } + static ImGuiDockNode* DockBuilderCopyNodeRec(ImGuiDockNode* src_node, ImGuiID dst_node_id_if_known, ImVector* out_node_remap_pairs) { ImGuiContext& g = *GImGui; @@ -17884,30 +20439,39 @@ static ImGuiDockNode* DockBuilderCopyNodeRec(ImGuiDockNode* src_node, ImGuiID ds dst_node->SizeRef = src_node->SizeRef; dst_node->SplitAxis = src_node->SplitAxis; dst_node->UpdateMergedFlags(); + out_node_remap_pairs->push_back(src_node->ID); out_node_remap_pairs->push_back(dst_node->ID); + for (int child_n = 0; child_n < IM_ARRAYSIZE(src_node->ChildNodes); child_n++) if (src_node->ChildNodes[child_n]) { dst_node->ChildNodes[child_n] = DockBuilderCopyNodeRec(src_node->ChildNodes[child_n], 0, out_node_remap_pairs); dst_node->ChildNodes[child_n]->ParentNode = dst_node; } + IMGUI_DEBUG_LOG_DOCKING("[docking] Fork node %08X -> %08X (%d childs)\n", src_node->ID, dst_node->ID, dst_node->IsSplitNode() ? 2 : 0); return dst_node; } + void ImGui::DockBuilderCopyNode(ImGuiID src_node_id, ImGuiID dst_node_id, ImVector* out_node_remap_pairs) { ImGuiContext& g = *GImGui; IM_ASSERT(src_node_id != 0); IM_ASSERT(dst_node_id != 0); IM_ASSERT(out_node_remap_pairs != NULL); + DockBuilderRemoveNode(dst_node_id); + ImGuiDockNode* src_node = DockContextFindNodeByID(&g, src_node_id); IM_ASSERT(src_node != NULL); + out_node_remap_pairs->clear(); DockBuilderCopyNodeRec(src_node, dst_node_id, out_node_remap_pairs); + IM_ASSERT((out_node_remap_pairs->Size % 2) == 0); } + void ImGui::DockBuilderCopyWindowSettings(const char* src_name, const char* dst_name) { ImGuiWindow* src_window = FindWindowByName(src_name); @@ -17940,6 +20504,7 @@ void ImGui::DockBuilderCopyWindowSettings(const char* src_name, const char* dst_ dst_settings->Collapsed = src_window->Collapsed; } } + // FIXME: Will probably want to change this signature, in particular how the window remapping pairs are passed. void ImGui::DockBuilderCopyDockSpace(ImGuiID src_dockspace_id, ImGuiID dst_dockspace_id, ImVector* in_window_remap_pairs) { @@ -17948,11 +20513,13 @@ void ImGui::DockBuilderCopyDockSpace(ImGuiID src_dockspace_id, ImGuiID dst_docks IM_ASSERT(dst_dockspace_id != 0); IM_ASSERT(in_window_remap_pairs != NULL); IM_ASSERT((in_window_remap_pairs->Size % 2) == 0); + // Duplicate entire dock // FIXME: When overwriting dst_dockspace_id, windows that aren't part of our dockspace window class but that are docked in a same node will be split apart, // whereas we could attempt to at least keep them together in a new, same floating node. ImVector node_remap_pairs; DockBuilderCopyNode(src_dockspace_id, dst_dockspace_id, &node_remap_pairs); + // Attempt to transition all the upcoming windows associated to dst_dockspace_id into the newly created hierarchy of dock nodes // (The windows associated to src_dockspace_id are staying in place) ImVector src_windows; @@ -17962,6 +20529,7 @@ void ImGui::DockBuilderCopyDockSpace(ImGuiID src_dockspace_id, ImGuiID dst_docks const char* dst_window_name = (*in_window_remap_pairs)[remap_window_n + 1]; ImGuiID src_window_id = ImHashStr(src_window_name); src_windows.push_back(src_window_id); + // Search in the remapping tables ImGuiID src_dock_id = 0; if (ImGuiWindow* src_window = FindWindowByID(src_window_id)) @@ -17976,6 +20544,7 @@ void ImGui::DockBuilderCopyDockSpace(ImGuiID src_dockspace_id, ImGuiID dst_docks //node_remap_pairs[dock_remap_n] = node_remap_pairs[dock_remap_n + 1] = 0; // Clear break; } + if (dst_dock_id != 0) { // Docked windows gets redocked into the new node hierarchy. @@ -17990,6 +20559,7 @@ void ImGui::DockBuilderCopyDockSpace(ImGuiID src_dockspace_id, ImGuiID dst_docks DockBuilderCopyWindowSettings(src_window_name, dst_window_name); } } + // Anything else in the source nodes of 'node_remap_pairs' are windows that are not included in the remapping list. // Find those windows and move to them to the cloned dock node. This may be optional? // Dock those are a second step as undocking would invalidate source dock nodes. @@ -18005,6 +20575,7 @@ void ImGui::DockBuilderCopyDockSpace(ImGuiID src_dockspace_id, ImGuiID dst_docks ImGuiWindow* window = node->Windows[window_n]; if (src_windows.contains(window->ID)) continue; + // Docked windows gets redocked into the new node hierarchy. IMGUI_DEBUG_LOG_DOCKING("[docking] Remap window '%s' %08X -> %08X\n", window->Name, src_dock_id, dst_dock_id); dock_remaining_windows.push_back(DockRemainingWindowTask(window, dst_dock_id)); @@ -18013,6 +20584,7 @@ void ImGui::DockBuilderCopyDockSpace(ImGuiID src_dockspace_id, ImGuiID dst_docks for (const DockRemainingWindowTask& task : dock_remaining_windows) DockBuilderDockWindow(task.Window->Name, task.DockId); } + // FIXME-DOCK: This is awkward because in series of split user is likely to loose access to its root node. void ImGui::DockBuilderFinish(ImGuiID root_id) { @@ -18020,6 +20592,7 @@ void ImGui::DockBuilderFinish(ImGuiID root_id) //DockContextRebuild(&g); DockContextBuildAddWindowsToNodes(&g, root_id); } + //----------------------------------------------------------------------------- // Docking: Begin/End Support Functions (called from Begin/End) //----------------------------------------------------------------------------- @@ -18029,6 +20602,7 @@ void ImGui::DockBuilderFinish(ImGuiID root_id) // - BeginDockableDragDropSource() // - BeginDockableDragDropTarget() //----------------------------------------------------------------------------- + bool ImGui::GetWindowAlwaysWantOwnTabBar(ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -18038,17 +20612,20 @@ bool ImGui::GetWindowAlwaysWantOwnTabBar(ImGuiWindow* window) return true; return false; } + static ImGuiDockNode* ImGui::DockContextBindNodeToWindow(ImGuiContext* ctx, ImGuiWindow* window) { ImGuiContext& g = *ctx; ImGuiDockNode* node = DockContextFindNodeByID(ctx, window->DockId); IM_ASSERT(window->DockNode == NULL); + // We should not be docking into a split node (SetWindowDock should avoid this) if (node && node->IsSplitNode()) { DockContextProcessUndockWindow(ctx, window); return NULL; } + // Create node if (node == NULL) { @@ -18056,6 +20633,7 @@ static ImGuiDockNode* ImGui::DockContextBindNodeToWindow(ImGuiContext* ctx, ImGu node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Window; node->LastFrameAlive = g.FrameCount; } + // If the node just turned visible and is part of a hierarchy, it doesn't have a Size assigned by DockNodeTreeUpdatePosSize() yet, // so we're forcing a Pos/Size update from the first ancestor that is already visible (often it will be the root node). // If we don't do this, the window will be assigned a zero-size on its first frame, which won't ideally warm up the layout. @@ -18069,6 +20647,7 @@ static ImGuiDockNode* ImGui::DockContextBindNodeToWindow(ImGuiContext* ctx, ImGu DockNodeUpdateHasCentralNodeChild(DockNodeGetRootNode(ancestor_node)); DockNodeTreeUpdatePosSize(ancestor_node, ancestor_node->Pos, ancestor_node->Size, node); } + // Add window to node bool node_was_visible = node->IsVisible; DockNodeAddWindow(node, window, true); @@ -18076,17 +20655,21 @@ static ImGuiDockNode* ImGui::DockContextBindNodeToWindow(ImGuiContext* ctx, ImGu IM_ASSERT(node == window->DockNode); return node; } + static void StoreDockStyleForWindow(ImGuiWindow* window) { ImGuiContext& g = *GImGui; for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++) window->DockStyle.Colors[color_n] = ImGui::ColorConvertFloat4ToU32(g.Style.Colors[GWindowDockStyleColors[color_n]]); } + void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) { ImGuiContext& g = *GImGui; + // Clear fields ahead so most early-out paths don't have to do it window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false; + const bool auto_dock_node = GetWindowAlwaysWantOwnTabBar(window); if (auto_dock_node) { @@ -18108,6 +20691,7 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) return; } } + // Bind to our dock node ImGuiDockNode* node = window->DockNode; if (node != NULL) @@ -18118,6 +20702,7 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) if (node == NULL) return; } + #if 0 // Undock if the ImGuiDockNodeFlags_NoDockingInCentralNode got set if (node->IsCentralNode && (node->Flags & ImGuiDockNodeFlags_NoDockingInCentralNode)) @@ -18126,6 +20711,7 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) return; } #endif + // Undock if our dockspace node disappeared // Note how we are testing for LastFrameAlive and NOT LastFrameActive. A DockSpace node can be maintained alive while being inactive with ImGuiDockNodeFlags_KeepAliveOnly. if (node->LastFrameAlive < g.FrameCount) @@ -18138,8 +20724,10 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) window->DockIsActive = true; return; } + // Store style overrides StoreDockStyleForWindow(window); + // Fast path return. It is common for windows to hold on a persistent DockId but be the only visible window, // and never create neither a host window neither a tab bar. // FIXME-DOCK: replace ->HostWindow NULL compare with something more explicit (~was initially intended as a first frame test) @@ -18151,17 +20739,20 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) DockNodeHideWindowDuringHostWindowCreation(window); return; } + // We can have zero-sized nodes (e.g. children of a small-size dockspace) IM_ASSERT(node->HostWindow); IM_ASSERT(node->IsLeafNode()); IM_ASSERT(node->Size.x >= 0.0f && node->Size.y >= 0.0f); node->State = ImGuiDockNodeState_HostWindowVisible; + // Undock if we are submitted earlier than the host window if (!(node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly) && window->BeginOrderWithinContext < node->HostWindow->BeginOrderWithinContext) { DockContextProcessUndockWindow(&g, window); return; } + // Position/Size window SetNextWindowPos(node->Pos); SetNextWindowSize(node->Size); @@ -18171,9 +20762,11 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) window->DockTabIsVisible = false; if (node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly) return; + // When the window is selected we mark it as visible. if (node->VisibleWindow == window) window->DockTabIsVisible = true; + // Update window flag IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) == 0); window->Flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoResize; @@ -18182,22 +20775,27 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) window->Flags |= ImGuiWindowFlags_NoTitleBar; else window->Flags &= ~ImGuiWindowFlags_NoTitleBar; // Clear the NoTitleBar flag in case the user set it: confusingly enough we need a title bar height so we are correctly offset, but it won't be displayed! + // Save new dock order only if the window has been visible once already // This allows multiple windows to be created in the same frame and have their respective dock orders preserved. if (node->TabBar && window->WasActive) window->DockOrder = (short)DockNodeGetTabOrder(window); + if ((node->WantCloseAll || node->WantCloseTabId == window->TabId) && p_open != NULL) *p_open = false; + // Update ChildId to allow returning from Child to Parent with Escape ImGuiWindow* parent_window = window->DockNode->HostWindow; window->ChildId = parent_window->GetID(window->Name); } + void ImGui::BeginDockableDragDropSource(ImGuiWindow* window) { ImGuiContext& g = *GImGui; IM_ASSERT(g.ActiveId == window->MoveId); IM_ASSERT(g.MovingWindow == window); IM_ASSERT(g.CurrentWindow == window); + // 0: Hold SHIFT to disable docking, 1: Hold SHIFT to enable docking. if (g.IO.ConfigDockingWithShift != g.IO.KeyShift) { @@ -18209,6 +20807,7 @@ void ImGui::BeginDockableDragDropSource(ImGuiWindow* window) SetTooltip("%s", LocalizeGetMsg(ImGuiLocKey_DockingHoldShiftToDock)); return; } + g.LastItemData.ID = window->MoveId; window = window->RootWindowDockTree; IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0); @@ -18221,9 +20820,11 @@ void ImGui::BeginDockableDragDropSource(ImGuiWindow* window) StoreDockStyleForWindow(window); // Store style overrides while dragging (even when not docked) because docking preview may need it. } } + void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window) { ImGuiContext& g = *GImGui; + //IM_ASSERT(window->RootWindowDockTree == window); // May also be a DockSpace IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0); if (!g.DragDropActive) @@ -18231,6 +20832,7 @@ void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window) //GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255)); if (!BeginDragDropTargetCustom(window->Rect(), window->ID)) return; + // Peek into the payload before calling AcceptDragDropPayload() so we can handle overlapping dock nodes with filtering // (this is a little unusual pattern, normally most code would call AcceptDragDropPayload directly) const ImGuiPayload* payload = &g.DragDropPayload; @@ -18239,6 +20841,7 @@ void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window) EndDragDropTarget(); return; } + ImGuiWindow* payload_window = *(ImGuiWindow**)payload->Data; if (AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect)) { @@ -18250,6 +20853,7 @@ void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window) { // Cannot assume that node will != NULL even though we passed the rectangle test: it depends on padding/spacing handled by DockNodeTreeFindVisibleNodeByPos(). node = DockNodeTreeFindVisibleNodeByPos(window->DockNodeAsHost, g.IO.MousePos); + // There is an edge case when docking into a dockspace which only has _inactive_ nodes (because none of the windows are active) // In this case we need to fallback into any leaf mode, possibly the central node. // FIXME-20181220: We should not have to test for IsLeafNode() here but we have another bug to fix first. @@ -18263,8 +20867,10 @@ void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window) else dock_into_floating_window = true; // Dock into a regular window } + const ImRect explicit_target_rect = (node && node->TabBar && !node->IsHiddenTabBar() && !node->IsNoTabBar()) ? node->TabBar->BarRect : ImRect(window->Pos, window->Pos + ImVec2(window->Size.x, GetFrameHeight())); const bool is_explicit_target = g.IO.ConfigDockingWithShift || IsMouseHoveringRect(explicit_target_rect.Min, explicit_target_rect.Max); + // Preview docking request and find out split direction/ratio //const bool do_preview = true; // Ignore testing for payload->IsPreview() which removes one frame of delay, but breaks overlapping drop targets within the same window. const bool do_preview = payload->IsPreview() || payload->IsDelivery(); @@ -18285,9 +20891,11 @@ void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window) DockNodePreviewDockSetup(window, node, payload_window, NULL, &split_inner, is_explicit_target, false); if (split_data == &split_outer) split_inner.IsDropAllowed = false; + // Draw inner then outer, so that previewed tab (in inner data) will be behind the outer drop boxes DockNodePreviewDockRender(window, node, payload_window, &split_inner); DockNodePreviewDockRender(window, node, payload_window, &split_outer); + // Queue docking request if (split_data->IsDropAllowed && payload->IsDelivery()) DockContextQueueDock(&g, window, split_data->SplitNode, payload_window, split_data->SplitDir, split_data->SplitRatio, split_data == &split_outer); @@ -18295,6 +20903,7 @@ void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window) } EndDragDropTarget(); } + //----------------------------------------------------------------------------- // Docking: Settings //----------------------------------------------------------------------------- @@ -18307,6 +20916,7 @@ void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window) // - DockSettingsHandler_DockNodeToSettings() // - DockSettingsHandler_WriteAll() //----------------------------------------------------------------------------- + static void ImGui::DockSettingsRenameNodeReferences(ImGuiID old_node_id, ImGuiID new_node_id) { ImGuiContext& g = *GImGui; @@ -18322,6 +20932,7 @@ static void ImGui::DockSettingsRenameNodeReferences(ImGuiID old_node_id, ImGuiID if (settings->DockId == old_node_id) settings->DockId = new_node_id; } + // Remove references stored in ImGuiWindowSettings to the given ImGuiDockNodeSettings static void ImGui::DockSettingsRemoveNodeReferences(ImGuiID* node_ids, int node_ids_count) { @@ -18339,6 +20950,7 @@ static void ImGui::DockSettingsRemoveNodeReferences(ImGuiID* node_ids, int node_ return; } } + static ImGuiDockNodeSettings* ImGui::DockSettingsFindNodeSettings(ImGuiContext* ctx, ImGuiID id) { // FIXME-OPT @@ -18348,6 +20960,7 @@ static ImGuiDockNodeSettings* ImGui::DockSettingsFindNodeSettings(ImGuiContext* return &dc->NodesSettings[n]; return NULL; } + // Clear settings data static void ImGui::DockSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*) { @@ -18355,6 +20968,7 @@ static void ImGui::DockSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettings dc->NodesSettings.clear(); DockContextClearNodes(ctx, 0, true); } + // Recreate nodes based on settings data static void ImGui::DockSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*) { @@ -18365,17 +20979,20 @@ static void ImGui::DockSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettings DockContextBuildNodesFromSettings(ctx, dc->NodesSettings.Data, dc->NodesSettings.Size); DockContextBuildAddWindowsToNodes(ctx, 0); } + static void* ImGui::DockSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) { if (strcmp(name, "Data") != 0) return NULL; return (void*)1; } + static void ImGui::DockSettingsHandler_ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler*, void*, const char* line) { char c = 0; int x = 0, y = 0; int r = 0; + // Parsing, e.g. // " DockNode ID=0x00000001 Pos=383,193 Size=201,322 Split=Y,0.506 " // " DockNode ID=0x00000002 Parent=0x00000001 " @@ -18410,6 +21027,7 @@ static void ImGui::DockSettingsHandler_ReadLine(ImGuiContext* ctx, ImGuiSettings node.Depth = parent_settings->Depth + 1; ctx->DockContext.NodesSettings.push_back(node); } + static void DockSettingsHandler_DockNodeToSettings(ImGuiDockContext* dc, ImGuiDockNode* node, int depth) { ImGuiDockNodeSettings node_settings; @@ -18430,12 +21048,14 @@ static void DockSettingsHandler_DockNodeToSettings(ImGuiDockContext* dc, ImGuiDo if (node->ChildNodes[1]) DockSettingsHandler_DockNodeToSettings(dc, node->ChildNodes[1], depth + 1); } + static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) { ImGuiContext& g = *ctx; ImGuiDockContext* dc = &ctx->DockContext; if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)) return; + // Gather settings data // (unlike our windows settings, because nodes are always built we can do a full rewrite of the SettingsNode buffer) dc->NodesSettings.resize(0); @@ -18444,9 +21064,11 @@ static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettings if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p) if (node->IsRootNode()) DockSettingsHandler_DockNodeToSettings(dc, node, 0); + int max_depth = 0; for (int node_n = 0; node_n < dc->NodesSettings.Size; node_n++) max_depth = ImMax((int)dc->NodesSettings[node_n].Depth, max_depth); + // Write to text buffer buf->appendf("[%s][Data]\n", handler->TypeName); for (int node_n = 0; node_n < dc->NodesSettings.Size; node_n++) @@ -18481,6 +21103,7 @@ static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettings buf->appendf(" NoCloseButton=1"); if (node_settings->SelectedTabId) buf->appendf(" Selected=0x%08X", node_settings->SelectedTabId); + // [DEBUG] Include comments in the .ini file to ease debugging (this makes saving slower!) if (g.IO.ConfigDebugIniSettings) if (ImGuiDockNode* node = DockContextFindNodeByID(ctx, node_settings->ID)) @@ -18498,10 +21121,13 @@ static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettings buf->appendf("'%s' ", settings->GetName()); } } + buf->appendf("\n"); } buf->appendf("\n"); } + + //----------------------------------------------------------------------------- // [SECTION] PLATFORM DEPENDENT HELPERS //----------------------------------------------------------------------------- @@ -18509,11 +21135,14 @@ static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettings // - Default shell function handlers // - Default IME handlers //----------------------------------------------------------------------------- + #if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) + #ifdef _MSC_VER #pragma comment(lib, "user32") #pragma comment(lib, "kernel32") #endif + // Win32 clipboard implementation // We use g.ClipboardHandlerData for temporary storage to ensure it is freed on Shutdown() static const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx) @@ -18538,6 +21167,7 @@ static const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx) ::CloseClipboard(); return g.ClipboardHandlerData.Data; } + static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext*, const char* text) { if (!::OpenClipboard(NULL)) @@ -18557,9 +21187,12 @@ static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext*, const char* t ::GlobalFree(wbuf_handle); ::CloseClipboard(); } + #elif defined(__APPLE__) && TARGET_OS_OSX && defined(IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS) + #include // Use old API to avoid need for separate .mm file static PasteboardRef main_clipboard = 0; + // OSX clipboard implementation // If you enable this you will need to add '-framework ApplicationServices' to your linker command-line! static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext*, const char* text) @@ -18574,12 +21207,14 @@ static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext*, const char* t CFRelease(cf_data); } } + static const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx) { ImGuiContext& g = *ctx; if (!main_clipboard) PasteboardCreate(kPasteboardClipboard, &main_clipboard); PasteboardSynchronize(main_clipboard); + ItemCount item_count = 0; PasteboardGetItemCount(main_clipboard, &item_count); for (ItemCount i = 0; i < item_count; i++) @@ -18605,13 +21240,16 @@ static const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx) } return NULL; } + #else + // Local Dear ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers. static const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx) { ImGuiContext& g = *ctx; return g.ClipboardHandlerData.empty() ? NULL : g.ClipboardHandlerData.begin(); } + static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const char* text) { ImGuiContext& g = *ctx; @@ -18621,8 +21259,11 @@ static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const cha memcpy(&g.ClipboardHandlerData[0], text, (size_t)(text_end - text)); g.ClipboardHandlerData[(int)(text_end - text)] = 0; } + #endif // Default clipboard handlers + //----------------------------------------------------------------------------- + #ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS #if defined(__APPLE__) && TARGET_OS_IPHONE #define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS @@ -18634,6 +21275,7 @@ static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const cha #define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS #endif #endif // #ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS + #ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS #ifdef _WIN32 #include // ShellExecuteA() @@ -18677,19 +21319,24 @@ static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext*, const char* path) #else static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext*, const char*) { return false; } #endif // Default shell handlers + //----------------------------------------------------------------------------- + // Win32 API IME support (for Asian languages, etc.) #if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) + #include #ifdef _MSC_VER #pragma comment(lib, "imm32") #endif + static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport* viewport, ImGuiPlatformImeData* data) { // Notify OS Input Method Editor of text input position HWND hwnd = (HWND)viewport->PlatformHandleRaw; if (hwnd == 0) return; + //::ImmAssociateContextEx(hwnd, NULL, data->WantVisible ? IACE_DEFAULT : 0); if (HIMC himc = ::ImmGetContext(hwnd)) { @@ -18706,17 +21353,26 @@ static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport* view ::ImmReleaseContext(hwnd, himc); } } + #else + static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport*, ImGuiPlatformImeData*) {} + #endif // Default IME handlers + //----------------------------------------------------------------------------- // [SECTION] METRICS/DEBUGGER WINDOW //----------------------------------------------------------------------------- +// - MetricsHelpMarker() [Internal] // - DebugRenderViewportThumbnail() [Internal] // - RenderViewportsThumbnails() [Internal] +// - DebugRenderKeyboardPreview() [Internal] // - DebugTextEncoding() -// - MetricsHelpMarker() [Internal] -// - ShowFontAtlas() [Internal] +// - DebugFlashStyleColorStop() [Internal] +// - DebugFlashStyleColor() +// - UpdateDebugToolFlashStyleColor() [Internal] +// - ShowFontAtlas() [Internal but called by Demo!] +// - DebugNodeTexture() [Internal] // - ShowMetricsWindow() // - DebugNodeColumns() [Internal] // - DebugNodeDockNode() [Internal] @@ -18732,11 +21388,29 @@ static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport*, ImG // - DebugNodeWindowsList() [Internal] // - DebugNodeWindowsListByBeginStackParent() [Internal] //----------------------------------------------------------------------------- + +#if !defined(IMGUI_DISABLE_DEMO_WINDOWS) || !defined(IMGUI_DISABLE_DEBUG_TOOLS) +// Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds. +static void MetricsHelpMarker(const char* desc) +{ + ImGui::TextDisabled("(?)"); + if (ImGui::BeginItemTooltip()) + { + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} +#endif + #ifndef IMGUI_DISABLE_DEBUG_TOOLS + void ImGui::DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* viewport, const ImRect& bb) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + ImVec2 scale = bb.GetSize() / viewport->Size; ImVec2 off = bb.Min - viewport->Pos * scale; float alpha_mul = (viewport->Flags & ImGuiViewportFlags_IsMinimized) ? 0.30f : 1.00f; @@ -18747,6 +21421,7 @@ void ImGui::DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* continue; if (thumb_window->Viewport != viewport) continue; + ImRect thumb_r = thumb_window->Rect(); ImRect title_r = thumb_window->TitleBarRect(); thumb_r = ImRect(ImTrunc(off + thumb_r.Min * scale), ImTrunc(off + thumb_r.Max * scale)); @@ -18763,10 +21438,12 @@ void ImGui::DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* if (viewport->ID == g.DebugMetricsConfig.HighlightViewportID) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255)); } + static void RenderViewportsThumbnails() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + // Draw monitor and calculate their boundaries float SCALE = 1.0f / 8.0f; ImRect bb_full(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); @@ -18780,6 +21457,7 @@ static void RenderViewportsThumbnails() window->DrawList->AddRect(monitor_draw_bb.Min, monitor_draw_bb.Max, (g.DebugMetricsConfig.HighlightMonitorIdx == g.PlatformIO.Monitors.index_from_ptr(&monitor)) ? IM_COL32(255, 255, 0, 255) : ImGui::GetColorU32(ImGuiCol_Border), 4.0f); window->DrawList->AddRectFilled(monitor_draw_bb.Min, monitor_draw_bb.Max, ImGui::GetColorU32(ImGuiCol_Border, 0.10f), 4.0f); } + // Draw viewports for (ImGuiViewportP* viewport : g.Viewports) { @@ -18788,12 +21466,14 @@ static void RenderViewportsThumbnails() } ImGui::Dummy(bb_full.GetSize() * SCALE); } + static int IMGUI_CDECL ViewportComparerByLastFocusedStampCount(const void* lhs, const void* rhs) { const ImGuiViewportP* a = *(const ImGuiViewportP* const*)lhs; const ImGuiViewportP* b = *(const ImGuiViewportP* const*)rhs; return b->LastFocusedStampCount - a->LastFocusedStampCount; } + // Draw an arbitrary US keyboard layout to visualize translated keys void ImGui::DebugRenderKeyboardPreview(ImDrawList* draw_list) { @@ -18806,9 +21486,11 @@ void ImGui::DebugRenderKeyboardPreview(ImDrawList* draw_list) const ImVec2 key_label_pos = ImVec2(7.0f, 4.0f) * scale; const ImVec2 key_step = ImVec2(key_size.x - 1.0f, key_size.y - 1.0f); const float key_row_offset = 9.0f * scale; + ImVec2 board_min = GetCursorScreenPos(); ImVec2 board_max = ImVec2(board_min.x + 3 * key_step.x + 2 * key_row_offset + 10.0f, board_min.y + 3 * key_step.y + 10.0f); ImVec2 start_pos = ImVec2(board_min.x + 5.0f - key_step.x, board_min.y); + struct KeyLayoutData { int Row, Col; const char* Label; ImGuiKey Key; }; const KeyLayoutData keys_to_display[] = { @@ -18816,6 +21498,7 @@ void ImGui::DebugRenderKeyboardPreview(ImDrawList* draw_list) { 1, 0, "", ImGuiKey_CapsLock }, { 1, 1, "A", ImGuiKey_A }, { 1, 2, "S", ImGuiKey_S }, { 1, 3, "D", ImGuiKey_D }, { 1, 4, "F", ImGuiKey_F }, { 2, 0, "", ImGuiKey_LeftShift },{ 2, 1, "Z", ImGuiKey_Z }, { 2, 2, "X", ImGuiKey_X }, { 2, 3, "C", ImGuiKey_C }, { 2, 4, "V", ImGuiKey_V } }; + // Elements rendered manually via ImDrawList API are not clipped automatically. // While not strictly necessary, here IsItemVisible() is used to avoid rendering these shapes when they are out of view. Dummy(board_max - board_min); @@ -18840,6 +21523,7 @@ void ImGui::DebugRenderKeyboardPreview(ImDrawList* draw_list) } draw_list->PopClipRect(); } + // Helper tool to diagnose between text encoding issues and font loading issues. Pass your UTF-8 string and verify that there are correct. void ImGui::DebugTextEncoding(const char* str) { @@ -18865,16 +21549,19 @@ void ImGui::DebugTextEncoding(const char* str) Text("0x%02X", (int)(unsigned char)p[byte_index]); } TableNextColumn(); - if (GetFont()->FindGlyphNoFallback((ImWchar)c)) - TextUnformatted(p, p + c_utf8_len); - else - TextUnformatted((c == IM_UNICODE_CODEPOINT_INVALID) ? "[invalid]" : "[missing]"); + TextUnformatted(p, p + c_utf8_len); + if (!GetFont()->IsGlyphInFont((ImWchar)c)) + { + SameLine(); + TextUnformatted("[missing]"); + } TableNextColumn(); Text("U+%04X", (int)c); p += c_utf8_len; } EndTable(); } + static void DebugFlashStyleColorStop() { ImGuiContext& g = *GImGui; @@ -18882,6 +21569,7 @@ static void DebugFlashStyleColorStop() g.Style.Colors[g.DebugFlashStyleColorIdx] = g.DebugFlashStyleColorBackup; g.DebugFlashStyleColorIdx = ImGuiCol_COUNT; } + // Flash a given style color for some + inhibit modifications of this color via PushStyleColor() calls. void ImGui::DebugFlashStyleColor(ImGuiCol idx) { @@ -18891,6 +21579,7 @@ void ImGui::DebugFlashStyleColor(ImGuiCol idx) g.DebugFlashStyleColorIdx = idx; g.DebugFlashStyleColorBackup = g.Style.Colors[idx]; } + void ImGui::UpdateDebugToolFlashStyleColor() { ImGuiContext& g = *GImGui; @@ -18901,6 +21590,7 @@ void ImGui::UpdateDebugToolFlashStyleColor() if ((g.DebugFlashStyleColorTime -= g.IO.DeltaTime) <= 0.0f) DebugFlashStyleColorStop(); } + static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureID tex_id) { union { void* ptr; int integer; } tex_id_opaque; @@ -18911,36 +21601,206 @@ static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTex ImFormatString(buf, buf_size, "0x%04X", tex_id_opaque.integer); return buf; } -// Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds. -static void MetricsHelpMarker(const char* desc) + +static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, const ImDrawCmd* cmd) { - ImGui::TextDisabled("(?)"); - if (ImGui::BeginItemTooltip()) - { - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextUnformatted(desc); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + char* buf_end = buf + buf_size; + if (cmd->TexRef._TexData != NULL) + buf += ImFormatString(buf, buf_end - buf, "#%03d: ", cmd->TexRef._TexData->UniqueID); + return FormatTextureIDForDebugDisplay(buf, (int)(buf_end - buf), cmd->TexRef.GetTexID()); // Calling TexRef::GetTexID() to avoid assert of cmd->GetTexID() } + +#ifdef IMGUI_ENABLE_FREETYPE +namespace ImGuiFreeType { IMGUI_API const ImFontLoader* GetFontLoader(); IMGUI_API bool DebugEditFontLoaderFlags(unsigned int* p_font_builder_flags); } +#endif + // [DEBUG] List fonts in a font atlas and display its texture void ImGui::ShowFontAtlas(ImFontAtlas* atlas) { + ImGuiContext& g = *GImGui; + ImGuiIO& io = g.IO; + ImGuiStyle& style = g.Style; + + BeginDisabled(); + CheckboxFlags("io.BackendFlags: RendererHasTextures", &io.BackendFlags, ImGuiBackendFlags_RendererHasTextures); + EndDisabled(); + ShowFontSelector("Font"); + //BeginDisabled((io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0); + if (DragFloat("FontSizeBase", &style.FontSizeBase, 0.20f, 5.0f, 100.0f, "%.0f")) + style._NextFrameFontSizeBase = style.FontSizeBase; // FIXME: Temporary hack until we finish remaining work. + SameLine(0.0f, 0.0f); Text(" (out %.2f)", GetFontSize()); + SameLine(); MetricsHelpMarker("- This is scaling font only. General scaling will come later."); + DragFloat("FontScaleMain", &style.FontScaleMain, 0.02f, 0.5f, 4.0f); + //BeginDisabled(io.ConfigDpiScaleFonts); + DragFloat("FontScaleDpi", &style.FontScaleDpi, 0.02f, 0.5f, 4.0f); + //SetItemTooltip("When io.ConfigDpiScaleFonts is set, this value is automatically overwritten."); + //EndDisabled(); + if ((io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + { + BulletText("Warning: Font scaling will NOT be smooth, because\nImGuiBackendFlags_RendererHasTextures is not set!"); + BulletText("For instructions, see:"); + SameLine(); + TextLinkOpenURL("docs/BACKENDS.md", "https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md"); + } + BulletText("Load a nice font for better results!"); + BulletText("Please submit feedback:"); + SameLine(); TextLinkOpenURL("#8465", "https://github.com/ocornut/imgui/issues/8465"); + BulletText("Read FAQ for more details:"); + SameLine(); TextLinkOpenURL("dearimgui.com/faq", "https://www.dearimgui.com/faq/"); + //EndDisabled(); + + SeparatorText("Font List"); + + ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; + Checkbox("Show font preview", &cfg->ShowFontPreview); + + // Font loaders + if (TreeNode("Loader", "Loader: \'%s\'", atlas->FontLoaderName ? atlas->FontLoaderName : "NULL")) + { + const ImFontLoader* loader_current = atlas->FontLoader; + BeginDisabled(!atlas->RendererHasTextures); +#ifdef IMGUI_ENABLE_STB_TRUETYPE + const ImFontLoader* loader_stbtruetype = ImFontAtlasGetFontLoaderForStbTruetype(); + if (RadioButton("stb_truetype", loader_current == loader_stbtruetype)) + atlas->SetFontLoader(loader_stbtruetype); +#else + BeginDisabled(); + RadioButton("stb_truetype", false); + SetItemTooltip("Requires #define IMGUI_ENABLE_STB_TRUETYPE"); + EndDisabled(); +#endif + SameLine(); +#ifdef IMGUI_ENABLE_FREETYPE + const ImFontLoader* loader_freetype = ImGuiFreeType::GetFontLoader(); + if (RadioButton("FreeType", loader_current == loader_freetype)) + atlas->SetFontLoader(loader_freetype); + if (loader_current == loader_freetype) + { + unsigned int loader_flags = atlas->FontLoaderFlags; + Text("Shared FreeType Loader Flags: 0x%08X", loader_flags); + if (ImGuiFreeType::DebugEditFontLoaderFlags(&loader_flags)) + { + for (ImFont* font : atlas->Fonts) + ImFontAtlasFontDestroyOutput(atlas, font); + atlas->FontLoaderFlags = loader_flags; + for (ImFont* font : atlas->Fonts) + ImFontAtlasFontInitOutput(atlas, font); + } + } +#else + BeginDisabled(); + RadioButton("FreeType", false); + SetItemTooltip("Requires #define IMGUI_ENABLE_FREETYPE + imgui_freetype.cpp."); + EndDisabled(); +#endif + EndDisabled(); + TreePop(); + } + + // Font list for (ImFont* font : atlas->Fonts) { PushID(font); DebugNodeFont(font); PopID(); } - if (TreeNode("Font Atlas", "Font Atlas (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) + + SeparatorText("Font Atlas"); + if (Button("Compact")) + atlas->CompactCache(); + SameLine(); + if (Button("Grow")) + ImFontAtlasTextureGrow(atlas); + SameLine(); + if (Button("Clear All")) + ImFontAtlasBuildClear(atlas); + SetItemTooltip("Destroy cache and custom rectangles."); + + for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) { - ImGuiContext& g = *GImGui; - PushStyleVar(ImGuiStyleVar_ImageBorderSize, ImMax(1.0f, g.Style.ImageBorderSize)); - ImageWithBg(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + ImTextureData* tex = atlas->TexList[tex_n]; + if (tex_n > 0) + SameLine(); + Text("Tex: %dx%d", tex->Width, tex->Height); + } + const int packed_surface_sqrt = (int)sqrtf((float)atlas->Builder->RectsPackedSurface); + const int discarded_surface_sqrt = (int)sqrtf((float)atlas->Builder->RectsDiscardedSurface); + Text("Packed rects: %d, area: about %d px ~%dx%d px", atlas->Builder->RectsPackedCount, atlas->Builder->RectsPackedSurface, packed_surface_sqrt, packed_surface_sqrt); + Text("incl. Discarded rects: %d, area: about %d px ~%dx%d px", atlas->Builder->RectsDiscardedCount, atlas->Builder->RectsDiscardedSurface, discarded_surface_sqrt, discarded_surface_sqrt); + + ImFontAtlasRectId highlight_r_id = ImFontAtlasRectId_Invalid; + if (TreeNode("Rects Index", "Rects Index (%d)", atlas->Builder->RectsPackedCount)) // <-- Use count of used rectangles + { + PushStyleVar(ImGuiStyleVar_ImageBorderSize, 1.0f); + if (BeginTable("##table", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY, ImVec2(0.0f, GetTextLineHeightWithSpacing() * 12))) + { + for (const ImFontAtlasRectEntry& entry : atlas->Builder->RectsIndex) + if (entry.IsUsed) + { + ImFontAtlasRectId id = ImFontAtlasRectId_Make(atlas->Builder->RectsIndex.index_from_ptr(&entry), entry.Generation); + ImFontAtlasRect r = {}; + atlas->GetCustomRect(id, &r); + const char* buf; + ImFormatStringToTempBuffer(&buf, NULL, "ID:%08X, used:%d, { w:%3d, h:%3d } { x:%4d, y:%4d }", id, entry.IsUsed, r.w, r.h, r.x, r.y); + TableNextColumn(); + Selectable(buf); + if (IsItemHovered()) + highlight_r_id = id; + TableNextColumn(); + Image(atlas->TexRef, ImVec2(r.w, r.h), r.uv0, r.uv1); + } + EndTable(); + } PopStyleVar(); TreePop(); } + + // Texture list + // (ensure the last texture always use the same ID, so we can keep it open neatly) + ImFontAtlasRect highlight_r; + if (highlight_r_id != ImFontAtlasRectId_Invalid) + atlas->GetCustomRect(highlight_r_id, &highlight_r); + for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) + { + if (tex_n == atlas->TexList.Size - 1) + SetNextItemOpen(true, ImGuiCond_Once); + DebugNodeTexture(atlas->TexList[tex_n], atlas->TexList.Size - 1 - tex_n, (highlight_r_id != ImFontAtlasRectId_Invalid) ? &highlight_r : NULL); + } } + +void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRect* highlight_rect) +{ + ImGuiContext& g = *GImGui; + PushID(int_id); + if (TreeNode("", "Texture #%03d (%dx%d pixels)", tex->UniqueID, tex->Width, tex->Height)) + { + ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; + Checkbox("Show used rect", &cfg->ShowTextureUsedRect); + PushStyleVar(ImGuiStyleVar_ImageBorderSize, ImMax(1.0f, g.Style.ImageBorderSize)); + ImVec2 p = GetCursorScreenPos(); + if (tex->WantDestroyNextFrame) + Dummy(ImVec2((float)tex->Width, (float)tex->Height)); + else + ImageWithBg(tex->GetTexRef(), ImVec2((float)tex->Width, (float)tex->Height), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + if (cfg->ShowTextureUsedRect) + GetWindowDrawList()->AddRect(ImVec2(p.x + tex->UsedRect.x, p.y + tex->UsedRect.y), ImVec2(p.x + tex->UsedRect.x + tex->UsedRect.w, p.y + tex->UsedRect.y + tex->UsedRect.h), IM_COL32(255, 0, 255, 255)); + if (highlight_rect != NULL) + { + ImRect r_outer(p.x, p.y, p.x + tex->Width, p.y + tex->Height); + ImRect r_inner(p.x + highlight_rect->x, p.y + highlight_rect->y, p.x + highlight_rect->x + highlight_rect->w, p.y + highlight_rect->y + highlight_rect->h); + RenderRectFilledWithHole(GetWindowDrawList(), r_outer, r_inner, IM_COL32(0, 0, 0, 100), 0.0f); + GetWindowDrawList()->AddRect(r_inner.Min - ImVec2(1, 1), r_inner.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255)); + } + PopStyleVar(); + + char texid_desc[30]; + Text("Status = %s (%d), Format = %s (%d), UseColors = %d", ImTextureDataGetStatusName(tex->Status), tex->Status, ImTextureDataGetFormatName(tex->Format), tex->Format, tex->UseColors); + Text("TexID = %s, BackendUserData = %p", FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), tex->TexID), tex->BackendUserData); + TreePop(); + } + PopID(); +} + void ImGui::ShowMetricsWindow(bool* p_open) { ImGuiContext& g = *GImGui; @@ -18950,13 +21810,16 @@ void ImGui::ShowMetricsWindow(bool* p_open) ShowDebugLogWindow(&cfg->ShowDebugLog); if (cfg->ShowIDStackTool) ShowIDStackToolWindow(&cfg->ShowIDStackTool); + if (!Begin("Dear ImGui Metrics/Debugger", p_open) || GetCurrentWindow()->BeginCount > 1) { End(); return; } + // [DEBUG] Clear debug breaks hooks after exactly one cycle. DebugBreakClearData(); + // Basic info Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); if (g.ContextName[0] != 0) @@ -18968,7 +21831,9 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("%d vertices, %d indices (%d triangles)", io.MetricsRenderVertices, io.MetricsRenderIndices, io.MetricsRenderIndices / 3); Text("%d visible windows, %d current allocations", io.MetricsRenderWindows, g.DebugAllocInfo.TotalAllocCount - g.DebugAllocInfo.TotalFreeCount); //SameLine(); if (SmallButton("GC")) { g.GcCompactAll = true; } + Separator(); + // Debugging enums enum { WRT_OuterRect, WRT_OuterRectClipped, WRT_InnerRect, WRT_InnerClipRect, WRT_WorkRect, WRT_Content, WRT_ContentIdeal, WRT_ContentRegionRect, WRT_Count }; // Windows Rect Type const char* wrt_rects_names[WRT_Count] = { "OuterRect", "OuterRectClipped", "InnerRect", "InnerClipRect", "WorkRect", "Content", "ContentIdeal", "ContentRegionRect" }; @@ -18978,6 +21843,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) cfg->ShowWindowsRectsType = WRT_WorkRect; if (cfg->ShowTablesRectsType < 0) cfg->ShowTablesRectsType = TRT_WorkRect; + struct Funcs { static ImRect GetTableRect(ImGuiTable* table, int rect_type, int n) @@ -18999,6 +21865,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) IM_ASSERT(0); return ImRect(); } + static ImRect GetWindowRect(ImGuiWindow* window, int rect_type) { if (rect_type == WRT_OuterRect) { return window->Rect(); } @@ -19013,6 +21880,11 @@ void ImGui::ShowMetricsWindow(bool* p_open) return ImRect(); } }; + +#ifdef IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS is enabled.\nMust disable after use! Otherwise Dear ImGui will run slower.\n"); +#endif + // Tools if (TreeNode("Tools")) { @@ -19024,13 +21896,17 @@ void ImGui::ShowMetricsWindow(bool* p_open) if (Checkbox("Show Item Picker", &g.DebugItemPickerActive) && g.DebugItemPickerActive) DebugStartItemPicker(); Checkbox("Show \"Debug Break\" buttons in other sections (io.ConfigDebugIsDebuggerPresent)", &g.IO.ConfigDebugIsDebuggerPresent); + SeparatorText("Visualize"); + Checkbox("Show Debug Log", &cfg->ShowDebugLog); SameLine(); MetricsHelpMarker("You can also call ImGui::ShowDebugLogWindow() from your code."); + Checkbox("Show ID Stack Tool", &cfg->ShowIDStackTool); SameLine(); MetricsHelpMarker("You can also call ImGui::ShowIDStackToolWindow() from your code."); + Checkbox("Show windows begin order", &cfg->ShowWindowsBeginOrder); Checkbox("Show windows rectangles", &cfg->ShowWindowsRects); SameLine(); @@ -19047,6 +21923,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) } Unindent(); } + Checkbox("Show tables rectangles", &cfg->ShowTablesRects); SameLine(); SetNextItemWidth(GetFontSize() * 12); @@ -19058,6 +21935,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImGuiTable* table = g.Tables.TryGetMapData(table_n); if (table == NULL || table->LastFrameActive < g.FrameCount - 1 || (table->OuterWindow != g.NavWindow && table->InnerWindow != g.NavWindow)) continue; + BulletText("Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name); if (IsItemHovered()) GetForegroundDrawList()->AddRect(table->OuterRect.Min - ImVec2(1, 1), table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); @@ -19091,10 +21969,13 @@ void ImGui::ShowMetricsWindow(bool* p_open) } } Checkbox("Show groups rectangles", &g.DebugShowGroupRects); // Storing in context as this is used by group code and prefers to be in hot-data + SeparatorText("Validate"); + Checkbox("Debug Begin/BeginChild return value", &io.ConfigDebugBeginReturnValueLoop); SameLine(); MetricsHelpMarker("Some calls to Begin()/BeginChild() will return false.\n\nWill cycle through window depths then repeat. Windows should be flickering while running."); + Checkbox("UTF-8 Encoding viewer", &cfg->ShowTextEncodingViewer); SameLine(); MetricsHelpMarker("You can also call ImGui::DebugTextEncoding() from your code with a given string to test that your UTF-8 encoding settings are correct."); @@ -19106,8 +21987,10 @@ void ImGui::ShowMetricsWindow(bool* p_open) if (buf[0] != 0) DebugTextEncoding(buf); } + TreePop(); } + // Windows if (TreeNode("Windows", "Windows (%d)", g.Windows.Size)) { @@ -19127,8 +22010,10 @@ void ImGui::ShowMetricsWindow(bool* p_open) DebugNodeWindowsListByBeginStackParent(temp_buffer.Data, temp_buffer.Size, NULL); TreePop(); } + TreePop(); } + // DrawLists int drawlist_count = 0; for (ImGuiViewportP* viewport : g.Viewports) @@ -19150,6 +22035,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) } TreePop(); } + // Viewports if (TreeNode("Viewports", "Viewports (%d)", g.Viewports.Size)) { @@ -19168,6 +22054,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) DebugNodePlatformMonitor(&g.FallbackMonitor, "Fallback", 0); TreePop(); } + SetNextItemOpen(true, ImGuiCond_Once); if (TreeNode("Windows Minimap")) { @@ -19175,6 +22062,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } cfg->HighlightViewportID = 0; + BulletText("MouseViewport: 0x%08X (UserHovered 0x%08X, LastHovered 0x%08X)", g.MouseViewport ? g.MouseViewport->ID : 0, g.IO.MouseHoveredViewport, g.MouseLastHoveredViewport ? g.MouseLastHoveredViewport->ID : 0); if (TreeNode("Inferred Z order (front-to-back)")) { @@ -19194,10 +22082,20 @@ void ImGui::ShowMetricsWindow(bool* p_open) } TreePop(); } + for (ImGuiViewportP* viewport : g.Viewports) DebugNodeViewport(viewport); TreePop(); } + + // Details for Fonts + for (ImFontAtlas* atlas : g.FontAtlases) + if (TreeNode((void*)atlas, "Fonts (%d), Textures (%d)", atlas->Fonts.Size, atlas->TexList.Size)) + { + ShowFontAtlas(atlas); + TreePop(); + } + // Details for Popups if (TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size)) { @@ -19211,6 +22109,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) } TreePop(); } + // Details for TabBars if (TreeNode("TabBars", "Tab Bars (%d)", g.TabBars.GetAliveCount())) { @@ -19223,6 +22122,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) } TreePop(); } + // Details for Tables if (TreeNode("Tables", "Tables (%d)", g.Tables.GetAliveCount())) { @@ -19231,25 +22131,21 @@ void ImGui::ShowMetricsWindow(bool* p_open) DebugNodeTable(table); TreePop(); } - // Details for Fonts - ImFontAtlas* atlas = g.IO.Fonts; - if (TreeNode("Fonts", "Fonts (%d)", atlas->Fonts.Size)) - { - ShowFontAtlas(atlas); - TreePop(); - } + // Details for InputText if (TreeNode("InputText")) { DebugNodeInputTextState(&g.InputTextState); TreePop(); } + // Details for TypingSelect if (TreeNode("TypingSelect", "TypingSelect (%d)", g.TypingSelectState.SearchBuffer[0] != 0 ? 1 : 0)) { DebugNodeTypingSelectState(&g.TypingSelectState); TreePop(); } + // Details for MultiSelect if (TreeNode("MultiSelect", "MultiSelect (%d)", g.MultiSelectStorage.GetAliveCount())) { @@ -19260,6 +22156,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) DebugNodeMultiSelectState(state); TreePop(); } + // Details for Docking #ifdef IMGUI_HAS_DOCK if (TreeNode("Docking")) @@ -19278,6 +22175,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } #endif // #ifdef IMGUI_HAS_DOCK + // Settings if (TreeNode("Settings")) { @@ -19308,12 +22206,14 @@ void ImGui::ShowMetricsWindow(bool* p_open) DebugNodeWindowSettings(settings); TreePop(); } + if (TreeNode("SettingsTables", "Settings packed data: Tables: %d bytes", g.SettingsTables.size())) { for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) DebugNodeTableSettings(settings); TreePop(); } + #ifdef IMGUI_HAS_DOCK if (TreeNode("SettingsDocking", "Settings packed data: Docking")) { @@ -19339,6 +22239,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } #endif // #ifdef IMGUI_HAS_DOCK + if (TreeNode("SettingsIniData", "Settings unpacked data (.ini): %d bytes", g.SettingsIniData.size())) { InputTextMultiline("##Ini", (char*)(void*)g.SettingsIniData.c_str(), g.SettingsIniData.Buf.Size, ImVec2(-FLT_MIN, GetTextLineHeight() * 20), ImGuiInputTextFlags_ReadOnly); @@ -19346,6 +22247,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) } TreePop(); } + // Settings if (TreeNode("Memory allocations")) { @@ -19366,6 +22268,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) } TreePop(); } + if (TreeNode("Inputs")) { Text("KEYBOARD/GAMEPAD/MOUSE KEYS"); @@ -19380,6 +22283,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) DebugRenderKeyboardPreview(GetWindowDrawList()); Unindent(); } + Text("MOUSE STATE"); { Indent(); @@ -19398,6 +22302,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("Pen Pressure: %.1f", io.PenPressure); // Note: currently unused Unindent(); } + Text("MOUSE WHEELING"); { Indent(); @@ -19406,6 +22311,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("WheelingAxisAvg[] = { %.3f, %.3f }, Main Axis: %s", g.WheelingAxisAvg.x, g.WheelingAxisAvg.y, (g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? "X" : (g.WheelingAxisAvg.x < g.WheelingAxisAvg.y) ? "Y" : ""); Unindent(); } + Text("KEY OWNERS"); { Indent(); @@ -19452,6 +22358,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) } TreePop(); } + if (TreeNode("Internal state")) { Text("WINDOWING"); @@ -19463,6 +22370,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("MovingWindow: '%s'", g.MovingWindow ? g.MovingWindow->Name : "NULL"); Text("MouseViewport: 0x%08X (UserHovered 0x%08X, LastHovered 0x%08X)", g.MouseViewport->ID, g.IO.MouseHoveredViewport, g.MouseLastHoveredViewport ? g.MouseLastHoveredViewport->ID : 0); Unindent(); + Text("ITEMS"); Indent(); Text("ActiveId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d, Source: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, g.ActiveIdAllowOverlap, GetInputSourceName(g.ActiveIdSource)); @@ -19474,6 +22382,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize); DebugLocateItemOnHover(g.DragDropPayload.SourceId); Unindent(); + Text("NAV,FOCUS"); Indent(); Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL"); @@ -19496,8 +22405,10 @@ void ImGui::ShowMetricsWindow(bool* p_open) } Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL"); Unindent(); + TreePop(); } + // Overlay: Display windows Rectangles and Begin Order if (cfg->ShowWindowsRects || cfg->ShowWindowsBeginOrder) { @@ -19521,6 +22432,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) } } } + // Overlay: Display Tables Rectangles if (cfg->ShowTablesRects) { @@ -19547,6 +22459,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) } } } + #ifdef IMGUI_HAS_DOCK // Overlay: Display Docking info if (cfg->ShowDockingNodes && g.IO.KeyCtrl && g.DebugHoveredDockNode) @@ -19566,8 +22479,10 @@ void ImGui::ShowMetricsWindow(bool* p_open) overlay_draw_list->AddText(NULL, 0.0f, pos, IM_COL32(255, 255, 255, 255), buf); } #endif // #ifdef IMGUI_HAS_DOCK + End(); } + void ImGui::DebugBreakClearData() { // Those fields are scattered in their respective subsystem to stay in hot-data locations @@ -19576,6 +22491,7 @@ void ImGui::DebugBreakClearData() g.DebugBreakInTable = 0; g.DebugBreakInShortcutRouting = ImGuiKey_None; } + void ImGui::DebugBreakButtonTooltip(bool keyboard_only, const char* description_of_location) { if (!BeginItemTooltip()) @@ -19587,6 +22503,7 @@ void ImGui::DebugBreakButtonTooltip(bool keyboard_only, const char* description_ TextUnformatted("Choose one way that doesn't interfere with what you are trying to debug!\nYou need a debugger attached or this will crash!"); EndTooltip(); } + // Special button that doesn't take focus, doesn't take input owner, and can be activated without a click etc. // In order to reduce interferences with the contents we are trying to debug into. bool ImGui::DebugBreakButton(const char* label, const char* description_of_location) @@ -19594,29 +22511,36 @@ bool ImGui::DebugBreakButton(const char* label, const char* description_of_locat ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; const ImGuiID id = window->GetID(label); const ImVec2 label_size = CalcTextSize(label, NULL, true); ImVec2 pos = window->DC.CursorPos + ImVec2(0.0f, window->DC.CurrLineTextBaseOffset); ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x * 2.0f, label_size.y); + const ImRect bb(pos, pos + size); ItemSize(size, 0.0f); if (!ItemAdd(bb, id)) return false; + // WE DO NOT USE ButtonEx() or ButtonBehavior() in order to reduce our side-effects. bool hovered = ItemHoverable(bb, id, g.CurrentItemFlags); bool pressed = hovered && (IsKeyChordPressed(g.DebugBreakKeyChord) || IsMouseClicked(0) || g.NavActivateId == id); DebugBreakButtonTooltip(false, description_of_location); + ImVec4 col4f = GetStyleColorVec4(hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); ImVec4 hsv; ColorConvertRGBtoHSV(col4f.x, col4f.y, col4f.z, hsv.x, hsv.y, hsv.z); ColorConvertHSVtoRGB(hsv.x + 0.20f, hsv.y, hsv.z, col4f.x, col4f.y, col4f.z); + RenderNavCursor(bb, id); RenderFrame(bb.Min, bb.Max, GetColorU32(col4f), true, g.Style.FrameRounding); RenderTextClipped(bb.Min, bb.Max, label, NULL, &label_size, g.Style.ButtonTextAlign, &bb); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return pressed; } + // [DEBUG] Display contents of Columns void ImGui::DebugNodeColumns(ImGuiOldColumns* columns) { @@ -19627,6 +22551,7 @@ void ImGui::DebugNodeColumns(ImGuiOldColumns* columns) BulletText("Column %02d: OffsetNorm %.3f (= %.1f px)", (int)columns->Columns.index_from_ptr(&column), column.OffsetNorm, GetColumnOffsetFromNorm(columns, column.OffsetNorm)); TreePop(); } + static void DebugNodeDockNodeFlags(ImGuiDockNodeFlags* p_flags, const char* label, bool enabled) { using namespace ImGui; @@ -19655,6 +22580,7 @@ static void DebugNodeDockNodeFlags(ImGuiDockNodeFlags* p_flags, const char* labe PopStyleVar(); PopID(); } + // [DEBUG] Display contents of ImDockNode void ImGui::DebugNodeDockNode(ImGuiDockNode* node, const char* label) { @@ -19708,9 +22634,11 @@ void ImGui::DebugNodeDockNode(ImGuiDockNode* node, const char* label) if (node->TabBar) DebugNodeTabBar(node->TabBar, "TabBar"); DebugNodeWindowsList(&node->Windows, "Windows"); + TreePop(); } } + // [DEBUG] Display contents of ImDrawList // Note that both 'window' and 'viewport' may be NULL here. Viewport is generally null of destroyed popups which previously owned a viewport. void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label) @@ -19729,13 +22657,16 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con TreePop(); return; } + ImDrawList* fg_draw_list = viewport ? GetForegroundDrawList(viewport) : NULL; // Render additional visuals into the top-most draw list if (window && IsItemHovered() && fg_draw_list) fg_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255)); if (!node_open) return; + if (window && !window->WasActive) TextDisabled("Warning: owning Window is inactive. This DrawList is not being rendered!"); + for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.Data; pcmd < draw_list->CmdBuffer.Data + cmd_count; pcmd++) { if (pcmd->UserCallback) @@ -19743,8 +22674,9 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData); continue; } - char texid_desc[20]; - FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd->TextureId); + + char texid_desc[30]; + FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd); char buf[300]; ImFormatString(buf, IM_ARRAYSIZE(buf), "DrawCmd:%5d tris, Tex %s, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount / 3, texid_desc, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); @@ -19753,6 +22685,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con DebugNodeDrawCmdShowMeshAndBoundingBox(fg_draw_list, draw_list, pcmd, cfg->ShowDrawCmdMesh, cfg->ShowDrawCmdBoundingBoxes); if (!pcmd_node_open) continue; + // Calculate approximate coverage area (touched pixel count) // This will be in pixels squared as long there's no post-scaling happening to the renderer output. const ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; @@ -19765,11 +22698,13 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos; total_area += ImTriangleArea(triangle[0], triangle[1], triangle[2]); } + // Display vertex information summary. Hover to get all triangles drawn in wire-frame ImFormatString(buf, IM_ARRAYSIZE(buf), "Mesh: ElemCount: %d, VtxOffset: +%d, IdxOffset: +%d, Area: ~%0.f px", pcmd->ElemCount, pcmd->VtxOffset, pcmd->IdxOffset, total_area); Selectable(buf); if (IsItemHovered() && fg_draw_list) DebugNodeDrawCmdShowMeshAndBoundingBox(fg_draw_list, draw_list, pcmd, true, false); + // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted. ImGuiListClipper clipper; clipper.Begin(pcmd->ElemCount / 3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible. @@ -19785,6 +22720,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con buf_p += ImFormatString(buf_p, buf_end - buf_p, "%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\n", (n == 0) ? "Vert:" : " ", idx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col); } + Selectable(buf, false); if (fg_draw_list && IsItemHovered()) { @@ -19798,10 +22734,12 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con } TreePop(); } + // [DEBUG] Display mesh/aabb of a ImDrawCmd void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb) { IM_ASSERT(show_mesh || show_aabb); + // Draw wire-frame version of all triangles ImRect clip_rect = draw_cmd->ClipRect; ImRect vtxs_rect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); @@ -19811,6 +22749,7 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co { ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; // We don't hold on those pointers past iterations as ->AddPolyline() may invalidate them if out_draw_list==draw_list ImDrawVert* vtx_buffer = draw_list->VtxBuffer.Data + draw_cmd->VtxOffset; + ImVec2 triangle[3]; for (int n = 0; n < 3; n++, idx_n++) vtxs_rect.Add((triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos)); @@ -19825,18 +22764,40 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co } out_draw_list->Flags = backup_flags; } + +// [DEBUG] Compute mask of inputs with the same codepoint. +static int CalcFontGlyphSrcOverlapMask(ImFontAtlas* atlas, ImFont* font, unsigned int codepoint) +{ + int mask = 0, count = 0; + for (int src_n = 0; src_n < font->Sources.Size; src_n++) + { + ImFontConfig* src = font->Sources[src_n]; + if (!(src->FontLoader ? src->FontLoader : atlas->FontLoader)->FontSrcContainsGlyph(atlas, src, (ImWchar)codepoint)) + continue; + mask |= (1 << src_n); + count++; + } + return count > 1 ? mask : 0; +} + // [DEBUG] Display details for a single font, called by ShowStyleEditor(). void ImGui::DebugNodeFont(ImFont* font) { - bool opened = TreeNode(font, "Font: \"%s\": %.2f px, %d glyphs, %d sources(s)", - font->Sources ? font->Sources[0].Name : "", font->FontSize, font->Glyphs.Size, font->SourcesCount); + ImGuiContext& g = *GImGui; + ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; + ImFontAtlas* atlas = font->ContainerAtlas; + bool opened = TreeNode(font, "Font: \"%s\": %d sources(s)", font->GetDebugName(), font->Sources.Size); + // Display preview text if (!opened) Indent(); Indent(); - PushFont(font); - Text("The quick brown fox jumps over the lazy dog"); - PopFont(); + if (cfg->ShowFontPreview) + { + PushFont(font, 0.0f); + Text("The quick brown fox jumps over the lazy dog"); + PopFont(); + } if (!opened) { Unindent(); @@ -19845,85 +22806,182 @@ void ImGui::DebugNodeFont(ImFont* font) } if (SmallButton("Set as default")) GetIO().FontDefault = font; + SameLine(); + BeginDisabled(atlas->Fonts.Size <= 1 || atlas->Locked); + if (SmallButton("Remove")) + atlas->RemoveFont(font); + EndDisabled(); + SameLine(); + if (SmallButton("Clear bakes")) + ImFontAtlasFontDiscardBakes(atlas, font, 0); + SameLine(); + if (SmallButton("Clear unused")) + ImFontAtlasFontDiscardBakes(atlas, font, 2); + // Display details +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS SetNextItemWidth(GetFontSize() * 8); DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); - SameLine(); MetricsHelpMarker( + /*SameLine(); MetricsHelpMarker( "Note that the default embedded font is NOT meant to be scaled.\n\n" "Font are currently rendered into bitmaps at a given size at the time of building the atlas. " "You may oversample them to get some flexibility with scaling. " "You can also render at multiple sizes and select which one to use at runtime.\n\n" - "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)"); - Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); + "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)");*/ +#endif + char c_str[5]; - Text("Fallback character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->FallbackChar), font->FallbackChar); - Text("Ellipsis character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->EllipsisChar), font->EllipsisChar); - const int surface_sqrt = (int)ImSqrt((float)font->MetricsTotalSurface); - Text("Texture Area: about %d px ~%dx%d px", font->MetricsTotalSurface, surface_sqrt, surface_sqrt); - for (int config_i = 0; config_i < font->SourcesCount; config_i++) - if (font->Sources) - { - const ImFontConfig* src = &font->Sources[config_i]; - int oversample_h, oversample_v; - ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v); - BulletText("Input %d: \'%s\', Oversample: (%d=>%d,%d=>%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", - config_i, src->Name, src->OversampleH, oversample_h, src->OversampleV, oversample_v, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y); - } - // Display all glyphs of the fonts in separate pages of 256 characters + ImTextCharToUtf8(c_str, font->FallbackChar); + Text("Fallback character: '%s' (U+%04X)", c_str, font->FallbackChar); + ImTextCharToUtf8(c_str, font->EllipsisChar); + Text("Ellipsis character: '%s' (U+%04X)", c_str, font->EllipsisChar); + + for (int src_n = 0; src_n < font->Sources.Size; src_n++) { - if (TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) + ImFontConfig* src = font->Sources[src_n]; + if (TreeNode(src, "Input %d: \'%s\' [%d], Oversample: %d,%d, PixelSnapH: %d, Offset: (%.1f,%.1f)", + src_n, src->Name, src->FontNo, src->OversampleH, src->OversampleV, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y)) { - ImDrawList* draw_list = GetWindowDrawList(); - const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); - const float cell_size = font->FontSize * 1; - const float cell_spacing = GetStyle().ItemSpacing.y; - for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + Text("Loader: '%s'", loader->Name ? loader->Name : "N/A"); +#ifdef IMGUI_ENABLE_FREETYPE + if (loader->Name != NULL && strcmp(loader->Name, "FreeType") == 0) { - // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) - // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT - // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) - if (!(base & 8191) && font->IsGlyphRangeUnused(base, base + 8191)) + unsigned int loader_flags = src->FontLoaderFlags; + Text("FreeType Loader Flags: 0x%08X", loader_flags); + if (ImGuiFreeType::DebugEditFontLoaderFlags(&loader_flags)) { - base += 8192 - 256; - continue; + ImFontAtlasFontDestroyOutput(atlas, font); + src->FontLoaderFlags = loader_flags; + ImFontAtlasFontInitOutput(atlas, font); } - int count = 0; - for (unsigned int n = 0; n < 256; n++) - if (font->FindGlyphNoFallback((ImWchar)(base + n))) - count++; - if (count <= 0) - continue; - if (!TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) - continue; - // Draw a 16x16 grid of glyphs - ImVec2 base_pos = GetCursorScreenPos(); - for (unsigned int n = 0; n < 256; n++) - { - // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions - // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. - ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); - ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); - const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base + n)); - draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); - if (!glyph) - continue; - font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); - if (IsMouseHoveringRect(cell_p1, cell_p2) && BeginTooltip()) - { - DebugNodeFontGlyph(font, glyph); - EndTooltip(); - } - } - Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); - TreePop(); } +#endif TreePop(); } } + if (font->Sources.Size > 1 && TreeNode("Input Glyphs Overlap Detection Tool")) + { + TextWrapped("- First Input that contains the glyph is used.\n" + "- Use ImFontConfig::GlyphExcludeRanges[] to specify ranges to ignore glyph in given Input.\n- Prefer using a small number of ranges as the list is scanned every time a new glyph is loaded,\n - e.g. GlyphExcludeRanges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 };\n- This tool doesn't cache results and is slow, don't keep it open!"); + if (BeginTable("table", 2)) + { + for (unsigned int c = 0; c < 0x10000; c++) + if (int overlap_mask = CalcFontGlyphSrcOverlapMask(atlas, font, c)) + { + unsigned int c_end = c + 1; + while (c_end < 0x10000 && CalcFontGlyphSrcOverlapMask(atlas, font, c_end) == overlap_mask) + c_end++; + if (TableNextColumn() && TreeNode((void*)(intptr_t)c, "U+%04X-U+%04X: %d codepoints in %d inputs", c, c_end - 1, c_end - c, ImCountSetBits(overlap_mask))) + { + char utf8_buf[5]; + for (unsigned int n = c; n < c_end; n++) + { + ImTextCharToUtf8(utf8_buf, n); + BulletText("Codepoint U+%04X (%s)", n, utf8_buf); + } + TreePop(); + } + TableNextColumn(); + for (int src_n = 0; src_n < font->Sources.Size; src_n++) + if (overlap_mask & (1 << src_n)) + { + Text("%d ", src_n); + SameLine(); + } + c = c_end - 1; + } + EndTable(); + } + TreePop(); + } + + // Display all glyphs of the fonts in separate pages of 256 characters + for (int baked_n = 0; baked_n < atlas->Builder->BakedPool.Size; baked_n++) + { + ImFontBaked* baked = &atlas->Builder->BakedPool[baked_n]; + if (baked->ContainerFont != font) + continue; + PushID(baked_n); + if (TreeNode("Glyphs", "Baked at { %.2fpx, d.%.2f }: %d glyphs%s", baked->Size, baked->RasterizerDensity, baked->Glyphs.Size, (baked->LastUsedFrame < atlas->Builder->FrameCount - 1) ? " *Unused*" : "")) + { + if (SmallButton("Load all")) + for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base++) + baked->FindGlyph((ImWchar)base); + + const int surface_sqrt = (int)ImSqrt((float)baked->MetricsTotalSurface); + Text("Ascent: %f, Descent: %f, Ascent-Descent: %f", baked->Ascent, baked->Descent, baked->Ascent - baked->Descent); + Text("Texture Area: about %d px ~%dx%d px", baked->MetricsTotalSurface, surface_sqrt, surface_sqrt); + for (int src_n = 0; src_n < font->Sources.Size; src_n++) + { + ImFontConfig* src = font->Sources[src_n]; + int oversample_h, oversample_v; + ImFontAtlasBuildGetOversampleFactors(src, baked, &oversample_h, &oversample_v); + BulletText("Input %d: \'%s\', Oversample: (%d=>%d,%d=>%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", + src_n, src->Name, src->OversampleH, oversample_h, src->OversampleV, oversample_v, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y); + } + + DebugNodeFontGlyphesForSrcMask(font, baked, ~0); + TreePop(); + } + PopID(); + } TreePop(); Unindent(); } -void ImGui::DebugNodeFontGlyph(ImFont*, const ImFontGlyph* glyph) + +void ImGui::DebugNodeFontGlyphesForSrcMask(ImFont* font, ImFontBaked* baked, int src_mask) +{ + ImDrawList* draw_list = GetWindowDrawList(); + const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); + const float cell_size = baked->Size * 1; + const float cell_spacing = GetStyle().ItemSpacing.y; + for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) + { + // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) + // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT + // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) + if (!(base & 8191) && font->IsGlyphRangeUnused(base, base + 8191)) + { + base += 8192 - 256; + continue; + } + + int count = 0; + for (unsigned int n = 0; n < 256; n++) + if (const ImFontGlyph* glyph = baked->IsGlyphLoaded((ImWchar)(base + n)) ? baked->FindGlyph((ImWchar)(base + n)) : NULL) + if (src_mask & (1 << glyph->SourceIdx)) + count++; + if (count <= 0) + continue; + if (!TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) + continue; + + // Draw a 16x16 grid of glyphs + ImVec2 base_pos = GetCursorScreenPos(); + for (unsigned int n = 0; n < 256; n++) + { + // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions + // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. + ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); + ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); + const ImFontGlyph* glyph = baked->IsGlyphLoaded((ImWchar)(base + n)) ? baked->FindGlyph((ImWchar)(base + n)) : NULL; + draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); + if (!glyph || (src_mask & (1 << glyph->SourceIdx)) == 0) + continue; + font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); + if (IsMouseHoveringRect(cell_p1, cell_p2) && BeginTooltip()) + { + DebugNodeFontGlyph(font, glyph); + EndTooltip(); + } + } + Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); + TreePop(); + } +} + +void ImGui::DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph) { Text("Codepoint: U+%04X", glyph->Codepoint); Separator(); @@ -19931,7 +22989,14 @@ void ImGui::DebugNodeFontGlyph(ImFont*, const ImFontGlyph* glyph) Text("AdvanceX: %.1f", glyph->AdvanceX); Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); + if (glyph->PackId >= 0) + { + ImTextureRect* r = ImFontAtlasPackGetRect(font->ContainerAtlas, glyph->PackId); + Text("PackId: %d (%dx%d rect at %d,%d)", glyph->PackId, r->w, r->h, r->x, r->y); + } + Text("SourceIdx: %d", glyph->SourceIdx); } + // [DEBUG] Display contents of ImGuiStorage void ImGui::DebugNodeStorage(ImGuiStorage* storage, const char* label) { @@ -19944,6 +23009,7 @@ void ImGui::DebugNodeStorage(ImGuiStorage* storage, const char* label) } TreePop(); } + // [DEBUG] Display contents of ImGuiTabBar void ImGui::DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label) { @@ -19984,6 +23050,7 @@ void ImGui::DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label) TreePop(); } } + void ImGui::DebugNodeViewport(ImGuiViewportP* viewport) { ImGuiContext& g = *GImGui; @@ -19994,8 +23061,9 @@ void ImGui::DebugNodeViewport(ImGuiViewportP* viewport) if (open) { ImGuiWindowFlags flags = viewport->Flags; - BulletText("Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\nWorkArea Inset Left: %.0f Top: %.0f, Right: %.0f, Bottom: %.0f\nMonitor: %d, DpiScale: %.0f%%", + BulletText("Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\nFrameBufferScale: (%.2f,%.2f)\nWorkArea Inset Left: %.0f Top: %.0f, Right: %.0f, Bottom: %.0f\nMonitor: %d, DpiScale: %.0f%%", viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y, + viewport->FramebufferScale.x, viewport->FramebufferScale.y, viewport->WorkInsetMin.x, viewport->WorkInsetMin.y, viewport->WorkInsetMax.x, viewport->WorkInsetMax.y, viewport->PlatformMonitor, viewport->DpiScale * 100.0f); if (viewport->Idx > 0) { SameLine(); if (SmallButton("Reset Pos")) { viewport->Pos = ImVec2(200, 200); viewport->UpdateWorkRect(); if (viewport->Window) viewport->Window->Pos = viewport->Pos; } } @@ -20019,6 +23087,7 @@ void ImGui::DebugNodeViewport(ImGuiViewportP* viewport) TreePop(); } } + void ImGui::DebugNodePlatformMonitor(ImGuiPlatformMonitor* monitor, const char* label, int idx) { BulletText("%s %d: DPI %.0f%%\n MainMin (%.0f,%.0f), MainMax (%.0f,%.0f), MainSize (%.0f,%.0f)\n WorkMin (%.0f,%.0f), WorkMax (%.0f,%.0f), WorkSize (%.0f,%.0f)", @@ -20026,6 +23095,7 @@ void ImGui::DebugNodePlatformMonitor(ImGuiPlatformMonitor* monitor, const char* monitor->MainPos.x, monitor->MainPos.y, monitor->MainPos.x + monitor->MainSize.x, monitor->MainPos.y + monitor->MainSize.y, monitor->MainSize.x, monitor->MainSize.y, monitor->WorkPos.x, monitor->WorkPos.y, monitor->WorkPos.x + monitor->WorkSize.x, monitor->WorkPos.y + monitor->WorkSize.y, monitor->WorkSize.x, monitor->WorkSize.y); } + void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) { if (window == NULL) @@ -20033,6 +23103,7 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) BulletText("%s: NULL", label); return; } + ImGuiContext& g = *GImGui; const bool is_active = window->WasActive; ImGuiTreeNodeFlags tree_node_flags = (window == g.NavWindow) ? ImGuiTreeNodeFlags_Selected : ImGuiTreeNodeFlags_None; @@ -20043,10 +23114,13 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255)); if (!open) return; + if (window->MemoryCompacted) TextDisabled("Note: some memory buffers have been compacted/freed."); + if (g.IO.ConfigDebugIsDebuggerPresent && DebugBreakButton("**DebugBreak**", "in Begin()")) g.DebugBreakInWindow = window->ID; + ImGuiWindowFlags flags = window->Flags; DebugNodeDrawList(window, window->Viewport, window->DrawList, "DrawList"); BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), ContentSize (%.1f,%.1f) Ideal (%.1f,%.1f)", window->Pos.x, window->Pos.y, window->Size.x, window->Size.y, window->ContentSize.x, window->ContentSize.y, window->ContentSizeIdeal.x, window->ContentSizeIdeal.y); @@ -20077,11 +23151,13 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++) BulletText("NavPreferredScoringPosRel[%d] = {%.1f,%.1f)", layer, (pr[layer].x == FLT_MAX ? -99999.0f : pr[layer].x), (pr[layer].y == FLT_MAX ? -99999.0f : pr[layer].y)); // Display as 99999.0f so it looks neater. BulletText("NavLayersActiveMask: %X, NavLastChildNavWindow: %s", window->DC.NavLayersActiveMask, window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL"); + BulletText("Viewport: %d%s, ViewportId: 0x%08X, ViewportPos: (%.1f,%.1f)", window->Viewport ? window->Viewport->Idx : -1, window->ViewportOwned ? " (Owned)" : "", window->ViewportId, window->ViewportPos.x, window->ViewportPos.y); BulletText("ViewportMonitor: %d", window->Viewport ? window->Viewport->PlatformMonitor : -1); BulletText("DockId: 0x%04X, DockOrder: %d, Act: %d, Vis: %d", window->DockId, window->DockOrder, window->DockIsActive, window->DockTabIsVisible); if (window->DockNode || window->DockNodeAsHost) DebugNodeDockNode(window->DockNodeAsHost ? window->DockNodeAsHost : window->DockNode, window->DockNodeAsHost ? "DockNodeAsHost" : "DockNode"); + if (window->RootWindow != window) { DebugNodeWindow(window->RootWindow, "RootWindow"); } if (window->RootWindowDockTree != window->RootWindow) { DebugNodeWindow(window->RootWindowDockTree, "RootWindowDockTree"); } if (window->ParentWindow != NULL) { DebugNodeWindow(window->ParentWindow, "ParentWindow"); } @@ -20096,6 +23172,7 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) DebugNodeStorage(&window->StateStorage, "Storage"); TreePop(); } + void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings* settings) { if (settings->WantDelete) @@ -20105,6 +23182,7 @@ void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings* settings) if (settings->WantDelete) EndDisabled(); } + void ImGui::DebugNodeWindowsList(ImVector* windows, const char* label) { if (!TreeNode(label, "%s (%d)", label, windows->Size)) @@ -20117,6 +23195,7 @@ void ImGui::DebugNodeWindowsList(ImVector* windows, const char* la } TreePop(); } + // FIXME-OPT: This is technically suboptimal, but it is simpler this way. void ImGui::DebugNodeWindowsListByBeginStackParent(ImGuiWindow** windows, int windows_size, ImGuiWindow* parent_in_begin_stack) { @@ -20129,14 +23208,16 @@ void ImGui::DebugNodeWindowsListByBeginStackParent(ImGuiWindow** windows, int wi ImFormatString(buf, IM_ARRAYSIZE(buf), "[%04d] Window", window->BeginOrderWithinContext); //BulletText("[%04d] Window '%s'", window->BeginOrderWithinContext, window->Name); DebugNodeWindow(window, buf); - Indent(); + TreePush(buf); DebugNodeWindowsListByBeginStackParent(windows + i + 1, windows_size - i - 1, window); - Unindent(); + TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DEBUG LOG WINDOW //----------------------------------------------------------------------------- + void ImGui::DebugLog(const char* fmt, ...) { va_list args; @@ -20144,6 +23225,7 @@ void ImGui::DebugLog(const char* fmt, ...) DebugLogV(fmt, args); va_end(args); } + void ImGui::DebugLogV(const char* fmt, va_list args) { ImGuiContext& g = *GImGui; @@ -20164,6 +23246,7 @@ void ImGui::DebugLogV(const char* fmt, va_list args) IMGUI_TEST_ENGINE_LOG("%.*s", new_size - old_size - (trailing_carriage_return ? 1 : 0), g.DebugLogBuf.begin() + old_size); #endif } + // FIXME-LAYOUT: To be done automatically via layout mode once we rework ItemSize/ItemAdd into ItemLayout. static void SameLineOrWrap(const ImVec2& size) { @@ -20173,11 +23256,13 @@ static void SameLineOrWrap(const ImVec2& size) if (window->WorkRect.Contains(ImRect(pos, pos + size))) ImGui::SameLine(); } + static void ShowDebugLogFlag(const char* name, ImGuiDebugLogFlags flags) { ImGuiContext& g = *GImGui; ImVec2 size(ImGui::GetFrameHeight() + g.Style.ItemInnerSpacing.x + ImGui::CalcTextSize(name).x, ImGui::GetFrameHeight()); SameLineOrWrap(size); // FIXME-LAYOUT: To be done automatically once we rework ItemSize/ItemAdd into ItemLayout. + bool highlight_errors = (flags == ImGuiDebugLogFlags_EventError && g.DebugLogSkippedErrors > 0); if (highlight_errors) ImGui::PushStyleColor(ImGuiCol_Text, ImLerp(g.Style.Colors[ImGuiCol_Text], ImVec4(1.0f, 0.0f, 0.0f, 1.0f), 0.30f)); @@ -20196,6 +23281,7 @@ static void ShowDebugLogFlag(const char* name, ImGuiDebugLogFlags flags) ImGui::SetItemTooltip("Hold SHIFT when clicking to enable for 2 frames only (useful for spammy log entries)"); } } + void ImGui::ShowDebugLogWindow(bool* p_open) { ImGuiContext& g = *GImGui; @@ -20206,21 +23292,24 @@ void ImGui::ShowDebugLogWindow(bool* p_open) End(); return; } + ImGuiDebugLogFlags all_enable_flags = ImGuiDebugLogFlags_EventMask_ & ~ImGuiDebugLogFlags_EventInputRouting; CheckboxFlags("All", &g.DebugLogFlags, all_enable_flags); SetItemTooltip("(except InputRouting which is spammy)"); + ShowDebugLogFlag("Errors", ImGuiDebugLogFlags_EventError); ShowDebugLogFlag("ActiveId", ImGuiDebugLogFlags_EventActiveId); ShowDebugLogFlag("Clipper", ImGuiDebugLogFlags_EventClipper); ShowDebugLogFlag("Docking", ImGuiDebugLogFlags_EventDocking); ShowDebugLogFlag("Focus", ImGuiDebugLogFlags_EventFocus); ShowDebugLogFlag("IO", ImGuiDebugLogFlags_EventIO); - //ShowDebugLogFlag("Font", ImGuiDebugLogFlags_EventFont); + ShowDebugLogFlag("Font", ImGuiDebugLogFlags_EventFont); ShowDebugLogFlag("Nav", ImGuiDebugLogFlags_EventNav); ShowDebugLogFlag("Popup", ImGuiDebugLogFlags_EventPopup); ShowDebugLogFlag("Selection", ImGuiDebugLogFlags_EventSelection); ShowDebugLogFlag("Viewport", ImGuiDebugLogFlags_EventViewport); ShowDebugLogFlag("InputRouting", ImGuiDebugLogFlags_EventInputRouting); + if (SmallButton("Clear")) { g.DebugLogBuf.clear(); @@ -20245,9 +23334,12 @@ void ImGui::ShowDebugLogWindow(bool* p_open) #endif EndPopup(); } + BeginChild("##log", ImVec2(0.0f, 0.0f), ImGuiChildFlags_Borders, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); + const ImGuiDebugLogFlags backup_log_flags = g.DebugLogFlags; g.DebugLogFlags &= ~ImGuiDebugLogFlags_EventClipper; + ImGuiListClipper clipper; clipper.Begin(g.DebugLogIndex.size()); while (clipper.Step()) @@ -20257,8 +23349,10 @@ void ImGui::ShowDebugLogWindow(bool* p_open) if (GetScrollY() >= GetScrollMaxY()) SetScrollHereY(1.0f); EndChild(); + End(); } + // Display line, search for 0xXXXXXXXX identifiers and call DebugLocateItemOnHover() when hovered. void ImGui::DebugTextUnformattedWithLocateItem(const char* line_begin, const char* line_end) { @@ -20280,9 +23374,11 @@ void ImGui::DebugTextUnformattedWithLocateItem(const char* line_begin, const cha p += 10; } } + //----------------------------------------------------------------------------- // [SECTION] OTHER DEBUG TOOLS (ITEM PICKER, ID STACK TOOL) //----------------------------------------------------------------------------- + // Draw a small cross at current CursorPos in current window's DrawList void ImGui::DebugDrawCursorPos(ImU32 col) { @@ -20292,6 +23388,7 @@ void ImGui::DebugDrawCursorPos(ImU32 col) window->DrawList->AddLine(ImVec2(pos.x, pos.y - 3.0f), ImVec2(pos.x, pos.y + 4.0f), col, 1.0f); window->DrawList->AddLine(ImVec2(pos.x - 3.0f, pos.y), ImVec2(pos.x + 4.0f, pos.y), col, 1.0f); } + // Draw a 10px wide rectangle around CurposPos.x using Line Y1/Y2 in current window's DrawList void ImGui::DebugDrawLineExtents(ImU32 col) { @@ -20304,6 +23401,7 @@ void ImGui::DebugDrawLineExtents(ImU32 col) window->DrawList->AddLine(ImVec2(curr_x - 0.5f, line_y1), ImVec2(curr_x - 0.5f, line_y2), col, 1.0f); window->DrawList->AddLine(ImVec2(curr_x - 5.0f, line_y2), ImVec2(curr_x + 5.0f, line_y2), col, 1.0f); } + // Draw last item rect in ForegroundDrawList (so it is always visible) void ImGui::DebugDrawItemRect(ImU32 col) { @@ -20311,8 +23409,10 @@ void ImGui::DebugDrawItemRect(ImU32 col) ImGuiWindow* window = g.CurrentWindow; GetForegroundDrawList(window)->AddRect(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, col); } + // [DEBUG] Locate item position/rectangle given an ID. static const ImU32 DEBUG_LOCATE_ITEM_COLOR = IM_COL32(0, 255, 0, 255); // Green + void ImGui::DebugLocateItem(ImGuiID target_id) { ImGuiContext& g = *GImGui; @@ -20320,6 +23420,7 @@ void ImGui::DebugLocateItem(ImGuiID target_id) g.DebugLocateFrames = 2; g.DebugBreakInLocateId = false; } + // FIXME: Doesn't work over through a modal window, because they clear HoveredWindow. void ImGui::DebugLocateItemOnHover(ImGuiID target_id) { @@ -20328,6 +23429,7 @@ void ImGui::DebugLocateItemOnHover(ImGuiID target_id) ImGuiContext& g = *GImGui; DebugLocateItem(target_id); GetForegroundDrawList(g.CurrentWindow)->AddRect(g.LastItemData.Rect.Min - ImVec2(3.0f, 3.0f), g.LastItemData.Rect.Max + ImVec2(3.0f, 3.0f), DEBUG_LOCATE_ITEM_COLOR); + // Can't easily use a context menu here because it will mess with focus, active id etc. if (g.IO.ConfigDebugIsDebuggerPresent && g.MouseStationaryTimer > 1.0f) { @@ -20336,12 +23438,15 @@ void ImGui::DebugLocateItemOnHover(ImGuiID target_id) g.DebugBreakInLocateId = true; } } + void ImGui::DebugLocateItemResolveWithLastItem() { ImGuiContext& g = *GImGui; + // [DEBUG] Debug break requested by user if (g.DebugBreakInLocateId) IM_DEBUG_BREAK(); + ImGuiLastItemData item_data = g.LastItemData; g.DebugLocateId = 0; ImDrawList* draw_list = GetForegroundDrawList(g.CurrentWindow); @@ -20352,11 +23457,13 @@ void ImGui::DebugLocateItemResolveWithLastItem() draw_list->AddRect(r.Min, r.Max, DEBUG_LOCATE_ITEM_COLOR); draw_list->AddLine(p1, p2, DEBUG_LOCATE_ITEM_COLOR); } + void ImGui::DebugStartItemPicker() { ImGuiContext& g = *GImGui; g.DebugItemPickerActive = true; } + // [DEBUG] Item picker tool - start with DebugStartItemPicker() - useful to visually select an item and break into its call-stack. void ImGui::UpdateDebugToolItemPicker() { @@ -20364,6 +23471,7 @@ void ImGui::UpdateDebugToolItemPicker() g.DebugItemPickerBreakId = 0; if (!g.DebugItemPickerActive) return; + const ImGuiID hovered_id = g.HoveredIdPreviousFrame; SetMouseCursor(ImGuiMouseCursor_Hand); if (IsKeyPressed(ImGuiKey_Escape)) @@ -20389,15 +23497,18 @@ void ImGui::UpdateDebugToolItemPicker() TextColored(GetStyleColorVec4(hovered_id ? ImGuiCol_Text : ImGuiCol_TextDisabled), "Click %s Button to break in debugger! (remap w/ Ctrl+Shift)", mouse_button_names[g.DebugItemPickerMouseButton]); EndTooltip(); } + // [DEBUG] ID Stack Tool: update queries. Called by NewFrame() void ImGui::UpdateDebugToolStackQueries() { ImGuiContext& g = *GImGui; ImGuiIDStackTool* tool = &g.DebugIDStackTool; + // Clear hook when id stack tool is not visible g.DebugHookIdInfo = 0; if (g.FrameCount != tool->LastActiveFrame + 1) return; + // Update queries. The steps are: -1: query Stack, >= 0: query each stack item // We can only perform 1 ID Info query every frame. This is designed so the GetID() tests are cheap and constant-time const ImGuiID query_id = g.HoveredIdPreviousFrame ? g.HoveredIdPreviousFrame : g.ActiveId; @@ -20409,11 +23520,13 @@ void ImGui::UpdateDebugToolStackQueries() } if (query_id == 0) return; + // Advance to next stack level when we got our result, or after 2 frames (in case we never get a result) int stack_level = tool->StackLevel; if (stack_level >= 0 && stack_level < tool->Results.Size) if (tool->Results[stack_level].QuerySuccess || tool->Results[stack_level].QueryFrameCount > 2) tool->StackLevel++; + // Update hook stack_level = tool->StackLevel; if (stack_level == -1) @@ -20424,12 +23537,14 @@ void ImGui::UpdateDebugToolStackQueries() tool->Results[stack_level].QueryFrameCount++; } } + // [DEBUG] ID Stack tool: hooks called by GetID() family functions void ImGui::DebugHookIdInfo(ImGuiID id, ImGuiDataType data_type, const void* data_id, const void* data_id_end) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiIDStackTool* tool = &g.DebugIDStackTool; + // Step 0: stack query // This assumes that the ID was computed with the current ID stack, which tends to be the case for our widget. if (tool->StackLevel == -1) @@ -20440,12 +23555,14 @@ void ImGui::DebugHookIdInfo(ImGuiID id, ImGuiDataType data_type, const void* dat tool->Results[n].ID = (n < window->IDStack.Size) ? window->IDStack[n] : id; return; } + // Step 1+: query for individual level IM_ASSERT(tool->StackLevel >= 0); if (tool->StackLevel != window->IDStack.Size) return; ImGuiStackLevelInfo* info = &tool->Results[tool->StackLevel]; IM_ASSERT(info->ID == id && info->QueryFrameCount > 0); + switch (data_type) { case ImGuiDataType_S32: @@ -20468,6 +23585,7 @@ void ImGui::DebugHookIdInfo(ImGuiID id, ImGuiDataType data_type, const void* dat info->QuerySuccess = true; info->DataType = data_type; } + static int StackToolFormatLevelInfo(ImGuiIDStackTool* tool, int n, bool format_for_ui, char* buf, size_t buf_size) { ImGuiStackLevelInfo* info = &tool->Results[n]; @@ -20484,6 +23602,7 @@ static int StackToolFormatLevelInfo(ImGuiIDStackTool* tool, int n, bool format_f #endif return ImFormatString(buf, buf_size, "???"); } + // ID Stack Tool: Display UI void ImGui::ShowIDStackToolWindow(bool* p_open) { @@ -20495,8 +23614,10 @@ void ImGui::ShowIDStackToolWindow(bool* p_open) End(); return; } + // Display hovered/active status ImGuiIDStackTool* tool = &g.DebugIDStackTool; + // Build and display path tool->ResultPathBuf.resize(0); for (int stack_n = 0; stack_n < tool->Results.Size; stack_n++) @@ -20514,6 +23635,7 @@ void ImGui::ShowIDStackToolWindow(bool* p_open) Text("0x%08X", tool->QueryId); SameLine(); MetricsHelpMarker("Hover an item with the mouse to display elements of the ID Stack leading to the item's final ID.\nEach level of the stack correspond to a PushID() call.\nAll levels of the stack are hashed together to make the final ID of a widget (ID displayed at the bottom level of the stack).\nRead FAQ entry about the ID stack for details."); + // CTRL+C to copy path const float time_since_copy = (float)g.Time - tool->CopyToClipboardLastTime; SameLine(); @@ -20525,11 +23647,14 @@ void ImGui::ShowIDStackToolWindow(bool* p_open) tool->CopyToClipboardLastTime = (float)g.Time; SetClipboardText(tool->ResultPathBuf.c_str()); } + Text("- Path \"%s\"", tool->ResultPathBuf.c_str()); #ifdef IMGUI_ENABLE_TEST_ENGINE Text("- Label \"%s\"", tool->QueryId ? ImGuiTestEngine_FindItemDebugLabel(&g, tool->QueryId) : ""); #endif + Separator(); + // Display decorated stack tool->LastActiveFrame = g.FrameCount; if (tool->Results.Size > 0 && BeginTable("##table", 3, ImGuiTableFlags_Borders)) @@ -20556,29 +23681,72 @@ void ImGui::ShowIDStackToolWindow(bool* p_open) } End(); } + #else + void ImGui::ShowMetricsWindow(bool*) {} void ImGui::ShowFontAtlas(ImFontAtlas*) {} void ImGui::DebugNodeColumns(ImGuiOldColumns*) {} void ImGui::DebugNodeDrawList(ImGuiWindow*, ImGuiViewportP*, const ImDrawList*, const char*) {} void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList*, const ImDrawList*, const ImDrawCmd*, bool, bool) {} void ImGui::DebugNodeFont(ImFont*) {} +void ImGui::DebugNodeFontGlyphesForSrcMask(ImFont*, ImFontBaked*, int) {} void ImGui::DebugNodeStorage(ImGuiStorage*, const char*) {} void ImGui::DebugNodeTabBar(ImGuiTabBar*, const char*) {} void ImGui::DebugNodeWindow(ImGuiWindow*, const char*) {} void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings*) {} void ImGui::DebugNodeWindowsList(ImVector*, const char*) {} void ImGui::DebugNodeViewport(ImGuiViewportP*) {} + void ImGui::ShowDebugLogWindow(bool*) {} void ImGui::ShowIDStackToolWindow(bool*) {} void ImGui::DebugStartItemPicker() {} void ImGui::DebugHookIdInfo(ImGuiID, ImGuiDataType, const void*, const void*) {} + #endif // #ifndef IMGUI_DISABLE_DEBUG_TOOLS + +#if !defined(IMGUI_DISABLE_DEMO_WINDOWS) || !defined(IMGUI_DISABLE_DEBUG_TOOLS) +// Demo helper function to select among loaded fonts. +// Here we use the regular BeginCombo()/EndCombo() api which is the more flexible one. +void ImGui::ShowFontSelector(const char* label) +{ + ImGuiIO& io = GetIO(); + ImFont* font_current = GetFont(); + if (BeginCombo(label, font_current->GetDebugName())) + { + for (ImFont* font : io.Fonts->Fonts) + { + PushID((void*)font); + if (Selectable(font->GetDebugName(), font == font_current)) + io.FontDefault = font; + if (font == font_current) + SetItemDefaultFocus(); + PopID(); + } + EndCombo(); + } + SameLine(); + if (io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) + MetricsHelpMarker( + "- Load additional fonts with io.Fonts->AddFontXXX() functions.\n" + "- Read FAQ and docs/FONTS.md for more details."); + else + MetricsHelpMarker( + "- Load additional fonts with io.Fonts->AddFontXXX() functions.\n" + "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" + "- Read FAQ and docs/FONTS.md for more details.\n" + "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); +} +#endif // #if !defined(IMGUI_DISABLE_DEMO_WINDOWS) || !defined(IMGUI_DISABLE_DEBUG_TOOLS) + //----------------------------------------------------------------------------- + // Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed. // Prefer just including imgui_internal.h from your code rather than using this define. If a declaration is missing from imgui_internal.h add it or request it on the github. #ifdef IMGUI_INCLUDE_IMGUI_USER_INL #include "imgui_user.inl" #endif + //----------------------------------------------------------------------------- -#endif // #ifndef IMGUI_DISABLE \ No newline at end of file + +#endif // #ifndef IMGUI_DISABLE diff --git a/external/reshade/deps/imgui/imgui.h b/external/reshade/deps/imgui/imgui.h index 8372e81..cf867ae 100644 --- a/external/reshade/deps/imgui/imgui.h +++ b/external/reshade/deps/imgui/imgui.h @@ -1,10 +1,12 @@ -// dear imgui, v1.91.9b +// dear imgui, v1.92.2b // (headers) + // Help: // - See links below. // - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that. // - Read top of imgui.cpp for more details, links and comments. // - Add '#define IMGUI_DEFINE_MATH_OPERATORS' before including this file (or in imconfig.h) to access courtesy maths operators for ImVec2 and ImVec4. + // Resources: // - FAQ ........................ https://dearimgui.com/faq (in repository as docs/FAQ.md) // - Homepage ................... https://github.com/ocornut/imgui @@ -19,21 +21,26 @@ // - Software using Dear ImGui https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui // - Issues & support ........... https://github.com/ocornut/imgui/issues // - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your apps) + // For first-time users having issues compiling/linking/running/loading fonts: // please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above. // Everything else should be asked in 'Issues'! We are building a database of cross-linked knowledge there. + // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.91.9b" -#define IMGUI_VERSION_NUM 19191 -#define IMGUI_HAS_TABLE -#define IMGUI_HAS_VIEWPORT // Viewport WIP branch -#define IMGUI_HAS_DOCK // Docking WIP branch +#define IMGUI_VERSION "1.92.2b" +#define IMGUI_VERSION_NUM 19222 +#define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 +#define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 +#define IMGUI_HAS_VIEWPORT // In 'docking' WIP branch. +#define IMGUI_HAS_DOCK // In 'docking' WIP branch. + /* + Index of this file: // [SECTION] Header mess // [SECTION] Forward declarations and basic types -// [SECTION] Texture identifier (ImTextureID) +// [SECTION] Texture identifiers (ImTextureID, ImTextureRef) // [SECTION] Dear ImGui end-user API functions // [SECTION] Flags & Enumerations // [SECTION] Tables API flags and structures (ImGuiTableFlags, ImGuiTableColumnFlags, ImGuiTableRowFlags, ImGuiTableBgTarget, ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs) @@ -44,27 +51,35 @@ Index of this file: // [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) // [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectIO, ImGuiSelectionRequest, ImGuiSelectionBasicStorage, ImGuiSelectionExternalStorage) // [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData) -// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont) +// [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureRect, ImTextureData) +// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFontBaked, ImFont) // [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport) // [SECTION] ImGuiPlatformIO + other Platform Dependent Interfaces (ImGuiPlatformMonitor, ImGuiPlatformImeData) // [SECTION] Obsolete functions and types + */ + #pragma once + // Configuration file with compile-time options // (edit imconfig.h or '#define IMGUI_USER_CONFIG "myfilename.h" from your build system) #ifdef IMGUI_USER_CONFIG #include IMGUI_USER_CONFIG #endif #include "imconfig.h" + #ifndef IMGUI_DISABLE + //----------------------------------------------------------------------------- // [SECTION] Header mess //----------------------------------------------------------------------------- + // Includes #include // FLT_MIN, FLT_MAX #include // va_list, va_start, va_end #include // ptrdiff_t, NULL #include // memset, memmove, memcpy, strlen, strchr, strcpy, strcmp + // Define attributes of all API symbols declarations (e.g. for DLL under Windows) // IMGUI_API is used for core imgui functions, IMGUI_IMPL_API is used for the default backends files (imgui_impl_xxx.h) // Using dear imgui via a shared library is not recommended: we don't guarantee backward nor forward ABI compatibility + this is a call-heavy library and function call overhead adds up. @@ -74,6 +89,7 @@ Index of this file: #ifndef IMGUI_IMPL_API #define IMGUI_IMPL_API IMGUI_API #endif + // Helper Macros #ifndef IM_ASSERT #include @@ -81,8 +97,10 @@ Index of this file: #endif #define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR) / sizeof(*(_ARR)))) // Size of a static C-style array. Don't use on pointers! #define IM_UNUSED(_VAR) ((void)(_VAR)) // Used to silence "unused variable warnings". Often useful as asserts may be stripped out from final builds. + // Check that version and structures layouts are matching between compiled imgui code and caller. Read comments above DebugCheckVersionAndDataLayout() for details. #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) + // Helper Macros - IM_FMTARGS, IM_FMTLIST: Apply printf-style warnings to our formatting functions. // (MSVC provides an equivalent mechanism via SAL Annotations but it would require the macros in a different // location. e.g. #include + void myprintf(_Printf_format_string_ const char* format, ...)) @@ -96,6 +114,7 @@ Index of this file: #define IM_FMTARGS(FMT) #define IM_FMTLIST(FMT) #endif + // Disable some of MSVC most aggressive Debug runtime checks in function header/footer (used in some simple/low-level functions) #if defined(_MSC_VER) && !defined(__clang__) && !defined(__INTEL_COMPILER) && !defined(IMGUI_DEBUG_PARANOID) #define IM_MSVC_RUNTIME_CHECKS_OFF __pragma(runtime_checks("",off)) __pragma(check_stack(off)) __pragma(strict_gs_check(push,off)) @@ -104,6 +123,7 @@ Index of this file: #define IM_MSVC_RUNTIME_CHECKS_OFF #define IM_MSVC_RUNTIME_CHECKS_RESTORE #endif + // Warnings #ifdef _MSC_VER #pragma warning (push) @@ -127,9 +147,11 @@ Index of this file: #pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #endif + //----------------------------------------------------------------------------- // [SECTION] Forward declarations and basic types //----------------------------------------------------------------------------- + // Scalar data types typedef unsigned int ImGuiID;// A unique ID used by widgets (typically the result of hashing a stack of string) typedef signed char ImS8; // 8-bit signed integer @@ -140,6 +162,7 @@ typedef signed int ImS32; // 32-bit signed integer == int typedef unsigned int ImU32; // 32-bit unsigned integer (often used to store packed colors) typedef signed long long ImS64; // 64-bit signed integer typedef unsigned long long ImU64; // 64-bit unsigned integer + // Forward declarations: ImDrawList, ImFontAtlas layer struct ImDrawChannel; // Temporary storage to output draw commands out of order, used by ImDrawListSplitter and ImDrawList::ChannelsSplit() struct ImDrawCmd; // A single draw command within a parent ImDrawList (generally maps to 1 GPU draw call, unless it is a callback) @@ -150,11 +173,17 @@ struct ImDrawListSplitter; // Helper to split a draw list into differen struct ImDrawVert; // A single vertex (pos + uv + col = 20 bytes by default. Override layout with IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT) struct ImFont; // Runtime data for a single font within a parent ImFontAtlas struct ImFontAtlas; // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader -struct ImFontBuilderIO; // Opaque interface to a font builder (stb_truetype or FreeType). +struct ImFontAtlasBuilder; // Opaque storage for building a ImFontAtlas +struct ImFontAtlasRect; // Output of ImFontAtlas::GetCustomRect() when using custom rectangles. +struct ImFontBaked; // Baked data for a ImFont at a given size. struct ImFontConfig; // Configuration data when adding a font or merging fonts struct ImFontGlyph; // A single font glyph (code point + coordinates within in ImFontAtlas + offset) struct ImFontGlyphRangesBuilder; // Helper to build glyph ranges from text/string data +struct ImFontLoader; // Opaque interface to a font loading backend (stb_truetype, FreeType etc.). +struct ImTextureData; // Specs and pixel storage for a texture used by Dear ImGui. +struct ImTextureRect; // Coordinates of a rectangle within a texture. struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 (*OBSOLETE* please avoid using) + // Forward declarations: ImGui layer struct ImGuiContext; // Dear ImGui context (opaque structure, unless including imgui_internal.h) struct ImGuiIO; // Main configuration and I/O between your application and ImGui (also see: ImGuiPlatformIO) @@ -180,6 +209,7 @@ struct ImGuiTextBuffer; // Helper to hold and append into a text buf struct ImGuiTextFilter; // Helper to parse and apply text filters (e.g. "aaaaa[,bbbbb][,ccccc]") struct ImGuiViewport; // A Platform Window (always 1 unless multi-viewport are enabled. One per platform window to output to). In the future may represent Platform Monitor struct ImGuiWindowClass; // Window class (rare/advanced uses: provide hints to the platform backend via altered viewport flags and parent/child info) + // Enumerations // - We don't use strongly typed enums much because they add constraints (can't extend in private code, can't store typed in bit fields, extra casting on iteration) // - Tip: Use your programming IDE navigation facilities on the names in the _central column_ below to find the actual flags/enum lists! @@ -197,6 +227,7 @@ typedef int ImGuiMouseButton; // -> enum ImGuiMouseButton_ // Enum: A typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A mouse cursor shape typedef int ImGuiStyleVar; // -> enum ImGuiStyleVar_ // Enum: A variable identifier for styling typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A color target for TableSetBgColor() + // Flags (declared as int to allow using as flags without overhead, and to not pollute the top of this file) // - Tip: Use your programming IDE navigation facilities on the names in the _central column_ below to find the actual flags/enum lists! // - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. @@ -204,7 +235,8 @@ typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A // - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. typedef int ImDrawFlags; // -> enum ImDrawFlags_ // Flags: for ImDrawList functions typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList instance -typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas build +typedef int ImFontFlags; // -> enum ImFontFlags_ // Flags: for ImFont +typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas typedef int ImGuiBackendFlags; // -> enum ImGuiBackendFlags_ // Flags: for io.BackendFlags typedef int ImGuiButtonFlags; // -> enum ImGuiButtonFlags_ // Flags: for InvisibleButton() typedef int ImGuiChildFlags; // -> enum ImGuiChildFlags_ // Flags: for BeginChild() @@ -231,6 +263,7 @@ typedef int ImGuiTableRowFlags; // -> enum ImGuiTableRowFlags_ // Flags: F typedef int ImGuiTreeNodeFlags; // -> enum ImGuiTreeNodeFlags_ // Flags: for TreeNode(), TreeNodeEx(), CollapsingHeader() typedef int ImGuiViewportFlags; // -> enum ImGuiViewportFlags_ // Flags: for ImGuiViewport typedef int ImGuiWindowFlags; // -> enum ImGuiWindowFlags_ // Flags: for Begin(), BeginChild() + // Character types // (we generally use UTF-8 encoded string in the API. This is storage specifically for a decoded character used for keyboard input and display) typedef unsigned int ImWchar32; // A single decoded U32 character/code point. We encode them as multi bytes UTF-8 when used in strings. @@ -240,15 +273,18 @@ typedef ImWchar32 ImWchar; #else typedef ImWchar16 ImWchar; #endif + // Multi-Selection item index or identifier when using BeginMultiSelect() // - Used by SetNextItemSelectionUserData() + and inside ImGuiMultiSelectIO structure. // - Most users are likely to use this store an item INDEX but this may be used to store a POINTER/ID as well. Read comments near ImGuiMultiSelectIO for details. typedef ImS64 ImGuiSelectionUserData; + // Callback and functions types typedef int (*ImGuiInputTextCallback)(ImGuiInputTextCallbackData* data); // Callback function for ImGui::InputText() typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data); // Callback function for ImGui::SetNextWindowSizeConstraints() typedef void* (*ImGuiMemAllocFunc)(size_t sz, void* user_data); // Function signature for ImGui::SetAllocatorFunctions() typedef void (*ImGuiMemFreeFunc)(void* ptr, void* user_data); // Function signature for ImGui::SetAllocatorFunctions() + // ImVec2: 2D vector used to store positions, sizes etc. [Compile-time configurable type] // - This is a frequently used type in the API. Consider using IM_VEC2_CLASS_EXTRA to create implicit cast from/to our preferred type. // - Add '#define IMGUI_DEFINE_MATH_OPERATORS' before including this file (or in imconfig.h) to access courtesy maths operators for ImVec2 and ImVec4. @@ -264,6 +300,7 @@ struct ImVec2 IM_VEC2_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec2. #endif }; + // ImVec4: 4D vector used to store clipping rectangles, colors etc. [Compile-time configurable type] struct ImVec4 { @@ -275,21 +312,74 @@ struct ImVec4 #endif }; IM_MSVC_RUNTIME_CHECKS_RESTORE + //----------------------------------------------------------------------------- -// [SECTION] Texture identifier (ImTextureID) +// [SECTION] Texture identifiers (ImTextureID, ImTextureRef) //----------------------------------------------------------------------------- -// ImTexture: user data for renderer backend to identify a texture [Compile-time configurable type] -// - To use something else than an opaque void* pointer: override with e.g. '#define ImTextureID MyTextureType*' in your imconfig.h file. -// - This can be whatever to you want it to be! read the FAQ about ImTextureID for details. -// - You can make this a structure with various constructors if you need. You will have to implement ==/!= operators. -// - (note: before v1.91.4 (2024/10/08) the default type for ImTextureID was void*. Use intermediary intptr_t cast and read FAQ if you have casting warnings) + +// ImTextureID = backend specific, low-level identifier for a texture uploaded in GPU/graphics system. +// [Compile-time configurable type] +// - When a Rendered Backend creates a texture, it store its native identifier into a ImTextureID value. +// (e.g. Used by DX11 backend to a `ID3D11ShaderResourceView*`; Used by OpenGL backends to store `GLuint`; +// Used by SDLGPU backend to store a `SDL_GPUTextureSamplerBinding*`, etc.). +// - User may submit their own textures to e.g. ImGui::Image() function by passing this value. +// - During the rendering loop, the Renderer Backend retrieve the ImTextureID, which stored inside a +// ImTextureRef, which is stored inside a ImDrawCmd. +// - Compile-time type configuration: +// - To use something other than a 64-bit value: add '#define ImTextureID MyTextureType*' in your imconfig.h file. +// - This can be whatever to you want it to be! read the FAQ entry about textures for details. +// - You may decide to store a higher-level structure containing texture, sampler, shader etc. with various +// constructors if you like. You will need to implement ==/!= operators. +// History: +// - In v1.91.4 (2024/10/08): the default type for ImTextureID was changed from 'void*' to 'ImU64'. This allowed backends requirig 64-bit worth of data to build on 32-bit architectures. Use intermediary intptr_t cast and read FAQ if you have casting warnings. +// - In v1.92.0 (2025/06/11): added ImTextureRef which carry either a ImTextureID either a pointer to internal texture atlas. All user facing functions taking ImTextureID changed to ImTextureRef #ifndef ImTextureID -typedef ImU64 ImTextureID; // Default: store a pointer or an integer fitting in a pointer (most renderer backends are ok with that) +typedef ImU64 ImTextureID; // Default: store up to 64-bits (any pointer or integer). A majority of backends are ok with that. #endif + +// Define this if you need 0 to be a valid ImTextureID for your backend. +#ifndef ImTextureID_Invalid +#define ImTextureID_Invalid ((ImTextureID)0) +#endif + +// ImTextureRef = higher-level identifier for a texture. Store a ImTextureID _or_ a ImTextureData*. +// The identifier is valid even before the texture has been uploaded to the GPU/graphics system. +// This is what gets passed to functions such as `ImGui::Image()`, `ImDrawList::AddImage()`. +// This is what gets stored in draw commands (`ImDrawCmd`) to identify a texture during rendering. +// - When a texture is created by user code (e.g. custom images), we directly stores the low-level ImTextureID. +// Because of this, when displaying your own texture you are likely to ever only manage ImTextureID values on your side. +// - When a texture is created by the backend, we stores a ImTextureData* which becomes an indirection +// to extract the ImTextureID value during rendering, after texture upload has happened. +// - To create a ImTextureRef from a ImTextureData you can use ImTextureData::GetTexRef(). +// We intentionally do not provide an ImTextureRef constructor for this: we don't expect this +// to be frequently useful to the end-user, and it would be erroneously called by many legacy code. +// - If you want to bind the current atlas when using custom rectangle, you can use io.Fonts->TexRef. +// - Binding generators for languages such as C (which don't have constructors), should provide a helper, e.g. +// inline ImTextureRef ImTextureRefFromID(ImTextureID tex_id) { ImTextureRef tex_ref = { ._TexData = NULL, .TexID = tex_id }; return tex_ref; } +// In 1.92 we changed most drawing functions using ImTextureID to use ImTextureRef. +// We intentionally do not provide an implicit ImTextureRef -> ImTextureID cast operator because it is technically lossy to convert ImTextureRef to ImTextureID before rendering. +IM_MSVC_RUNTIME_CHECKS_OFF +struct ImTextureRef +{ + ImTextureRef() { _TexData = NULL; _TexID = ImTextureID_Invalid; } + ImTextureRef(ImTextureID tex_id) { _TexData = NULL; _TexID = tex_id; } +#if !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && !defined(ImTextureID) + ImTextureRef(void* tex_id) { _TexData = NULL; _TexID = (ImTextureID)(size_t)tex_id; } // For legacy backends casting to ImTextureID +#endif + + inline ImTextureID GetTexID() const; // == (_TexData ? _TexData->TexID : _TexID) // Implemented below in the file. + + // Members (either are set, never both!) + ImTextureData* _TexData; // A texture, generally owned by a ImFontAtlas. Will convert to ImTextureID during render loop, after texture has been uploaded. + ImTextureID _TexID; // _OR_ Low-level backend texture identifier, if already uploaded or created by user/app. Generally provided to e.g. ImGui::Image() calls. +}; +IM_MSVC_RUNTIME_CHECKS_RESTORE + //----------------------------------------------------------------------------- // [SECTION] Dear ImGui end-user API functions // (Note that ImGui:: being a namespace, you can add extra ImGui:: functions in your own separate file. Please don't modify imgui source files!) //----------------------------------------------------------------------------- + namespace ImGui { // Context creation and access @@ -300,6 +390,7 @@ namespace ImGui IMGUI_API void DestroyContext(ImGuiContext* ctx = NULL); // NULL = destroy current context IMGUI_API ImGuiContext* GetCurrentContext(); IMGUI_API void SetCurrentContext(ImGuiContext* ctx); + // Main IMGUI_API ImGuiIO& GetIO(); // access the ImGuiIO structure (mouse/keyboard/gamepad inputs, time, various configuration options/flags) IMGUI_API ImGuiPlatformIO& GetPlatformIO(); // access the ImGuiPlatformIO structure (mostly hooks/functions to connect to platform/renderer and OS Clipboard, IME etc.) @@ -307,7 +398,8 @@ namespace ImGui IMGUI_API void NewFrame(); // start a new Dear ImGui frame, you can submit any command from this point until Render()/EndFrame(). IMGUI_API void EndFrame(); // ends the Dear ImGui frame. automatically called by Render(). If you don't need to render data (skipping rendering) you may call EndFrame() without Render()... but you'll have wasted CPU already! If you don't need to render, better to not create any windows and not call NewFrame() at all! IMGUI_API void Render(); // ends the Dear ImGui frame, finalize the draw data. You can then get call GetDrawData(). - IMGUI_API ImDrawData* GetDrawData(); // valid after Render() and until the next call to NewFrame(). this is what you have to render. + IMGUI_API ImDrawData* GetDrawData(); // valid after Render() and until the next call to NewFrame(). Call ImGui_ImplXXXX_RenderDrawData() function in your Renderer Backend to render. + // Demo, Debug, Information IMGUI_API void ShowDemoWindow(bool* p_open = NULL); // create Demo window. demonstrate most ImGui features. call this to learn about the library! try to make it always available in your application! IMGUI_API void ShowMetricsWindow(bool* p_open = NULL); // create Metrics/Debugger window. display Dear ImGui internals: windows, draw commands, various internal state, etc. @@ -319,10 +411,12 @@ namespace ImGui IMGUI_API void ShowFontSelector(const char* label); // add font selector block (not a window), essentially a combo listing the loaded fonts. IMGUI_API void ShowUserGuide(); // add basic help/info block (not a window): how to manipulate ImGui as an end-user (mouse/keyboard controls). IMGUI_API const char* GetVersion(); // get the compiled version string e.g. "1.80 WIP" (essentially the value for IMGUI_VERSION from the compiled version of imgui.cpp) + // Styles IMGUI_API void StyleColorsDark(ImGuiStyle* dst = NULL); // new, recommended style (default) IMGUI_API void StyleColorsLight(ImGuiStyle* dst = NULL); // best used with borders and a custom, thicker font IMGUI_API void StyleColorsClassic(ImGuiStyle* dst = NULL); // classic imgui style + // Windows // - Begin() = push window to the stack and start appending to it. End() = pop window from the stack. // - Passing 'bool* p_open != NULL' shows a window-closing widget in the upper-right corner of the window, @@ -337,6 +431,7 @@ namespace ImGui // - Note that the bottom of window stack always contains a window called "Debug". IMGUI_API bool Begin(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); IMGUI_API void End(); + // Child Windows // - Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window. Child windows can embed their own child. // - Before 1.90 (November 2023), the "ImGuiChildFlags child_flags = 0" parameter was "bool border = false". @@ -358,6 +453,7 @@ namespace ImGui IMGUI_API bool BeginChild(const char* str_id, const ImVec2& size = ImVec2(0, 0), ImGuiChildFlags child_flags = 0, ImGuiWindowFlags window_flags = 0); IMGUI_API bool BeginChild(ImGuiID id, const ImVec2& size = ImVec2(0, 0), ImGuiChildFlags child_flags = 0, ImGuiWindowFlags window_flags = 0); IMGUI_API void EndChild(); + // Windows Utilities // - 'current window' = the window we are appending into while inside a Begin()/End() block. 'next window' = next window we will Begin() into. IMGUI_API bool IsWindowAppearing(); @@ -371,6 +467,7 @@ namespace ImGui IMGUI_API float GetWindowWidth(); // get current window width (IT IS UNLIKELY YOU EVER NEED TO USE THIS). Shortcut for GetWindowSize().x. IMGUI_API float GetWindowHeight(); // get current window height (IT IS UNLIKELY YOU EVER NEED TO USE THIS). Shortcut for GetWindowSize().y. IMGUI_API ImGuiViewport*GetWindowViewport(); // get viewport currently associated to the current window. + // Window manipulation // - Prefer using SetNextXXX functions (before Begin) rather that SetXXX functions (after Begin). IMGUI_API void SetNextWindowPos(const ImVec2& pos, ImGuiCond cond = 0, const ImVec2& pivot = ImVec2(0, 0)); // set next window position. call before Begin(). use pivot=(0.5f,0.5f) to center on given point, etc. @@ -386,11 +483,11 @@ namespace ImGui IMGUI_API void SetWindowSize(const ImVec2& size, ImGuiCond cond = 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0, 0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. IMGUI_API void SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed(). IMGUI_API void SetWindowFocus(); // (not recommended) set current window to be focused / top-most. prefer using SetNextWindowFocus(). - IMGUI_API void SetWindowFontScale(float scale); // [OBSOLETE] set font scale. Adjust IO.FontGlobalScale if you want to scale all windows. This is an old API! For correct scaling, prefer to reload font + rebuild ImFontAtlas + call style.ScaleAllSizes(). IMGUI_API void SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond = 0); // set named window position. IMGUI_API void SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond = 0); // set named window size. set axis to 0.0f to force an auto-fit on this axis. IMGUI_API void SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond = 0); // set named window collapsed state IMGUI_API void SetWindowFocus(const char* name); // set named window to be focused / top-most. use NULL to remove focus. + // Windows Scrolling // - Any change of Scroll will be applied at the beginning of next frame in the first call to Begin(). // - You may instead use SetNextWindowScroll() prior to calling Begin() to avoid this delay, as an alternative to using SetScrollX()/SetScrollY(). @@ -404,9 +501,30 @@ namespace ImGui IMGUI_API void SetScrollHereY(float center_y_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead. IMGUI_API void SetScrollFromPosX(float local_x, float center_x_ratio = 0.5f); // adjust scrolling amount to make given position visible. Generally GetCursorStartPos() + offset to compute a valid position. IMGUI_API void SetScrollFromPosY(float local_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position visible. Generally GetCursorStartPos() + offset to compute a valid position. - // Parameters stacks (shared) - IMGUI_API void PushFont(ImFont* font); // use NULL as a shortcut to push default font + + // Parameters stacks (font) + // - PushFont(font, 0.0f) // Change font and keep current size + // - PushFont(NULL, 20.0f) // Keep font and change current size + // - PushFont(font, 20.0f) // Change font and set size to 20.0f + // - PushFont(font, style.FontSizeBase * 2.0f) // Change font and set size to be twice bigger than current size. + // - PushFont(font, font->LegacySize) // Change font and set size to size passed to AddFontXXX() function. Same as pre-1.92 behavior. + // *IMPORTANT* before 1.92, fonts had a single size. They can now be dynamically be adjusted. + // - In 1.92 we have REMOVED the single parameter version of PushFont() because it seems like the easiest way to provide an error-proof transition. + // - PushFont(font) before 1.92 = PushFont(font, font->LegacySize) after 1.92 // Use default font size as passed to AddFontXXX() function. + // *IMPORTANT* global scale factors are applied over the provided size. + // - Global scale factors are: 'style.FontScaleMain', 'style.FontScaleDpi' and maybe more. + // - If you want to apply a factor to the _current_ font size: + // - CORRECT: PushFont(NULL, style.FontSizeBase) // use current unscaled size == does nothing + // - CORRECT: PushFont(NULL, style.FontSizeBase * 2.0f) // use current unscaled size x2 == make text twice bigger + // - INCORRECT: PushFont(NULL, GetFontSize()) // INCORRECT! using size after global factors already applied == GLOBAL SCALING FACTORS WILL APPLY TWICE! + // - INCORRECT: PushFont(NULL, GetFontSize() * 2.0f) // INCORRECT! using size after global factors already applied == GLOBAL SCALING FACTORS WILL APPLY TWICE! + IMGUI_API void PushFont(ImFont* font, float font_size_base_unscaled); // Use NULL as a shortcut to keep current font. Use 0.0f to keep current size. IMGUI_API void PopFont(); + IMGUI_API ImFont* GetFont(); // get current font + IMGUI_API float GetFontSize(); // get current scaled font size (= height in pixels). AFTER global scale factors applied. *IMPORTANT* DO NOT PASS THIS VALUE TO PushFont()! Use ImGui::GetStyle().FontSizeBase to get value before global scale factors. + IMGUI_API ImFontBaked* GetFontBaked(); // get current font bound at current size // == GetFont()->GetFontBaked(GetFontSize()) + + // Parameters stacks (shared) IMGUI_API void PushStyleColor(ImGuiCol idx, ImU32 col); // modify a style color. always use this if you modify the style after NewFrame(). IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); IMGUI_API void PopStyleColor(int count = 1); @@ -417,6 +535,7 @@ namespace ImGui IMGUI_API void PopStyleVar(int count = 1); IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); // modify specified shared item flag, e.g. PushItemFlag(ImGuiItemFlags_NoTabStop, true) IMGUI_API void PopItemFlag(); + // Parameters stacks (current window) IMGUI_API void PushItemWidth(float item_width); // push width of items for common large "item+label" widgets. >0.0f: width in pixels, <0.0f align xx pixels to the right of window (so -FLT_MIN always align width to the right side). IMGUI_API void PopItemWidth(); @@ -424,15 +543,15 @@ namespace ImGui IMGUI_API float CalcItemWidth(); // width of item given pushed settings and current cursor position. NOT necessarily the width of last item unlike most 'Item' functions. IMGUI_API void PushTextWrapPos(float wrap_local_pos_x = 0.0f); // push word-wrapping position for Text*() commands. < 0.0f: no wrapping; 0.0f: wrap to end of window (or column); > 0.0f: wrap at 'wrap_pos_x' position in window local space IMGUI_API void PopTextWrapPos(); + // Style read access // - Use the ShowStyleEditor() function to interactively see/edit the colors. - IMGUI_API ImFont* GetFont(); // get current font - IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a white pixel, useful to draw custom shapes via the ImDrawList API IMGUI_API ImU32 GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f); // retrieve given style color with style alpha applied and optional extra alpha multiplier, packed as a 32-bit value suitable for ImDrawList IMGUI_API ImU32 GetColorU32(const ImVec4& col); // retrieve given color with style alpha applied, packed as a 32-bit value suitable for ImDrawList IMGUI_API ImU32 GetColorU32(ImU32 col, float alpha_mul = 1.0f); // retrieve given color with style alpha applied, packed as a 32-bit value suitable for ImDrawList IMGUI_API const ImVec4& GetStyleColorVec4(ImGuiCol idx); // retrieve style color as stored in ImGuiStyle structure. use to feed back into PushStyleColor(), otherwise use GetColorU32() to get style color with style alpha baked in. + // Layout cursor positioning // - By "cursor" we mean the current output position. // - The typical widget behavior is to output themselves at the current cursor position, then move the cursor one line down. @@ -453,6 +572,7 @@ namespace ImGui IMGUI_API void SetCursorPosX(float local_x); // [window-local] " IMGUI_API void SetCursorPosY(float local_y); // [window-local] " IMGUI_API ImVec2 GetCursorStartPos(); // [window-local] initial cursor position, in window-local coordinates. Call GetCursorScreenPos() after Begin() to get the absolute coordinates version. + // Other layout functions IMGUI_API void Separator(); // separator, generally horizontal. inside a menu bar or in horizontal layout mode, this becomes a vertical separator. IMGUI_API void SameLine(float offset_from_start_x=0.0f, float spacing=-1.0f); // call between widgets or groups to layout them horizontally. X position given in window coordinates. @@ -468,6 +588,7 @@ namespace ImGui IMGUI_API float GetTextLineHeightWithSpacing(); // ~ FontSize + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of text) IMGUI_API float GetFrameHeight(); // ~ FontSize + style.FramePadding.y * 2 IMGUI_API float GetFrameHeightWithSpacing(); // ~ FontSize + style.FramePadding.y * 2 + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of framed widgets) + // ID stack/scopes // Read the FAQ (docs/FAQ.md or http://dearimgui.com/faq) for more details about how ID are handled in dear imgui. // - Those questions are answered and impacted by understanding of the ID stack system: @@ -488,6 +609,7 @@ namespace ImGui IMGUI_API ImGuiID GetID(const char* str_id_begin, const char* str_id_end); IMGUI_API ImGuiID GetID(const void* ptr_id); IMGUI_API ImGuiID GetID(int int_id); + // Widgets: Text IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // raw text without formatting. Roughly equivalent to Text("%s", text) but: A) doesn't require null terminated string if 'text_end' is specified, B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text. IMGUI_API void Text(const char* fmt, ...) IM_FMTARGS(1); // formatted text @@ -503,6 +625,7 @@ namespace ImGui IMGUI_API void BulletText(const char* fmt, ...) IM_FMTARGS(1); // shortcut for Bullet()+Text() IMGUI_API void BulletTextV(const char* fmt, va_list args) IM_FMTLIST(1); IMGUI_API void SeparatorText(const char* label); // currently: formatted text with a horizontal line + // Widgets: Main // - Most widgets return true when the value has been changed or when pressed/selected // - You may also use one of the many IsItemXXX functions (e.g. IsItemActive, IsItemHovered, etc.) to query widget state. @@ -518,15 +641,18 @@ namespace ImGui IMGUI_API void ProgressBar(float fraction, const ImVec2& size_arg = ImVec2(-FLT_MIN, 0), const char* overlay = NULL); IMGUI_API void Bullet(); // draw a small circle + keep the cursor on the same line. advance cursor x position by GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses IMGUI_API bool TextLink(const char* label); // hyperlink text button, return true when clicked - IMGUI_API void TextLinkOpenURL(const char* label, const char* url = NULL); // hyperlink text button, automatically open file/url when clicked + IMGUI_API bool TextLinkOpenURL(const char* label, const char* url = NULL); // hyperlink text button, automatically open file/url when clicked + // Widgets: Images - // - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples + // - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above. // - Image() pads adds style.ImageBorderSize on each side, ImageButton() adds style.FramePadding on each side. // - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. - IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1)); - IMGUI_API void ImageWithBg(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); - IMGUI_API bool ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); + // - An obsolete version of Image(), before 1.91.9 (March 2025), had a 'tint_col' parameter which is now supported by the ImageWithBg() function. + IMGUI_API void Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1)); + IMGUI_API void ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); + IMGUI_API bool ImageButton(const char* str_id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); + // Widgets: Combo Box (Dropdown) // - The BeginCombo()/EndCombo() api allows you to manage your contents and selection state however you want it, by creating e.g. Selectable() items. // - The old Combo() api are helpers over BeginCombo()/EndCombo() which are kept available for convenience purpose. This is analogous to how ListBox are created. @@ -535,6 +661,7 @@ namespace ImGui IMGUI_API bool Combo(const char* label, int* current_item, const char* const items[], int items_count, int popup_max_height_in_items = -1); IMGUI_API bool Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int popup_max_height_in_items = -1); // Separate items with \0 within a string, end item-list with \0\0. e.g. "One\0Two\0Three\0" IMGUI_API bool Combo(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int popup_max_height_in_items = -1); + // Widgets: Drag Sliders // - CTRL+Click on any drag box to turn them into an input box. Manually input values aren't clamped by default and can go off-bounds. Use ImGuiSliderFlags_AlwaysClamp to always clamp. // - For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every function, note that a 'float v[X]' function argument is the same as 'float* v', @@ -559,6 +686,7 @@ namespace ImGui IMGUI_API bool DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = "%d", const char* format_max = NULL, ImGuiSliderFlags flags = 0); IMGUI_API bool DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed = 1.0f, const void* p_min = NULL, const void* p_max = NULL, const char* format = NULL, ImGuiSliderFlags flags = 0); IMGUI_API bool DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed = 1.0f, const void* p_min = NULL, const void* p_max = NULL, const char* format = NULL, ImGuiSliderFlags flags = 0); + // Widgets: Regular Sliders // - CTRL+Click on any slider to turn them into an input box. Manually input values aren't clamped by default and can go off-bounds. Use ImGuiSliderFlags_AlwaysClamp to always clamp. // - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. @@ -579,6 +707,7 @@ namespace ImGui IMGUI_API bool VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0); IMGUI_API bool VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format = "%d", ImGuiSliderFlags flags = 0); IMGUI_API bool VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format = NULL, ImGuiSliderFlags flags = 0); + // Widgets: Input with Keyboard // - If you want to use InputText() with std::string or any custom dynamic string type, see misc/cpp/imgui_stdlib.h and comments in imgui_demo.cpp. // - Most of the ImGuiInputTextFlags flags are only useful for InputText() and not for InputFloatX, InputIntX, InputDouble etc. @@ -596,6 +725,7 @@ namespace ImGui IMGUI_API bool InputDouble(const char* label, double* v, double step = 0.0, double step_fast = 0.0, const char* format = "%.6f", ImGuiInputTextFlags flags = 0); IMGUI_API bool InputScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_step = NULL, const void* p_step_fast = NULL, const char* format = NULL, ImGuiInputTextFlags flags = 0); IMGUI_API bool InputScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_step = NULL, const void* p_step_fast = NULL, const char* format = NULL, ImGuiInputTextFlags flags = 0); + // Widgets: Color Editor/Picker (tip: the ColorEdit* functions have a little color square that can be left-clicked to open a picker, and right-clicked to open an option menu.) // - Note that in C++ a 'float v[X]' function argument is the _same_ as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. // - You can pass the address of a first float element out of a contiguous structure, e.g. &myvector.x @@ -605,6 +735,7 @@ namespace ImGui IMGUI_API bool ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags = 0, const float* ref_col = NULL); IMGUI_API bool ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // display a color square/button, hover for details, return true when pressed. IMGUI_API void SetColorEditOptions(ImGuiColorEditFlags flags); // initialize current options (generally on application startup) if you want to select a default format, picker type, etc. User will be able to change many settings, unless you pass the _NoOptions flag to your calls. + // Widgets: Trees // - TreeNode functions return true when the node is open, in which case you need to also call TreePop() when you are finished displaying the tree node contents. IMGUI_API bool TreeNode(const char* label); @@ -625,11 +756,13 @@ namespace ImGui IMGUI_API bool CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags = 0); // when 'p_visible != NULL': if '*p_visible==true' display an additional small close button on upper right of the header which will set the bool to false when clicked, if '*p_visible==false' don't display the header. IMGUI_API void SetNextItemOpen(bool is_open, ImGuiCond cond = 0); // set next TreeNode/CollapsingHeader open state. IMGUI_API void SetNextItemStorageID(ImGuiID storage_id); // set id to use for open/close storage (default to same as item id). + // Widgets: Selectables // - A selectable highlights when hovered, and can display another color when selected. // - Neighbors selectable extend their highlight bounds in order to leave no gap between them. This is so a series of selected Selectable appear contiguous. IMGUI_API bool Selectable(const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // "bool selected" carry the selection state (read-only). Selectable() is clicked is returns true so you can modify your selection state. size.x==0.0: use remaining width, size.x>0.0: specify width. size.y==0.0: use label height, size.y>0.0: specify height IMGUI_API bool Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // "bool* p_selected" point to the selection state (read-write), as a convenient helper. + // Multi-selection system for Selectable(), Checkbox(), TreeNode() functions [BETA] // - This enables standard multi-selection/range-selection idioms (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc.) in a way that also allow a clipper to be used. // - ImGuiSelectionUserData is often used to store your item index within the current view (but may store something else). @@ -641,29 +774,33 @@ namespace ImGui IMGUI_API ImGuiMultiSelectIO* EndMultiSelect(); IMGUI_API void SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data); IMGUI_API bool IsItemToggledSelection(); // Was the last item selection state toggled? Useful if you need the per-item information _before_ reaching EndMultiSelect(). We only returns toggle _event_ in order to handle clipping correctly. + // Widgets: List Boxes // - This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label. // - If you don't need a label you can probably simply use BeginChild() with the ImGuiChildFlags_FrameStyle flag for the same result. // - You can submit contents and manage your selection state however you want it, by creating e.g. Selectable() or any other items. - // - The simplified/old ListBox() api are helpers over BeginListBox()/EndListBox() which are kept available for convenience purpose. This is analoguous to how Combos are created. + // - The simplified/old ListBox() api are helpers over BeginListBox()/EndListBox() which are kept available for convenience purpose. This is analogous to how Combos are created. // - Choose frame width: size.x > 0.0f: custom / size.x < 0.0f or -FLT_MIN: right-align / size.x = 0.0f (default): use current ItemWidth // - Choose frame height: size.y > 0.0f: custom / size.y < 0.0f or -FLT_MIN: bottom-align / size.y = 0.0f (default): arbitrary default height which can fit ~7 items IMGUI_API bool BeginListBox(const char* label, const ImVec2& size = ImVec2(0, 0)); // open a framed scrolling region IMGUI_API void EndListBox(); // only call EndListBox() if BeginListBox() returned true! IMGUI_API bool ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items = -1); IMGUI_API bool ListBox(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int height_in_items = -1); + // Widgets: Data Plotting // - Consider using ImPlot (https://github.com/epezent/implot) which is much better! IMGUI_API void PlotLines(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float)); IMGUI_API void PlotLines(const char* label, float(*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0)); IMGUI_API void PlotHistogram(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float)); IMGUI_API void PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0)); + // Widgets: Value() Helpers. // - Those are merely shortcut to calling Text() with a format string. Output single value in "name: value" format (tip: freely declare more in your code to handle your types. you can add functions to the ImGui namespace) IMGUI_API void Value(const char* prefix, bool b); IMGUI_API void Value(const char* prefix, int v); IMGUI_API void Value(const char* prefix, unsigned int v); IMGUI_API void Value(const char* prefix, float v, const char* float_format = NULL); + // Widgets: Menus // - Use BeginMenuBar() on a window ImGuiWindowFlags_MenuBar to append to its menu bar. // - Use BeginMainMenuBar() to create a menu bar at the top of the screen and append to it. @@ -677,6 +814,7 @@ namespace ImGui IMGUI_API void EndMenu(); // only call EndMenu() if BeginMenu() returns true! IMGUI_API bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true); // return true when activated. IMGUI_API bool MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled = true); // return true when activated + toggle (*p_selected) if p_selected != NULL + // Tooltips // - Tooltips are windows following the mouse. They do not take focus away. // - A tooltip window can contain items of any types. @@ -685,6 +823,7 @@ namespace ImGui IMGUI_API void EndTooltip(); // only call EndTooltip() if BeginTooltip()/BeginItemTooltip() returns true! IMGUI_API void SetTooltip(const char* fmt, ...) IM_FMTARGS(1); // set a text-only tooltip. Often used after a ImGui::IsItemHovered() check. Override any previous call to SetTooltip(). IMGUI_API void SetTooltipV(const char* fmt, va_list args) IM_FMTLIST(1); + // Tooltips: helpers for showing a tooltip when hovering an item // - BeginItemTooltip() is a shortcut for the 'if (IsItemHovered(ImGuiHoveredFlags_ForTooltip) && BeginTooltip())' idiom. // - SetItemTooltip() is a shortcut for the 'if (IsItemHovered(ImGuiHoveredFlags_ForTooltip)) { SetTooltip(...); }' idiom. @@ -692,6 +831,7 @@ namespace ImGui IMGUI_API bool BeginItemTooltip(); // begin/append a tooltip window if preceding item was hovered. IMGUI_API void SetItemTooltip(const char* fmt, ...) IM_FMTARGS(1); // set a text-only tooltip if preceding item was hovered. override any previous call to SetTooltip(). IMGUI_API void SetItemTooltipV(const char* fmt, va_list args) IM_FMTLIST(1); + // Popups, Modals // - They block normal mouse hovering detection (and therefore most mouse interactions) behind them. // - If not modal: they can be closed by clicking anywhere outside them, or by pressing ESCAPE. @@ -705,6 +845,7 @@ namespace ImGui IMGUI_API bool BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0); // return true if the popup is open, and you can start outputting to it. IMGUI_API bool BeginPopupModal(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); // return true if the modal is open, and you can start outputting to it. IMGUI_API void EndPopup(); // only call EndPopup() if BeginPopupXXX() returns true! + // Popups: open/close functions // - OpenPopup(): set popup state to open. ImGuiPopupFlags are available for opening options. // - If not modal: they can be closed by clicking anywhere outside them, or by pressing ESCAPE. @@ -717,6 +858,7 @@ namespace ImGui IMGUI_API void OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags = 0); // id overload to facilitate calling from nested stacks IMGUI_API void OpenPopupOnItemClick(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // helper to open popup when clicked on last item. Default to ImGuiPopupFlags_MouseButtonRight == 1. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors) IMGUI_API void CloseCurrentPopup(); // manually close the popup we have begin-ed into. + // Popups: open+begin combined functions helpers // - Helpers to do OpenPopup+BeginPopup where the Open action is triggered by e.g. hovering an item and right-clicking. // - They are convenient to easily create context menus, hence the name. @@ -725,11 +867,13 @@ namespace ImGui IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // open+begin popup when clicked on last item. Use str_id==NULL to associate the popup to previous item. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1);// open+begin popup when clicked on current window. IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1); // open+begin popup when clicked in void (where there are no windows). + // Popups: query functions // - IsPopupOpen(): return true if the popup is open at the current BeginPopup() level of the popup stack. // - IsPopupOpen() with ImGuiPopupFlags_AnyPopupId: return true if any popup is open at the current BeginPopup() level of the popup stack. // - IsPopupOpen() with ImGuiPopupFlags_AnyPopupId + ImGuiPopupFlags_AnyPopupLevel: return true if any popup is open. IMGUI_API bool IsPopupOpen(const char* str_id, ImGuiPopupFlags flags = 0); // return true if the popup is open. + // Tables // - Full-featured replacement for old Columns API. // - See Demo->Tables for demo code. See top of imgui_tables.cpp for general commentary. @@ -756,6 +900,7 @@ namespace ImGui IMGUI_API void TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row. IMGUI_API bool TableNextColumn(); // append into the next column (or first column of next row if currently in last column). Return true when column is visible. IMGUI_API bool TableSetColumnIndex(int column_n); // append into the specified column. Return true when column is visible. + // Tables: Headers & Columns declaration // - Use TableSetupColumn() to specify label, resizing policy, default width/weight, id, various other flags etc. // - Use TableHeadersRow() to create a header row and automatically submit a TableHeader() for each column. @@ -769,6 +914,7 @@ namespace ImGui IMGUI_API void TableHeader(const char* label); // submit one header cell manually (rarely used) IMGUI_API void TableHeadersRow(); // submit a row with headers cells based on data provided to TableSetupColumn() + submit context menu IMGUI_API void TableAngledHeadersRow(); // submit a row with angled headers for every column with the ImGuiTableColumnFlags_AngledHeader flag. MUST BE FIRST ROW. + // Tables: Sorting & Miscellaneous functions // - Sorting: call TableGetSortSpecs() to retrieve latest sort specs for the table. NULL when not sorting. // When 'sort_specs->SpecsDirty == true' you should sort your data. It will be true when sorting specs have @@ -778,12 +924,13 @@ namespace ImGui IMGUI_API ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable(). IMGUI_API int TableGetColumnCount(); // return number of columns (value passed to BeginTable) IMGUI_API int TableGetColumnIndex(); // return current column index. - IMGUI_API int TableGetRowIndex(); // return current row index. + IMGUI_API int TableGetRowIndex(); // return current row index (header rows are accounted for) IMGUI_API const char* TableGetColumnName(int column_n = -1); // return "" if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column. IMGUI_API ImGuiTableColumnFlags TableGetColumnFlags(int column_n = -1); // return column flags so you can query their Enabled/Visible/Sorted/Hovered status flags. Pass -1 to use current column. IMGUI_API void TableSetColumnEnabled(int column_n, bool v);// change user accessible enabled/disabled state of a column. Set to false to hide the column. User can use the context menu to change this themselves (right-click in headers, or right-click in columns body with ImGuiTableFlags_ContextMenuInBody) IMGUI_API int TableGetHoveredColumn(); // return hovered column. return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. Can also use (TableGetColumnFlags() & ImGuiTableColumnFlags_IsHovered) instead. IMGUI_API void TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n = -1); // change the color of a cell, row, or column. See ImGuiTableBgTarget_ flags for details. + // Legacy Columns API (prefer using Tables!) // - You can also use SameLine(pos_x) to mimic simplified columns. IMGUI_API void Columns(int count = 1, const char* id = NULL, bool borders = true); @@ -794,6 +941,7 @@ namespace ImGui IMGUI_API float GetColumnOffset(int column_index = -1); // get position of column line (in pixels, from the left side of the contents region). pass -1 to use current column, otherwise 0..GetColumnsCount() inclusive. column 0 is typically 0.0f IMGUI_API void SetColumnOffset(int column_index, float offset_x); // set position of column line (in pixels, from the left side of the contents region). pass -1 to use current column IMGUI_API int GetColumnsCount(); + // Tab Bars, Tabs // - Note: Tabs are automatically created by the docking system (when in 'docking' branch). Use this to create tab bars/tabs yourself. IMGUI_API bool BeginTabBar(const char* str_id, ImGuiTabBarFlags flags = 0); // create and append into a TabBar @@ -802,6 +950,7 @@ namespace ImGui IMGUI_API void EndTabItem(); // only call EndTabItem() if BeginTabItem() returns true! IMGUI_API bool TabItemButton(const char* label, ImGuiTabItemFlags flags = 0); // create a Tab behaving like a button. return true when clicked. cannot be selected in the tab bar. IMGUI_API void SetTabItemClosed(const char* tab_or_docked_window_label); // notify TabBar or Docking system of a closed tab/window ahead (useful to reduce visual flicker on reorderable tab bars). For tab-bar: call after BeginTabBar() and before Tab submissions. Otherwise call with a window name. + // Docking // [BETA API] Enable with io.ConfigFlags |= ImGuiConfigFlags_DockingEnable. // Note: You can use most Docking facilities without calling any API. You DO NOT need to call DockSpace() to use Docking! @@ -821,6 +970,7 @@ namespace ImGui IMGUI_API void SetNextWindowClass(const ImGuiWindowClass* window_class); // set next window class (control docking compatibility + provide hints to platform backend via custom viewport flags and platform parent/child relationship) IMGUI_API ImGuiID GetWindowDockID(); IMGUI_API bool IsWindowDocked(); // is current window docked into another window? + // Logging/Capture // - All text output from the interface can be captured into tty/file/clipboard. By default, tree nodes are automatically opened during logging. IMGUI_API void LogToTTY(int auto_open_depth = -1); // start logging to tty (stdout) @@ -830,6 +980,7 @@ namespace ImGui IMGUI_API void LogButtons(); // helper to display buttons for logging to tty/file/clipboard IMGUI_API void LogText(const char* fmt, ...) IM_FMTARGS(1); // pass text data straight to log (without being displayed) IMGUI_API void LogTextV(const char* fmt, va_list args) IM_FMTLIST(1); + // Drag and Drop // - On source items, call BeginDragDropSource(), if it returns true also call SetDragDropPayload() + EndDragDropSource(). // - On target candidates, call BeginDragDropTarget(), if it returns true also call AcceptDragDropPayload() + EndDragDropTarget(). @@ -842,6 +993,7 @@ namespace ImGui IMGUI_API const ImGuiPayload* AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags = 0); // accept contents of a given type. If ImGuiDragDropFlags_AcceptBeforeDelivery is set you can peek into the payload before the mouse button is released. IMGUI_API void EndDragDropTarget(); // only call EndDragDropTarget() if BeginDragDropTarget() returns true! IMGUI_API const ImGuiPayload* GetDragDropPayload(); // peek directly into the current payload from anywhere. returns NULL when drag and drop is finished or inactive. use ImGuiPayload::IsDataType() to test for the payload type. + // Disabling [BETA API] // - Disable all user interactions and dim items visuals (applying style.DisabledAlpha over current colors) // - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep everything disabled) @@ -849,17 +1001,22 @@ namespace ImGui // - BeginDisabled(false)/EndDisabled() essentially does nothing but is provided to facilitate use of boolean expressions (as a micro-optimization: if you have tens of thousands of BeginDisabled(false)/EndDisabled() pairs, you might want to reformulate your code to avoid making those calls) IMGUI_API void BeginDisabled(bool disabled = true); IMGUI_API void EndDisabled(); + // Clipping // - Mouse hovering is affected by ImGui::PushClipRect() calls, unlike direct calls to ImDrawList::PushClipRect() which are render only. IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect); IMGUI_API void PopClipRect(); + // Focus, Activation IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a newly appearing window. IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget. + // Keyboard/Gamepad Navigation IMGUI_API void SetNavCursorVisible(bool visible); // alter visibility of keyboard/gamepad cursor. by default: show when using an arrow key, hide when clicking with mouse. + // Overlapping mode IMGUI_API void SetNextItemAllowOverlap(); // allow next item to be overlapped by a subsequent item. Useful with invisible buttons, selectable, treenode covering an area where subsequent items may need to be added. Note that both Selectable() and TreeNode() have dedicated flags doing this. + // Item/Widgets Utilities and Query Functions // - Most of the functions are referring to the previous Item that has been submitted. // - See Demo Window under "Widgets->Querying Status" for an interactive visualization of most of those functions. @@ -880,14 +1037,17 @@ namespace ImGui IMGUI_API ImVec2 GetItemRectMin(); // get upper-left bounding rectangle of the last item (screen space) IMGUI_API ImVec2 GetItemRectMax(); // get lower-right bounding rectangle of the last item (screen space) IMGUI_API ImVec2 GetItemRectSize(); // get size of last item + // Viewports // - Currently represents the Platform Window created by the application which is hosting our Dear ImGui windows. // - In 'docking' branch with multi-viewport enabled, we extend this concept to have multiple active viewports. // - In the future we will extend this concept further to also represent Platform Monitor and support a "no main platform window" operation mode. IMGUI_API ImGuiViewport* GetMainViewport(); // return primary/default viewport. This can never be NULL. + // Background/Foreground Draw Lists IMGUI_API ImDrawList* GetBackgroundDrawList(ImGuiViewport* viewport = NULL); // get background draw list for the given viewport or viewport associated to the current window. this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents. IMGUI_API ImDrawList* GetForegroundDrawList(ImGuiViewport* viewport = NULL); // get foreground draw list for the given viewport or viewport associated to the current window. this draw list will be the top-most rendered one. Useful to quickly draw shapes/text over dear imgui contents. + // Miscellaneous Utilities IMGUI_API bool IsRectVisible(const ImVec2& size); // test if rectangle (of given size, starting from cursor position) is visible / not clipped. IMGUI_API bool IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max); // test if rectangle (in screen space) is visible / not clipped. to perform coarse clipping on user's side. @@ -897,13 +1057,16 @@ namespace ImGui IMGUI_API const char* GetStyleColorName(ImGuiCol idx); // get a string corresponding to the enum value (for display, saving, etc.). IMGUI_API void SetStateStorage(ImGuiStorage* storage); // replace current window storage with our own (if you want to manipulate it yourself, typically clear subsection of it) IMGUI_API ImGuiStorage* GetStateStorage(); + // Text Utilities IMGUI_API ImVec2 CalcTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); + // Color Utilities IMGUI_API ImVec4 ColorConvertU32ToFloat4(ImU32 in); IMGUI_API ImU32 ColorConvertFloat4ToU32(const ImVec4& in); IMGUI_API void ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v); IMGUI_API void ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b); + // Inputs Utilities: Keyboard/Mouse/Gamepad // - the ImGuiKey enum contains all possible keyboard, mouse and gamepad inputs (e.g. ImGuiKey_A, ImGuiKey_MouseLeft, ImGuiKey_GamepadDpadUp...). // - (legacy: before v1.87, we used ImGuiKey to carry native/user indices as defined by each backends. This was obsoleted in 1.87 (2022-02) and completely removed in 1.91.5 (2024-11). See https://github.com/ocornut/imgui/issues/4921) @@ -915,6 +1078,7 @@ namespace ImGui IMGUI_API int GetKeyPressedAmount(ImGuiKey key, float repeat_delay, float rate); // uses provided repeat rate/delay. return a count, most often 0 or 1 but might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate IMGUI_API const char* GetKeyName(ImGuiKey key); // [DEBUG] returns English name of the key. Those names are provided for debugging purpose and are not meant to be saved persistently nor compared. IMGUI_API void SetNextFrameWantCaptureKeyboard(bool want_capture_keyboard); // Override io.WantCaptureKeyboard flag next frame (said flag is left for your application to handle, typically when true it instructs your app to ignore inputs). e.g. force capture keyboard when your widget is being hovered. This is equivalent to setting "io.WantCaptureKeyboard = want_capture_keyboard"; after the next NewFrame() call. + // Inputs Utilities: Shortcut Testing & Routing [BETA] // - ImGuiKeyChord = a ImGuiKey + optional ImGuiMod_Alt/ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Super. // ImGuiKey_C // Accepted by functions taking ImGuiKey or ImGuiKeyChord arguments) @@ -932,6 +1096,7 @@ namespace ImGui // - Visualize registered routes in 'Metrics/Debugger->Inputs'. IMGUI_API bool Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0); IMGUI_API void SetNextItemShortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0); + // Inputs Utilities: Key/Input Ownership [BETA] // - One common use case would be to allow your items to disable standard inputs behaviors such // as Tab or Alt key handling, Mouse Wheel scrolling, etc. @@ -939,6 +1104,7 @@ namespace ImGui // - Reminder ImGuiKey enum include access to mouse buttons and gamepad, so key ownership can apply to them. // - Many related features are still in imgui_internal.h. For instance, most IsKeyXXX()/IsMouseXXX() functions have an owner-id-aware version. IMGUI_API void SetItemKeyOwner(ImGuiKey key); // Set key owner to last item ID if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'. + // Inputs Utilities: Mouse // - To refer to a mouse button, you may use named enums in your code e.g. ImGuiMouseButton_Left, ImGuiMouseButton_Right. // - You can also use regular integer: it is forever guaranteed that 0=Left, 1=Right, 2=Middle. @@ -959,11 +1125,13 @@ namespace ImGui IMGUI_API void ResetMouseDragDelta(ImGuiMouseButton button = 0); // IMGUI_API ImGuiMouseCursor GetMouseCursor(); // get desired mouse cursor shape. Important: reset in ImGui::NewFrame(), this is updated during the frame. valid before Render(). If you use software rendering by setting io.MouseDrawCursor ImGui will render those for you IMGUI_API void SetMouseCursor(ImGuiMouseCursor cursor_type); // set desired mouse cursor shape - IMGUI_API void SetNextFrameWantCaptureMouse(bool want_capture_mouse); // Override io.WantCaptureMouse flag next frame (said flag is left for your application to handle, typical when true it instucts your app to ignore inputs). This is equivalent to setting "io.WantCaptureMouse = want_capture_mouse;" after the next NewFrame() call. + IMGUI_API void SetNextFrameWantCaptureMouse(bool want_capture_mouse); // Override io.WantCaptureMouse flag next frame (said flag is left for your application to handle, typical when true it instructs your app to ignore inputs). This is equivalent to setting "io.WantCaptureMouse = want_capture_mouse;" after the next NewFrame() call. + // Clipboard Utilities // - Also see the LogToClipboard() function to capture GUI into clipboard, or easily output text data to the clipboard. IMGUI_API const char* GetClipboardText(); IMGUI_API void SetClipboardText(const char* text); + // Settings/.Ini Utilities // - The disk functions are automatically called if io.IniFilename != NULL (default is "imgui.ini"). // - Set io.IniFilename to NULL to load/save manually. Read io.WantSaveIniSettings description about handling .ini saving manually. @@ -972,6 +1140,7 @@ namespace ImGui IMGUI_API void LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size=0); // call after CreateContext() and before the first call to NewFrame() to provide .ini data from your own data source. IMGUI_API void SaveIniSettingsToDisk(const char* ini_filename); // this is automatically called (if io.IniFilename is not empty) a few seconds after any modification that should be reflected in the .ini file (and also by DestroyContext). IMGUI_API const char* SaveIniSettingsToMemory(size_t* out_ini_size = NULL); // return a zero-terminated string with the .ini data which you can save by your own mean. call when io.WantSaveIniSettings is set, then save data by your own mean and clear io.WantSaveIniSettings. + // Debug Utilities // - Your main debugging friend is the ShowMetricsWindow() function, which is also accessible from Demo->Tools->Metrics Debugger IMGUI_API void DebugTextEncoding(const char* text); @@ -982,6 +1151,7 @@ namespace ImGui IMGUI_API void DebugLog(const char* fmt, ...) IM_FMTARGS(1); // Call via IMGUI_DEBUG_LOG() for maximum stripping in caller code! IMGUI_API void DebugLogV(const char* fmt, va_list args) IM_FMTLIST(1); #endif + // Memory Allocators // - Those functions are not reliant on the current context. // - DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() @@ -990,6 +1160,7 @@ namespace ImGui IMGUI_API void GetAllocatorFunctions(ImGuiMemAllocFunc* p_alloc_func, ImGuiMemFreeFunc* p_free_func, void** p_user_data); IMGUI_API void* MemAlloc(size_t size); IMGUI_API void MemFree(void* ptr); + // (Optional) Platform/OS interface for multi-viewport support // Read comments around the ImGuiPlatformIO structure for more details. // Note: You may use GetWindowViewport() to get the current viewport of the current window. @@ -998,10 +1169,13 @@ namespace ImGui IMGUI_API void DestroyPlatformWindows(); // call DestroyWindow platform functions for all viewports. call from backend Shutdown() if you need to close platform windows before imgui shutdown. otherwise will be called by DestroyContext(). IMGUI_API ImGuiViewport* FindViewportByID(ImGuiID id); // this is a helper for backends. IMGUI_API ImGuiViewport* FindViewportByPlatformHandle(void* platform_handle); // this is a helper for backends. the type platform_handle is decided by the backend (e.g. HWND, MyWindow*, GLFWwindow* etc.) + } // namespace ImGui + //----------------------------------------------------------------------------- // [SECTION] Flags & Enumerations //----------------------------------------------------------------------------- + // Flags for ImGui::Begin() // (Those are per-window flags. There are shared flags in ImGuiIO: io.ConfigWindowsResizeFromEdges and io.ConfigWindowsMoveFromTitleBarOnly) enum ImGuiWindowFlags_ @@ -1030,6 +1204,7 @@ enum ImGuiWindowFlags_ ImGuiWindowFlags_NoNav = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, ImGuiWindowFlags_NoDecoration = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse, ImGuiWindowFlags_NoInputs = ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, + // [Internal] ImGuiWindowFlags_DockNodeHost = 1 << 23, // Don't use! For internal use by Begin()/NewFrame() ImGuiWindowFlags_ChildWindow = 1 << 24, // Don't use! For internal use by BeginChild() @@ -1037,12 +1212,14 @@ enum ImGuiWindowFlags_ ImGuiWindowFlags_Popup = 1 << 26, // Don't use! For internal use by BeginPopup() ImGuiWindowFlags_Modal = 1 << 27, // Don't use! For internal use by BeginPopupModal() ImGuiWindowFlags_ChildMenu = 1 << 28, // Don't use! For internal use by BeginMenu() + // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiWindowFlags_NavFlattened = 1 << 29, // Obsoleted in 1.90.9: Use ImGuiChildFlags_NavFlattened in BeginChild() call. ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 30, // Obsoleted in 1.90.0: Use ImGuiChildFlags_AlwaysUseWindowPadding in BeginChild() call. #endif }; + // Flags for ImGui::BeginChild() // (Legacy: bit 0 must always correspond to ImGuiChildFlags_Borders to be backward compatible with old API using 'bool border = false'. // About using AutoResizeX/AutoResizeY flags: @@ -1064,11 +1241,13 @@ enum ImGuiChildFlags_ ImGuiChildFlags_AlwaysAutoResize = 1 << 6, // Combined with AutoResizeX/AutoResizeY. Always measure size even when child is hidden, always return true, always disable clipping optimization! NOT RECOMMENDED. ImGuiChildFlags_FrameStyle = 1 << 7, // Style the child window like a framed item: use FrameBg, FrameRounding, FrameBorderSize, FramePadding instead of ChildBg, ChildRounding, ChildBorderSize, WindowPadding. ImGuiChildFlags_NavFlattened = 1 << 8, // [BETA] Share focus scope, allow keyboard/gamepad navigation to cross over parent border to this child or between sibling child windows. + // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiChildFlags_Border = ImGuiChildFlags_Borders, // Renamed in 1.91.1 (August 2024) for consistency. #endif }; + // Flags for ImGui::PushItemFlag() // (Those are shared by all items) enum ImGuiItemFlags_ @@ -1081,6 +1260,7 @@ enum ImGuiItemFlags_ ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window. ImGuiItemFlags_AllowDuplicateId = 1 << 5, // false // Allow submitting an item with the same identifier as an item already submitted this frame without triggering a warning tooltip if io.ConfigDebugHighlightIdConflicts is set. }; + // Flags for ImGui::InputText() // (Those are per-item flags. There are shared flags in ImGuiIO: io.ConfigInputTextCursorBlink and io.ConfigInputTextEnterKeepActive) enum ImGuiInputTextFlags_ @@ -1092,11 +1272,13 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_CharsScientific = 1 << 2, // Allow 0123456789.+-*/eE (Scientific notation input) ImGuiInputTextFlags_CharsUppercase = 1 << 3, // Turn a..z into A..Z ImGuiInputTextFlags_CharsNoBlank = 1 << 4, // Filter out spaces, tabs + // Inputs ImGuiInputTextFlags_AllowTabInput = 1 << 5, // Pressing TAB input a '\t' character into the text field ImGuiInputTextFlags_EnterReturnsTrue = 1 << 6, // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider using IsItemDeactivatedAfterEdit() instead! ImGuiInputTextFlags_EscapeClearsAll = 1 << 7, // Escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 8, // In multi-line mode, validate with Enter, add new line with Ctrl+Enter (default is opposite: validate with Ctrl+Enter, add line with Enter). + // Other options ImGuiInputTextFlags_ReadOnly = 1 << 9, // Read-only mode ImGuiInputTextFlags_Password = 1 << 10, // Password mode, display all characters as '*', disable copy @@ -1106,8 +1288,10 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_DisplayEmptyRefVal = 1 << 14, // InputFloat(), InputInt(), InputScalar() etc. only: when value is zero, do not display it. Generally used with ImGuiInputTextFlags_ParseEmptyRefVal. ImGuiInputTextFlags_NoHorizontalScroll = 1 << 15, // Disable following the cursor horizontally ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). + // Elide display / Alignment ImGuiInputTextFlags_ElideLeft = 1 << 17, // When text doesn't fit, elide left side to ensure right side stays visible. Useful for path/filenames. Single-line only! + // Callback features ImGuiInputTextFlags_CallbackCompletion = 1 << 18, // Callback on pressing TAB (for completion handling) ImGuiInputTextFlags_CallbackHistory = 1 << 19, // Callback on pressing Up/Down arrows (for history handling) @@ -1115,9 +1299,11 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_CallbackCharFilter = 1 << 21, // Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. ImGuiInputTextFlags_CallbackResize = 1 << 22, // Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) ImGuiInputTextFlags_CallbackEdit = 1 << 23, // Callback on any edit. Note that InputText() already returns true on edit + you can always use IsItemEdited(). The callback is useful to manipulate the underlying buffer while focus is active. + // Obsolete names //ImGuiInputTextFlags_AlwaysInsertMode = ImGuiInputTextFlags_AlwaysOverwrite // [renamed in 1.82] name was not matching behavior }; + // Flags for ImGui::TreeNodeEx(), ImGui::CollapsingHeader*() enum ImGuiTreeNodeFlags_ { @@ -1139,13 +1325,22 @@ enum ImGuiTreeNodeFlags_ ImGuiTreeNodeFlags_SpanAllColumns = 1 << 14, // Frame will span all columns of its container table (label will still fit in current column) ImGuiTreeNodeFlags_LabelSpanAllColumns = 1 << 15, // Label will span all columns of its container table //ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 16, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible - ImGuiTreeNodeFlags_NavLeftJumpsBackHere = 1 << 17, // (WIP) Nav: left direction may move to this TreeNode() from any of its child (items submitted between TreeNode and TreePop) + ImGuiTreeNodeFlags_NavLeftJumpsToParent = 1 << 17, // Nav: left arrow moves back to parent. This is processed in TreePop() when there's an unfullfilled Left nav request remaining. ImGuiTreeNodeFlags_CollapsingHeader = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_NoAutoOpenOnLog, + + // [EXPERIMENTAL] Draw lines connecting TreeNode hierarchy. Discuss in GitHub issue #2920. + // Default value is pulled from style.TreeLinesFlags. May be overridden in TreeNode calls. + ImGuiTreeNodeFlags_DrawLinesNone = 1 << 18, // No lines drawn + ImGuiTreeNodeFlags_DrawLinesFull = 1 << 19, // Horizontal lines to child nodes. Vertical line drawn down to TreePop() position: cover full contents. Faster (for large trees). + ImGuiTreeNodeFlags_DrawLinesToNodes = 1 << 20, // Horizontal lines to child nodes. Vertical line drawn down to bottom-most child node. Slower (for large trees). + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - ImGuiTreeNodeFlags_AllowItemOverlap = ImGuiTreeNodeFlags_AllowOverlap, // Renamed in 1.89.7 - ImGuiTreeNodeFlags_SpanTextWidth = ImGuiTreeNodeFlags_SpanLabelWidth,// Renamed in 1.90.7 + ImGuiTreeNodeFlags_NavLeftJumpsBackHere = ImGuiTreeNodeFlags_NavLeftJumpsToParent, // Renamed in 1.92.0 + ImGuiTreeNodeFlags_SpanTextWidth = ImGuiTreeNodeFlags_SpanLabelWidth, // Renamed in 1.90.7 + ImGuiTreeNodeFlags_AllowItemOverlap = ImGuiTreeNodeFlags_AllowOverlap, // Renamed in 1.89.7 #endif }; + // Flags for OpenPopup*(), BeginPopupContext*(), IsPopupOpen() functions. // - To be backward compatible with older API which took an 'int mouse_button = 1' argument instead of 'ImGuiPopupFlags flags', // we need to treat small flags values as a mouse button index, so we encode the mouse button in the first few bits of the flags. @@ -1170,6 +1365,7 @@ enum ImGuiPopupFlags_ ImGuiPopupFlags_AnyPopupLevel = 1 << 11, // For IsPopupOpen(): search/test at any level of the popup stack (default test in the current level) ImGuiPopupFlags_AnyPopup = ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel, }; + // Flags for ImGui::Selectable() enum ImGuiSelectableFlags_ { @@ -1180,11 +1376,13 @@ enum ImGuiSelectableFlags_ ImGuiSelectableFlags_Disabled = 1 << 3, // Cannot be selected, display grayed out text ImGuiSelectableFlags_AllowOverlap = 1 << 4, // (WIP) Hit testing to allow subsequent widgets to overlap this one ImGuiSelectableFlags_Highlight = 1 << 5, // Make the item be displayed as if it is hovered + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiSelectableFlags_DontClosePopups = ImGuiSelectableFlags_NoAutoClosePopups, // Renamed in 1.91.0 ImGuiSelectableFlags_AllowItemOverlap = ImGuiSelectableFlags_AllowOverlap, // Renamed in 1.89.7 #endif }; + // Flags for ImGui::BeginCombo() enum ImGuiComboFlags_ { @@ -1199,6 +1397,7 @@ enum ImGuiComboFlags_ ImGuiComboFlags_WidthFitPreview = 1 << 7, // Width dynamically calculated from preview contents ImGuiComboFlags_HeightMask_ = ImGuiComboFlags_HeightSmall | ImGuiComboFlags_HeightRegular | ImGuiComboFlags_HeightLarge | ImGuiComboFlags_HeightLargest, }; + // Flags for ImGui::BeginTabBar() enum ImGuiTabBarFlags_ { @@ -1210,11 +1409,19 @@ enum ImGuiTabBarFlags_ ImGuiTabBarFlags_NoTabListScrollingButtons = 1 << 4, // Disable scrolling buttons (apply when fitting policy is ImGuiTabBarFlags_FittingPolicyScroll) ImGuiTabBarFlags_NoTooltip = 1 << 5, // Disable tooltips when hovering a tab ImGuiTabBarFlags_DrawSelectedOverline = 1 << 6, // Draw selected overline markers over selected tab - ImGuiTabBarFlags_FittingPolicyResizeDown = 1 << 7, // Resize tabs when they don't fit - ImGuiTabBarFlags_FittingPolicyScroll = 1 << 8, // Add scroll buttons when tabs don't fit - ImGuiTabBarFlags_FittingPolicyMask_ = ImGuiTabBarFlags_FittingPolicyResizeDown | ImGuiTabBarFlags_FittingPolicyScroll, - ImGuiTabBarFlags_FittingPolicyDefault_ = ImGuiTabBarFlags_FittingPolicyResizeDown, + + // Fitting/Resize policy + ImGuiTabBarFlags_FittingPolicyMixed = 1 << 7, // Shrink down tabs when they don't fit, until width is style.TabMinWidthShrink, then enable scrolling buttons. + ImGuiTabBarFlags_FittingPolicyShrink = 1 << 8, // Shrink down tabs when they don't fit + ImGuiTabBarFlags_FittingPolicyScroll = 1 << 9, // Enable scrolling buttons when tabs don't fit + ImGuiTabBarFlags_FittingPolicyMask_ = ImGuiTabBarFlags_FittingPolicyMixed | ImGuiTabBarFlags_FittingPolicyShrink | ImGuiTabBarFlags_FittingPolicyScroll, + ImGuiTabBarFlags_FittingPolicyDefault_ = ImGuiTabBarFlags_FittingPolicyMixed, + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiTabBarFlags_FittingPolicyResizeDown = ImGuiTabBarFlags_FittingPolicyShrink, // Renamed in 1.92.2 +#endif }; + // Flags for ImGui::BeginTabItem() enum ImGuiTabItemFlags_ { @@ -1229,6 +1436,7 @@ enum ImGuiTabItemFlags_ ImGuiTabItemFlags_Trailing = 1 << 7, // Enforce the tab position to the right of the tab bar (before the scrolling buttons) ImGuiTabItemFlags_NoAssumedClosure = 1 << 8, // Tab is selected when trying to close + closure is not immediately assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar. }; + // Flags for ImGui::IsWindowFocused() enum ImGuiFocusedFlags_ { @@ -1240,6 +1448,7 @@ enum ImGuiFocusedFlags_ ImGuiFocusedFlags_DockHierarchy = 1 << 4, // Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow) ImGuiFocusedFlags_RootAndChildWindows = ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows, }; + // Flags for ImGui::IsItemHovered(), ImGui::IsWindowHovered() // Note: if you are trying to check whether your mouse should be dispatched to Dear ImGui or to your app, you should use 'io.WantCaptureMouse' instead! Please read the FAQ! // Note: windows with the ImGuiWindowFlags_NoInputs flag are ignored by IsWindowHovered() calls. @@ -1261,6 +1470,7 @@ enum ImGuiHoveredFlags_ ImGuiHoveredFlags_AllowWhenOverlapped = ImGuiHoveredFlags_AllowWhenOverlappedByItem | ImGuiHoveredFlags_AllowWhenOverlappedByWindow, ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped, ImGuiHoveredFlags_RootAndChildWindows = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows, + // Tooltips mode // - typically used in IsItemHovered() + SetTooltip() sequence. // - this is a shortcut to pull flags from 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav' where you can reconfigure desired behavior. @@ -1268,6 +1478,7 @@ enum ImGuiHoveredFlags_ // - for frequently actioned or hovered items providing a tooltip, you want may to use ImGuiHoveredFlags_ForTooltip (stationary + delay) so the tooltip doesn't show too often. // - for items which main purpose is to be hovered, or items with low affordance, or in less consistent apps, prefer no delay or shorter delay. ImGuiHoveredFlags_ForTooltip = 1 << 12, // Shortcut for standard flags when using IsItemHovered() + SetTooltip() sequence. + // (Advanced) Mouse Hovering delays. // - generally you can use ImGuiHoveredFlags_ForTooltip to use application-standardized flags. // - use those if you need specific overrides. @@ -1277,6 +1488,7 @@ enum ImGuiHoveredFlags_ ImGuiHoveredFlags_DelayNormal = 1 << 16, // IsItemHovered() only: Return true after style.HoverDelayNormal elapsed (~0.40 sec) (shared between items) + requires mouse to be stationary for style.HoverStationaryDelay (once per item). ImGuiHoveredFlags_NoSharedDelay = 1 << 17, // IsItemHovered() only: Disable shared delay system where moving from one item to the next keeps the previous timer for a short time (standard for tooltips with long delays) }; + // Flags for ImGui::DockSpace(), shared/inherited by child nodes. // (Some flags can be applied to individual nodes directly) // FIXME-DOCK: Also see ImGuiDockNodeFlagsPrivate_ which may involve using the WIP and internal DockBuilder api. @@ -1291,11 +1503,13 @@ enum ImGuiDockNodeFlags_ ImGuiDockNodeFlags_NoResize = 1 << 5, // Saved // Disable resizing node using the splitter/separators. Useful with programmatically setup dockspaces. ImGuiDockNodeFlags_AutoHideTabBar = 1 << 6, // // Tab bar will automatically hide when there is a single window in the dock node. ImGuiDockNodeFlags_NoUndocking = 1 << 7, // // Disable undocking this node. + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiDockNodeFlags_NoSplit = ImGuiDockNodeFlags_NoDockingSplit, // Renamed in 1.90 ImGuiDockNodeFlags_NoDockingInCentralNode = ImGuiDockNodeFlags_NoDockingOverCentralNode, // Renamed in 1.90 #endif }; + // Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload() enum ImGuiDragDropFlags_ { @@ -1314,13 +1528,16 @@ enum ImGuiDragDropFlags_ ImGuiDragDropFlags_AcceptNoDrawDefaultRect = 1 << 11, // Do not draw the default highlight rectangle when hovering over target. ImGuiDragDropFlags_AcceptNoPreviewTooltip = 1 << 12, // Request hiding the BeginDragDropSource tooltip from the BeginDragDropTarget site. ImGuiDragDropFlags_AcceptPeekOnly = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect, // For peeking ahead and inspecting the payload before delivery. + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiDragDropFlags_SourceAutoExpirePayload = ImGuiDragDropFlags_PayloadAutoExpire, // Renamed in 1.90.9 #endif }; + // Standard Drag and Drop payload types. You can define you own payload types using short strings. Types starting with '_' are defined by Dear ImGui. #define IMGUI_PAYLOAD_TYPE_COLOR_3F "_COL3F" // float[3]: Standard type for colors, without alpha. User code may use this type. #define IMGUI_PAYLOAD_TYPE_COLOR_4F "_COL4F" // float[4]: Standard type for colors. User code may use this type. + // A primary data type enum ImGuiDataType_ { @@ -1338,6 +1555,7 @@ enum ImGuiDataType_ ImGuiDataType_String, // char* (provided for user convenience, not supported by scalar widgets) ImGuiDataType_COUNT }; + // A cardinal direction enum ImGuiDir : int { @@ -1348,6 +1566,7 @@ enum ImGuiDir : int ImGuiDir_Down = 3, ImGuiDir_COUNT }; + // A sorting direction enum ImGuiSortDirection : ImU8 { @@ -1355,6 +1574,7 @@ enum ImGuiSortDirection : ImU8 ImGuiSortDirection_Ascending = 1, // Ascending = 0->9, A->Z etc. ImGuiSortDirection_Descending = 2 // Descending = 9->0, Z->A etc. }; + // A key identifier (ImGuiKey_XXX or ImGuiMod_XXX value): can represent Keyboard, Mouse and Gamepad values. // All our named keys are >= 512. Keys value 0 to 511 are left unused and were legacy native/opaque key values (< 1.87). // Support for legacy keys was completely removed in 1.91.5. @@ -1366,6 +1586,7 @@ enum ImGuiKey : int // Keyboard ImGuiKey_None = 0, ImGuiKey_NamedKey_BEGIN = 512, // First valid key value (other than 0) + ImGuiKey_Tab = 512, // == ImGuiKey_NamedKey_BEGIN ImGuiKey_LeftArrow, ImGuiKey_RightArrow, @@ -1381,7 +1602,7 @@ enum ImGuiKey : int ImGuiKey_Space, ImGuiKey_Enter, ImGuiKey_Escape, - ImGuiKey_LeftCtrl, ImGuiKey_LeftShift, ImGuiKey_LeftAlt, ImGuiKey_LeftSuper, + ImGuiKey_LeftCtrl, ImGuiKey_LeftShift, ImGuiKey_LeftAlt, ImGuiKey_LeftSuper, // Also see ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiMod_Alt, ImGuiMod_Super below! ImGuiKey_RightCtrl, ImGuiKey_RightShift, ImGuiKey_RightAlt, ImGuiKey_RightSuper, ImGuiKey_Menu, ImGuiKey_0, ImGuiKey_1, ImGuiKey_2, ImGuiKey_3, ImGuiKey_4, ImGuiKey_5, ImGuiKey_6, ImGuiKey_7, ImGuiKey_8, ImGuiKey_9, @@ -1420,41 +1641,51 @@ enum ImGuiKey : int ImGuiKey_AppBack, // Available on some keyboard/mouses. Often referred as "Browser Back" ImGuiKey_AppForward, ImGuiKey_Oem102, // Non-US backslash. - // Gamepad (some of those are analog values, 0.0f to 1.0f) // NAVIGATION ACTION + + // Gamepad + // (analog values are 0.0f to 1.0f) // (download controller mapping PNG/PSD at http://dearimgui.com/controls_sheets) - ImGuiKey_GamepadStart, // Menu (Xbox) + (Switch) Start/Options (PS) - ImGuiKey_GamepadBack, // View (Xbox) - (Switch) Share (PS) - ImGuiKey_GamepadFaceLeft, // X (Xbox) Y (Switch) Square (PS) // Tap: Toggle Menu. Hold: Windowing mode (Focus/Move/Resize windows) - ImGuiKey_GamepadFaceRight, // B (Xbox) A (Switch) Circle (PS) // Cancel / Close / Exit - ImGuiKey_GamepadFaceUp, // Y (Xbox) X (Switch) Triangle (PS) // Text Input / On-screen Keyboard - ImGuiKey_GamepadFaceDown, // A (Xbox) B (Switch) Cross (PS) // Activate / Open / Toggle / Tweak - ImGuiKey_GamepadDpadLeft, // D-pad Left // Move / Tweak / Resize Window (in Windowing mode) - ImGuiKey_GamepadDpadRight, // D-pad Right // Move / Tweak / Resize Window (in Windowing mode) - ImGuiKey_GamepadDpadUp, // D-pad Up // Move / Tweak / Resize Window (in Windowing mode) - ImGuiKey_GamepadDpadDown, // D-pad Down // Move / Tweak / Resize Window (in Windowing mode) - ImGuiKey_GamepadL1, // L Bumper (Xbox) L (Switch) L1 (PS) // Tweak Slower / Focus Previous (in Windowing mode) - ImGuiKey_GamepadR1, // R Bumper (Xbox) R (Switch) R1 (PS) // Tweak Faster / Focus Next (in Windowing mode) - ImGuiKey_GamepadL2, // L Trig. (Xbox) ZL (Switch) L2 (PS) [Analog] - ImGuiKey_GamepadR2, // R Trig. (Xbox) ZR (Switch) R2 (PS) [Analog] - ImGuiKey_GamepadL3, // L Stick (Xbox) L3 (Switch) L3 (PS) - ImGuiKey_GamepadR3, // R Stick (Xbox) R3 (Switch) R3 (PS) - ImGuiKey_GamepadLStickLeft, // [Analog] // Move Window (in Windowing mode) - ImGuiKey_GamepadLStickRight, // [Analog] // Move Window (in Windowing mode) - ImGuiKey_GamepadLStickUp, // [Analog] // Move Window (in Windowing mode) - ImGuiKey_GamepadLStickDown, // [Analog] // Move Window (in Windowing mode) - ImGuiKey_GamepadRStickLeft, // [Analog] - ImGuiKey_GamepadRStickRight, // [Analog] - ImGuiKey_GamepadRStickUp, // [Analog] - ImGuiKey_GamepadRStickDown, // [Analog] + // // XBOX | SWITCH | PLAYSTA. | -> ACTION + ImGuiKey_GamepadStart, // Menu | + | Options | + ImGuiKey_GamepadBack, // View | - | Share | + ImGuiKey_GamepadFaceLeft, // X | Y | Square | Tap: Toggle Menu. Hold: Windowing mode (Focus/Move/Resize windows) + ImGuiKey_GamepadFaceRight, // B | A | Circle | Cancel / Close / Exit + ImGuiKey_GamepadFaceUp, // Y | X | Triangle | Text Input / On-screen Keyboard + ImGuiKey_GamepadFaceDown, // A | B | Cross | Activate / Open / Toggle / Tweak + ImGuiKey_GamepadDpadLeft, // D-pad Left | " | " | Move / Tweak / Resize Window (in Windowing mode) + ImGuiKey_GamepadDpadRight, // D-pad Right | " | " | Move / Tweak / Resize Window (in Windowing mode) + ImGuiKey_GamepadDpadUp, // D-pad Up | " | " | Move / Tweak / Resize Window (in Windowing mode) + ImGuiKey_GamepadDpadDown, // D-pad Down | " | " | Move / Tweak / Resize Window (in Windowing mode) + ImGuiKey_GamepadL1, // L Bumper | L | L1 | Tweak Slower / Focus Previous (in Windowing mode) + ImGuiKey_GamepadR1, // R Bumper | R | R1 | Tweak Faster / Focus Next (in Windowing mode) + ImGuiKey_GamepadL2, // L Trigger | ZL | L2 | [Analog] + ImGuiKey_GamepadR2, // R Trigger | ZR | R2 | [Analog] + ImGuiKey_GamepadL3, // L Stick | L3 | L3 | + ImGuiKey_GamepadR3, // R Stick | R3 | R3 | + ImGuiKey_GamepadLStickLeft, // | | | [Analog] Move Window (in Windowing mode) + ImGuiKey_GamepadLStickRight, // | | | [Analog] Move Window (in Windowing mode) + ImGuiKey_GamepadLStickUp, // | | | [Analog] Move Window (in Windowing mode) + ImGuiKey_GamepadLStickDown, // | | | [Analog] Move Window (in Windowing mode) + ImGuiKey_GamepadRStickLeft, // | | | [Analog] + ImGuiKey_GamepadRStickRight, // | | | [Analog] + ImGuiKey_GamepadRStickUp, // | | | [Analog] + ImGuiKey_GamepadRStickDown, // | | | [Analog] + // Aliases: Mouse Buttons (auto-submitted from AddMouseButtonEvent() calls) // - This is mirroring the data also written to io.MouseDown[], io.MouseWheel, in a format allowing them to be accessed via standard key API. ImGuiKey_MouseLeft, ImGuiKey_MouseRight, ImGuiKey_MouseMiddle, ImGuiKey_MouseX1, ImGuiKey_MouseX2, ImGuiKey_MouseWheelX, ImGuiKey_MouseWheelY, + // [Internal] Reserved for mod storage ImGuiKey_ReservedForModCtrl, ImGuiKey_ReservedForModShift, ImGuiKey_ReservedForModAlt, ImGuiKey_ReservedForModSuper, + + // [Internal] If you need to iterate all keys (for e.g. an input mapper) you may use ImGuiKey_NamedKey_BEGIN..ImGuiKey_NamedKey_END. ImGuiKey_NamedKey_END, + ImGuiKey_NamedKey_COUNT = ImGuiKey_NamedKey_END - ImGuiKey_NamedKey_BEGIN, + // Keyboard Modifiers (explicitly submitted by backend via AddKeyEvent() calls) - // - This is mirroring the data also written to io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper, in a format allowing - // them to be accessed via standard key API, allowing calls such as IsKeyPressed(), IsKeyReleased(), querying duration etc. + // - Any functions taking a ImGuiKeyChord parameter can binary-or those with regular keys, e.g. Shortcut(ImGuiMod_Ctrl | ImGuiKey_S). + // - Those are written back into io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper for convenience, + // but may be accessed via standard key API such as IsKeyPressed(), IsKeyReleased(), querying duration etc. // - Code polling every key (e.g. an interface to detect a key press for input mapping) might want to ignore those // and prefer using the real keys (e.g. ImGuiKey_LeftCtrl, ImGuiKey_RightCtrl instead of ImGuiMod_Ctrl). // - In theory the value of keyboard modifiers should be roughly equivalent to a logical or of the equivalent left/right keys. @@ -1467,10 +1698,7 @@ enum ImGuiKey : int ImGuiMod_Alt = 1 << 14, // Option/Menu ImGuiMod_Super = 1 << 15, // Windows/Super (non-macOS), Ctrl (macOS) ImGuiMod_Mask_ = 0xF000, // 4-bits - // [Internal] If you need to iterate all keys (for e.g. an input mapper) you may use ImGuiKey_NamedKey_BEGIN..ImGuiKey_NamedKey_END. - ImGuiKey_NamedKey_COUNT = ImGuiKey_NamedKey_END - ImGuiKey_NamedKey_BEGIN, - //ImGuiKey_KeysData_SIZE = ImGuiKey_NamedKey_COUNT, // Size of KeysData[]: only hold named keys - //ImGuiKey_KeysData_OFFSET = ImGuiKey_NamedKey_BEGIN, // Accesses to io.KeysData[] must use (key - ImGuiKey_NamedKey_BEGIN) index. + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiKey_COUNT = ImGuiKey_NamedKey_END, // Obsoleted in 1.91.5 because it was extremely misleading (since named keys don't start at 0 anymore) ImGuiMod_Shortcut = ImGuiMod_Ctrl, // Removed in 1.90.7, you can now simply use ImGuiMod_Ctrl @@ -1478,6 +1706,7 @@ enum ImGuiKey : int //ImGuiKey_KeyPadEnter = ImGuiKey_KeypadEnter, // Renamed in 1.87 #endif }; + // Flags for Shortcut(), SetNextItemShortcut(), // (and for upcoming extended versions of IsKeyPressed(), IsMouseClicked(), Shortcut(), SetKeyOwner(), SetItemKeyOwner() that are still in imgui_internal.h) // Don't mistake with ImGuiInputTextFlags! (which is for ImGui::InputText() function) @@ -1485,6 +1714,7 @@ enum ImGuiInputFlags_ { ImGuiInputFlags_None = 0, ImGuiInputFlags_Repeat = 1 << 0, // Enable repeat. Return true on successive repeats. Default for legacy IsKeyPressed(). NOT Default for legacy IsMouseClicked(). MUST BE == 1. + // Flags for Shortcut(), SetNextItemShortcut() // - Routing policies: RouteGlobal+OverActive >> RouteActive or RouteFocused (if owner is active item) >> RouteGlobal+OverFocused >> RouteFocused (if in focused window stack) >> RouteGlobal. // - Default policy is RouteFocused. Can select only 1 policy among all available. @@ -1497,9 +1727,11 @@ enum ImGuiInputFlags_ ImGuiInputFlags_RouteOverActive = 1 << 15, // Option: global route: higher priority than active item. Unlikely you need to use that: will interfere with every active items, e.g. CTRL+A registered by InputText will be overridden by this. May not be fully honored as user/internal code is likely to always assume they can access keys when active. ImGuiInputFlags_RouteUnlessBgFocused = 1 << 16, // Option: global route: will not be applied if underlying background/void is focused (== no Dear ImGui windows are focused). Useful for overlay applications. ImGuiInputFlags_RouteFromRootWindow = 1 << 17, // Option: route evaluated from the point of view of root window rather than current window. + // Flags for SetNextItemShortcut() ImGuiInputFlags_Tooltip = 1 << 18, // Automatically display a tooltip when hovering item [BETA] Unsure of right api (opt-in/opt-out) }; + // Configuration flags stored in io.ConfigFlags. Set by user/application. enum ImGuiConfigFlags_ { @@ -1509,21 +1741,26 @@ enum ImGuiConfigFlags_ ImGuiConfigFlags_NoMouse = 1 << 4, // Instruct dear imgui to disable mouse inputs and interactions. ImGuiConfigFlags_NoMouseCursorChange = 1 << 5, // Instruct backend to not alter mouse cursor shape and visibility. Use if the backend cursor changes are interfering with yours and you don't want to use SetMouseCursor() to change mouse cursor. You may want to honor requests from imgui by reading GetMouseCursor() yourself instead. ImGuiConfigFlags_NoKeyboard = 1 << 6, // Instruct dear imgui to disable keyboard inputs and interactions. This is done by ignoring keyboard events and clearing existing states. + // [BETA] Docking ImGuiConfigFlags_DockingEnable = 1 << 7, // Docking enable flags. + // [BETA] Viewports // When using viewports it is recommended that your default value for ImGuiCol_WindowBg is opaque (Alpha=1.0) so transition to a viewport won't be noticeable. ImGuiConfigFlags_ViewportsEnable = 1 << 10, // Viewport enable flags (require both ImGuiBackendFlags_PlatformHasViewports + ImGuiBackendFlags_RendererHasViewports set by the respective backends) - ImGuiConfigFlags_DpiEnableScaleViewports= 1 << 14, // [BETA: Don't use] FIXME-DPI: Reposition and resize imgui windows when the DpiScale of a viewport changed (mostly useful for the main viewport hosting other window). Note that resizing the main window itself is up to your application. - ImGuiConfigFlags_DpiEnableScaleFonts = 1 << 15, // [BETA: Don't use] FIXME-DPI: Request bitmap-scaled fonts to match DpiScale. This is a very low-quality workaround. The correct way to handle DPI is _currently_ to replace the atlas and/or fonts in the Platform_OnChangedViewport callback, but this is all early work in progress. + // User storage (to allow your backend/engine to communicate to code that may be shared between multiple projects. Those flags are NOT used by core Dear ImGui) ImGuiConfigFlags_IsSRGB = 1 << 20, // Application is SRGB-aware. ImGuiConfigFlags_IsTouchScreen = 1 << 21, // Application is using a touch screen instead of a mouse. + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiConfigFlags_NavEnableSetMousePos = 1 << 2, // [moved/renamed in 1.91.4] -> use bool io.ConfigNavMoveSetMousePos ImGuiConfigFlags_NavNoCaptureKeyboard = 1 << 3, // [moved/renamed in 1.91.4] -> use bool io.ConfigNavCaptureKeyboard + ImGuiConfigFlags_DpiEnableScaleFonts = 1 << 14, // [moved/renamed in 1.92.0] -> use bool io.ConfigDpiScaleFonts + ImGuiConfigFlags_DpiEnableScaleViewports= 1 << 15, // [moved/renamed in 1.92.0] -> use bool io.ConfigDpiScaleViewports #endif }; + // Backend capabilities flags stored in io.BackendFlags. Set by imgui_impl_xxx or custom backend. enum ImGuiBackendFlags_ { @@ -1532,11 +1769,14 @@ enum ImGuiBackendFlags_ ImGuiBackendFlags_HasMouseCursors = 1 << 1, // Backend Platform supports honoring GetMouseCursor() value to change the OS cursor shape. ImGuiBackendFlags_HasSetMousePos = 1 << 2, // Backend Platform supports io.WantSetMousePos requests to reposition the OS mouse position (only used if io.ConfigNavMoveSetMousePos is set). ImGuiBackendFlags_RendererHasVtxOffset = 1 << 3, // Backend Renderer supports ImDrawCmd::VtxOffset. This enables output of large meshes (64K+ vertices) while still using 16-bit indices. + ImGuiBackendFlags_RendererHasTextures = 1 << 4, // Backend Renderer supports ImTextureData requests to create/update/destroy textures. This enables incremental texture updates and texture reloads. See https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md for instructions on how to upgrade your custom backend. + // [BETA] Viewports ImGuiBackendFlags_PlatformHasViewports = 1 << 10, // Backend Platform supports multiple viewports. ImGuiBackendFlags_HasMouseHoveredViewport=1 << 11, // Backend Platform supports calling io.AddMouseViewportEvent() with the viewport under the mouse. IF POSSIBLE, ignore viewports with the ImGuiViewportFlags_NoInputs flag (Win32 backend, GLFW 3.30+ backend can do this, SDL backend cannot). If this cannot be done, Dear ImGui needs to use a flawed heuristic to find the viewport under. ImGuiBackendFlags_RendererHasViewports = 1 << 12, // Backend Renderer supports multiple viewports. }; + // Enumeration for PushStyleColor() / PopStyleColor() enum ImGuiCol_ { @@ -1573,6 +1813,7 @@ enum ImGuiCol_ ImGuiCol_ResizeGrip, // Resize grip in lower-right and lower-left corners of windows. ImGuiCol_ResizeGripHovered, ImGuiCol_ResizeGripActive, + ImGuiCol_InputTextCursor, // InputText cursor/caret ImGuiCol_TabHovered, // Tab background, when hovered ImGuiCol_Tab, // Tab background, when tab-bar is focused & tab is unselected ImGuiCol_TabSelected, // Tab background, when tab-bar is focused & tab is selected @@ -1592,13 +1833,15 @@ enum ImGuiCol_ ImGuiCol_TableRowBg, // Table row background (even rows) ImGuiCol_TableRowBgAlt, // Table row background (odd rows) ImGuiCol_TextLink, // Hyperlink color - ImGuiCol_TextSelectedBg, + ImGuiCol_TextSelectedBg, // Selected text inside an InputText + ImGuiCol_TreeLines, // Tree node hierarchy outlines when using ImGuiTreeNodeFlags_DrawLines ImGuiCol_DragDropTarget, // Rectangle highlighting a drop target ImGuiCol_NavCursor, // Color of keyboard/gamepad navigation cursor/rectangle, when visible ImGuiCol_NavWindowingHighlight, // Highlight window when using CTRL+TAB ImGuiCol_NavWindowingDimBg, // Darken/colorize entire screen behind the CTRL+TAB window list, when active ImGuiCol_ModalWindowDimBg, // Darken/colorize entire screen behind a modal window, when one is active ImGuiCol_COUNT, + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiCol_TabActive = ImGuiCol_TabSelected, // [renamed in 1.90.9] ImGuiCol_TabUnfocused = ImGuiCol_TabDimmed, // [renamed in 1.90.9] @@ -1606,6 +1849,7 @@ enum ImGuiCol_ ImGuiCol_NavHighlight = ImGuiCol_NavCursor, // [renamed in 1.91.4] #endif }; + // Enumeration for PushStyleVar() / PopStyleVar() to temporarily modify the ImGuiStyle structure. // - The enum only refers to fields of ImGuiStyle which makes sense to be pushed/popped inside UI code. // During initialization or between frames, feel free to just poke into ImGuiStyle directly. @@ -1642,10 +1886,14 @@ enum ImGuiStyleVar_ ImGuiStyleVar_ImageBorderSize, // float ImageBorderSize ImGuiStyleVar_TabRounding, // float TabRounding ImGuiStyleVar_TabBorderSize, // float TabBorderSize + ImGuiStyleVar_TabMinWidthBase, // float TabMinWidthBase + ImGuiStyleVar_TabMinWidthShrink, // float TabMinWidthShrink ImGuiStyleVar_TabBarBorderSize, // float TabBarBorderSize ImGuiStyleVar_TabBarOverlineSize, // float TabBarOverlineSize ImGuiStyleVar_TableAngledHeadersAngle, // float TableAngledHeadersAngle ImGuiStyleVar_TableAngledHeadersTextAlign,// ImVec2 TableAngledHeadersTextAlign + ImGuiStyleVar_TreeLinesSize, // float TreeLinesSize + ImGuiStyleVar_TreeLinesRounding, // float TreeLinesRounding ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign ImGuiStyleVar_SeparatorTextBorderSize, // float SeparatorTextBorderSize @@ -1654,6 +1902,7 @@ enum ImGuiStyleVar_ ImGuiStyleVar_DockingSeparatorSize, // float DockingSeparatorSize ImGuiStyleVar_COUNT }; + // Flags for InvisibleButton() [extended in imgui_internal.h] enum ImGuiButtonFlags_ { @@ -1664,6 +1913,7 @@ enum ImGuiButtonFlags_ ImGuiButtonFlags_MouseButtonMask_ = ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight | ImGuiButtonFlags_MouseButtonMiddle, // [Internal] ImGuiButtonFlags_EnableNav = 1 << 3, // InvisibleButton(): do not disable navigation/tabbing. Otherwise disabled by default. }; + // Flags for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton() enum ImGuiColorEditFlags_ { @@ -1678,6 +1928,7 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_NoSidePreview = 1 << 8, // // ColorPicker: disable bigger color preview on right side of the picker, use small color square preview instead. ImGuiColorEditFlags_NoDragDrop = 1 << 9, // // ColorEdit: disable drag and drop target. ColorButton: disable drag and drop source. ImGuiColorEditFlags_NoBorder = 1 << 10, // // ColorButton: disable border (which is enforced by default) + // Alpha preview // - Prior to 1.91.8 (2025/01/21): alpha was made opaque in the preview by default using old name ImGuiColorEditFlags_AlphaPreview. // - We now display the preview as transparent by default. You can use ImGuiColorEditFlags_AlphaOpaque to use old behavior. @@ -1685,6 +1936,7 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_AlphaOpaque = 1 << 11, // // ColorEdit, ColorPicker, ColorButton: disable alpha in the preview,. Contrary to _NoAlpha it may still be edited when calling ColorEdit4()/ColorPicker4(). For ColorButton() this does the same as _NoAlpha. ImGuiColorEditFlags_AlphaNoBg = 1 << 12, // // ColorEdit, ColorPicker, ColorButton: disable rendering a checkerboard background behind transparent color. ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 13, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half transparent preview. + // User Options (right-click on widget to change some of them). ImGuiColorEditFlags_AlphaBar = 1 << 16, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. ImGuiColorEditFlags_HDR = 1 << 19, // // (WIP) ColorEdit: Currently only disable 0.0f..1.0f limits in RGBA edition (note: you probably want to use ImGuiColorEditFlags_Float flag as well). @@ -1697,21 +1949,25 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_PickerHueWheel = 1 << 26, // [Picker] // ColorPicker: wheel for Hue, triangle for Sat/Value. ImGuiColorEditFlags_InputRGB = 1 << 27, // [Input] // ColorEdit, ColorPicker: input and output data in RGB format. ImGuiColorEditFlags_InputHSV = 1 << 28, // [Input] // ColorEdit, ColorPicker: input and output data in HSV format. + // Defaults Options. You can set application defaults using SetColorEditOptions(). The intent is that you probably don't want to // override them in most of your calls. Let the user choose via the option menu and/or call SetColorEditOptions() once during startup. ImGuiColorEditFlags_DefaultOptions_ = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_PickerHueBar, + // [Internal] Masks ImGuiColorEditFlags_AlphaMask_ = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaOpaque | ImGuiColorEditFlags_AlphaNoBg | ImGuiColorEditFlags_AlphaPreviewHalf, ImGuiColorEditFlags_DisplayMask_ = ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_DisplayHex, ImGuiColorEditFlags_DataTypeMask_ = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_Float, ImGuiColorEditFlags_PickerMask_ = ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_PickerHueBar, ImGuiColorEditFlags_InputMask_ = ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_InputHSV, + // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiColorEditFlags_AlphaPreview = 0, // [Removed in 1.91.8] This is the default now. Will display a checkerboard unless ImGuiColorEditFlags_AlphaNoBg is set. #endif //ImGuiColorEditFlags_RGB = ImGuiColorEditFlags_DisplayRGB, ImGuiColorEditFlags_HSV = ImGuiColorEditFlags_DisplayHSV, ImGuiColorEditFlags_HEX = ImGuiColorEditFlags_DisplayHex // [renamed in 1.69] }; + // Flags for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc. // We use the same sets of flags for DragXXX() and SliderXXX() functions as the features are the same and it makes it easier to swap them. // (Those are per-item flags. There is shared behavior flag too: ImGuiIO: io.ConfigDragClickToInputText) @@ -1728,6 +1984,7 @@ enum ImGuiSliderFlags_ ImGuiSliderFlags_AlwaysClamp = ImGuiSliderFlags_ClampOnInput | ImGuiSliderFlags_ClampZeroRange, ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from the previous API that has got miscast to this enum, and will trigger an assert if needed. }; + // Identify a mouse button. // Those values are guaranteed to be stable and we frequently use 0/1 directly. Named enums provided for convenience. enum ImGuiMouseButton_ @@ -1737,6 +1994,7 @@ enum ImGuiMouseButton_ ImGuiMouseButton_Middle = 2, ImGuiMouseButton_COUNT = 5 }; + // Enumeration for GetMouseCursor() // User code may request backend to display given cursor by calling SetMouseCursor(), which is why we have some cursors that are marked unused here enum ImGuiMouseCursor_ @@ -1755,6 +2013,7 @@ enum ImGuiMouseCursor_ ImGuiMouseCursor_NotAllowed, // When hovering something with disallowed interaction. Usually a crossed circle. ImGuiMouseCursor_COUNT }; + // Enumeration for AddMouseSourceEvent() actual source of Mouse Input data. // Historically we use "Mouse" terminology everywhere to indicate pointer data, e.g. MousePos, IsMousePressed(), io.AddMousePosEvent() // But that "Mouse" data can come from different source which occasionally may be useful for application to know about. @@ -1766,6 +2025,7 @@ enum ImGuiMouseSource : int ImGuiMouseSource_Pen, // Input is coming from a pressure/magnetic pen (often used in conjunction with high-sampling rates). ImGuiMouseSource_COUNT }; + // Enumeration for ImGui::SetNextWindow***(), SetWindow***(), SetNextItem***() functions // Represent a condition. // Important: Treat as a regular enum! Do NOT combine multiple values using binary operators! All the functions above treat 0 as a shortcut to ImGuiCond_Always. @@ -1777,9 +2037,11 @@ enum ImGuiCond_ ImGuiCond_FirstUseEver = 1 << 2, // Set the variable if the object/window has no persistently saved data (no entry in .ini file) ImGuiCond_Appearing = 1 << 3, // Set the variable if the object/window is appearing after being hidden/inactive (or the first time) }; + //----------------------------------------------------------------------------- // [SECTION] Tables API flags and structures (ImGuiTableFlags, ImGuiTableColumnFlags, ImGuiTableRowFlags, ImGuiTableBgTarget, ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs) //----------------------------------------------------------------------------- + // Flags for ImGui::BeginTable() // - Important! Sizing policies have complex and subtle side effects, much more so than you would expect. // Read comments/demos carefully + experiment with live demos to get acquainted with them. @@ -1849,9 +2111,11 @@ enum ImGuiTableFlags_ ImGuiTableFlags_SortTristate = 1 << 27, // Allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0). // Miscellaneous ImGuiTableFlags_HighlightHoveredColumn = 1 << 28, // Highlight column headers when hovered (may evolve into a fuller highlight) + // [Internal] Combinations and masks ImGuiTableFlags_SizingMask_ = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_SizingFixedSame | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_SizingStretchSame, }; + // Flags for ImGui::TableSetupColumn() enum ImGuiTableColumnFlags_ { @@ -1876,23 +2140,27 @@ enum ImGuiTableColumnFlags_ ImGuiTableColumnFlags_IndentEnable = 1 << 16, // Use current Indent value when entering cell (default for column 0). ImGuiTableColumnFlags_IndentDisable = 1 << 17, // Ignore current Indent value when entering cell (default for columns > 0). Indentation changes _within_ the cell will still be honored. ImGuiTableColumnFlags_AngledHeader = 1 << 18, // TableHeadersRow() will submit an angled header row for this column. Note this will add an extra row. + // Output status flags, read-only via TableGetColumnFlags() ImGuiTableColumnFlags_IsEnabled = 1 << 24, // Status: is enabled == not hidden by user/api (referred to as "Hide" in _DefaultHide and _NoHide) flags. ImGuiTableColumnFlags_IsVisible = 1 << 25, // Status: is visible == is enabled AND not clipped by scrolling. ImGuiTableColumnFlags_IsSorted = 1 << 26, // Status: is currently part of the sort specs ImGuiTableColumnFlags_IsHovered = 1 << 27, // Status: is hovered by mouse + // [Internal] Combinations and masks ImGuiTableColumnFlags_WidthMask_ = ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_WidthFixed, ImGuiTableColumnFlags_IndentMask_ = ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_IndentDisable, ImGuiTableColumnFlags_StatusMask_ = ImGuiTableColumnFlags_IsEnabled | ImGuiTableColumnFlags_IsVisible | ImGuiTableColumnFlags_IsSorted | ImGuiTableColumnFlags_IsHovered, ImGuiTableColumnFlags_NoDirectResize_ = 1 << 30, // [Internal] Disable user resizing this column directly (it may however we resized indirectly from its left edge) }; + // Flags for ImGui::TableNextRow() enum ImGuiTableRowFlags_ { ImGuiTableRowFlags_None = 0, ImGuiTableRowFlags_Headers = 1 << 0, // Identify header row (set default background color + width of its contents accounted differently for auto column width) }; + // Enum for ImGui::TableSetBgColor() // Background colors are rendering in 3 layers: // - Layer 0: draw with RowBg0 color if set, otherwise draw with ColumnBg0 if set. @@ -1909,6 +2177,7 @@ enum ImGuiTableBgTarget_ ImGuiTableBgTarget_RowBg1 = 2, // Set row background color 1 (generally used for selection marking) ImGuiTableBgTarget_CellBg = 3, // Set cell background color (top-most color) }; + // Sorting specifications for a table (often handling sort specs for a single column, occasionally more) // Obtained by calling TableGetSortSpecs(). // When 'SpecsDirty == true' you can sort your data. It will be true with sorting specs have changed since last call, or the first time. @@ -1918,8 +2187,10 @@ struct ImGuiTableSortSpecs const ImGuiTableColumnSortSpecs* Specs; // Pointer to sort spec array. int SpecsCount; // Sort spec count. Most often 1. May be > 1 when ImGuiTableFlags_SortMulti is enabled. May be == 0 when ImGuiTableFlags_SortTristate is enabled. bool SpecsDirty; // Set to true when specs have changed since last time! Use this to sort again, then clear the flag. + ImGuiTableSortSpecs() { memset(this, 0, sizeof(*this)); } }; + // Sorting specification for one column of a table (sizeof == 12 bytes) struct ImGuiTableColumnSortSpecs { @@ -1927,24 +2198,30 @@ struct ImGuiTableColumnSortSpecs ImS16 ColumnIndex; // Index of the column ImS16 SortOrder; // Index within parent ImGuiTableSortSpecs (always stored in order starting from 0, tables sorted on a single criteria will always have a 0 here) ImGuiSortDirection SortDirection; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending + ImGuiTableColumnSortSpecs() { memset(this, 0, sizeof(*this)); } }; + //----------------------------------------------------------------------------- // [SECTION] Helpers: Debug log, memory allocations macros, ImVector<> //----------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // Debug Logging into ShowDebugLogWindow(), tty and more. //----------------------------------------------------------------------------- + #ifndef IMGUI_DISABLE_DEBUG_TOOLS #define IMGUI_DEBUG_LOG(...) ImGui::DebugLog(__VA_ARGS__) #else #define IMGUI_DEBUG_LOG(...) ((void)0) #endif + //----------------------------------------------------------------------------- // IM_MALLOC(), IM_FREE(), IM_NEW(), IM_PLACEMENT_NEW(), IM_DELETE() // We call C++ constructor on own allocated memory via the placement "new(ptr) Type()" syntax. // Defining a custom placement new() with a custom parameter allows us to bypass including which on some platforms complains when user has disabled exceptions. //----------------------------------------------------------------------------- + struct ImNewWrapper {}; inline void* operator new(size_t, ImNewWrapper, void* ptr) { return ptr; } inline void operator delete(void*, ImNewWrapper, void*) {} // This is only required so we can use the symmetrical new() @@ -1953,6 +2230,7 @@ inline void operator delete(void*, ImNewWrapper, void*) {} // This is only re #define IM_PLACEMENT_NEW(_PTR) new(ImNewWrapper(), _PTR) #define IM_NEW(_TYPE) new(ImNewWrapper(), ImGui::MemAlloc(sizeof(_TYPE))) _TYPE template void IM_DELETE(T* p) { if (p) { p->~T(); ImGui::MemFree(p); } } + //----------------------------------------------------------------------------- // ImVector<> // Lightweight std::vector<>-like class to avoid dragging dependencies (also, some implementations of STL with debug enabled are absurdly slow, we bypass it so our code runs fast in debug). @@ -1963,6 +2241,7 @@ template void IM_DELETE(T* p) { if (p) { p->~T(); ImGui::MemFree(p // - Important: our implementation does NOT call C++ constructors/destructors, we treat everything as raw data! This is intentional but be extra mindful of that, // Do NOT use this class as a std::vector replacement in your own code! Many of the structures used by dear imgui can be safely initialized by a zero-memset. //----------------------------------------------------------------------------- + IM_MSVC_RUNTIME_CHECKS_OFF template struct ImVector @@ -1970,18 +2249,22 @@ struct ImVector int Size; int Capacity; T* Data; + // Provide standard typedefs but we don't use them ourselves. typedef T value_type; typedef value_type* iterator; typedef const value_type* const_iterator; + // Constructors, destructor inline ImVector() { Size = Capacity = 0; Data = NULL; } inline ImVector(const ImVector& src) { Size = Capacity = 0; Data = NULL; operator=(src); } - inline ImVector& operator=(const ImVector& src) { clear(); resize(src.Size); if (src.Data) memcpy(Data, src.Data, (size_t)Size * sizeof(T)); return *this; } + inline ImVector& operator=(const ImVector& src) { clear(); resize(src.Size); if (Data && src.Data) memcpy(Data, src.Data, (size_t)Size * sizeof(T)); return *this; } inline ~ImVector() { if (Data) IM_FREE(Data); } // Important: does not destruct anything + inline void clear() { if (Data) { Size = Capacity = 0; IM_FREE(Data); Data = NULL; } } // Important: does not destruct anything inline void clear_delete() { for (int n = 0; n < Size; n++) IM_DELETE(Data[n]); clear(); } // Important: never called automatically! always explicit. inline void clear_destruct() { for (int n = 0; n < Size; n++) Data[n].~T(); clear(); } // Important: never called automatically! always explicit. + inline bool empty() const { return Size == 0; } inline int size() const { return Size; } inline int size_in_bytes() const { return Size * (int)sizeof(T); } @@ -1989,6 +2272,7 @@ struct ImVector inline int capacity() const { return Capacity; } inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Data[i]; } inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Data[i]; } + inline T* begin() { return Data; } inline const T* begin() const { return Data; } inline T* end() { return Data + Size; } @@ -1998,12 +2282,14 @@ struct ImVector inline T& back() { IM_ASSERT(Size > 0); return Data[Size - 1]; } inline const T& back() const { IM_ASSERT(Size > 0); return Data[Size - 1]; } inline void swap(ImVector& rhs) { int rhs_size = rhs.Size; rhs.Size = Size; Size = rhs_size; int rhs_cap = rhs.Capacity; rhs.Capacity = Capacity; Capacity = rhs_cap; T* rhs_data = rhs.Data; rhs.Data = Data; Data = rhs_data; } + inline int _grow_capacity(int sz) const { int new_capacity = Capacity ? (Capacity + Capacity / 2) : 8; return new_capacity > sz ? new_capacity : sz; } inline void resize(int new_size) { if (new_size > Capacity) reserve(_grow_capacity(new_size)); Size = new_size; } inline void resize(int new_size, const T& v) { if (new_size > Capacity) reserve(_grow_capacity(new_size)); if (new_size > Size) for (int n = Size; n < new_size; n++) memcpy(&Data[n], &v, sizeof(v)); Size = new_size; } inline void shrink(int new_size) { IM_ASSERT(new_size <= Size); Size = new_size; } // Resize a vector to a smaller size, guaranteed not to cause a reallocation inline void reserve(int new_capacity) { if (new_capacity <= Capacity) return; T* new_data = (T*)IM_ALLOC((size_t)new_capacity * sizeof(T)); if (Data) { memcpy(new_data, Data, (size_t)Size * sizeof(T)); IM_FREE(Data); } Data = new_data; Capacity = new_capacity; } inline void reserve_discard(int new_capacity) { if (new_capacity <= Capacity) return; if (Data) IM_FREE(Data); Data = (T*)IM_ALLOC((size_t)new_capacity * sizeof(T)); Capacity = new_capacity; } + // NB: It is illegal to call push_back/push_front/insert with a reference pointing inside the ImVector data itself! e.g. v.push_back(v[10]) is forbidden. inline void push_back(const T& v) { if (Size == Capacity) reserve(_grow_capacity(Size + 1)); memcpy(&Data[Size], &v, sizeof(v)); Size++; } inline void pop_back() { IM_ASSERT(Size > 0); Size--; } @@ -2021,6 +2307,7 @@ struct ImVector inline int index_from_ptr(const T* it) const { IM_ASSERT(it >= Data && it < Data + Size); const ptrdiff_t off = it - Data; return (int)off; } }; IM_MSVC_RUNTIME_CHECKS_RESTORE + //----------------------------------------------------------------------------- // [SECTION] ImGuiStyle //----------------------------------------------------------------------------- @@ -2028,8 +2315,15 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE // During the frame, use ImGui::PushStyleVar(ImGuiStyleVar_XXXX)/PopStyleVar() to alter the main style values, // and ImGui::PushStyleColor(ImGuiCol_XXX)/PopStyleColor() for colors. //----------------------------------------------------------------------------- + struct ImGuiStyle { + // Font scaling + // - recap: ImGui::GetFontSize() == FontSizeBase * (FontScaleMain * FontScaleDpi * other_scaling_factors) + float FontSizeBase; // Current base font size before external global factors are applied. Use PushFont(NULL, size) to modify. Use ImGui::GetFontSize() to obtain scaled value. + float FontScaleMain; // Main global scale factor. May be set by application once, or exposed to end-user. + float FontScaleDpi; // Additional global scale factor from viewport/monitor contents scale. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor DPI. + float Alpha; // Global alpha applies to everything in Dear ImGui. float DisabledAlpha; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. ImVec2 WindowPadding; // Padding within a window. @@ -2060,12 +2354,17 @@ struct ImGuiStyle float ImageBorderSize; // Thickness of border around Image() calls. float TabRounding; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. float TabBorderSize; // Thickness of border around tabs. + float TabMinWidthBase; // Minimum tab width, to make tabs larger than their contents. TabBar buttons are not affected. + float TabMinWidthShrink; // Minimum tab width after shrinking, when using ImGuiTabBarFlags_FittingPolicyMixed policy. float TabCloseButtonMinWidthSelected; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. float TabCloseButtonMinWidthUnselected; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected. float TabBarBorderSize; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. float TabBarOverlineSize; // Thickness of tab-bar overline, which highlights the selected tab-bar. float TableAngledHeadersAngle; // Angle of angled headers (supported values range from -50.0f degrees to +50.0f degrees). ImVec2 TableAngledHeadersTextAlign;// Alignment of angled headers within the cell + ImGuiTreeNodeFlags TreeLinesFlags; // Default way to draw lines connecting TreeNode hierarchy. ImGuiTreeNodeFlags_DrawLinesNone or ImGuiTreeNodeFlags_DrawLinesFull or ImGuiTreeNodeFlags_DrawLinesToNodes. + float TreeLinesSize; // Thickness of outlines when using ImGuiTreeNodeFlags_DrawLines. + float TreeLinesRounding; // Radius of lines connecting child nodes to the vertical line. ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered). ImVec2 SelectableTextAlign; // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. @@ -2081,8 +2380,10 @@ struct ImGuiStyle bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. float CircleTessellationMaxError; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. + // Colors ImVec4 Colors[ImGuiCol_COUNT]; + // Behaviors // (It is possible to modify those fields mid-frame if specific behavior need it, unlike e.g. configuration fields in ImGuiIO) float HoverStationaryDelay; // Delay for IsItemHovered(ImGuiHoveredFlags_Stationary). Time required to consider mouse stationary. @@ -2090,13 +2391,21 @@ struct ImGuiStyle float HoverDelayNormal; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). " ImGuiHoveredFlags HoverFlagsForTooltipMouse;// Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse. ImGuiHoveredFlags HoverFlagsForTooltipNav; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. - IMGUI_API ImGuiStyle(); - IMGUI_API void ScaleAllSizes(float scale_factor); + + // [Internal] + float _MainScale; // FIXME-WIP: Reference scale, as applied by ScaleAllSizes(). + float _NextFrameFontSizeBase; // FIXME: Temporary hack until we finish remaining work. + + // Functions + IMGUI_API ImGuiStyle(); + IMGUI_API void ScaleAllSizes(float scale_factor); // Scale all spacing/padding/thickness values. Do not scale fonts. + // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // TabMinWidthForCloseButton = TabCloseButtonMinWidthUnselected // Renamed in 1.91.9. #endif }; + //----------------------------------------------------------------------------- // [SECTION] ImGuiIO //----------------------------------------------------------------------------- @@ -2108,6 +2417,7 @@ struct ImGuiStyle //----------------------------------------------------------------------------- // Also see ImGui::GetPlatformIO() and ImGuiPlatformIO struct for OS/platform related functions: clipboard, IME etc. //----------------------------------------------------------------------------- + // [Internal] Storage used by IsKeyDown(), IsKeyPressed() etc functions. // If prior to 1.87 you used io.KeysDownDuration[] (which was marked as internal), you should use GetKeyData(key)->DownDuration and *NOT* io.KeysData[key]->DownDuration. struct ImGuiKeyData @@ -2117,25 +2427,28 @@ struct ImGuiKeyData float DownDurationPrev; // Last frame duration the key has been down float AnalogValue; // 0.0f..1.0f for gamepad values }; + struct ImGuiIO { //------------------------------------------------------------------ // Configuration // Default value //------------------------------------------------------------------ + ImGuiConfigFlags ConfigFlags; // = 0 // See ImGuiConfigFlags_ enum. Set by user/application. Keyboard/Gamepad navigation options, etc. ImGuiBackendFlags BackendFlags; // = 0 // See ImGuiBackendFlags_ enum. Set by backend (imgui_impl_xxx files or custom backend) to communicate features supported by the backend. - ImVec2 DisplaySize; // // Main display size, in pixels (generally == GetMainViewport()->Size). May change every frame. + ImVec2 DisplaySize; // // Main display size, in pixels (== GetMainViewport()->Size). May change every frame. + ImVec2 DisplayFramebufferScale; // = (1, 1) // Main display density. For retina display where window coordinates are different from framebuffer coordinates. This will affect font density + will end up in ImDrawData::FramebufferScale. float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. May change every frame. float IniSavingRate; // = 5.0f // Minimum time between saving positions/sizes to .ini file, in seconds. const char* IniFilename; // = "imgui.ini" // Path to .ini file (important: default "imgui.ini" is relative to current working dir!). Set NULL to disable automatic .ini loading/saving or if you want to manually call LoadIniSettingsXXX() / SaveIniSettingsXXX() functions. const char* LogFilename; // = "imgui_log.txt"// Path to .log file (default parameter to ImGui::LogToFile when no file is specified). void* UserData; // = NULL // Store your own data. + // Font system ImFontAtlas*Fonts; // // Font atlas: load, rasterize and pack one or more fonts into a single texture. - float FontGlobalScale; // = 1.0f // Global scale all fonts - bool FontAllowUserScaling; // = false // [OBSOLETE] Allow user scaling text of individual window with CTRL+Wheel. ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. - ImVec2 DisplayFramebufferScale; // = (1, 1) // For retina display or other situations where window coordinates are different from framebuffer coordinates. This generally ends up in ImDrawData::FramebufferScale. + bool FontAllowUserScaling; // = false // [OBSOLETE] Allow user scaling text of individual window with CTRL+Wheel. + // Keyboard/Gamepad Navigation options bool ConfigNavSwapGamepadButtons; // = false // Swap Activate<>Cancel (A<>B) buttons, matching typical "Nintendo/Japanese style" gamepad layout. bool ConfigNavMoveSetMousePos; // = false // Directional/tabbing navigation teleports the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is difficult. Will update io.MousePos and set io.WantSetMousePos=true. @@ -2144,16 +2457,25 @@ struct ImGuiIO bool ConfigNavEscapeClearFocusWindow;// = false // Pressing Escape can clear focused window as well (super set of io.ConfigNavEscapeClearFocusItem). bool ConfigNavCursorVisibleAuto; // = true // Using directional navigation key makes the cursor visible. Mouse click hides the cursor. bool ConfigNavCursorVisibleAlways; // = false // Navigation cursor is always visible. + // Docking options (when ImGuiConfigFlags_DockingEnable is set) bool ConfigDockingNoSplit; // = false // Simplified docking mode: disable window splitting, so docking is limited to merging multiple windows together into tab-bars. bool ConfigDockingWithShift; // = false // Enable docking with holding Shift key (reduce visual noise, allows dropping in wider space) bool ConfigDockingAlwaysTabBar; // = false // [BETA] [FIXME: This currently creates regression with auto-sizing and general overhead] Make every single floating window display within a docking node. bool ConfigDockingTransparentPayload;// = false // [BETA] Make window or viewport transparent when docking and only display docking boxes on the target viewport. Useful if rendering of multiple viewport cannot be synced. Best used with ConfigViewportsNoAutoMerge. + // Viewport options (when ImGuiConfigFlags_ViewportsEnable is set) bool ConfigViewportsNoAutoMerge; // = false; // Set to make all floating imgui windows always create their own viewport. Otherwise, they are merged into the main host viewports when overlapping it. May also set ImGuiViewportFlags_NoAutoMerge on individual viewport. bool ConfigViewportsNoTaskBarIcon; // = false // Disable default OS task bar icon flag for secondary viewports. When a viewport doesn't want a task bar icon, ImGuiViewportFlags_NoTaskBarIcon will be set on it. bool ConfigViewportsNoDecoration; // = true // Disable default OS window decoration flag for secondary viewports. When a viewport doesn't want window decorations, ImGuiViewportFlags_NoDecoration will be set on it. Enabling decoration can create subsequent issues at OS levels (e.g. minimum window size). bool ConfigViewportsNoDefaultParent; // = false // Disable default OS parenting to main viewport for secondary viewports. By default, viewports are marked with ParentViewportId = , expecting the platform backend to setup a parent/child relationship between the OS windows (some backend may ignore this). Set to true if you want the default to be 0, then all viewports will be top-level OS windows. + bool ConfigViewportPlatformFocusSetsImGuiFocus; //= true // When a platform window is focused (e.g. using Alt+Tab, clicking Platform Title Bar), apply corresponding focus on imgui windows (may clear focus/active id from imgui windows location in other platform windows). In principle this is better enabled but we provide an opt-out, because some Linux window managers tend to eagerly focus windows (e.g. on mouse hover, or even a simple window pos/size change). + + // DPI/Scaling options + // This may keep evolving during 1.92.x releases. Expect some turbulence. + bool ConfigDpiScaleFonts; // = false // [EXPERIMENTAL] Automatically overwrite style.FontScaleDpi when Monitor DPI changes. This will scale fonts but _NOT_ scale sizes/padding for now. + bool ConfigDpiScaleViewports; // = false // [EXPERIMENTAL] Scale Dear ImGui and Platform Windows when Monitor DPI changes. + // Miscellaneous options // (you can visualize and interact with all options in 'Demo->Configuration') bool MouseDrawCursor; // = false // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). Cannot be easily renamed to 'io.ConfigXXX' because this is frequently used by backend implementations. @@ -2167,6 +2489,7 @@ struct ImGuiIO bool ConfigWindowsCopyContentsWithCtrlC; // = false // [EXPERIMENTAL] CTRL+C copy the contents of focused window into the clipboard. Experimental because: (1) has known issues with nested Begin/End pairs (2) text output quality varies (3) text output is in submission order rather than spatial order. bool ConfigScrollbarScrollByPage; // = true // Enable scrolling page by page when clicking outside the scrollbar grab. When disabled, always scroll to clicked location. When enabled, Shift+Click scrolls to clicked location. float ConfigMemoryCompactTimer; // = 60.0f // Timer (in seconds) to free transient windows/tables memory buffers when unused. Set to -1.0f to disable. + // Inputs Behaviors // (other variables, ones which are expected to be tweaked within UI code, are exposed in ImGuiStyle) float MouseDoubleClickTime; // = 0.30f // Time for a double-click, in seconds. @@ -2174,9 +2497,11 @@ struct ImGuiIO float MouseDragThreshold; // = 6.0f // Distance threshold before considering we are dragging. float KeyRepeatDelay; // = 0.275f // When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.). float KeyRepeatRate; // = 0.050f // When holding a key/button, rate at which it repeats, in seconds. + //------------------------------------------------------------------ // Debug options //------------------------------------------------------------------ + // Options to configure Error Handling and how we handle recoverable errors [EXPERIMENTAL] // - Error recovery is provided as a way to facilitate: // - Recovery after a programming error (native code or scripting language - the later tends to facilitate iterating on code while running). @@ -2195,33 +2520,40 @@ struct ImGuiIO bool ConfigErrorRecoveryEnableAssert; // = true // Enable asserts on recoverable error. By default call IM_ASSERT() when returning from a failing IM_ASSERT_USER_ERROR() bool ConfigErrorRecoveryEnableDebugLog; // = true // Enable debug log output on recoverable errors. bool ConfigErrorRecoveryEnableTooltip; // = true // Enable tooltip on recoverable errors. The tooltip include a way to enable asserts if they were disabled. + // Option to enable various debug tools showing buttons that will call the IM_DEBUG_BREAK() macro. // - The Item Picker tool will be available regardless of this being enabled, in order to maximize its discoverability. // - Requires a debugger being attached, otherwise IM_DEBUG_BREAK() options will appear to crash your application. // e.g. io.ConfigDebugIsDebuggerPresent = ::IsDebuggerPresent() on Win32, or refer to ImOsIsDebuggerPresent() imgui_test_engine/imgui_te_utils.cpp for a Unix compatible version). bool ConfigDebugIsDebuggerPresent; // = false // Enable various tools calling IM_DEBUG_BREAK(). + // Tools to detect code submitting items with conflicting/duplicate IDs // - Code should use PushID()/PopID() in loops, or append "##xx" to same-label identifiers. // - Empty label e.g. Button("") == same ID as parent widget/node. Use Button("##xx") instead! // - See FAQ https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-about-the-id-stack-system bool ConfigDebugHighlightIdConflicts;// = true // Highlight and show an error message popup when multiple items have conflicting identifiers. bool ConfigDebugHighlightIdConflictsShowItemPicker;//=true // Show "Item Picker" button in aforementioned popup. + // Tools to test correct Begin/End and BeginChild/EndChild behaviors. // - Presently Begin()/End() and BeginChild()/EndChild() needs to ALWAYS be called in tandem, regardless of return value of BeginXXX() // - This is inconsistent with other BeginXXX functions and create confusion for many users. // - We expect to update the API eventually. In the meanwhile we provide tools to facilitate checking user-code behavior. bool ConfigDebugBeginReturnValueOnce;// = false // First-time calls to Begin()/BeginChild() will return false. NEEDS TO BE SET AT APPLICATION BOOT TIME if you don't want to miss windows. bool ConfigDebugBeginReturnValueLoop;// = false // Some calls to Begin()/BeginChild() will return false. Will cycle through window depths then repeat. Suggested use: add "io.ConfigDebugBeginReturnValue = io.KeyShift" in your main loop then occasionally press SHIFT. Windows should be flickering while running. + // Option to deactivate io.AddFocusEvent(false) handling. // - May facilitate interactions with a debugger when focus loss leads to clearing inputs data. // - Backends may have other side-effects on focus loss, so this will reduce side-effects but not necessary remove all of them. bool ConfigDebugIgnoreFocusLoss; // = false // Ignore io.AddFocusEvent(false), consequently not calling io.ClearInputKeys()/io.ClearInputMouse() in input processing. + // Option to audit .ini data bool ConfigDebugIniSettings; // = false // Save .ini data with extra comments (particularly helpful for Docking, but makes saving slower) + //------------------------------------------------------------------ // Platform Identifiers // (the imgui_impl_xxxx backend files are setting those up for you) //------------------------------------------------------------------ + // Nowadays those would be stored in ImGuiPlatformIO but we are leaving them here for legacy reasons. // Optional: Platform/Renderer backend name (informational only! will be displayed in About Window) + User data for backend/wrappers to store their own stuff. const char* BackendPlatformName; // = NULL @@ -2229,9 +2561,11 @@ struct ImGuiIO void* BackendPlatformUserData; // = NULL // User data for platform backend void* BackendRendererUserData; // = NULL // User data for renderer backend void* BackendLanguageUserData; // = NULL // User data for non C++ programming language backend + //------------------------------------------------------------------ // Input - Call before calling NewFrame() //------------------------------------------------------------------ + // Input Functions IMGUI_API void AddKeyEvent(ImGuiKey key, bool down); // Queue a new key down/up event. Key should be "translated" (as in, generally ImGuiKey_A matches the key end-user would use to emit an 'A' character) IMGUI_API void AddKeyAnalogEvent(ImGuiKey key, bool down, float v); // Queue a new key down/up event for analog values (e.g. ImGuiKey_Gamepad_ values). Dead-zones should be handled by the backend. @@ -2244,6 +2578,7 @@ struct ImGuiIO IMGUI_API void AddInputCharacter(unsigned int c); // Queue a new character input IMGUI_API void AddInputCharacterUTF16(ImWchar16 c); // Queue a new character input from a UTF-16 character, it can be a surrogate IMGUI_API void AddInputCharactersUTF8(const char* str); // Queue a new characters input from a UTF-8 string + IMGUI_API void SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native_scancode, int native_legacy_index = -1); // [Optional] Specify index for legacy <1.87 IsKeyXXX() functions with native indices + specify native keycode, scancode. IMGUI_API void SetAppAcceptingEvents(bool accepting_events); // Set master flag for accepting key/mouse/text events (default to true). Useful if you have native dialog boxes that are interrupting your application loop/refresh, and you want to disable events being queued while your app is frozen. IMGUI_API void ClearEventsQueue(); // Clear all incoming events. @@ -2252,11 +2587,13 @@ struct ImGuiIO #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS IMGUI_API void ClearInputCharacters(); // [Obsoleted in 1.89.8] Clear the current frame text input buffer. Now included within ClearInputKeys(). #endif + //------------------------------------------------------------------ // Output - Updated by NewFrame() or EndFrame()/Render() // (when reading from the io.WantCaptureMouse, io.WantCaptureKeyboard flags to dispatch your inputs, it is // generally easier and more correct to use their state BEFORE calling NewFrame(). See FAQ for details!) //------------------------------------------------------------------ + bool WantCaptureMouse; // Set when Dear ImGui will use mouse inputs, in this case do not dispatch them to your main game/application (either way, always pass on mouse inputs to imgui). (e.g. unclicked mouse is hovering over an imgui window, widget is active, mouse was clicked over an imgui window, etc.). bool WantCaptureKeyboard; // Set when Dear ImGui will use keyboard inputs, in this case do not dispatch them to your main game/application (either way, always pass keyboard inputs to imgui). (e.g. InputText active, or an imgui window is focused and navigation is enabled, etc.). bool WantTextInput; // Mobile/console: when set, you may display an on-screen keyboard. This is set by Dear ImGui when it wants textual keyboard input to happen (e.g. when a InputText widget is active). @@ -2270,10 +2607,13 @@ struct ImGuiIO int MetricsRenderWindows; // Number of visible windows int MetricsActiveWindows; // Number of active windows ImVec2 MouseDelta; // Mouse delta. Note that this is zero if either current or previous position are invalid (-FLT_MAX,-FLT_MAX), so a disappearing/reappearing mouse won't have a huge delta. + //------------------------------------------------------------------ // [Internal] Dear ImGui will maintain those fields. Forward compatibility not guaranteed! //------------------------------------------------------------------ + ImGuiContext* Ctx; // Parent UI context (needs to be set explicitly by parent). + // Main Input State // (this block used to be written by backend, since 1.87 it is best to NOT write to those directly, call the AddXXX functions above instead) // (reading from those variables is fair game, as they are extremely unlikely to be moving anywhere) @@ -2283,13 +2623,14 @@ struct ImGuiIO float MouseWheelH; // Mouse wheel Horizontal. >0 scrolls Left, <0 scrolls Right. Most users don't have a mouse with a horizontal wheel, may not be filled by all backends. ImGuiMouseSource MouseSource; // Mouse actual input peripheral (Mouse/TouchScreen/Pen). ImGuiID MouseHoveredViewport; // (Optional) Modify using io.AddMouseViewportEvent(). With multi-viewports: viewport the OS mouse is hovering. If possible _IGNORING_ viewports with the ImGuiViewportFlags_NoInputs flag is much better (few backends can handle that). Set io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport if you can provide this info. If you don't imgui will infer the value using the rectangles and last focused time of the viewports it knows about (ignoring other OS windows). - bool KeyCtrl; // Keyboard modifier down: Control + bool KeyCtrl; // Keyboard modifier down: Ctrl (non-macOS), Cmd (macOS) bool KeyShift; // Keyboard modifier down: Shift bool KeyAlt; // Keyboard modifier down: Alt - bool KeySuper; // Keyboard modifier down: Cmd/Super/Windows + bool KeySuper; // Keyboard modifier down: Windows/Super (non-macOS), Ctrl (macOS) + // Other state maintained from data above + IO function calls ImGuiKeyChord KeyMods; // Key mods flags (any of ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Alt/ImGuiMod_Super flags, same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper but merged into flags. Read-only, updated by NewFrame() - ImGuiKeyData KeysData[ImGuiKey_NamedKey_COUNT];// Key state for all known keys. Use IsKeyXXX() functions to access this. + ImGuiKeyData KeysData[ImGuiKey_NamedKey_COUNT];// Key state for all known keys. MUST use 'key - ImGuiKey_NamedKey_BEGIN' as index. Use IsKeyXXX() functions to access this. bool WantCaptureMouseUnlessPopupClose; // Alternative to WantCaptureMouse: (WantCaptureMouse == true && WantCaptureMouseUnlessPopupClose == false) when a click over void is expected to close a popup. ImVec2 MousePosPrev; // Previous mouse position (note that MouseDelta is not necessary == MousePos-MousePosPrev, in case either position is invalid) ImVec2 MouseClickedPos[5]; // Position at time of clicking @@ -2313,6 +2654,7 @@ struct ImGuiIO bool AppAcceptingEvents; // Only modify via SetAppAcceptingEvents() ImWchar16 InputQueueSurrogate; // For AddInputCharacterUTF16() ImVector InputQueueCharacters; // Queue of _characters_ input (obtained by platform backend). Fill using AddInputCharacter() helper. + // Legacy: before 1.87, we required backend to fill io.KeyMap[] (imgui->native map) during initialization and io.KeysDown[] (native indices) every frame. // This is still temporarily supported as a legacy feature. However the new preferred scheme is for backend to call io.AddKeyEvent(). // Old (<1.87): ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Space]) --> New (1.87+) ImGui::IsKeyPressed(ImGuiKey_Space) @@ -2322,18 +2664,24 @@ struct ImGuiIO //bool KeysDown[ImGuiKey_COUNT]; // [LEGACY] Input: Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). This used to be [512] sized. It is now ImGuiKey_COUNT to allow legacy io.KeysDown[GetKeyIndex(...)] to work without an overflow. //float NavInputs[ImGuiNavInput_COUNT]; // [LEGACY] Since 1.88, NavInputs[] was removed. Backends from 1.60 to 1.86 won't build. Feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. //void* ImeWindowHandle; // [Obsoleted in 1.87] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor positioning. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + float FontGlobalScale; // Moved io.FontGlobalScale to style.FontScaleMain in 1.92 (June 2025) + // Legacy: before 1.91.1, clipboard functions were stored in ImGuiIO instead of ImGuiPlatformIO. // As this is will affect all users of custom engines/backends, we are providing proper legacy redirection (will obsolete). -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS const char* (*GetClipboardTextFn)(void* user_data); void (*SetClipboardTextFn)(void* user_data, const char* text); void* ClipboardUserData; #endif + IMGUI_API ImGuiIO(); }; + //----------------------------------------------------------------------------- // [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiPayload) //----------------------------------------------------------------------------- + // Shared state of InputText(), passed as an argument to your callback when a ImGuiInputTextFlags_Callback* flag is used. // The callback function should return 0 by default. // Callbacks (follow a flag name and see comments in ImGuiInputTextFlags_ declarations for more details) @@ -2349,6 +2697,7 @@ struct ImGuiInputTextCallbackData ImGuiInputTextFlags EventFlag; // One ImGuiInputTextFlags_Callback* // Read-only ImGuiInputTextFlags Flags; // What user passed to InputText() // Read-only void* UserData; // What user passed to InputText() // Read-only + // Arguments for the different callback events // - During Resize callback, Buf will be same as your input buffer. // - However, during Completion/History/Always callback, Buf always points to our own internal data (it is not the same as your buffer)! Changes to it will be reflected into your own buffer shortly after the callback. @@ -2363,6 +2712,7 @@ struct ImGuiInputTextCallbackData int CursorPos; // // Read-write // [Completion,History,Always] int SelectionStart; // // Read-write // [Completion,History,Always] == to SelectionEnd when no selection) int SelectionEnd; // // Read-write // [Completion,History,Always] + // Helper functions for text manipulation. // Use those function to benefit from the CallbackResize behaviors. Calling those function reset the selection. IMGUI_API ImGuiInputTextCallbackData(); @@ -2372,6 +2722,7 @@ struct ImGuiInputTextCallbackData void ClearSelection() { SelectionStart = SelectionEnd = BufTextLen; } bool HasSelection() const { return SelectionStart != SelectionEnd; } }; + // Resizing callback data to apply custom constraint. As enabled by SetNextWindowSizeConstraints(). Callback is called during the next Begin(). // NB: For basic min/max size constraint on each axis you don't need to use the callback! The SetNextWindowSizeConstraints() parameters are enough. struct ImGuiSizeCallbackData @@ -2381,6 +2732,7 @@ struct ImGuiSizeCallbackData ImVec2 CurrentSize; // Read-only. Current window size. ImVec2 DesiredSize; // Read-write. Desired size, based on user's mouse position. Write to this field to restrain resizing. }; + // [ALPHA] Rarely used / very advanced uses only. Use with SetNextWindowClass() and DockSpace() functions. // Important: the content of this class is still highly WIP and likely to change and be refactored // before we stabilize Docking features. Please be mindful if using this. @@ -2399,14 +2751,17 @@ struct ImGuiWindowClass ImGuiDockNodeFlags DockNodeFlagsOverrideSet; // [EXPERIMENTAL] Dock node flags to set when a window of this class is hosted by a dock node (it doesn't have to be selected!) bool DockingAlwaysTabBar; // Set to true to enforce single floating windows of this class always having their own docking node (equivalent of setting the global io.ConfigDockingAlwaysTabBar) bool DockingAllowUnclassed; // Set to true to allow windows of this class to be docked/merged with an unclassed window. // FIXME-DOCK: Move to DockNodeFlags override? + ImGuiWindowClass() { memset(this, 0, sizeof(*this)); ParentViewportId = (ImGuiID)-1; DockingAllowUnclassed = true; } }; + // Data payload for Drag and Drop operations: AcceptDragDropPayload(), GetDragDropPayload() struct ImGuiPayload { // Members void* Data; // Data (copied and owned by dear imgui) int DataSize; // Data size + // [Internal] ImGuiID SourceId; // Source item id ImGuiID SourceParentId; // Source parent id (if available) @@ -2414,15 +2769,18 @@ struct ImGuiPayload char DataType[32 + 1]; // Data type tag (short user-supplied string, 32 characters max) bool Preview; // Set when AcceptDragDropPayload() was called and mouse has been hovering the target item (nb: handle overlapping drag targets) bool Delivery; // Set when AcceptDragDropPayload() was called and mouse button is released over the target item. + ImGuiPayload() { Clear(); } void Clear() { SourceId = SourceParentId = 0; Data = NULL; DataSize = 0; memset(DataType, 0, sizeof(DataType)); DataFrameCount = -1; Preview = Delivery = false; } bool IsDataType(const char* type) const { return DataFrameCount != -1 && strcmp(type, DataType) == 0; } bool IsPreview() const { return Preview; } bool IsDelivery() const { return Delivery; } }; + //----------------------------------------------------------------------------- // [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) //----------------------------------------------------------------------------- + // Helper: Unicode defines #define IM_UNICODE_CODEPOINT_INVALID 0xFFFD // Invalid Unicode code point (standard value). #ifdef IMGUI_USE_WCHAR32 @@ -2430,6 +2788,7 @@ struct ImGuiPayload #else #define IM_UNICODE_CODEPOINT_MAX 0xFFFF // Maximum Unicode code point supported by this build. #endif + // Helper: Execute a block of code at maximum once a frame. Convenient if you want to quickly create a UI within deep-nested code that runs multiple times every frame. // Usage: static ImGuiOnceUponAFrame oaf; if (oaf) ImGui::Text("This will be called only once per frame"); struct ImGuiOnceUponAFrame @@ -2438,6 +2797,7 @@ struct ImGuiOnceUponAFrame mutable int RefFrame; operator bool() const { int current_frame = ImGui::GetFrameCount(); if (RefFrame == current_frame) return false; RefFrame = current_frame; return true; } }; + // Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" struct ImGuiTextFilter { @@ -2447,11 +2807,13 @@ struct ImGuiTextFilter IMGUI_API void Build(); void Clear() { InputBuf[0] = 0; Build(); } bool IsActive() const { return !Filters.empty(); } + // [Internal] struct ImGuiTextRange { const char* b; const char* e; + ImGuiTextRange() { b = e = NULL; } ImGuiTextRange(const char* _b, const char* _e) { b = _b; e = _e; } bool empty() const { return b == e; } @@ -2461,12 +2823,14 @@ struct ImGuiTextFilter ImVectorFilters; int CountGrep; }; + // Helper: Growable text buffer for logging/accumulating text // (this could be called 'ImGuiTextBuilder' / 'ImGuiStringBuilder') struct ImGuiTextBuffer { ImVector Buf; IMGUI_API static char EmptyString[1]; + ImGuiTextBuffer() { } inline char operator[](int i) const { IM_ASSERT(Buf.Data != NULL); return Buf.Data[i]; } const char* begin() const { return Buf.Data ? &Buf.front() : EmptyString; } @@ -2481,6 +2845,7 @@ struct ImGuiTextBuffer IMGUI_API void appendf(const char* fmt, ...) IM_FMTARGS(2); IMGUI_API void appendfv(const char* fmt, va_list args) IM_FMTLIST(2); }; + // [Internal] Key+Value for ImGuiStorage struct ImGuiStoragePair { @@ -2490,6 +2855,7 @@ struct ImGuiStoragePair ImGuiStoragePair(ImGuiID _key, float _val) { key = _key; val_f = _val; } ImGuiStoragePair(ImGuiID _key, void* _val) { key = _key; val_p = _val; } }; + // Helper: Key->Value storage // Typically you don't have to worry about this since a storage is held within each Window. // We use it to e.g. store collapse state for a tree (Int 0/1) @@ -2502,6 +2868,7 @@ struct ImGuiStorage { // [Internal] ImVector Data; + // - Get***() functions find pair, never add/allocate. Pairs are sorted so a query is O(log N) // - Set***() functions find pair, insertion on demand if missing. // - Sorted insertion is costly, paid once. A typical frame shouldn't need to insert any new pair. @@ -2514,6 +2881,7 @@ struct ImGuiStorage IMGUI_API void SetFloat(ImGuiID key, float val); IMGUI_API void* GetVoidPtr(ImGuiID key) const; // default_val is NULL IMGUI_API void SetVoidPtr(ImGuiID key, void* val); + // - Get***Ref() functions finds pair, insert on demand if missing, return pointer. Useful if you intend to do Get+Set. // - References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer. // - A typical use case where this is convenient for quick hacking (e.g. add storage during a live Edit&Continue session if you can't modify existing struct) @@ -2522,14 +2890,17 @@ struct ImGuiStorage IMGUI_API bool* GetBoolRef(ImGuiID key, bool default_val = false); IMGUI_API float* GetFloatRef(ImGuiID key, float default_val = 0.0f); IMGUI_API void** GetVoidPtrRef(ImGuiID key, void* default_val = NULL); + // Advanced: for quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. IMGUI_API void BuildSortByKey(); // Obsolete: use on your own storage if you know only integer are being stored (open/close all tree nodes) IMGUI_API void SetAllInt(int val); + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS //typedef ::ImGuiStoragePair ImGuiStoragePair; // 1.90.8: moved type outside struct #endif }; + // Helper: Manually clip large list of items. // If you have lots evenly spaced items and you have random access to the list, you can perform coarse // clipping based on visibility to only submit items that are in view. @@ -2557,9 +2928,10 @@ struct ImGuiListClipper int DisplayEnd; // End of items to display (exclusive) int ItemsCount; // [Internal] Number of items float ItemsHeight; // [Internal] Height of item after a first step and item submission can calculate it - float StartPosY; // [Internal] Cursor position at the time of Begin() or after table frozen rows are all processed + double StartPosY; // [Internal] Cursor position at the time of Begin() or after table frozen rows are all processed double StartSeekOffsetY; // [Internal] Account for frozen rows in a table and initial loss of precision in very large windows. void* TempData; // [Internal] Internal data + // items_count: Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step, and you can call SeekCursorForItem() manually if you need) // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing(). IMGUI_API ImGuiListClipper(); @@ -2567,20 +2939,24 @@ struct ImGuiListClipper IMGUI_API void Begin(int items_count, float items_height = -1.0f); IMGUI_API void End(); // Automatically called on the last call of Step() that returns false. IMGUI_API bool Step(); // Call until it returns false. The DisplayStart/DisplayEnd fields will be set and you can process/draw those items. + // Call IncludeItemByIndex() or IncludeItemsByIndex() *BEFORE* first call to Step() if you need a range of items to not be clipped, regardless of their visibility. // (Due to alignment / padding of certain items it is possible that an extra item may be included on either end of the display range). inline void IncludeItemByIndex(int item_index) { IncludeItemsByIndex(item_index, item_index + 1); } IMGUI_API void IncludeItemsByIndex(int item_begin, int item_end); // item_end is exclusive e.g. use (42, 42+1) to make item 42 never clipped. + // Seek cursor toward given item. This is automatically called while stepping. // - The only reason to call this is: you can use ImGuiListClipper::Begin(INT_MAX) if you don't know item count ahead of time. // - In this case, after all steps are done, you'll want to call SeekCursorForItem(item_count). IMGUI_API void SeekCursorForItem(int item_index); + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS inline void IncludeRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.9] - inline void ForceDisplayRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.6] + //inline void ForceDisplayRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.6] //inline ImGuiListClipper(int items_count, float items_height = -1.0f) { memset(this, 0, sizeof(*this)); ItemsCount = -1; Begin(items_count, items_height); } // [removed in 1.79] #endif }; + // Helpers: ImVec2/ImVec4 operators // - It is important that we are keeping those disabled by default so they don't leak in user space. // - This is in order to allow user enabling implicit cast operators between ImVec2/ImVec4 and their own types (using IM_VEC2_CLASS_EXTRA in imconfig.h) @@ -2589,28 +2965,35 @@ struct ImGuiListClipper #ifdef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED IM_MSVC_RUNTIME_CHECKS_OFF -static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } -static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } -static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } -static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } -static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } -static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } -static inline ImVec2 operator-(const ImVec2& lhs) { return ImVec2(-lhs.x, -lhs.y); } -static inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } -static inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } -static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } -static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } -static inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; } -static inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; } -static inline bool operator==(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } -static inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } -static inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); } -static inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); } -static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); } -static inline bool operator==(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w; } -static inline bool operator!=(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z || lhs.w != rhs.w; } +// ImVec2 operators +inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } +inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } +inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } +inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } +inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } +inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } +inline ImVec2 operator-(const ImVec2& lhs) { return ImVec2(-lhs.x, -lhs.y); } +inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } +inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } +inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } +inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } +inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; } +inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; } +inline bool operator==(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } +inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } +// ImVec4 operators +inline ImVec4 operator*(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); } +inline ImVec4 operator/(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); } +inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); } +inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); } +inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); } +inline ImVec4 operator/(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z, lhs.w / rhs.w); } +inline ImVec4 operator-(const ImVec4& lhs) { return ImVec4(-lhs.x, -lhs.y, -lhs.z, -lhs.w); } +inline bool operator==(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w; } +inline bool operator!=(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z || lhs.w != rhs.w; } IM_MSVC_RUNTIME_CHECKS_RESTORE #endif + // Helpers macros to generate 32-bit encoded colors // - User can declare their own format by #defining the 5 _SHIFT/_MASK macros in their imconfig file. // - Any setting other than the default will need custom backend support. The only standard backend that supports anything else than the default is DirectX9. @@ -2633,6 +3016,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE #define IM_COL32_WHITE IM_COL32(255,255,255,255) // Opaque white = 0xFFFFFFFF #define IM_COL32_BLACK IM_COL32(0,0,0,255) // Opaque black #define IM_COL32_BLACK_TRANS IM_COL32(0,0,0,0) // Transparent black = 0x00000000 + // Helper: ImColor() implicitly converts colors to either ImU32 (packed 4x1 byte) or ImVec4 (4x1 float) // Prefer using IM_COL32() macros if you want a guaranteed compile-time ImU32 for usage with ImDrawList API. // **Avoid storing ImColor! Store either u32 of ImVec4. This is not a full-featured color class. MAY OBSOLETE. @@ -2640,6 +3024,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE struct ImColor { ImVec4 Value; + constexpr ImColor() { } constexpr ImColor(float r, float g, float b, float a = 1.0f) : Value(r, g, b, a) { } constexpr ImColor(const ImVec4& col) : Value(col) {} @@ -2647,13 +3032,16 @@ struct ImColor constexpr ImColor(ImU32 rgba) : Value((float)((rgba >> IM_COL32_R_SHIFT) & 0xFF) * (1.0f / 255.0f), (float)((rgba >> IM_COL32_G_SHIFT) & 0xFF) * (1.0f / 255.0f), (float)((rgba >> IM_COL32_B_SHIFT) & 0xFF) * (1.0f / 255.0f), (float)((rgba >> IM_COL32_A_SHIFT) & 0xFF) * (1.0f / 255.0f)) {} inline operator ImU32() const { return ImGui::ColorConvertFloat4ToU32(Value); } inline operator ImVec4() const { return Value; } + // FIXME-OBSOLETE: May need to obsolete/cleanup those helpers. inline void SetHSV(float h, float s, float v, float a = 1.0f){ ImGui::ColorConvertHSVtoRGB(h, s, v, Value.x, Value.y, Value.z); Value.w = a; } static ImColor HSV(float h, float s, float v, float a = 1.0f) { float r, g, b; ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); return ImColor(r, g, b, a); } }; + //----------------------------------------------------------------------------- // [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiSelectionRequestType, ImGuiSelectionRequest, ImGuiMultiSelectIO, ImGuiSelectionBasicStorage) //----------------------------------------------------------------------------- + // Multi-selection system // Documentation at: https://github.com/ocornut/imgui/wiki/Multi-Select // - Refer to 'Demo->Widgets->Selection State & Multi-Select' for demos using this. @@ -2689,6 +3077,7 @@ struct ImColor // between two ImGuiSelectionUserData, which may be a convenient way to use part of the feature with less code work. // - As most users will want to store an index, for convenience and to reduce confusion we use ImS64 instead of void*, // being syntactically easier to downcast. Feel free to reinterpret_cast and store a pointer inside. + // Flags for BeginMultiSelect() enum ImGuiMultiSelectFlags_ { @@ -2711,6 +3100,7 @@ enum ImGuiMultiSelectFlags_ //ImGuiMultiSelectFlags_RangeSelect2d = 1 << 15, // Shift+Selection uses 2d geometry instead of linear sequence, so possible to use Shift+up/down to select vertically in grid. Analogous to what BoxSelect does. ImGuiMultiSelectFlags_NavWrapX = 1 << 16, // [Temporary] Enable navigation wrapping on X axis. Provided as a convenience because we don't have a design for the general Nav API for this yet. When the more general feature be public we may obsolete this flag in favor of new one. }; + // Main IO structure returned by BeginMultiSelect()/EndMultiSelect(). // This mainly contains a list of selection requests. // - Use 'Demo->Tools->Debug Log->Selection' to see requests as they happen. @@ -2726,6 +3116,7 @@ struct ImGuiMultiSelectIO bool RangeSrcReset; // app:w / ms:r // (If using deletion) Set before EndMultiSelect() to reset ResetSrcItem (e.g. if deleted selection). int ItemsCount; // ms:w, app:r / app:r // 'int items_count' parameter to BeginMultiSelect() is copied here for convenience, allowing simpler calls to your ApplyRequests handler. Not used internally. }; + // Selection request type enum ImGuiSelectionRequestType { @@ -2733,6 +3124,7 @@ enum ImGuiSelectionRequestType ImGuiSelectionRequestType_SetAll, // Request app to clear selection (if Selected==false) or select all items (if Selected==true). We cannot set RangeFirstItem/RangeLastItem as its contents is entirely up to user (not necessarily an index) ImGuiSelectionRequestType_SetRange, // Request app to select/unselect [RangeFirstItem..RangeLastItem] items (inclusive) based on value of Selected. Only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false. }; + // Selection request item struct ImGuiSelectionRequest { @@ -2743,6 +3135,7 @@ struct ImGuiSelectionRequest ImGuiSelectionUserData RangeFirstItem; // / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from top to bottom). ImGuiSelectionUserData RangeLastItem; // / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from bottom to top). Inclusive! }; + // Optional helper to store multi-selection state + apply multi-selection requests. // - Used by our demos and provided as a convenience to easily implement basic multi-selection. // - Iterate selection with 'void* it = NULL; ImGuiID id; while (selection.GetNextSelectedItem(&it, &id)) { ... }' @@ -2768,6 +3161,7 @@ struct ImGuiSelectionBasicStorage ImGuiID (*AdapterIndexToStorageId)(ImGuiSelectionBasicStorage* self, int idx); // e.g. selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { return ((MyItems**)self->UserData)[idx]->ID; }; int _SelectionOrder;// [Internal] Increasing counter to store selection order ImGuiStorage _Storage; // [Internal] Selection set. Think of this as similar to e.g. std::set. Prefer not accessing directly: iterate with GetNextSelectedItem(). + // Methods IMGUI_API ImGuiSelectionBasicStorage(); IMGUI_API void ApplyRequests(ImGuiMultiSelectIO* ms_io); // Apply selection requests coming from BeginMultiSelect() and EndMultiSelect() functions. It uses 'items_count' passed to BeginMultiSelect() @@ -2778,6 +3172,7 @@ struct ImGuiSelectionBasicStorage IMGUI_API bool GetNextSelectedItem(void** opaque_it, ImGuiID* out_id); // Iterate selection with 'void* it = NULL; ImGuiID id; while (selection.GetNextSelectedItem(&it, &id)) { ... }' inline ImGuiID GetStorageIdFromIndex(int idx) { return AdapterIndexToStorageId(this, idx); } // Convert index to item id based on provided adapter. }; + // Optional helper to apply multi-selection requests to existing randomly accessible storage. // Convenient if you want to quickly wire multi-select API on e.g. an array of bool or items storing their own selection state. struct ImGuiSelectionExternalStorage @@ -2785,24 +3180,29 @@ struct ImGuiSelectionExternalStorage // Members void* UserData; // User data for use by adapter function // e.g. selection.UserData = (void*)my_items; void (*AdapterSetItemSelected)(ImGuiSelectionExternalStorage* self, int idx, bool selected); // e.g. AdapterSetItemSelected = [](ImGuiSelectionExternalStorage* self, int idx, bool selected) { ((MyItems**)self->UserData)[idx]->Selected = selected; } + // Methods IMGUI_API ImGuiSelectionExternalStorage(); IMGUI_API void ApplyRequests(ImGuiMultiSelectIO* ms_io); // Apply selection requests by using AdapterSetItemSelected() calls }; + //----------------------------------------------------------------------------- // [SECTION] Drawing API (ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawListFlags, ImDrawList, ImDrawData) // Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList. //----------------------------------------------------------------------------- + // The maximum line width to bake anti-aliased textures for. Build atlas with ImFontAtlasFlags_NoBakedLines to disable baking. #ifndef IM_DRAWLIST_TEX_LINES_WIDTH_MAX #define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) #endif + // ImDrawIdx: vertex index. [Compile-time configurable type] // - To use 16-bit indices + allow large meshes: backend need to set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset' and handle ImDrawCmd::VtxOffset (recommended). // - To use 32-bit indices: override with '#define ImDrawIdx unsigned int' in your imconfig.h file. #ifndef ImDrawIdx typedef unsigned short ImDrawIdx; // Default: 16-bit (for maximum compatibility with renderer backends) #endif + // ImDrawCallback: Draw callbacks for advanced uses [configurable type: override in imconfig.h] // NB: You most likely do NOT need to use draw callbacks just to create your own widget or customized UI rendering, // you can poke into the draw list for that! Draw callback may be useful for example to: @@ -2813,20 +3213,22 @@ typedef unsigned short ImDrawIdx; // Default: 16-bit (for maximum compatibilit #ifndef ImDrawCallback typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* cmd); #endif + // Special Draw callback value to request renderer backend to reset the graphics/render state. // The renderer backend needs to handle this special value, otherwise it will crash trying to call a function at this address. // This is useful, for example, if you submitted callbacks which you know have altered the render state and you want it to be restored. // Render state is not reset by default because they are many perfectly useful way of altering render state (e.g. changing shader/blending settings before an Image call). #define ImDrawCallback_ResetRenderState (ImDrawCallback)(-8) + // Typically, 1 command = 1 GPU draw call (unless command is a callback) // - VtxOffset: When 'io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset' is enabled, // this fields allow us to render meshes larger than 64K vertices while keeping 16-bit indices. // Backends made for <1.71. will typically ignore the VtxOffset fields. -// - The ClipRect/TextureId/VtxOffset fields must be contiguous as we memcmp() them together (this is asserted for). +// - The ClipRect/TexRef/VtxOffset fields must be contiguous as we memcmp() them together (this is asserted for). struct ImDrawCmd { ImVec4 ClipRect; // 4*4 // Clipping rectangle (x1, y1, x2, y2). Subtract ImDrawData->DisplayPos to get clipping rectangle in "viewport" coordinates - ImTextureID TextureId; // 4-8 // User-provided texture ID. Set by user in ImfontAtlas::SetTexID() for fonts or passed to Image*() functions. Ignore if never using images or multiple fonts atlas. + ImTextureRef TexRef; // 16 // Reference to a font/texture atlas (where backend called ImTextureData::SetTexID()) or to a user-provided texture ID (via e.g. ImGui::Image() calls). Both will lead to a ImTextureID value. unsigned int VtxOffset; // 4 // Start offset in vertex buffer. ImGuiBackendFlags_RendererHasVtxOffset: always 0, otherwise may be >0 to support meshes larger than 64K vertices with 16-bit indices. unsigned int IdxOffset; // 4 // Start offset in index buffer. unsigned int ElemCount; // 4 // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[]. @@ -2834,10 +3236,14 @@ struct ImDrawCmd void* UserCallbackData; // 4-8 // Callback user data (when UserCallback != NULL). If called AddCallback() with size == 0, this is a copy of the AddCallback() argument. If called AddCallback() with size > 0, this is pointing to a buffer where data is stored. int UserCallbackDataSize; // 4 // Size of callback user data when using storage, otherwise 0. int UserCallbackDataOffset;// 4 // [Internal] Offset of callback user data when using storage, otherwise -1. + ImDrawCmd() { memset(this, 0, sizeof(*this)); } // Also ensure our padding fields are zeroed + // Since 1.83: returns ImTextureID associated with this draw call. Warning: DO NOT assume this is always same as 'TextureId' (we will change this function for an upcoming feature) - inline ImTextureID GetTexID() const { return TextureId; } + // Since 1.92: removed ImDrawCmd::TextureId field, the getter function must be used! + inline ImTextureID GetTexID() const; // == (TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID }; + // Vertex layout #ifndef IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert @@ -2853,19 +3259,22 @@ struct ImDrawVert // NOTE: IMGUI DOESN'T CLEAR THE STRUCTURE AND DOESN'T CALL A CONSTRUCTOR SO ANY CUSTOM FIELD WILL BE UNINITIALIZED. IF YOU ADD EXTRA FIELDS (SUCH AS A 'Z' COORDINATES) YOU WILL NEED TO CLEAR THEM DURING RENDER OR TO IGNORE THEM. IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT; #endif + // [Internal] For use by ImDrawList struct ImDrawCmdHeader { ImVec4 ClipRect; - ImTextureID TextureId; + ImTextureRef TexRef; unsigned int VtxOffset; }; + // [Internal] For use by ImDrawListSplitter struct ImDrawChannel { ImVector _CmdBuffer; ImVector _IdxBuffer; }; + // Split/Merge functions are used to split the draw list into different layers which can be drawn into out of order. // This is used by the Columns/Tables API, so items of each column can be batched together in a same draw call. struct ImDrawListSplitter @@ -2873,6 +3282,7 @@ struct ImDrawListSplitter int _Current; // Current channel number (0) int _Count; // Number of active channels (1+) ImVector _Channels; // Draw channels (not resized down so _Count might be < Channels.Size) + inline ImDrawListSplitter() { memset(this, 0, sizeof(*this)); } inline ~ImDrawListSplitter() { ClearFreeMemory(); } inline void Clear() { _Current = 0; _Count = 1; } // Do not clear Channels[] so our allocations are reused next frame @@ -2881,6 +3291,7 @@ struct ImDrawListSplitter IMGUI_API void Merge(ImDrawList* draw_list); IMGUI_API void SetCurrentChannel(ImDrawList* draw_list, int channel_idx); }; + // Flags for ImDrawList functions // (Legacy: bit 0 must always correspond to ImDrawFlags_Closed to be backward compatible with old API using a bool. Bits 1..3 must be unused) enum ImDrawFlags_ @@ -2900,6 +3311,7 @@ enum ImDrawFlags_ ImDrawFlags_RoundCornersDefault_ = ImDrawFlags_RoundCornersAll, // Default to ALL corners if none of the _RoundCornersXX flags are specified. ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone, }; + // Flags for ImDrawList instance. Those are set automatically by ImGui:: functions from ImGuiIO settings, and generally not manipulated directly. // It is however possible to temporarily alter flags between calls to ImDrawList:: functions. enum ImDrawListFlags_ @@ -2910,6 +3322,7 @@ enum ImDrawListFlags_ ImDrawListFlags_AntiAliasedFill = 1 << 2, // Enable anti-aliased edge around filled shapes (rounded rectangles, circles). ImDrawListFlags_AllowVtxOffset = 1 << 3, // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled. }; + // Draw command list // This is the low-level list of polygons that ImGui:: functions are filling. At the end of the frame, // all command lists are passed to your ImGuiIO::RenderDrawListFn function for rendering. @@ -2926,6 +3339,7 @@ struct ImDrawList ImVector IdxBuffer; // Index buffer. Each command consume ImDrawCmd::ElemCount of those ImVector VtxBuffer; // Vertex buffer. ImDrawListFlags Flags; // Flags, you may poke into these to adjust anti-aliasing settings per-primitive. + // [Internal, used while building lists] unsigned int _VtxCurrentIdx; // [Internal] generally == VtxBuffer.Size unless we are past 64K vertices, in which case this gets reset to 0. ImDrawListSharedData* _Data; // Pointer to shared draw data (you can use ImGui::GetDrawListSharedData() to get the one from current ImGui context) @@ -2935,21 +3349,24 @@ struct ImDrawList ImDrawCmdHeader _CmdHeader; // [Internal] template of active commands. Fields should match those of CmdBuffer.back(). ImDrawListSplitter _Splitter; // [Internal] for channels api (note: prefer using your own persistent instance of ImDrawListSplitter!) ImVector _ClipRectStack; // [Internal] - ImVector _TextureIdStack; // [Internal] + ImVector _TextureStack; // [Internal] ImVector _CallbacksDataBuf; // [Internal] float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content const char* _OwnerName; // Pointer to owner window's name for debugging + // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData(). // (advanced: you may create and use your own ImDrawListSharedData so you can use ImDrawList without ImGui, but that's more involved) IMGUI_API ImDrawList(ImDrawListSharedData* shared_data); IMGUI_API ~ImDrawList(); + IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect = false); // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) IMGUI_API void PushClipRectFullScreen(); IMGUI_API void PopClipRect(); - IMGUI_API void PushTextureID(ImTextureID texture_id); - IMGUI_API void PopTextureID(); + IMGUI_API void PushTexture(ImTextureRef tex_ref); + IMGUI_API void PopTexture(); inline ImVec2 GetClipRectMin() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.x, cr.y); } inline ImVec2 GetClipRectMax() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.z, cr.w); } + // Primitives // - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. // - For rectangular primitives, "p_min" and "p_max" represent the upper-left and lower-right corners. @@ -2975,19 +3392,22 @@ struct ImDrawList IMGUI_API void AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); IMGUI_API void AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0); // Cubic Bezier (4 control points) IMGUI_API void AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0); // Quadratic Bezier (3 control points) + // General polygon // - Only simple polygons are supported by filling functions (no self-intersections, no holes). // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience for the user but not used by the main library. IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness); IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col); + // Image primitives - // - Read FAQ to understand what ImTextureID is. + // - Read FAQ to understand what ImTextureID/ImTextureRef are. // - "p_min" and "p_max" represent the upper-left and lower-right corners of the rectangle. // - "uv_min" and "uv_max" represent the normalized texture coordinates to use for those corners. Using (0,0)->(1,1) texture coordinates will generally display the entire texture. - IMGUI_API void AddImage(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min = ImVec2(0, 0), const ImVec2& uv_max = ImVec2(1, 1), ImU32 col = IM_COL32_WHITE); - IMGUI_API void AddImageQuad(ImTextureID user_texture_id, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1 = ImVec2(0, 0), const ImVec2& uv2 = ImVec2(1, 0), const ImVec2& uv3 = ImVec2(1, 1), const ImVec2& uv4 = ImVec2(0, 1), ImU32 col = IM_COL32_WHITE); - IMGUI_API void AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags = 0); + IMGUI_API void AddImage(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min = ImVec2(0, 0), const ImVec2& uv_max = ImVec2(1, 1), ImU32 col = IM_COL32_WHITE); + IMGUI_API void AddImageQuad(ImTextureRef tex_ref, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1 = ImVec2(0, 0), const ImVec2& uv2 = ImVec2(1, 0), const ImVec2& uv3 = ImVec2(1, 1), const ImVec2& uv4 = ImVec2(0, 1), ImU32 col = IM_COL32_WHITE); + IMGUI_API void AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags = 0); + // Stateful path API, add points then finish with PathFillConvex() or PathStroke() // - Important: filled shapes must always use clockwise winding order! The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. // so e.g. 'PathArcTo(center, radius, PI * -0.5f, PI)' is ok, whereas 'PathArcTo(center, radius, PI, PI * -0.5f)' won't have correct anti-aliasing when followed by PathFillConvex(). @@ -3003,6 +3423,7 @@ struct ImDrawList IMGUI_API void PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0); // Cubic Bezier (4 control points) IMGUI_API void PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, int num_segments = 0); // Quadratic Bezier (3 control points) IMGUI_API void PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, ImDrawFlags flags = 0); + // Advanced: Draw Callbacks // - May be used to alter render state (change sampler, blending, current shader). May be used to emit custom rendering commands (difficult to do correctly, but possible). // - Use special ImDrawCallback_ResetRenderState callback to instruct backend to reset its render state to the default. @@ -3013,9 +3434,11 @@ struct ImDrawList // - If userdata_size > 0, we copy/store 'userdata_size' bytes pointed to by 'userdata'. We store them in a buffer stored inside the drawlist. ImDrawCmd::UserCallbackData will point inside that buffer so you have to retrieve data from there. Your callback may need to use ImDrawCmd::UserCallbackDataSize if you expect dynamically-sized data. // - Support for userdata_size > 0 was added in v1.91.4, October 2024. So earlier code always only allowed to copy/store a simple void*. IMGUI_API void AddCallback(ImDrawCallback callback, void* userdata, size_t userdata_size = 0); + // Advanced: Miscellaneous IMGUI_API void AddDrawCmd(); // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible IMGUI_API ImDrawList* CloneOutput() const; // Create a clone of the CmdBuffer/IdxBuffer/VtxBuffer. + // Advanced: Channels // - Use to split render into layers. By switching channels to can render out-of-order (e.g. submit FG primitives before BG primitives) // - Use to minimize draw calls (e.g. if going back-and-forth between multiple clipping rectangles, prefer to append into separate channels then merge at the end) @@ -3025,6 +3448,7 @@ struct ImDrawList inline void ChannelsSplit(int count) { _Splitter.Split(this, count); } inline void ChannelsMerge() { _Splitter.Merge(this); } inline void ChannelsSetCurrent(int n) { _Splitter.SetCurrentChannel(this, n); } + // Advanced: Primitives allocations // - We render triangles (three vertices) // - All primitives needs to be reserved via PrimReserve() beforehand. @@ -3036,39 +3460,49 @@ struct ImDrawList inline void PrimWriteVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) { _VtxWritePtr->pos = pos; _VtxWritePtr->uv = uv; _VtxWritePtr->col = col; _VtxWritePtr++; _VtxCurrentIdx++; } inline void PrimWriteIdx(ImDrawIdx idx) { *_IdxWritePtr = idx; _IdxWritePtr++; } inline void PrimVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) { PrimWriteIdx((ImDrawIdx)_VtxCurrentIdx); PrimWriteVtx(pos, uv, col); } // Write vertex with unique index + // Obsolete names +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + inline void PushTextureID(ImTextureRef tex_ref) { PushTexture(tex_ref); } // RENAMED in 1.92.x + inline void PopTextureID() { PopTexture(); } // RENAMED in 1.92.x +#endif //inline void AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f) { AddEllipse(center, ImVec2(radius_x, radius_y), col, rot, num_segments, thickness); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0) { AddEllipseFilled(center, ImVec2(radius_x, radius_y), col, rot, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void PathEllipticalArcTo(const ImVec2& center, float radius_x, float radius_y, float rot, float a_min, float a_max, int num_segments = 0) { PathEllipticalArcTo(center, ImVec2(radius_x, radius_y), rot, a_min, a_max, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void AddBezierCurve(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0) { AddBezierCubic(p1, p2, p3, p4, col, thickness, num_segments); } // OBSOLETED in 1.80 (Jan 2021) //inline void PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0) { PathBezierCubicCurveTo(p2, p3, p4, num_segments); } // OBSOLETED in 1.80 (Jan 2021) + // [Internal helpers] + IMGUI_API void _SetDrawListSharedData(ImDrawListSharedData* data); IMGUI_API void _ResetForNewFrame(); IMGUI_API void _ClearFreeMemory(); IMGUI_API void _PopUnusedDrawCmd(); IMGUI_API void _TryMergeDrawCmds(); IMGUI_API void _OnChangedClipRect(); - IMGUI_API void _OnChangedTextureID(); + IMGUI_API void _OnChangedTexture(); IMGUI_API void _OnChangedVtxOffset(); - IMGUI_API void _SetTextureID(ImTextureID texture_id); + IMGUI_API void _SetTexture(ImTextureRef tex_ref); IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const; IMGUI_API void _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step); IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); }; + // All draw data to render a Dear ImGui frame // (NB: the style and the naming convention here is a little inconsistent, we currently preserve them for backward compatibility purpose, // as this is one of the oldest structure exposed by the library! Basically, ImDrawList == CmdList) struct ImDrawData { bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. - int CmdListsCount; // Number of ImDrawList* to render + int CmdListsCount; // == CmdLists.Size. (OBSOLETE: exists for legacy reasons). Number of ImDrawList* to render. int TotalIdxCount; // For convenience, sum of all ImDrawList's IdxBuffer.Size int TotalVtxCount; // For convenience, sum of all ImDrawList's VtxBuffer.Size ImVector CmdLists; // Array of ImDrawList* to render. The ImDrawLists are owned by ImGuiContext and only pointed to from here. ImVec2 DisplayPos; // Top-left position of the viewport to render (== top-left of the orthogonal projection matrix to use) (== GetMainViewport()->Pos for the main viewport, == (0.0) in most single-viewport applications) ImVec2 DisplaySize; // Size of the viewport to render (== GetMainViewport()->Size for the main viewport, == io.DisplaySize in most single-viewport applications) - ImVec2 FramebufferScale; // Amount of pixels for each unit of DisplaySize. Based on io.DisplayFramebufferScale. Generally (1,1) on normal display, (2,2) on OSX with Retina display. + ImVec2 FramebufferScale; // Amount of pixels for each unit of DisplaySize. Copied from viewport->FramebufferScale (== io.DisplayFramebufferScale for main viewport). Generally (1,1) on normal display, (2,2) on OSX with Retina display. ImGuiViewport* OwnerViewport; // Viewport carrying the ImDrawData instance, might be of use to the renderer (generally not). + ImVector* Textures; // List of textures to update. Most of the times the list is shared by all ImDrawData, has only 1 texture and it doesn't need any update. This almost always points to ImGui::GetPlatformIO().Textures[]. May be overriden or set to NULL if you want to manually update textures. + // Functions ImDrawData() { Clear(); } IMGUI_API void Clear(); @@ -3076,52 +3510,153 @@ struct ImDrawData IMGUI_API void DeIndexAllBuffers(); // Helper to convert all buffers from indexed to non-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! IMGUI_API void ScaleClipRects(const ImVec2& fb_scale); // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than Dear ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. }; + +//----------------------------------------------------------------------------- +// [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureRect, ImTextureData) +//----------------------------------------------------------------------------- +// In principle, the only data types that user/application code should care about are 'ImTextureRef' and 'ImTextureID'. +// They are defined above in this header file. Read their description to the difference between ImTextureRef and ImTextureID. +// FOR ALL OTHER ImTextureXXXX TYPES: ONLY CORE LIBRARY AND RENDERER BACKENDS NEED TO KNOW AND CARE ABOUT THEM. +//----------------------------------------------------------------------------- + +#undef Status // X11 headers are leaking this. + +// We intentionally support a limited amount of texture formats to limit burden on CPU-side code and extension. +// Most standard backends only support RGBA32 but we provide a single channel option for low-resource/embedded systems. +enum ImTextureFormat +{ + ImTextureFormat_RGBA32, // 4 components per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + ImTextureFormat_Alpha8, // 1 component per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight +}; + +// Status of a texture to communicate with Renderer Backend. +enum ImTextureStatus +{ + ImTextureStatus_OK, + ImTextureStatus_Destroyed, // Backend destroyed the texture. + ImTextureStatus_WantCreate, // Requesting backend to create the texture. Set status OK when done. + ImTextureStatus_WantUpdates, // Requesting backend to update specific blocks of pixels (write to texture portions which have never been used before). Set status OK when done. + ImTextureStatus_WantDestroy, // Requesting backend to destroy the texture. Set status to Destroyed when done. +}; + +// Coordinates of a rectangle within a texture. +// When a texture is in ImTextureStatus_WantUpdates state, we provide a list of individual rectangles to copy to the graphics system. +// You may use ImTextureData::Updates[] for the list, or ImTextureData::UpdateBox for a single bounding box. +struct ImTextureRect +{ + unsigned short x, y; // Upper-left coordinates of rectangle to update + unsigned short w, h; // Size of rectangle to update (in pixels) +}; + +// Specs and pixel storage for a texture used by Dear ImGui. +// This is only useful for (1) core library and (2) backends. End-user/applications do not need to care about this. +// Renderer Backends will create a GPU-side version of this. +// Why does we store two identifiers: TexID and BackendUserData? +// - ImTextureID TexID = lower-level identifier stored in ImDrawCmd. ImDrawCmd can refer to textures not created by the backend, and for which there's no ImTextureData. +// - void* BackendUserData = higher-level opaque storage for backend own book-keeping. Some backends may have enough with TexID and not need both. + // In columns below: who reads/writes each fields? 'r'=read, 'w'=write, 'core'=main library, 'backend'=renderer backend +struct ImTextureData +{ + //------------------------------------------ core / backend --------------------------------------- + int UniqueID; // w - // [DEBUG] Sequential index to facilitate identifying a texture when debugging/printing. Unique per atlas. + ImTextureStatus Status; // rw rw // ImTextureStatus_OK/_WantCreate/_WantUpdates/_WantDestroy. Always use SetStatus() to modify! + void* BackendUserData; // - rw // Convenience storage for backend. Some backends may have enough with TexID. + ImTextureID TexID; // r w // Backend-specific texture identifier. Always use SetTexID() to modify! The identifier will stored in ImDrawCmd::GetTexID() and passed to backend's RenderDrawData function. + ImTextureFormat Format; // w r // ImTextureFormat_RGBA32 (default) or ImTextureFormat_Alpha8 + int Width; // w r // Texture width + int Height; // w r // Texture height + int BytesPerPixel; // w r // 4 or 1 + unsigned char* Pixels; // w r // Pointer to buffer holding 'Width*Height' pixels and 'Width*Height*BytesPerPixels' bytes. + ImTextureRect UsedRect; // w r // Bounding box encompassing all past and queued Updates[]. + ImTextureRect UpdateRect; // w r // Bounding box encompassing all queued Updates[]. + ImVector Updates; // w r // Array of individual updates. + int UnusedFrames; // w r // In order to facilitate handling Status==WantDestroy in some backend: this is a count successive frames where the texture was not used. Always >0 when Status==WantDestroy. + unsigned short RefCount; // w r // Number of contexts using this texture. Used during backend shutdown. + bool UseColors; // w r // Tell whether our texture data is known to use colors (rather than just white + alpha). + bool WantDestroyNextFrame; // rw - // [Internal] Queued to set ImTextureStatus_WantDestroy next frame. May still be used in the current frame. + + // Functions + ImTextureData() { memset(this, 0, sizeof(*this)); Status = ImTextureStatus_Destroyed; TexID = ImTextureID_Invalid; } + ~ImTextureData() { DestroyPixels(); } + IMGUI_API void Create(ImTextureFormat format, int w, int h); + IMGUI_API void DestroyPixels(); + void* GetPixels() { IM_ASSERT(Pixels != NULL); return Pixels; } + void* GetPixelsAt(int x, int y) { IM_ASSERT(Pixels != NULL); return Pixels + (x + y * Width) * BytesPerPixel; } + int GetSizeInBytes() const { return Width * Height * BytesPerPixel; } + int GetPitch() const { return Width * BytesPerPixel; } + ImTextureRef GetTexRef() { ImTextureRef tex_ref; tex_ref._TexData = this; tex_ref._TexID = ImTextureID_Invalid; return tex_ref; } + ImTextureID GetTexID() const { return TexID; } + + // Called by Renderer backend + void SetTexID(ImTextureID tex_id) { TexID = tex_id; } // Call after creating or destroying the texture. Never modify TexID directly! + void SetStatus(ImTextureStatus status) { Status = status; } // Call after honoring a request. Never modify Status directly! +}; + //----------------------------------------------------------------------------- // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontAtlasFlags, ImFontAtlas, ImFontGlyphRangesBuilder, ImFont) //----------------------------------------------------------------------------- + // A font input/source (we may rename this to ImFontSource in the future) struct ImFontConfig { + // Data Source + char Name[40]; // // Name (strictly to ease debugging, hence limited size buffer) void* FontData; // // TTF/OTF data int FontDataSize; // // TTF/OTF data size bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself). + + // Options bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. bool PixelSnapH; // false // Align every glyph AdvanceX to pixel boundaries. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. - int FontNo; // 0 // Index of font within TTF/OTF file - int OversampleH; // 0 (2) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1 or 2 depending on size. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details. - int OversampleV; // 0 (1) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1. This is not really useful as we don't use sub-pixel positions on the Y axis. - float SizePixels; // // Size in pixels for rasterizer (more or less maps to the resulting font height). - //ImVec2 GlyphExtraSpacing; // 0, 0 // (REMOVED IN 1.91.9: use GlyphExtraAdvanceX) - ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input. - const ImWchar* GlyphRanges; // NULL // THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). - float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font - float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs - float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. - unsigned int FontBuilderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. - float RasterizerMultiply; // 1.0f // Linearly brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. This is a silly thing we may remove in the future. - float RasterizerDensity; // 1.0f // DPI scale for rasterization, not altering other font metrics: make it easy to swap between e.g. a 100% and a 400% fonts for a zooming display. IMPORTANT: If you increase this it is expected that you increase font scale accordingly, otherwise quality may look lowered. + bool PixelSnapV; // true // Align Scaled GlyphOffset.y to pixel boundaries. + ImS8 OversampleH; // 0 (2) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1 or 2 depending on size. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details. + ImS8 OversampleV; // 0 (1) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1. This is not really useful as we don't use sub-pixel positions on the Y axis. ImWchar EllipsisChar; // 0 // Explicitly specify Unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used. + float SizePixels; // // Size in pixels for rasterizer (more or less maps to the resulting font height). + const ImWchar* GlyphRanges; // NULL // *LEGACY* THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). + const ImWchar* GlyphExcludeRanges; // NULL // Pointer to a small user-provided list of Unicode ranges (2 value per range, values are inclusive, zero-terminated list). This is very close to GlyphRanges[] but designed to exclude ranges from a font source, when merging fonts with overlapping glyphs. Use "Input Glyphs Overlap Detection Tool" to find about your overlapping ranges. + //ImVec2 GlyphExtraSpacing; // 0, 0 // (REMOVED AT IT SEEMS LARGELY OBSOLETE. PLEASE REPORT IF YOU WERE USING THIS). Extra spacing (in pixels) between glyphs when rendered: essentially add to glyph->AdvanceX. Only X axis is supported for now. + ImVec2 GlyphOffset; // 0, 0 // Offset (in pixels) all glyphs from this font input. Absolute value for default size, other sizes will scale this value. + float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font. Absolute value for default size, other sizes will scale this value. + float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs + float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. // FIXME-NEWATLAS: Intentionally unscaled + ImU32 FontNo; // 0 // Index of font within TTF/OTF file + unsigned int FontLoaderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. + //unsigned int FontBuilderFlags; // -- // [Renamed in 1.92] Ue FontLoaderFlags. + float RasterizerMultiply; // 1.0f // Linearly brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. This is a silly thing we may remove in the future. + float RasterizerDensity; // 1.0f // [LEGACY: this only makes sense when ImGuiBackendFlags_RendererHasTextures is not supported] DPI scale multiplier for rasterization. Not altering other font metrics: makes it easy to swap between e.g. a 100% and a 400% fonts for a zooming display, or handle Retina screen. IMPORTANT: If you change this it is expected that you increase/decrease font scale roughly to the inverse of this, otherwise quality may look lowered. + // [Internal] - char Name[40]; // Name (strictly to ease debugging) - ImFont* DstFont; + ImFontFlags Flags; // Font flags (don't use just yet, will be exposed in upcoming 1.92.X updates) + ImFont* DstFont; // Target font (as we merging fonts, multiple ImFontConfig may target the same font) + const ImFontLoader* FontLoader; // Custom font backend for this source (default source is the one stored in ImFontAtlas) + void* FontLoaderData; // Font loader opaque storage (per font config) + IMGUI_API ImFontConfig(); }; + // Hold rendering data for one glyph. -// (Note: some language parsers may fail to convert the 31+1 bitfield members, in this case maybe drop store a single u32 or we can rework this) +// (Note: some language parsers may fail to convert the bitfield members, in this case maybe drop store a single u32 or we can rework this) struct ImFontGlyph { unsigned int Colored : 1; // Flag to indicate glyph is colored and should generally ignore tinting (make it usable with no shift on little-endian as this is used in loops) unsigned int Visible : 1; // Flag to indicate glyph has no visible pixels (e.g. space). Allow early out when rendering. - unsigned int Codepoint : 30; // 0x0000..0x10FFFF - float AdvanceX; // Horizontal distance to advance layout with - float X0, Y0, X1, Y1; // Glyph corners - float U0, V0, U1, V1; // Texture coordinates + unsigned int SourceIdx : 4; // Index of source in parent font + unsigned int Codepoint : 26; // 0x0000..0x10FFFF + float AdvanceX; // Horizontal distance to advance cursor/layout position. + float X0, Y0, X1, Y1; // Glyph corners. Offsets from current cursor/layout position. + float U0, V0, U1, V1; // Texture coordinates for the current value of ImFontAtlas->TexRef. Cached equivalent of calling GetCustomRect() with PackId. + int PackId; // [Internal] ImFontAtlasRectId value (FIXME: Cold data, could be moved elsewhere?) + + ImFontGlyph() { memset(this, 0, sizeof(*this)); PackId = -1; } }; + // Helper to build glyph ranges from text/string data. Feed your application strings/characters to it then call BuildRanges(). // This is essentially a tightly packed of vector of 64k booleans = 8KB storage. struct ImFontGlyphRangesBuilder { ImVector UsedChars; // Store 1-bit per Unicode code point (0=unused, 1=used) + ImFontGlyphRangesBuilder() { Clear(); } inline void Clear() { int size_in_bytes = (IM_UNICODE_CODEPOINT_MAX + 1) / 8; UsedChars.resize(size_in_bytes / (int)sizeof(ImU32)); memset(UsedChars.Data, 0, (size_t)size_in_bytes); } inline bool GetBit(size_t n) const { int off = (int)(n >> 5); ImU32 mask = 1u << (n & 31); return (UsedChars[off] & mask) != 0; } // Get bit n in the array @@ -3131,20 +3666,24 @@ struct ImFontGlyphRangesBuilder IMGUI_API void AddRanges(const ImWchar* ranges); // Add ranges, e.g. builder.AddRanges(ImFontAtlas::GetGlyphRangesDefault()) to force add all of ASCII/Latin+Ext IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges }; -// See ImFontAtlas::AddCustomRectXXX functions. -struct ImFontAtlasCustomRect + +// An opaque identifier to a rectangle in the atlas. -1 when invalid. +// The rectangle may move and UV may be invalidated, use GetCustomRect() to retrieve it. +typedef int ImFontAtlasRectId; +#define ImFontAtlasRectId_Invalid -1 + +// Output of ImFontAtlas::GetCustomRect() when using custom rectangles. +// Those values may not be cached/stored as they are only valid for the current value of atlas->TexRef +// (this is in theory derived from ImTextureRect but we use separate structures for reasons) +struct ImFontAtlasRect { - unsigned short X, Y; // Output // Packed position in Atlas - // [Internal] - unsigned short Width, Height; // Input // Desired rectangle dimension - unsigned int GlyphID : 31; // Input // For custom font glyphs only (ID < 0x110000) - unsigned int GlyphColored : 1; // Input // For custom font glyphs only: glyph is colored, removed tinting. - float GlyphAdvanceX; // Input // For custom font glyphs only: glyph xadvance - ImVec2 GlyphOffset; // Input // For custom font glyphs only: glyph display offset - ImFont* Font; // Input // For custom font glyphs only: target font - ImFontAtlasCustomRect() { X = Y = 0xFFFF; Width = Height = 0; GlyphID = 0; GlyphColored = 0; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0, 0); Font = NULL; } - bool IsPacked() const { return X != 0xFFFF; } + unsigned short x, y; // Position (in current texture) + unsigned short w, h; // Size + ImVec2 uv0, uv1; // UV coordinates (in current texture) + + ImFontAtlasRect() { memset(this, 0, sizeof(*this)); } }; + // Flags for ImFontAtlas build enum ImFontAtlasFlags_ { @@ -3153,16 +3692,19 @@ enum ImFontAtlasFlags_ ImFontAtlasFlags_NoMouseCursors = 1 << 1, // Don't build software mouse cursors into the atlas (save a little texture memory) ImFontAtlasFlags_NoBakedLines = 1 << 2, // Don't build thick line textures into the atlas (save a little texture memory, allow support for point/nearest filtering). The AntiAliasedLinesUseTex features uses them, otherwise they will be rendered using polygons (more expensive for CPU/GPU). }; + // Load and rasterize multiple TTF/OTF fonts into a same texture. The font atlas will build a single texture holding: // - One or more fonts. // - Custom graphics data needed to render the shapes needed by Dear ImGui. // - Mouse cursor shapes for software cursor rendering (unless setting 'Flags |= ImFontAtlasFlags_NoMouseCursors' in the font atlas). -// It is the user-code responsibility to setup/build the atlas, then upload the pixel data into a texture accessible by your graphics api. -// - Optionally, call any of the AddFont*** functions. If you don't call any, the default font embedded in the code will be loaded for you. -// - Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. -// - Upload the pixels data into a texture within your graphics system (see imgui_impl_xxxx.cpp examples) +// - If you don't call any AddFont*** functions, the default font embedded in the code will be loaded for you. +// It is the rendering backend responsibility to upload texture into your graphics API: +// - ImGui_ImplXXXX_RenderDrawData() functions generally iterate platform_io->Textures[] to create/update/destroy each ImTextureData instance. +// - Backend then set ImTextureData's TexID and BackendUserData. +// - Texture id are passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID/ImTextureRef for more details. +// Legacy path: +// - Call Build() + GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. // - Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture in a format natural to your graphics API. -// This value will be passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID for more details. // Common pitfalls: // - If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until the // atlas is build (when calling GetTexData*** or Build()). We only copy the pointer, not the data. @@ -3176,32 +3718,49 @@ struct ImFontAtlas IMGUI_API ~ImFontAtlas(); IMGUI_API ImFont* AddFont(const ImFontConfig* font_cfg); IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL); - IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); - IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed. - IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_data_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. - IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. - IMGUI_API void ClearInputData(); // Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts. - IMGUI_API void ClearFonts(); // Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). - IMGUI_API void ClearTexData(); // Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. - IMGUI_API void Clear(); // Clear all input and output. - // Build atlas, retrieve pixel data. - // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). - // The pitch is always = Width * BytesPerPixels (1 or 4) - // Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into - // the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste. - IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. - IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel - IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel - bool IsBuilt() const { return Fonts.Size > 0 && TexReady; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent... - void SetTexID(ImTextureID id) { TexID = id; } + IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); + IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed. + IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. + IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. + IMGUI_API void RemoveFont(ImFont* font); + + IMGUI_API void Clear(); // Clear everything (input fonts, output glyphs/textures) + IMGUI_API void CompactCache(); // Compact cached glyphs and texture. + IMGUI_API void SetFontLoader(const ImFontLoader* font_loader); // Change font loader at runtime. + + // As we are transitioning toward a new font system, we expect to obsolete those soon: + IMGUI_API void ClearInputData(); // [OBSOLETE] Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts. + IMGUI_API void ClearFonts(); // [OBSOLETE] Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). + IMGUI_API void ClearTexData(); // [OBSOLETE] Clear CPU-side copy of the texture data. Saves RAM once the texture has been copied to graphics memory. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // Legacy path for build atlas + retrieving pixel data. + // - User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). + // - The pitch is always = Width * BytesPerPixels (1 or 4) + // - Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into + // the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste. + // - From 1.92 with backends supporting ImGuiBackendFlags_RendererHasTextures: + // - Calling Build(), GetTexDataAsAlpha8(), GetTexDataAsRGBA32() is not needed. + // - In backend: replace calls to ImFontAtlas::SetTexID() with calls to ImTextureData::SetTexID() after honoring texture creation. + IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. + IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + void SetTexID(ImTextureID id) { IM_ASSERT(TexRef._TexID == ImTextureID_Invalid); TexRef._TexData->TexID = id; } // Called by legacy backends. May be called before texture creation. + void SetTexID(ImTextureRef id) { IM_ASSERT(TexRef._TexID == ImTextureID_Invalid && id._TexData == NULL); TexRef._TexData->TexID = id._TexID; } // Called by legacy backends. + bool IsBuilt() const { return Fonts.Size > 0 && TexIsBuilt; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent.. +#endif + //------------------------------------------- // Glyph Ranges //------------------------------------------- + + // Since 1.92: specifying glyph ranges is only useful/necessary if your backend doesn't support ImGuiBackendFlags_RendererHasTextures! + IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) // NB: Make sure that your string are UTF-8 and NOT in your local code page. // Read https://github.com/ocornut/imgui/blob/master/docs/FONTS.md/#about-utf-8-encoding for details. // NB: Consider using ImFontGlyphRangesBuilder to build glyph ranges from textual data. - IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin IMGUI_API const ImWchar* GetGlyphRangesGreek(); // Default + Greek and Coptic IMGUI_API const ImWchar* GetGlyphRangesKorean(); // Default + Korean characters IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 2999 Ideographs @@ -3210,108 +3769,215 @@ struct ImFontAtlas IMGUI_API const ImWchar* GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters IMGUI_API const ImWchar* GetGlyphRangesThai(); // Default + Thai characters IMGUI_API const ImWchar* GetGlyphRangesVietnamese(); // Default + Vietnamese characters +#endif + //------------------------------------------- // [ALPHA] Custom Rectangles/Glyphs API //------------------------------------------- - // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. - // - After calling Build(), you can query the rectangle position and render your pixels. - // - If you render colored output, set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. - // - You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), - // so you can render e.g. custom colorful icons and use them as regular glyphs. + + // Register and retrieve custom rectangles + // - You can request arbitrary rectangles to be packed into the atlas, for your own purpose. + // - Since 1.92.X, packing is done immediately in the function call (previously packing was done during the Build call) + // - You can render your pixels into the texture right after calling the AddCustomRect() functions. + // - VERY IMPORTANT: + // - Texture may be created/resized at any time when calling ImGui or ImFontAtlas functions. + // - IT WILL INVALIDATE RECTANGLE DATA SUCH AS UV COORDINATES. Always use latest values from GetCustomRect(). + // - UV coordinates are associated to the current texture identifier aka 'atlas->TexRef'. Both TexRef and UV coordinates are typically changed at the same time. + // - If you render colored output into your custom rectangles: set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. // - Read docs/FONTS.md for more details about using colorful icons. - // - Note: this API may be redesigned later in order to support multi-monitor varying DPI settings. - IMGUI_API int AddCustomRectRegular(int width, int height); - IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); - ImFontAtlasCustomRect* GetCustomRectByIndex(int index) { IM_ASSERT(index >= 0); return &CustomRects[index]; } - // [Internal] - IMGUI_API void CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const; + // - Note: this API may be reworked further in order to facilitate supporting e.g. multi-monitor, varying DPI settings. + // - (Pre-1.92 names) ------------> (1.92 names) + // - GetCustomRectByIndex() --> Use GetCustomRect() + // - CalcCustomRectUV() --> Use GetCustomRect() and read uv0, uv1 fields. + // - AddCustomRectRegular() --> Renamed to AddCustomRect() + // - AddCustomRectFontGlyph() --> Prefer using custom ImFontLoader inside ImFontConfig + // - ImFontAtlasCustomRect --> Renamed to ImFontAtlasRect + IMGUI_API ImFontAtlasRectId AddCustomRect(int width, int height, ImFontAtlasRect* out_r = NULL);// Register a rectangle. Return -1 (ImFontAtlasRectId_Invalid) on error. + IMGUI_API void RemoveCustomRect(ImFontAtlasRectId id); // Unregister a rectangle. Existing pixels will stay in texture until resized / garbage collected. + IMGUI_API bool GetCustomRect(ImFontAtlasRectId id, ImFontAtlasRect* out_r) const; // Get rectangle coordinates for current texture. Valid immediately, never store this (read above)! + //------------------------------------------- // Members //------------------------------------------- + // Input ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) - ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. - int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. + ImTextureFormat TexDesiredFormat; // Desired texture format (default to ImTextureFormat_RGBA32 but may be changed to ImTextureFormat_Alpha8). int TexGlyphPadding; // FIXME: Should be called "TexPackPadding". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). + int TexMinWidth; // Minimum desired texture width. Must be a power of two. Default to 512. + int TexMinHeight; // Minimum desired texture height. Must be a power of two. Default to 128. + int TexMaxWidth; // Maximum desired texture width. Must be a power of two. Default to 8192. + int TexMaxHeight; // Maximum desired texture height. Must be a power of two. Default to 8192. void* UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). + + // Output + // - Because textures are dynamically created/resized, the current texture identifier may changed at *ANY TIME* during the frame. + // - This should not affect you as you can always use the latest value. But note that any precomputed UV coordinates are only valid for the current TexRef. +#ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImTextureRef TexRef; // Latest texture identifier == TexData->GetTexRef(). +#else + union { ImTextureRef TexRef; ImTextureRef TexID; }; // Latest texture identifier == TexData->GetTexRef(). // RENAMED TexID to TexRef in 1.92.x +#endif + ImTextureData* TexData; // Latest texture. + // [Internal] - // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. - bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. - bool TexReady; // Set when texture was built matching current font input - bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format. - unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight - unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 - int TexWidth; // Texture width calculated during Build(). - int TexHeight; // Texture height calculated during Build(). - ImVec2 TexUvScale; // = (1.0f/TexWidth, 1.0f/TexHeight) - ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel + ImVector TexList; // Texture list (most often TexList.Size == 1). TexData is always == TexList.back(). DO NOT USE DIRECTLY, USE GetDrawData().Textures[]/GetPlatformIO().Textures[] instead! + bool Locked; // Marked as locked during ImGui::NewFrame()..EndFrame() scope if TexUpdates are not supported. Any attempt to modify the atlas will assert. + bool RendererHasTextures;// Copy of (BackendFlags & ImGuiBackendFlags_RendererHasTextures) from supporting context. + bool TexIsBuilt; // Set when texture was built matching current font input. Mostly useful for legacy IsBuilt() call. + bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format or conversion process. + ImVec2 TexUvScale; // = (1.0f/TexData->TexWidth, 1.0f/TexData->TexHeight). May change as new texture gets created. + ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel. May change as new texture gets created. ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. - ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. ImVector Sources; // Source/configuration data ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines - // [Internal] Font builder - const ImFontBuilderIO* FontBuilderIO; // Opaque interface to a font builder (default to stb_truetype, can be changed to use FreeType by defining IMGUI_ENABLE_FREETYPE). - unsigned int FontBuilderFlags; // Shared flags (for all fonts) for custom font builder. THIS IS BUILD IMPLEMENTATION DEPENDENT. Per-font override is also available in ImFontConfig. - // [Internal] Packing data - int PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors - int PackIdLines; // Custom texture rectangle ID for baked anti-aliased lines + int TexNextUniqueID; // Next value to be stored in TexData->UniqueID + int FontNextUniqueID; // Next value to be stored in ImFont->FontID + ImVector DrawListSharedDatas; // List of users for this atlas. Typically one per Dear ImGui context. + ImFontAtlasBuilder* Builder; // Opaque interface to our data that doesn't need to be public and may be discarded when rebuilding. + const ImFontLoader* FontLoader; // Font loader opaque interface (default to use FreeType when IMGUI_ENABLE_FREETYPE is defined, otherwise default to use stb_truetype). Use SetFontLoader() to change this at runtime. + const char* FontLoaderName; // Font loader name (for display e.g. in About box) == FontLoader->Name + void* FontLoaderData; // Font backend opaque storage + unsigned int FontLoaderFlags; // Shared flags (for all fonts) for font loader. THIS IS BUILD IMPLEMENTATION DEPENDENT (e.g. Per-font override is also available in ImFontConfig). + int RefCount; // Number of contexts using this atlas + ImGuiContext* OwnerContext; // Context which own the atlas will be in charge of updating and destroying it. + // [Obsolete] - //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ - //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // Legacy: You can request your rectangles to be mapped as font glyph (given a font + Unicode point), so you can render e.g. custom colorful icons and use them as regular glyphs. --> Prefer using a custom ImFontLoader. + ImFontAtlasRect TempRect; // For old GetCustomRectByIndex() API + inline ImFontAtlasRectId AddCustomRectRegular(int w, int h) { return AddCustomRect(w, h); } // RENAMED in 1.92.X + inline const ImFontAtlasRect* GetCustomRectByIndex(ImFontAtlasRectId id) { return GetCustomRect(id, &TempRect) ? &TempRect : NULL; } // OBSOLETED in 1.92.X + inline void CalcCustomRectUV(const ImFontAtlasRect* r, ImVec2* out_uv_min, ImVec2* out_uv_max) const { *out_uv_min = r->uv0; *out_uv_max = r->uv1; } // OBSOLETED in 1.92.X + IMGUI_API ImFontAtlasRectId AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.X: Use custom ImFontLoader in ImFontConfig + IMGUI_API ImFontAtlasRectId AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // ADDED AND OBSOLETED in 1.92.X +#endif + //unsigned int FontBuilderFlags; // OBSOLETED in 1.92.X: Renamed to FontLoaderFlags. + //int TexDesiredWidth; // OBSOLETED in 1.92.X: Force texture width before calling Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height) + //typedef ImFontAtlasRect ImFontAtlasCustomRect; // OBSOLETED in 1.92.X + //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ + //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ }; -// Font runtime data and rendering -// ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32(). -struct ImFont + +// Font runtime data for a given size +// Important: pointers to ImFontBaked are only valid for the current frame. +struct ImFontBaked { // [Internal] Members: Hot ~20/24 bytes (for CalcTextSize) ImVector IndexAdvanceX; // 12-16 // out // Sparse. Glyphs->AdvanceX in a directly indexable way (cache-friendly for CalcTextSize functions which only this info, and are often bottleneck in large UI). - float FallbackAdvanceX; // 4 // out // = FallbackGlyph->AdvanceX - float FontSize; // 4 // in // Height of characters/line, set during loading (don't change after loading) - // [Internal] Members: Hot ~28/40 bytes (for RenderText loop) + float FallbackAdvanceX; // 4 // out // FindGlyph(FallbackChar)->AdvanceX + float Size; // 4 // in // Height of characters/line, set during loading (doesn't change after loading) + float RasterizerDensity; // 4 // in // Density this is baked at + + // [Internal] Members: Hot ~28/36 bytes (for RenderText loop) ImVector IndexLookup; // 12-16 // out // Sparse. Index glyphs by Unicode code-point. ImVector Glyphs; // 12-16 // out // All glyphs. - ImFontGlyph* FallbackGlyph; // 4-8 // out // = FindGlyph(FontFallbackChar) - // [Internal] Members: Cold ~32/40 bytes + int FallbackGlyphIndex; // 4 // out // Index of FontFallbackChar + + // [Internal] Members: Cold + float Ascent, Descent; // 4+4 // out // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) + unsigned int MetricsTotalSurface:26;// 3 // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) + unsigned int WantDestroy:1; // 0 // // Queued for destroy + unsigned int LoadNoFallback:1; // 0 // // Disable loading fallback in lower-level calls. + unsigned int LoadNoRenderOnLayout:1;// 0 // // Enable a two-steps mode where CalcTextSize() calls will load AdvanceX *without* rendering/packing glyphs. Only advantagous if you know that the glyph is unlikely to actually be rendered, otherwise it is slower because we'd do one query on the first CalcTextSize and one query on the first Draw. + int LastUsedFrame; // 4 // // Record of that time this was bounds + ImGuiID BakedId; // 4 // // Unique ID for this baked storage + ImFont* ContainerFont; // 4-8 // in // Parent font + void* FontLoaderDatas; // 4-8 // // Font loader opaque storage (per baked font * sources): single contiguous buffer allocated by imgui, passed to loader. + + // Functions + IMGUI_API ImFontBaked(); + IMGUI_API void ClearOutputData(); + IMGUI_API ImFontGlyph* FindGlyph(ImWchar c); // Return U+FFFD glyph if requested glyph doesn't exists. + IMGUI_API ImFontGlyph* FindGlyphNoFallback(ImWchar c); // Return NULL if glyph doesn't exist + IMGUI_API float GetCharAdvance(ImWchar c); + IMGUI_API bool IsGlyphLoaded(ImWchar c); +}; + +// Font flags +// (in future versions as we redesign font loading API, this will become more important and better documented. for now please consider this as internal/advanced use) +enum ImFontFlags_ +{ + ImFontFlags_None = 0, + ImFontFlags_NoLoadError = 1 << 1, // Disable throwing an error/assert when calling AddFontXXX() with missing file/data. Calling code is expected to check AddFontXXX() return value. + ImFontFlags_NoLoadGlyphs = 1 << 2, // [Internal] Disable loading new glyphs. + ImFontFlags_LockBakedSizes = 1 << 3, // [Internal] Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. Important: if you use this to preload given sizes, consider the possibility of multiple font density used on Retina display. +}; + +// Font runtime data and rendering +// - ImFontAtlas automatically loads a default embedded font for you if you didn't load one manually. +// - Since 1.92.X a font may be rendered as any size! Therefore a font doesn't have one specific size. +// - Use 'font->GetFontBaked(size)' to retrieve the ImFontBaked* corresponding to a given size. +// - If you used g.Font + g.FontSize (which is frequent from the ImGui layer), you can use g.FontBaked as a shortcut, as g.FontBaked == g.Font->GetFontBaked(g.FontSize). +struct ImFont +{ + // [Internal] Members: Hot ~12-20 bytes + ImFontBaked* LastBaked; // 4-8 // Cache last bound baked. NEVER USE DIRECTLY. Use GetFontBaked(). + ImFontAtlas* ContainerAtlas; // 4-8 // What we have been loaded into. + ImFontFlags Flags; // 4 // Font flags. + float CurrentRasterizerDensity; // Current rasterizer density. This is a varying state of the font. + + // [Internal] Members: Cold ~24-52 bytes // Conceptually Sources[] is the list of font sources merged to create this font. - ImFontAtlas* ContainerAtlas; // 4-8 // out // What we has been loaded into - ImFontConfig* Sources; // 4-8 // in // Pointer within ContainerAtlas->Sources[], to SourcesCount instances - short SourcesCount; // 2 // in // Number of ImFontConfig involved in creating this font. Usually 1, or >1 when merging multiple font sources into one ImFont. - short EllipsisCharCount; // 1 // out // 1 or 3 + ImGuiID FontId; // Unique identifier for the font + float LegacySize; // 4 // in // Font size passed to AddFont(). Use for old code calling PushFont() expecting to use that size. (use ImGui::GetFontBaked() to get font baked at current bound size). + ImVector Sources; // 16 // in // List of sources. Pointers within ContainerAtlas->Sources[] ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') - float EllipsisWidth; // 4 // out // Total ellipsis Width - float EllipsisCharStep; // 4 // out // Step between characters when EllipsisCount > 0 - float Scale; // 4 // in // Base font scale (1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() - float Ascent, Descent; // 4+4 // out // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) - int MetricsTotalSurface;// 4 // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) - bool DirtyLookupTables; // 1 // out // ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. + bool EllipsisAutoBake; // 1 // // Mark when the "..." glyph needs to be generated. + ImGuiStorage RemapPairs; // 16 // // Remapping pairs when using AddRemapChar(), otherwise empty. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + float Scale; // 4 // in // Legacy base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() +#endif + // Methods IMGUI_API ImFont(); IMGUI_API ~ImFont(); - IMGUI_API ImFontGlyph* FindGlyph(ImWchar c); - IMGUI_API ImFontGlyph* FindGlyphNoFallback(ImWchar c); - float GetCharAdvance(ImWchar c) { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } + IMGUI_API bool IsGlyphInFont(ImWchar c); bool IsLoaded() const { return ContainerAtlas != NULL; } - const char* GetDebugName() const { return Sources ? Sources->Name : ""; } + const char* GetDebugName() const { return Sources.Size ? Sources[0]->Name : ""; } // Fill ImFontConfig::Name. + // [Internal] Don't use! // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. + IMGUI_API ImFontBaked* GetFontBaked(float font_size, float density = -1.0f); // Get or create baked data for given size IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL); // utf8 - IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width); - IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c); + IMGUI_API const char* CalcWordWrapPosition(float size, const char* text, const char* text_end, float wrap_width); + IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c, const ImVec4* cpu_fine_clip = NULL); IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + inline const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { return CalcWordWrapPosition(LegacySize * scale, text, text_end, wrap_width); } +#endif + // [Internal] Don't use! - IMGUI_API void BuildLookupTable(); IMGUI_API void ClearOutputData(); - IMGUI_API void GrowIndex(int new_size); - IMGUI_API void AddGlyph(const ImFontConfig* src_cfg, ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x); - IMGUI_API void AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built. + IMGUI_API void AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint); // Makes 'from_codepoint' character points to 'to_codepoint' glyph. IMGUI_API bool IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last); }; + +// This is provided for consistency (but we don't actually use this) +inline ImTextureID ImTextureRef::GetTexID() const +{ + IM_ASSERT(!(_TexData != NULL && _TexID != ImTextureID_Invalid)); + return _TexData ? _TexData->TexID : _TexID; +} + +// Using an indirection to avoid patching ImDrawCmd after a SetTexID() call (but this could be an alternative solution too) +inline ImTextureID ImDrawCmd::GetTexID() const +{ + // If you are getting this assert: A renderer backend with support for ImGuiBackendFlags_RendererHasTextures (1.92) + // must iterate and handle ImTextureData requests stored in ImDrawData::Textures[]. + ImTextureID tex_id = TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID; // == TexRef.GetTexID() above. + if (TexRef._TexData != NULL) + IM_ASSERT(tex_id != ImTextureID_Invalid && "ImDrawCmd is referring to ImTextureData that wasn't uploaded to graphics system. Backend must call ImTextureData::SetTexID() after handling ImTextureStatus_WantCreate request!"); + return tex_id; +} + //----------------------------------------------------------------------------- // [SECTION] Viewports //----------------------------------------------------------------------------- + // Flags stored in ImGuiViewport::Flags, giving indications to the platform backends. enum ImGuiViewportFlags_ { @@ -3328,10 +3994,12 @@ enum ImGuiViewportFlags_ ImGuiViewportFlags_NoAutoMerge = 1 << 9, // Platform Window: Avoid merging this window into another host window. This can only be set via ImGuiWindowClass viewport flags override (because we need to now ahead if we are going to create a viewport in the first place!). ImGuiViewportFlags_TopMost = 1 << 10, // Platform Window: Display on top (for tooltips only). ImGuiViewportFlags_CanHostOtherWindows = 1 << 11, // Viewport can host multiple imgui windows (secondary viewports are associated to a single window). // FIXME: In practice there's still probably code making the assumption that this is always and only on the MainViewport. Will fix once we add support for "no main viewport". + // Output status flags (from Platform) ImGuiViewportFlags_IsMinimized = 1 << 12, // Platform Window: Window is minimized, can skip render. When minimized we tend to avoid using the viewport pos/size for clipping window or testing if they are contained in the viewport. ImGuiViewportFlags_IsFocused = 1 << 13, // Platform Window: Window is focused (last call to Platform_GetWindowFocus() returned true) }; + // - Currently represents the Platform Window created by the application which is hosting our Dear ImGui windows. // - With multi-viewport enabled, we extend this concept to have multiple active viewports. // - In the future we will extend this concept further to also represent Platform Monitor and support a "no main platform window" operation mode. @@ -3345,11 +4013,13 @@ struct ImGuiViewport ImGuiViewportFlags Flags; // See ImGuiViewportFlags_ ImVec2 Pos; // Main Area: Position of the viewport (Dear ImGui coordinates are the same as OS desktop/native coordinates) ImVec2 Size; // Main Area: Size of the viewport. + ImVec2 FramebufferScale; // Density of the viewport for Retina display (always 1,1 on Windows, may be 2,2 etc on macOS/iOS). This will affect font rasterizer density. ImVec2 WorkPos; // Work Area: Position of the viewport minus task bars, menus bars, status bars (>= Pos) ImVec2 WorkSize; // Work Area: Size of the viewport minus task bars, menu bars, status bars (<= Size) float DpiScale; // 1.0f = 96 DPI = No extra scale. ImGuiID ParentViewportId; // (Advanced) 0: no parent. Instruct the platform backend to setup a parent/child relationship between platform windows. ImDrawData* DrawData; // The ImDrawData corresponding to this viewport. Valid after Render() and until the next call to NewFrame(). + // Platform/Backend Dependent Data // Our design separate the Renderer and Platform backends to facilitate combining default backends with each others. // When our create your own backend for a custom engine, it is possible that both Renderer and Platform will be handled @@ -3363,15 +4033,19 @@ struct ImGuiViewport bool PlatformRequestMove; // Platform window requested move (e.g. window was moved by the OS / host window manager, authoritative position will be OS window position) bool PlatformRequestResize; // Platform window requested resize (e.g. window was resized by the OS / host window manager, authoritative size will be OS window size) bool PlatformRequestClose; // Platform window requested closure (e.g. window was moved by the OS / host window manager, e.g. pressing ALT-F4) + ImGuiViewport() { memset(this, 0, sizeof(*this)); } ~ImGuiViewport() { IM_ASSERT(PlatformUserData == NULL && RendererUserData == NULL); } + // Helpers ImVec2 GetCenter() const { return ImVec2(Pos.x + Size.x * 0.5f, Pos.y + Size.y * 0.5f); } ImVec2 GetWorkCenter() const { return ImVec2(WorkPos.x + WorkSize.x * 0.5f, WorkPos.y + WorkSize.y * 0.5f); } }; + //----------------------------------------------------------------------------- // [SECTION] ImGuiPlatformIO + other Platform Dependent Interfaces (ImGuiPlatformMonitor, ImGuiPlatformImeData) //----------------------------------------------------------------------------- + // [BETA] (Optional) Multi-Viewport Support! // If you are new to Dear ImGui and trying to integrate it into your engine, you can probably ignore this for now. // @@ -3417,38 +4091,52 @@ struct ImGuiViewport // You may decide to setup the platform_io's *RenderWindow and *SwapBuffers pointers and call your functions through those pointers, // or you may decide to never setup those pointers and call your code directly. They are a convenience, not an obligatory interface. //----------------------------------------------------------------------------- + // Access via ImGui::GetPlatformIO() struct ImGuiPlatformIO { IMGUI_API ImGuiPlatformIO(); + //------------------------------------------------------------------ // Input - Interface with OS and Platform backend (most common stuff) //------------------------------------------------------------------ + // Optional: Access OS clipboard // (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures) const char* (*Platform_GetClipboardTextFn)(ImGuiContext* ctx); void (*Platform_SetClipboardTextFn)(ImGuiContext* ctx, const char* text); void* Platform_ClipboardUserData; + // Optional: Open link/folder/file in OS Shell // (default to use ShellExecuteW() on Windows, system() on Linux/Mac) bool (*Platform_OpenInShellFn)(ImGuiContext* ctx, const char* path); void* Platform_OpenInShellUserData; + // Optional: Notify OS Input Method Editor of the screen position of your cursor for text input position (e.g. when using Japanese/Chinese IME on Windows) // (default to use native imm32 api on Windows) void (*Platform_SetImeDataFn)(ImGuiContext* ctx, ImGuiViewport* viewport, ImGuiPlatformImeData* data); void* Platform_ImeUserData; //void (*SetPlatformImeDataFn)(ImGuiViewport* viewport, ImGuiPlatformImeData* data); // [Renamed to platform_io.PlatformSetImeDataFn in 1.91.1] + // Optional: Platform locale // [Experimental] Configure decimal point e.g. '.' or ',' useful for some languages (e.g. German), generally pulled from *localeconv()->decimal_point ImWchar Platform_LocaleDecimalPoint; // '.' + //------------------------------------------------------------------ // Input - Interface with Renderer Backend //------------------------------------------------------------------ + + // Optional: Maximum texture size supported by renderer (used to adjust how we size textures). 0 if not known. + int Renderer_TextureMaxWidth; + int Renderer_TextureMaxHeight; + // Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure. void* Renderer_RenderState; + //------------------------------------------------------------------ // Input - Interface with Platform & Renderer backends for Multi-Viewport support //------------------------------------------------------------------ + // For reference, the second column shows which function are generally calling the Platform Functions: // N = ImGui::NewFrame() ~ beginning of the dear imgui frame: read info from platform/OS windows (latest size/position) // F = ImGui::Begin(), ImGui::EndFrame() ~ during the dear imgui frame @@ -3456,9 +4144,11 @@ struct ImGuiPlatformIO // R = ImGui::RenderPlatformWindowsDefault() ~ render // D = ImGui::DestroyPlatformWindows() ~ shutdown // The general idea is that NewFrame() we will read the current Platform/OS state, and UpdatePlatformWindows() will write to it. + // The handlers are designed so we can mix and match two imgui_impl_xxxx files, one Platform backend and one Renderer backend. // Custom engine backends will often provide both Platform and Renderer interfaces together and so may not need to use all functions. // Platform functions are typically called _before_ their Renderer counterpart, apart from Destroy which are called the other way. + // Platform Backend functions (e.g. Win32, GLFW, SDL) ------------------- Called by ----- void (*Platform_CreateWindow)(ImGuiViewport* vp); // . . U . . // Create a new platform window for the given viewport void (*Platform_DestroyWindow)(ImGuiViewport* vp); // N . U . D // @@ -3467,6 +4157,7 @@ struct ImGuiPlatformIO ImVec2 (*Platform_GetWindowPos)(ImGuiViewport* vp); // N . . . . // void (*Platform_SetWindowSize)(ImGuiViewport* vp, ImVec2 size); // . . U . . // Set platform window client area size (ignoring OS decorations such as OS title bar etc.) ImVec2 (*Platform_GetWindowSize)(ImGuiViewport* vp); // N . . . . // Get platform window client area size + ImVec2 (*Platform_GetWindowFramebufferScale)(ImGuiViewport* vp); // N . . . . // Return viewport density. Always 1,1 on Windows, often 2,2 on Retina display on macOS/iOS. MUST BE INTEGER VALUES. void (*Platform_SetWindowFocus)(ImGuiViewport* vp); // N . . . . // Move window to front and set input focus bool (*Platform_GetWindowFocus)(ImGuiViewport* vp); // . . U . . // bool (*Platform_GetWindowMinimized)(ImGuiViewport* vp); // N . . . . // Get platform window minimized state. When minimized, we generally won't attempt to get/set size and contents will be culled more easily @@ -3479,23 +4170,32 @@ struct ImGuiPlatformIO void (*Platform_OnChangedViewport)(ImGuiViewport* vp); // . F . . . // (Optional) [BETA] FIXME-DPI: DPI handling: Called during Begin() every time the viewport we are outputting into changes, so backend has a chance to swap fonts to adjust style. ImVec4 (*Platform_GetWindowWorkAreaInsets)(ImGuiViewport* vp); // N . . . . // (Optional) [BETA] Get initial work area inset for the viewport (won't be covered by main menu bar, dockspace over viewport etc.). Default to (0,0),(0,0). 'safeAreaInsets' in iOS land, 'DisplayCutout' in Android land. int (*Platform_CreateVkSurface)(ImGuiViewport* vp, ImU64 vk_inst, const void* vk_allocators, ImU64* out_vk_surface); // (Optional) For a Vulkan Renderer to call into Platform code (since the surface creation needs to tie them both). + // Renderer Backend functions (e.g. DirectX, OpenGL, Vulkan) ------------ Called by ----- void (*Renderer_CreateWindow)(ImGuiViewport* vp); // . . U . . // Create swap chain, frame buffers etc. (called after Platform_CreateWindow) void (*Renderer_DestroyWindow)(ImGuiViewport* vp); // N . U . D // Destroy swap chain, frame buffers etc. (called before Platform_DestroyWindow) void (*Renderer_SetWindowSize)(ImGuiViewport* vp, ImVec2 size); // . . U . . // Resize swap chain, frame buffers etc. (called after Platform_SetWindowSize) void (*Renderer_RenderWindow)(ImGuiViewport* vp, void* render_arg); // . . . R . // (Optional) Clear framebuffer, setup render target, then render the viewport->DrawData. 'render_arg' is the value passed to RenderPlatformWindowsDefault(). void (*Renderer_SwapBuffers)(ImGuiViewport* vp, void* render_arg); // . . . R . // (Optional) Call Present/SwapBuffers. 'render_arg' is the value passed to RenderPlatformWindowsDefault(). + // (Optional) Monitor list // - Updated by: app/backend. Update every frame to dynamically support changing monitor or DPI configuration. // - Used by: dear imgui to query DPI info, clamp popups/tooltips within same monitor and not have them straddle monitors. ImVector Monitors; + //------------------------------------------------------------------ - // Output - List of viewports to render into platform windows + // Output //------------------------------------------------------------------ + + // Textures list (the list is updated by calling ImGui::EndFrame or ImGui::Render) + // The ImGui_ImplXXXX_RenderDrawData() function of each backend generally access this via ImDrawData::Textures which points to this. The array is available here mostly because backends will want to destroy textures on shutdown. + ImVector Textures; // List of textures used by Dear ImGui (most often 1) + contents of external texture list is automatically appended into this. + // Viewports list (the list is updated by calling ImGui::EndFrame or ImGui::Render) // (in the future we will attempt to organize this feature to remove the need for a "main viewport") ImVector Viewports; // Main viewports, followed by all secondary viewports. }; + // (Optional) This is required when enabling multi-viewport. Represent the bounds of each connected monitor/display and their DPI. // We use this information for multiple DPI support + clamping the position of popups and tooltips so they don't straddle multiple monitors. struct ImGuiPlatformMonitor @@ -3506,46 +4206,56 @@ struct ImGuiPlatformMonitor void* PlatformHandle; // Backend dependant data (e.g. HMONITOR, GLFWmonitor*, SDL Display Index, NSScreen*) ImGuiPlatformMonitor() { MainPos = MainSize = WorkPos = WorkSize = ImVec2(0, 0); DpiScale = 1.0f; PlatformHandle = NULL; } }; -// (Optional) Support for IME (Input Method Editor) via the platform_io.Platform_SetImeDataFn() function. + +// (Optional) Support for IME (Input Method Editor) via the platform_io.Platform_SetImeDataFn() function. Handler is called during EndFrame(). struct ImGuiPlatformImeData { - bool WantVisible; // A widget wants the IME to be visible - ImVec2 InputPos; // Position of the input cursor - float InputLineHeight; // Line height + bool WantVisible; // A widget wants the IME to be visible. + bool WantTextInput; // A widget wants text input, not necessarily IME to be visible. This is automatically set to the upcoming value of io.WantTextInput. + ImVec2 InputPos; // Position of input cursor (for IME). + float InputLineHeight; // Line height (for IME). + ImGuiID ViewportId; // ID of platform window/viewport. + ImGuiPlatformImeData() { memset(this, 0, sizeof(*this)); } }; + //----------------------------------------------------------------------------- // [SECTION] Obsolete functions and types // (Will be removed! Read 'API BREAKING CHANGES' section in imgui.cpp for details) // Please keep your copy of dear imgui up to date! Occasionally set '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' in imconfig.h to stay ahead. //----------------------------------------------------------------------------- + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS namespace ImGui { + // OBSOLETED in 1.92.0 (from June 2025) + inline void PushFont(ImFont* font) { PushFont(font, font ? font->LegacySize : 0.0f); } + IMGUI_API void SetWindowFontScale(float scale); // Set font scale factor for current window. Prefer using PushFont(NULL, style.FontSizeBase * factor) or use style.FontScaleMain to scale all windows. // OBSOLETED in 1.91.9 (from February 2025) - IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col); // <-- border_col was removed in favor of ImGuiCol_ImageBorder. + IMGUI_API void Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col); // <-- 'border_col' was removed in favor of ImGuiCol_ImageBorder. If you use 'tint_col', use ImageWithBg() instead. // OBSOLETED in 1.91.0 (from July 2024) - static inline void PushButtonRepeat(bool repeat) { PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); } - static inline void PopButtonRepeat() { PopItemFlag(); } - static inline void PushTabStop(bool tab_stop) { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); } - static inline void PopTabStop() { PopItemFlag(); } + inline void PushButtonRepeat(bool repeat) { PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); } + inline void PopButtonRepeat() { PopItemFlag(); } + inline void PushTabStop(bool tab_stop) { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); } + inline void PopTabStop() { PopItemFlag(); } IMGUI_API ImVec2 GetContentRegionMax(); // Content boundaries max (e.g. window boundaries including scrolling, or current column boundaries). You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()! IMGUI_API ImVec2 GetWindowContentRegionMin(); // Content boundaries min for the window (roughly (0,0)-Scroll), in window-local coordinates. You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()! IMGUI_API ImVec2 GetWindowContentRegionMax(); // Content boundaries max for the window (roughly (0,0)+Size-Scroll), in window-local coordinates. You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()! // OBSOLETED in 1.90.0 (from September 2023) - static inline bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags window_flags = 0) { return BeginChild(id, size, ImGuiChildFlags_FrameStyle, window_flags); } - static inline void EndChildFrame() { EndChild(); } - //static inline bool BeginChild(const char* str_id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags){ return BeginChild(str_id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders - //static inline bool BeginChild(ImGuiID id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags) { return BeginChild(id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders - static inline void ShowStackToolWindow(bool* p_open = NULL) { ShowIDStackToolWindow(p_open); } + inline bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags window_flags = 0) { return BeginChild(id, size, ImGuiChildFlags_FrameStyle, window_flags); } + inline void EndChildFrame() { EndChild(); } + //inline bool BeginChild(const char* str_id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags){ return BeginChild(str_id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders + //inline bool BeginChild(ImGuiID id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags) { return BeginChild(id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders + inline void ShowStackToolWindow(bool* p_open = NULL) { ShowIDStackToolWindow(p_open); } IMGUI_API bool Combo(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int popup_max_height_in_items = -1); IMGUI_API bool ListBox(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int height_in_items = -1); // OBSOLETED in 1.89.7 (from June 2023) IMGUI_API void SetItemAllowOverlap(); // Use SetNextItemAllowOverlap() before item. - // OBSOLETED in 1.89.4 (from March 2023) - static inline void PushAllowKeyboardFocus(bool tab_stop) { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); } - static inline void PopAllowKeyboardFocus() { PopItemFlag(); } + // Some of the older obsolete names along with their replacement (commented out so they are not reported in IDE) + //-- OBSOLETED in 1.89.4 (from March 2023) + //static inline void PushAllowKeyboardFocus(bool tab_stop) { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); } + //static inline void PopAllowKeyboardFocus() { PopItemFlag(); } //-- OBSOLETED in 1.89 (from August 2022) //IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); // --> Use new ImageButton() signature (explicit item id, regular FramePadding). Refer to code in 1.91 if you want to grab a copy of this version. //-- OBSOLETED in 1.88 (from May 2022) @@ -3607,6 +4317,26 @@ namespace ImGui //static inline float GetWindowFontSize() { return GetFontSize(); } // OBSOLETED in 1.48 //static inline void SetScrollPosHere() { SetScrollHere(); } // OBSOLETED in 1.42 } + +//-- OBSOLETED in 1.92.x: ImFontAtlasCustomRect becomes ImTextureRect +// - ImFontAtlasCustomRect::X,Y --> ImTextureRect::x,y +// - ImFontAtlasCustomRect::Width,Height --> ImTextureRect::w,h +// - ImFontAtlasCustomRect::GlyphColored --> if you need to write to this, instead you can write to 'font->Glyphs.back()->Colored' after calling AddCustomRectFontGlyph() +// We could make ImTextureRect an union to use old names, but 1) this would be confusing 2) the fix is easy 3) ImFontAtlasCustomRect was always a rather esoteric api. +typedef ImFontAtlasRect ImFontAtlasCustomRect; +/*struct ImFontAtlasCustomRect +{ + unsigned short X, Y; // Output // Packed position in Atlas + unsigned short Width, Height; // Input // [Internal] Desired rectangle dimension + unsigned int GlyphID:31; // Input // [Internal] For custom font glyphs only (ID < 0x110000) + unsigned int GlyphColored:1; // Input // [Internal] For custom font glyphs only: glyph is colored, removed tinting. + float GlyphAdvanceX; // Input // [Internal] For custom font glyphs only: glyph xadvance + ImVec2 GlyphOffset; // Input // [Internal] For custom font glyphs only: glyph display offset + ImFont* Font; // Input // [Internal] For custom font glyphs only: target font + ImFontAtlasCustomRect() { X = Y = 0xFFFF; Width = Height = 0; GlyphID = 0; GlyphColored = 0; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0, 0); Font = NULL; } + bool IsPacked() const { return X != 0xFFFF; } +};*/ + //-- OBSOLETED in 1.82 (from Mars 2021): flags for AddRect(), AddRectFilled(), AddImageRounded(), PathRect() //typedef ImDrawFlags ImDrawCornerFlags; //enum ImDrawCornerFlags_ @@ -3622,27 +4352,35 @@ namespace ImGui // ImDrawCornerFlags_Left = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft, // ImDrawCornerFlags_Right = ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight, //}; + // RENAMED and MERGED both ImGuiKey_ModXXX and ImGuiModFlags_XXX into ImGuiMod_XXX (from September 2022) // RENAMED ImGuiKeyModFlags -> ImGuiModFlags in 1.88 (from April 2022). Exceptionally commented out ahead of obscolescence schedule to reduce confusion and because they were not meant to be used in the first place. //typedef ImGuiKeyChord ImGuiModFlags; // == int. We generally use ImGuiKeyChord to mean "a ImGuiKey or-ed with any number of ImGuiMod_XXX value", so you may store mods in there. //enum ImGuiModFlags_ { ImGuiModFlags_None = 0, ImGuiModFlags_Ctrl = ImGuiMod_Ctrl, ImGuiModFlags_Shift = ImGuiMod_Shift, ImGuiModFlags_Alt = ImGuiMod_Alt, ImGuiModFlags_Super = ImGuiMod_Super }; //typedef ImGuiKeyChord ImGuiKeyModFlags; // == int //enum ImGuiKeyModFlags_ { ImGuiKeyModFlags_None = 0, ImGuiKeyModFlags_Ctrl = ImGuiMod_Ctrl, ImGuiKeyModFlags_Shift = ImGuiMod_Shift, ImGuiKeyModFlags_Alt = ImGuiMod_Alt, ImGuiKeyModFlags_Super = ImGuiMod_Super }; + #define IM_OFFSETOF(_TYPE,_MEMBER) offsetof(_TYPE, _MEMBER) // OBSOLETED IN 1.90 (now using C++11 standard version) + #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // RENAMED IMGUI_DISABLE_METRICS_WINDOW > IMGUI_DISABLE_DEBUG_TOOLS in 1.88 (from June 2022) #ifdef IMGUI_DISABLE_METRICS_WINDOW #error IMGUI_DISABLE_METRICS_WINDOW was renamed to IMGUI_DISABLE_DEBUG_TOOLS, please use new name. #endif + //----------------------------------------------------------------------------- + #if defined(__clang__) #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif + #ifdef _MSC_VER #pragma warning (pop) #endif + // Include imgui_user.h at the end of imgui.h // May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included. #ifdef IMGUI_INCLUDE_IMGUI_USER_H @@ -3652,4 +4390,5 @@ namespace ImGui #include "imgui_user.h" #endif #endif + #endif // #ifndef IMGUI_DISABLE diff --git a/external/reshade/deps/imgui/imgui_demo.cpp b/external/reshade/deps/imgui/imgui_demo.cpp index 6980de6..de2bdee 100644 --- a/external/reshade/deps/imgui/imgui_demo.cpp +++ b/external/reshade/deps/imgui/imgui_demo.cpp @@ -1,5 +1,6 @@ -// dear imgui, v1.91.9b +// dear imgui, v1.92.2b // (demo code) + // Help: // - Read FAQ at http://dearimgui.com/faq // - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that. @@ -8,10 +9,12 @@ // - Read 'Programmer guide' in imgui.cpp for notes on how to setup Dear ImGui in your codebase. // Read top of imgui.cpp and imgui.h for many details, documentation, comments, links. // Get the latest version at https://github.com/ocornut/imgui + // How to easily locate code? // - Use Tools->Item Picker to debug break in code by clicking any widgets: https://github.com/ocornut/imgui/wiki/Debug-Tools // - Browse an online version the demo with code linked to hovered widgets: https://pthom.github.io/imgui_manual_online/manual/imgui_manual.html // - Find a visible string and search for it in the code! + //--------------------------------------------------- // PLEASE DO NOT REMOVE THIS FILE FROM YOUR PROJECT! //--------------------------------------------------- @@ -27,6 +30,7 @@ // In another situation, whenever you have Dear ImGui available you probably want this to be available for reference. // Thank you, // -Your beloved friend, imgui_demo.cpp (which you won't delete) + //-------------------------------------------- // ABOUT THE MEANING OF THE 'static' KEYWORD: //-------------------------------------------- @@ -40,6 +44,7 @@ // doesn't need to be reentrant or used in multiple threads. // This might be a pattern you will want to use in your code, but most of the data you would be working // with in a complex codebase is likely going to be stored outside your functions. + //----------------------------------------- // ABOUT THE CODING STYLE OF OUR DEMO CODE //----------------------------------------- @@ -52,13 +57,17 @@ // by imgui.h using the IMGUI_DEFINE_MATH_OPERATORS define. For your own sources file they are optional // and require you either enable those, either provide your own via IM_VEC2_CLASS_EXTRA in imconfig.h. // Because we can't assume anything about your support of maths operators, we cannot use them in imgui_demo.cpp. + // Navigating this file: // - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. // - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. // - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. // - You can search/grep for all sections listed in the index to find the section. + /* + Index of this file: + // [SECTION] Forward Declarations // [SECTION] Helpers // [SECTION] Demo Window / ShowDemoWindow() @@ -73,6 +82,7 @@ Index of this file: // [SECTION] DemoWindowWidgetsDisableBlocks() // [SECTION] DemoWindowWidgetsDragAndDrop() // [SECTION] DemoWindowWidgetsDragsAndSliders() +// [SECTION] DemoWindowWidgetsFonts() // [SECTION] DemoWindowWidgetsImages() // [SECTION] DemoWindowWidgetsListBoxes() // [SECTION] DemoWindowWidgetsMultiComponents() @@ -111,12 +121,16 @@ Index of this file: // [SECTION] Example App: Docking, DockSpace / ShowExampleAppDockSpace() // [SECTION] Example App: Documents Handling / ShowExampleAppDocuments() // [SECTION] Example App: Assets Browser / ShowExampleAppAssetsBrowser() + */ + #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif + #include "imgui.h" #ifndef IMGUI_DISABLE + // System includes #include // toupper #include // INT_MIN, INT_MAX @@ -130,12 +144,14 @@ Index of this file: #ifdef __EMSCRIPTEN__ #include // __EMSCRIPTEN_major__ etc. #endif + // Visual Studio warnings #ifdef _MSC_VER #pragma warning (disable: 4127) // condition expression is constant #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen #pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to an 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). #endif + // Clang/GCC warnings with -Weverything #if defined(__clang__) #if __has_warning("-Wunknown-warning-option") @@ -167,12 +183,14 @@ Index of this file: #pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1 #pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers #endif + // Play it nice with Windows users (Update: May 2018, Notepad now supports Unix-style carriage returns!) #ifdef _WIN32 #define IM_NEWLINE "\r\n" #else #define IM_NEWLINE "\n" #endif + // Helpers #if defined(_MSC_VER) && !defined(snprintf) #define snprintf _snprintf @@ -180,6 +198,7 @@ Index of this file: #if defined(_MSC_VER) && !defined(vsnprintf) #define vsnprintf _vsnprintf #endif + // Format specifiers for 64-bit values (hasn't been decently standardized before VS2013) #if !defined(PRId64) && defined(_MSC_VER) #define PRId64 "I64d" @@ -188,6 +207,7 @@ Index of this file: #define PRId64 "lld" #define PRIu64 "llu" #endif + // Helpers macros // We normally try to not use many helpers in imgui_demo.cpp in order to make code easier to copy and paste, // but making an exception here as those are largely simplifying code... @@ -195,6 +215,7 @@ Index of this file: #define IM_MIN(A, B) (((A) < (B)) ? (A) : (B)) #define IM_MAX(A, B) (((A) >= (B)) ? (A) : (B)) #define IM_CLAMP(V, MN, MX) ((V) < (MN) ? (MN) : (V) > (MX) ? (MX) : (V)) + // Enforce cdecl calling convention for functions called by the standard library, // in case compilation settings changed the default to e.g. __vectorcall #ifndef IMGUI_CDECL @@ -204,10 +225,13 @@ Index of this file: #define IMGUI_CDECL #endif #endif + //----------------------------------------------------------------------------- // [SECTION] Forward Declarations //----------------------------------------------------------------------------- + #if !defined(IMGUI_DISABLE_DEMO_WINDOWS) + // Forward Declarations struct ImGuiDemoWindowData; static void ShowExampleAppMainMenuBar(); @@ -226,6 +250,7 @@ static void ShowExampleAppFullscreen(bool* p_open); static void ShowExampleAppLongText(bool* p_open); static void ShowExampleAppWindowTitles(bool* p_open); static void ShowExampleMenuFile(); + // We split the contents of the big ShowDemoWindow() function into smaller functions // (because the link time of very large functions tends to grow non-linearly) static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data); @@ -235,13 +260,16 @@ static void DemoWindowPopups(); static void DemoWindowTables(); static void DemoWindowColumns(); static void DemoWindowInputs(); + // Helper tree functions used by Property Editor & Multi-Select demos struct ExampleTreeNode; static ExampleTreeNode* ExampleTree_CreateNode(const char* name, int uid, ExampleTreeNode* parent); static void ExampleTree_DestroyNode(ExampleTreeNode* node); + //----------------------------------------------------------------------------- // [SECTION] Helpers //----------------------------------------------------------------------------- + // Helper to display a little (?) mark which shows a tooltip when hovered. // In your own code you may want to display an actual icon if you are using a merged icon fonts (see docs/FONTS.md) static void HelpMarker(const char* desc) @@ -255,6 +283,7 @@ static void HelpMarker(const char* desc) ImGui::EndTooltip(); } } + static void ShowDockingDisabledMessage() { ImGuiIO& io = ImGui::GetIO(); @@ -264,6 +293,7 @@ static void ShowDockingDisabledMessage() if (ImGui::SmallButton("click here")) io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; } + // Helper to wire demo markers located in code to an interactive browser typedef void (*ImGuiDemoMarkerCallback)(const char* file, int line, const char* section, void* user_data); extern ImGuiDemoMarkerCallback GImGuiDemoMarkerCallback; @@ -271,9 +301,11 @@ extern void* GImGuiDemoMarkerCallbackUserData; ImGuiDemoMarkerCallback GImGuiDemoMarkerCallback = NULL; void* GImGuiDemoMarkerCallbackUserData = NULL; #define IMGUI_DEMO_MARKER(section) do { if (GImGuiDemoMarkerCallback != NULL) GImGuiDemoMarkerCallback(__FILE__, __LINE__, section, GImGuiDemoMarkerCallbackUserData); } while (0) + //----------------------------------------------------------------------------- // [SECTION] Demo Window / ShowDemoWindow() //----------------------------------------------------------------------------- + // Data to be shared across different functions of the demo. struct ImGuiDemoWindowData { @@ -293,17 +325,21 @@ struct ImGuiDemoWindowData bool ShowAppFullscreen = false; bool ShowAppLongText = false; bool ShowAppWindowTitles = false; + // Dear ImGui Tools (accessible from the "Tools" menu) bool ShowMetrics = false; bool ShowDebugLog = false; bool ShowIDStackTool = false; bool ShowStyleEditor = false; bool ShowAbout = false; + // Other data bool DisableSections = false; ExampleTreeNode* DemoTree = NULL; + ~ImGuiDemoWindowData() { if (DemoTree) ExampleTree_DestroyNode(DemoTree); } }; + // Demonstrate most Dear ImGui features (this is big function!) // You may execute this function to experiment with the UI and understand what it does. // You may then search for keywords in the code when you are interested by a specific feature. @@ -312,10 +348,13 @@ void ImGui::ShowDemoWindow(bool* p_open) // Exceptionally add an extra assert here for people confused about initial Dear ImGui setup // Most functions would normally just assert/crash if the context is missing. IM_ASSERT(ImGui::GetCurrentContext() != NULL && "Missing Dear ImGui context. Refer to examples app!"); + // Verify ABI compatibility between caller code and compiled version of Dear ImGui. This helps detects some build issues. IMGUI_CHECKVERSION(); + // Stored data static ImGuiDemoWindowData demo_data; + // Examples Apps (accessible from the "Examples" menu) if (demo_data.ShowMainMenuBar) { ShowExampleAppMainMenuBar(); } if (demo_data.ShowAppDockSpace) { ShowExampleAppDockSpace(&demo_data.ShowAppDockSpace); } // Important: Process the Docking app first, as explicit DockSpace() nodes needs to be submitted early (read comments near the DockSpace function) @@ -332,6 +371,7 @@ void ImGui::ShowDemoWindow(bool* p_open) if (demo_data.ShowAppFullscreen) { ShowExampleAppFullscreen(&demo_data.ShowAppFullscreen); } if (demo_data.ShowAppLongText) { ShowExampleAppLongText(&demo_data.ShowAppLongText); } if (demo_data.ShowAppWindowTitles) { ShowExampleAppWindowTitles(&demo_data.ShowAppWindowTitles); } + // Dear ImGui Tools (accessible from the "Tools" menu) if (demo_data.ShowMetrics) { ImGui::ShowMetricsWindow(&demo_data.ShowMetrics); } if (demo_data.ShowDebugLog) { ImGui::ShowDebugLogWindow(&demo_data.ShowDebugLog); } @@ -343,6 +383,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::ShowStyleEditor(); ImGui::End(); } + // Demonstrate the various window flags. Typically you would just use the default! static bool no_titlebar = false; static bool no_scrollbar = false; @@ -356,6 +397,7 @@ void ImGui::ShowDemoWindow(bool* p_open) static bool no_bring_to_front = false; static bool no_docking = false; static bool unsaved_document = false; + ImGuiWindowFlags window_flags = 0; if (no_titlebar) window_flags |= ImGuiWindowFlags_NoTitleBar; if (no_scrollbar) window_flags |= ImGuiWindowFlags_NoScrollbar; @@ -369,11 +411,13 @@ void ImGui::ShowDemoWindow(bool* p_open) if (no_docking) window_flags |= ImGuiWindowFlags_NoDocking; if (unsaved_document) window_flags |= ImGuiWindowFlags_UnsavedDocument; if (no_close) p_open = NULL; // Don't pass our bool* to Begin + // We specify a default position/size in case there's no data in the .ini file. // We only do it to make the demo applications a little more welcoming, but typically this isn't required. const ImGuiViewport* main_viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(ImVec2(main_viewport->WorkPos.x + 650, main_viewport->WorkPos.y + 20), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(550, 680), ImGuiCond_FirstUseEver); + // Main body of the Demo window starts here. if (!ImGui::Begin("Dear ImGui Demo", p_open, window_flags)) { @@ -381,13 +425,27 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::End(); return; } - // Most "big" widgets share a common width settings by default. See 'Demo->Layout->Widgets Width' for details. - ImGui::PushItemWidth(ImGui::GetFontSize() * -12); // e.g. Leave a fixed amount of width for labels (by passing a negative value), the rest goes to widgets. - //ImGui::PushItemWidth(-ImGui::GetWindowWidth() * 0.35f); // e.g. Use 2/3 of the space for widgets and 1/3 for labels (right align) + + // Most framed widgets share a common width settings. Remaining width is used for the label. + // The width of the frame may be changed with PushItemWidth() or SetNextItemWidth(). + // - Positive value for absolute size, negative value for right-alignment. + // - The default value is about GetWindowWidth() * 0.65f. + // - See 'Demo->Layout->Widgets Width' for details. + // Here we change the frame width based on how much width we want to give to the label. + const float label_width_base = ImGui::GetFontSize() * 12; // Some amount of width for label, based on font size. + const float label_width_max = ImGui::GetContentRegionAvail().x * 0.40f; // ...but always leave some room for framed widgets. + const float label_width = IM_MIN(label_width_base, label_width_max); + ImGui::PushItemWidth(-label_width); // Right-align: framed items will leave 'label_width' available for the label. + //ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.40f); // e.g. Use 40% width for framed widgets, leaving 60% width for labels. + //ImGui::PushItemWidth(-ImGui::GetContentRegionAvail().x * 0.40f); // e.g. Use 40% width for labels, leaving 60% width for framed widgets. + //ImGui::PushItemWidth(ImGui::GetFontSize() * -12); // e.g. Use XXX width for labels, leaving the rest for framed widgets. + // Menu Bar DemoWindowMenuBar(&demo_data); + ImGui::Text("dear imgui says hello! (%s) (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); ImGui::Spacing(); + IMGUI_DEMO_MARKER("Help"); if (ImGui::CollapsingHeader("Help")) { @@ -396,6 +454,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::BulletText("The \"Examples\" menu above leads to more demo contents."); ImGui::BulletText("The \"Tools\" menu above gives access to: About Box, Style Editor,\n" "and Metrics/Debugger (general purpose Dear ImGui debugging tool)."); + ImGui::SeparatorText("PROGRAMMER GUIDE:"); ImGui::BulletText("See the ShowDemoWindow() code in imgui_demo.cpp. <- you are here!"); ImGui::BulletText("See comments in imgui.cpp."); @@ -405,13 +464,16 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::TextLinkOpenURL("https://www.dearimgui.com/faq/"); ImGui::BulletText("Set 'io.ConfigFlags |= NavEnableKeyboard' for keyboard controls."); ImGui::BulletText("Set 'io.ConfigFlags |= NavEnableGamepad' for gamepad controls."); + ImGui::SeparatorText("USER GUIDE:"); ImGui::ShowUserGuide(); } + IMGUI_DEMO_MARKER("Configuration"); if (ImGui::CollapsingHeader("Configuration")) { ImGuiIO& io = ImGui::GetIO(); + if (ImGui::TreeNode("Configuration##2")) { ImGui::SeparatorText("General"); @@ -421,6 +483,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("Enable gamepad controls. Require backend to set io.BackendFlags |= ImGuiBackendFlags_HasGamepad.\n\nRead instructions in imgui.cpp for details."); ImGui::CheckboxFlags("io.ConfigFlags: NoMouse", &io.ConfigFlags, ImGuiConfigFlags_NoMouse); ImGui::SameLine(); HelpMarker("Instruct dear imgui to disable mouse inputs and interactions."); + // The "NoMouse" option can get us stuck with a disabled mouse! Let's provide an alternative way to fix it: if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) { @@ -433,14 +496,17 @@ void ImGui::ShowDemoWindow(bool* p_open) if (ImGui::IsKeyPressed(ImGuiKey_Space) || (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard)) io.ConfigFlags &= ~ImGuiConfigFlags_NoMouse; } + ImGui::CheckboxFlags("io.ConfigFlags: NoMouseCursorChange", &io.ConfigFlags, ImGuiConfigFlags_NoMouseCursorChange); ImGui::SameLine(); HelpMarker("Instruct backend to not alter mouse cursor shape and visibility."); ImGui::CheckboxFlags("io.ConfigFlags: NoKeyboard", &io.ConfigFlags, ImGuiConfigFlags_NoKeyboard); ImGui::SameLine(); HelpMarker("Instruct dear imgui to disable keyboard inputs and interactions."); + ImGui::Checkbox("io.ConfigInputTrickleEventQueue", &io.ConfigInputTrickleEventQueue); ImGui::SameLine(); HelpMarker("Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates."); ImGui::Checkbox("io.MouseDrawCursor", &io.MouseDrawCursor); ImGui::SameLine(); HelpMarker("Instruct Dear ImGui to render a mouse cursor itself. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something)."); + ImGui::SeparatorText("Keyboard/Gamepad Navigation"); ImGui::Checkbox("io.ConfigNavSwapGamepadButtons", &io.ConfigNavSwapGamepadButtons); ImGui::Checkbox("io.ConfigNavMoveSetMousePos", &io.ConfigNavMoveSetMousePos); @@ -454,6 +520,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("Using directional navigation key makes the cursor visible. Mouse click hides the cursor."); ImGui::Checkbox("io.ConfigNavCursorVisibleAlways", &io.ConfigNavCursorVisibleAlways); ImGui::SameLine(); HelpMarker("Navigation cursor is always visible."); + ImGui::SeparatorText("Docking"); ImGui::CheckboxFlags("io.ConfigFlags: DockingEnable", &io.ConfigFlags, ImGuiConfigFlags_DockingEnable); ImGui::SameLine(); @@ -474,6 +541,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("Make window or viewport transparent when docking and only display docking boxes on the target viewport. Useful if rendering of multiple viewport cannot be synced. Best used with ConfigViewportsNoAutoMerge."); ImGui::Unindent(); } + ImGui::SeparatorText("Multi-viewports"); ImGui::CheckboxFlags("io.ConfigFlags: ViewportsEnable", &io.ConfigFlags, ImGuiConfigFlags_ViewportsEnable); ImGui::SameLine(); HelpMarker("[beta] Enable beta multi-viewports support. See ImGuiPlatformIO for details."); @@ -488,8 +556,17 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("Toggling this at runtime is normally unsupported (most platform backends won't refresh the decoration right away)."); ImGui::Checkbox("io.ConfigViewportsNoDefaultParent", &io.ConfigViewportsNoDefaultParent); ImGui::SameLine(); HelpMarker("Toggling this at runtime is normally unsupported (most platform backends won't refresh the parenting right away)."); + ImGui::Checkbox("io.ConfigViewportPlatformFocusSetsImGuiFocus", &io.ConfigViewportPlatformFocusSetsImGuiFocus); + ImGui::SameLine(); HelpMarker("When a platform window is focused (e.g. using Alt+Tab, clicking Platform Title Bar), apply corresponding focus on imgui windows (may clear focus/active id from imgui windows location in other platform windows). In principle this is better enabled but we provide an opt-out, because some Linux window managers tend to eagerly focus windows (e.g. on mouse hover, or even a simple window pos/size change)."); ImGui::Unindent(); } + + //ImGui::SeparatorText("DPI/Scaling"); + //ImGui::Checkbox("io.ConfigDpiScaleFonts", &io.ConfigDpiScaleFonts); + //ImGui::SameLine(); HelpMarker("Experimental: Automatically update style.FontScaleDpi when Monitor DPI changes. This will scale fonts but NOT style sizes/padding for now."); + //ImGui::Checkbox("io.ConfigDpiScaleViewports", &io.ConfigDpiScaleViewports); + //ImGui::SameLine(); HelpMarker("Experimental: Scale Dear ImGui and Platform Windows when Monitor DPI changes."); + ImGui::SeparatorText("Windows"); ImGui::Checkbox("io.ConfigWindowsResizeFromEdges", &io.ConfigWindowsResizeFromEdges); ImGui::SameLine(); HelpMarker("Enable resizing of windows from their edges and from the lower-left corner.\nThis requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback."); @@ -498,6 +575,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("*EXPERIMENTAL* CTRL+C copy the contents of focused window into the clipboard.\n\nExperimental because:\n- (1) has known issues with nested Begin/End pairs.\n- (2) text output quality varies.\n- (3) text output is in submission order rather than spatial order."); ImGui::Checkbox("io.ConfigScrollbarScrollByPage", &io.ConfigScrollbarScrollByPage); ImGui::SameLine(); HelpMarker("Enable scrolling page by page when clicking outside the scrollbar grab.\nWhen disabled, always scroll to clicked location.\nWhen enabled, Shift+Click scrolls to clicked location."); + ImGui::SeparatorText("Widgets"); ImGui::Checkbox("io.ConfigInputTextCursorBlink", &io.ConfigInputTextCursorBlink); ImGui::SameLine(); HelpMarker("Enable blinking cursor (optional as some users consider it to be distracting)."); @@ -508,8 +586,10 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::Checkbox("io.ConfigMacOSXBehaviors", &io.ConfigMacOSXBehaviors); ImGui::SameLine(); HelpMarker("Swap Cmd<>Ctrl keys, enable various MacOS style behaviors."); ImGui::Text("Also see Style->Rendering for rendering options."); + // Also read: https://github.com/ocornut/imgui/wiki/Error-Handling ImGui::SeparatorText("Error Handling"); + ImGui::Checkbox("io.ConfigErrorRecovery", &io.ConfigErrorRecovery); ImGui::SameLine(); HelpMarker( "Options to configure how we handle recoverable errors.\n" @@ -523,6 +603,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::Checkbox("io.ConfigErrorRecoveryEnableTooltip", &io.ConfigErrorRecoveryEnableTooltip); if (!io.ConfigErrorRecoveryEnableAssert && !io.ConfigErrorRecoveryEnableDebugLog && !io.ConfigErrorRecoveryEnableTooltip) io.ConfigErrorRecoveryEnableAssert = io.ConfigErrorRecoveryEnableDebugLog = io.ConfigErrorRecoveryEnableTooltip = true; + // Also read: https://github.com/ocornut/imgui/wiki/Debug-Tools ImGui::SeparatorText("Debug"); ImGui::Checkbox("io.ConfigDebugIsDebuggerPresent", &io.ConfigDebugIsDebuggerPresent); @@ -539,15 +620,18 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("Option to deactivate io.AddFocusEvent(false) handling. May facilitate interactions with a debugger when focus loss leads to clearing inputs data."); ImGui::Checkbox("io.ConfigDebugIniSettings", &io.ConfigDebugIniSettings); ImGui::SameLine(); HelpMarker("Option to save .ini data with extra comments (particularly helpful for Docking, but makes saving slower)."); + ImGui::TreePop(); ImGui::Spacing(); } + IMGUI_DEMO_MARKER("Configuration/Backend Flags"); if (ImGui::TreeNode("Backend Flags")) { HelpMarker( "Those flags are set by the backends (imgui_impl_xxx files) to specify their capabilities.\n" "Here we expose them as read-only fields to avoid breaking interactions with your backend."); + // Make a local copy to avoid modifying actual backend flags. // FIXME: Maybe we need a BeginReadonly() equivalent to keep label bright? ImGui::BeginDisabled(); @@ -557,13 +641,16 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::CheckboxFlags("io.BackendFlags: PlatformHasViewports", &io.BackendFlags, ImGuiBackendFlags_PlatformHasViewports); ImGui::CheckboxFlags("io.BackendFlags: HasMouseHoveredViewport",&io.BackendFlags, ImGuiBackendFlags_HasMouseHoveredViewport); ImGui::CheckboxFlags("io.BackendFlags: RendererHasVtxOffset", &io.BackendFlags, ImGuiBackendFlags_RendererHasVtxOffset); + ImGui::CheckboxFlags("io.BackendFlags: RendererHasTextures", &io.BackendFlags, ImGuiBackendFlags_RendererHasTextures); ImGui::CheckboxFlags("io.BackendFlags: RendererHasViewports", &io.BackendFlags, ImGuiBackendFlags_RendererHasViewports); ImGui::EndDisabled(); + ImGui::TreePop(); ImGui::Spacing(); } - IMGUI_DEMO_MARKER("Configuration/Style"); - if (ImGui::TreeNode("Style")) + + IMGUI_DEMO_MARKER("Configuration/Style, Fonts"); + if (ImGui::TreeNode("Style, Fonts")) { ImGui::Checkbox("Style Editor", &demo_data.ShowStyleEditor); ImGui::SameLine(); @@ -571,6 +658,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::TreePop(); ImGui::Spacing(); } + IMGUI_DEMO_MARKER("Configuration/Capture, Logging"); if (ImGui::TreeNode("Capture/Logging")) { @@ -579,6 +667,7 @@ void ImGui::ShowDemoWindow(bool* p_open) "a window or a block. Tree nodes can be automatically expanded.\n" "Try opening any of the contents below in this window and then click one of the \"Log To\" button."); ImGui::LogButtons(); + HelpMarker("You can also call ImGui::LogText() to output directly to the log without a visual output."); if (ImGui::Button("Copy \"Hello, world!\" to clipboard")) { @@ -589,6 +678,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::TreePop(); } } + IMGUI_DEMO_MARKER("Window options"); if (ImGui::CollapsingHeader("Window options")) { @@ -609,19 +699,23 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::EndTable(); } } + // All demo contents DemoWindowWidgets(&demo_data); DemoWindowLayout(); DemoWindowPopups(); DemoWindowTables(); DemoWindowInputs(); + // End of ShowDemoWindow() ImGui::PopItemWidth(); ImGui::End(); } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowMenuBar() //----------------------------------------------------------------------------- + static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data) { IMGUI_DEMO_MARKER("Menu"); @@ -637,6 +731,7 @@ static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data) { IMGUI_DEMO_MARKER("Menu/Examples"); ImGui::MenuItem("Main menu bar", NULL, &demo_data->ShowMainMenuBar); + ImGui::SeparatorText("Mini apps"); ImGui::MenuItem("Assets Browser", NULL, &demo_data->ShowAppAssetsBrowser); ImGui::MenuItem("Console", NULL, &demo_data->ShowAppConsole); @@ -647,12 +742,14 @@ static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data) ImGui::MenuItem("Property editor", NULL, &demo_data->ShowAppPropertyEditor); ImGui::MenuItem("Simple layout", NULL, &demo_data->ShowAppLayout); ImGui::MenuItem("Simple overlay", NULL, &demo_data->ShowAppSimpleOverlay); + ImGui::SeparatorText("Concepts"); ImGui::MenuItem("Auto-resizing window", NULL, &demo_data->ShowAppAutoResize); ImGui::MenuItem("Constrained-resizing window", NULL, &demo_data->ShowAppConstrainedResize); ImGui::MenuItem("Fullscreen window", NULL, &demo_data->ShowAppFullscreen); ImGui::MenuItem("Long text display", NULL, &demo_data->ShowAppLongText); ImGui::MenuItem("Manipulating window titles", NULL, &demo_data->ShowAppWindowTitles); + ImGui::EndMenu(); } //if (ImGui::MenuItem("MenuItem")) {} // You can also use MenuItem() inside a menu bar! @@ -684,14 +781,17 @@ static void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data) ImGui::SetItemTooltip("Requires io.ConfigDebugIsDebuggerPresent=true to be set.\n\nWe otherwise disable some extra features to avoid casual users crashing the application."); ImGui::MenuItem("Style Editor", NULL, &demo_data->ShowStyleEditor); ImGui::MenuItem("About Dear ImGui", NULL, &demo_data->ShowAbout); + ImGui::EndMenu(); } ImGui::EndMenuBar(); } } + //----------------------------------------------------------------------------- // [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor & Multi-Select demos) //----------------------------------------------------------------------------- + // Simple representation for a tree // (this is designed to be simple to understand for our demos, not to be fancy or efficient etc.) struct ExampleTreeNode @@ -702,12 +802,14 @@ struct ExampleTreeNode ExampleTreeNode* Parent = NULL; ImVector Childs; unsigned short IndexInParent = 0; // Maintaining this allows us to implement linear traversal more easily + // Leaf Data bool HasData = false; // All leaves have data bool DataMyBool = true; int DataMyInt = 128; ImVec2 DataMyVec2 = ImVec2(0.0f, 3.141592f); }; + // Simple representation of struct metadata/serialization data. // (this is a minimal version of what a typical advanced application may provide) struct ExampleMemberInfo @@ -717,6 +819,7 @@ struct ExampleMemberInfo int DataCount; // Member count (1 when scalar) int Offset; // Offset inside parent structure }; + // Metadata description of ExampleTreeNode struct. static const ExampleMemberInfo ExampleTreeNodeMemberInfos[] { @@ -725,6 +828,7 @@ static const ExampleMemberInfo ExampleTreeNodeMemberInfos[] { "MyInt", ImGuiDataType_S32, 1, offsetof(ExampleTreeNode, DataMyInt) }, { "MyVec2", ImGuiDataType_Float, 2, offsetof(ExampleTreeNode, DataMyVec2) }, }; + static ExampleTreeNode* ExampleTree_CreateNode(const char* name, int uid, ExampleTreeNode* parent) { ExampleTreeNode* node = IM_NEW(ExampleTreeNode); @@ -736,12 +840,14 @@ static ExampleTreeNode* ExampleTree_CreateNode(const char* name, int uid, Exampl parent->Childs.push_back(node); return node; } + static void ExampleTree_DestroyNode(ExampleTreeNode* node) { for (ExampleTreeNode* child_node : node->Childs) ExampleTree_DestroyNode(child_node); IM_DELETE(node); } + // Create example tree data // (this allocates _many_ more times than most other code in either Dear ImGui or others demo) static ExampleTreeNode* ExampleTree_CreateDemoTree() @@ -772,15 +878,18 @@ static ExampleTreeNode* ExampleTree_CreateDemoTree() } return node_L0; } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsBasic() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsBasic() { IMGUI_DEMO_MARKER("Widgets/Basic"); if (ImGui::TreeNode("Basic")) { ImGui::SeparatorText("General"); + IMGUI_DEMO_MARKER("Widgets/Basic/Button"); static int clicked = 0; if (ImGui::Button("Button")) @@ -790,14 +899,20 @@ static void DemoWindowWidgetsBasic() ImGui::SameLine(); ImGui::Text("Thanks for clicking me!"); } + IMGUI_DEMO_MARKER("Widgets/Basic/Checkbox"); static bool check = true; ImGui::Checkbox("checkbox", &check); + IMGUI_DEMO_MARKER("Widgets/Basic/RadioButton"); static int e = 0; ImGui::RadioButton("radio a", &e, 0); ImGui::SameLine(); ImGui::RadioButton("radio b", &e, 1); ImGui::SameLine(); ImGui::RadioButton("radio c", &e, 2); + + ImGui::AlignTextToFramePadding(); + ImGui::TextLinkOpenURL("Hyperlink", "https://github.com/ocornut/imgui/wiki/Error-Handling"); + // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style. IMGUI_DEMO_MARKER("Widgets/Basic/Buttons (Colored)"); for (int i = 0; i < 7; i++) @@ -812,12 +927,14 @@ static void DemoWindowWidgetsBasic() ImGui::PopStyleColor(3); ImGui::PopID(); } + // Use AlignTextToFramePadding() to align text baseline to the baseline of framed widgets elements // (otherwise a Text+SameLine+Button sequence will have the text a little too high by default!) // See 'Demo->Layout->Text Baseline Alignment' for details. ImGui::AlignTextToFramePadding(); ImGui::Text("Hold to repeat:"); ImGui::SameLine(); + // Arrow buttons with Repeater IMGUI_DEMO_MARKER("Widgets/Basic/Buttons (Repeating)"); static int counter = 0; @@ -829,10 +946,14 @@ static void DemoWindowWidgetsBasic() ImGui::PopItemFlag(); ImGui::SameLine(); ImGui::Text("%d", counter); + ImGui::Button("Tooltip"); ImGui::SetItemTooltip("I am a tooltip"); + ImGui::LabelText("label", "Value"); + ImGui::SeparatorText("Inputs"); + { // To wire InputText() with std::string or any other custom string type, // see the "Text Input > Resize Callback" section of this demo, and the misc/cpp/imgui_stdlib.h file. @@ -851,24 +972,32 @@ static void DemoWindowWidgetsBasic() "You can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputText() " "to a dynamic string type. See misc/cpp/imgui_stdlib.h for an example (this is not demonstrated " "in imgui_demo.cpp)."); + static char str1[128] = ""; ImGui::InputTextWithHint("input text (w/ hint)", "enter text here", str1, IM_ARRAYSIZE(str1)); + IMGUI_DEMO_MARKER("Widgets/Basic/InputInt, InputFloat"); static int i0 = 123; ImGui::InputInt("input int", &i0); + static float f0 = 0.001f; ImGui::InputFloat("input float", &f0, 0.01f, 1.0f, "%.3f"); + static double d0 = 999999.00000001; ImGui::InputDouble("input double", &d0, 0.01f, 1.0f, "%.8f"); + static float f1 = 1.e10f; ImGui::InputFloat("input scientific", &f1, 0.0f, 0.0f, "%e"); ImGui::SameLine(); HelpMarker( "You can input value using the scientific notation,\n" " e.g. \"1e+8\" becomes \"100000000\"."); + static float vec4a[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; ImGui::InputFloat3("input float3", vec4a); } + ImGui::SeparatorText("Drags"); + { IMGUI_DEMO_MARKER("Widgets/Basic/DragInt, DragFloat"); static int i1 = 50, i2 = 42, i3 = 128; @@ -879,23 +1008,29 @@ static void DemoWindowWidgetsBasic() "Double-click or CTRL+click to input value."); ImGui::DragInt("drag int 0..100", &i2, 1, 0, 100, "%d%%", ImGuiSliderFlags_AlwaysClamp); ImGui::DragInt("drag int wrap 100..200", &i3, 1, 100, 200, "%d", ImGuiSliderFlags_WrapAround); + static float f1 = 1.00f, f2 = 0.0067f; ImGui::DragFloat("drag float", &f1, 0.005f); ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns"); //ImGui::DragFloat("drag wrap -1..1", &f3, 0.005f, -1.0f, 1.0f, NULL, ImGuiSliderFlags_WrapAround); } + ImGui::SeparatorText("Sliders"); + { IMGUI_DEMO_MARKER("Widgets/Basic/SliderInt, SliderFloat"); static int i1 = 0; ImGui::SliderInt("slider int", &i1, -1, 3); ImGui::SameLine(); HelpMarker("CTRL+click to input value."); + static float f1 = 0.123f, f2 = 0.0f; ImGui::SliderFloat("slider float", &f1, 0.0f, 1.0f, "ratio = %.3f"); ImGui::SliderFloat("slider float (log)", &f2, -10.0f, 10.0f, "%.4f", ImGuiSliderFlags_Logarithmic); + IMGUI_DEMO_MARKER("Widgets/Basic/SliderAngle"); static float angle = 0.0f; ImGui::SliderAngle("slider angle", &angle); + // Using the format string to display a name instead of an integer. // Here we completely omit '%d' from the format string, so it'll only display a name. // This technique can also be used with DragInt(). @@ -907,7 +1042,9 @@ static void DemoWindowWidgetsBasic() ImGui::SliderInt("slider enum", &elem, 0, Element_COUNT - 1, elem_name); // Use ImGuiSliderFlags_NoInput flag to disable CTRL+Click here. ImGui::SameLine(); HelpMarker("Using the format string parameter to display a name instead of the underlying integer."); } + ImGui::SeparatorText("Selectors/Pickers"); + { IMGUI_DEMO_MARKER("Widgets/Basic/ColorEdit3, ColorEdit4"); static float col1[3] = { 1.0f, 0.0f, 0.2f }; @@ -918,8 +1055,10 @@ static void DemoWindowWidgetsBasic() "Click and hold to use drag and drop.\n" "Right-click on the color square to show options.\n" "CTRL+click on individual component to input value.\n"); + ImGui::ColorEdit4("color 2", col2); } + { // Using the _simplified_ one-liner Combo() api here // See "Combo" section for examples of how to use the more flexible BeginCombo()/EndCombo() api. @@ -931,6 +1070,7 @@ static void DemoWindowWidgetsBasic() "Using the simplified one-liner Combo API here.\n" "Refer to the \"Combo\" section below for an explanation of how to use the more flexible and general BeginCombo/EndCombo API."); } + { // Using the _simplified_ one-liner ListBox() api here // See "List boxes" section for examples of how to use the more flexible BeginListBox()/EndListBox() api. @@ -942,17 +1082,21 @@ static void DemoWindowWidgetsBasic() "Using the simplified one-liner ListBox API here.\n" "Refer to the \"List boxes\" section below for an explanation of how to use the more flexible and general BeginListBox/EndListBox API."); } + // Testing ImGuiOnceUponAFrame helper. //static ImGuiOnceUponAFrame once; //for (int i = 0; i < 5; i++) // if (once) // ImGui::Text("This will be displayed only once."); + ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsBullets() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsBullets() { IMGUI_DEMO_MARKER("Widgets/Bullets"); @@ -970,9 +1114,11 @@ static void DemoWindowWidgetsBullets() ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsCollapsingHeaders() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsCollapsingHeaders() { IMGUI_DEMO_MARKER("Widgets/Collapsing Headers"); @@ -999,9 +1145,11 @@ static void DemoWindowWidgetsCollapsingHeaders() ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsColorAndPickers() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsColorAndPickers() { IMGUI_DEMO_MARKER("Widgets/Color"); @@ -1009,6 +1157,7 @@ static void DemoWindowWidgetsColorAndPickers() { static ImVec4 color = ImVec4(114.0f / 255.0f, 144.0f / 255.0f, 154.0f / 255.0f, 200.0f / 255.0f); static ImGuiColorEditFlags base_flags = ImGuiColorEditFlags_None; + ImGui::SeparatorText("Options"); ImGui::CheckboxFlags("ImGuiColorEditFlags_NoAlpha", &base_flags, ImGuiColorEditFlags_NoAlpha); ImGui::CheckboxFlags("ImGuiColorEditFlags_AlphaOpaque", &base_flags, ImGuiColorEditFlags_AlphaOpaque); @@ -1017,6 +1166,7 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::CheckboxFlags("ImGuiColorEditFlags_NoDragDrop", &base_flags, ImGuiColorEditFlags_NoDragDrop); ImGui::CheckboxFlags("ImGuiColorEditFlags_NoOptions", &base_flags, ImGuiColorEditFlags_NoOptions); ImGui::SameLine(); HelpMarker("Right-click on the individual color widget to show options."); ImGui::CheckboxFlags("ImGuiColorEditFlags_HDR", &base_flags, ImGuiColorEditFlags_HDR); ImGui::SameLine(); HelpMarker("Currently all this does is to lift the 0..1 limits on dragging widgets."); + IMGUI_DEMO_MARKER("Widgets/Color/ColorEdit"); ImGui::SeparatorText("Inline color editor"); ImGui::Text("Color widget:"); @@ -1024,12 +1174,15 @@ static void DemoWindowWidgetsColorAndPickers() "Click on the color square to open a color picker.\n" "CTRL+click on individual component to input value.\n"); ImGui::ColorEdit3("MyColor##1", (float*)&color, base_flags); + IMGUI_DEMO_MARKER("Widgets/Color/ColorEdit (HSV, with Alpha)"); ImGui::Text("Color widget HSV with Alpha:"); ImGui::ColorEdit4("MyColor##2", (float*)&color, ImGuiColorEditFlags_DisplayHSV | base_flags); + IMGUI_DEMO_MARKER("Widgets/Color/ColorEdit (float display)"); ImGui::Text("Color widget with Float Display:"); ImGui::ColorEdit4("MyColor##2f", (float*)&color, ImGuiColorEditFlags_Float | base_flags); + IMGUI_DEMO_MARKER("Widgets/Color/ColorButton (with Picker)"); ImGui::Text("Color button with Picker:"); ImGui::SameLine(); HelpMarker( @@ -1037,8 +1190,10 @@ static void DemoWindowWidgetsColorAndPickers() "With the ImGuiColorEditFlags_NoLabel flag you can pass a non-empty label which will only " "be used for the tooltip and picker popup."); ImGui::ColorEdit4("MyColor##3", (float*)&color, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | base_flags); + IMGUI_DEMO_MARKER("Widgets/Color/ColorButton (with custom Picker popup)"); ImGui::Text("Color button with Custom Picker Popup:"); + // Generate a default palette. The palette will persist and can be edited. static bool saved_palette_init = true; static ImVec4 saved_palette[32] = {}; @@ -1052,6 +1207,7 @@ static void DemoWindowWidgetsColorAndPickers() } saved_palette_init = false; } + static ImVec4 backup_color; bool open_popup = ImGui::ColorButton("MyColor##3b", color, base_flags); ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x); @@ -1067,6 +1223,7 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::Separator(); ImGui::ColorPicker4("##picker", (float*)&color, base_flags | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoSmallPreview); ImGui::SameLine(); + ImGui::BeginGroup(); // Lock X position ImGui::Text("Current"); ImGui::ColorButton("##current", color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60, 40)); @@ -1080,9 +1237,11 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::PushID(n); if ((n % 8) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); + ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip; if (ImGui::ColorButton("##palette", saved_palette[n], palette_button_flags, ImVec2(20, 20))) color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z, color.w); // Preserve alpha! + // Allow user to drop colors into each palette entry. Note that ColorButton() is already a // drag source by default, unless specifying the ImGuiColorEditFlags_NoDragDrop flag. if (ImGui::BeginDragDropTarget()) @@ -1093,23 +1252,28 @@ static void DemoWindowWidgetsColorAndPickers() memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4); ImGui::EndDragDropTarget(); } + ImGui::PopID(); } ImGui::EndGroup(); ImGui::EndPopup(); } + IMGUI_DEMO_MARKER("Widgets/Color/ColorButton (simple)"); ImGui::Text("Color button only:"); static bool no_border = false; ImGui::Checkbox("ImGuiColorEditFlags_NoBorder", &no_border); ImGui::ColorButton("MyColor##3c", *(ImVec4*)&color, base_flags | (no_border ? ImGuiColorEditFlags_NoBorder : 0), ImVec2(80, 80)); + IMGUI_DEMO_MARKER("Widgets/Color/ColorPicker"); ImGui::SeparatorText("Color picker"); + static bool ref_color = false; static ImVec4 ref_color_v(1.0f, 0.0f, 1.0f, 0.5f); static int picker_mode = 0; static int display_mode = 0; static ImGuiColorEditFlags color_picker_flags = ImGuiColorEditFlags_AlphaBar; + ImGui::PushID("Color picker"); ImGui::CheckboxFlags("ImGuiColorEditFlags_NoAlpha", &color_picker_flags, ImGuiColorEditFlags_NoAlpha); ImGui::CheckboxFlags("ImGuiColorEditFlags_AlphaBar", &color_picker_flags, ImGuiColorEditFlags_AlphaBar); @@ -1124,13 +1288,16 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::ColorEdit4("##RefColor", &ref_color_v.x, ImGuiColorEditFlags_NoInputs | base_flags); } } + ImGui::Combo("Picker Mode", &picker_mode, "Auto/Current\0ImGuiColorEditFlags_PickerHueBar\0ImGuiColorEditFlags_PickerHueWheel\0"); ImGui::SameLine(); HelpMarker("When not specified explicitly, user can right-click the picker to change mode."); + ImGui::Combo("Display Mode", &display_mode, "Auto/Current\0ImGuiColorEditFlags_NoInputs\0ImGuiColorEditFlags_DisplayRGB\0ImGuiColorEditFlags_DisplayHSV\0ImGuiColorEditFlags_DisplayHex\0"); ImGui::SameLine(); HelpMarker( "ColorEdit defaults to displaying RGB inputs if you don't specify a display mode, " "but the user can change it with a right-click on those inputs.\n\nColorPicker defaults to displaying RGB+HSV+Hex " "if you don't specify a display mode.\n\nYou can change the defaults using SetColorEditOptions()."); + ImGuiColorEditFlags flags = base_flags | color_picker_flags; if (picker_mode == 1) flags |= ImGuiColorEditFlags_PickerHueBar; if (picker_mode == 2) flags |= ImGuiColorEditFlags_PickerHueWheel; @@ -1139,6 +1306,7 @@ static void DemoWindowWidgetsColorAndPickers() if (display_mode == 3) flags |= ImGuiColorEditFlags_DisplayHSV; if (display_mode == 4) flags |= ImGuiColorEditFlags_DisplayHex; ImGui::ColorPicker4("MyColor##4", (float*)&color, flags, ref_color ? &ref_color_v.x : NULL); + ImGui::Text("Set defaults in code:"); ImGui::SameLine(); HelpMarker( "SetColorEditOptions() is designed to allow you to set boot-time default.\n" @@ -1149,6 +1317,7 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::SetColorEditOptions(ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_PickerHueBar); if (ImGui::Button("Default: Float + HDR + Hue Wheel")) ImGui::SetColorEditOptions(ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_PickerHueWheel); + // Always display a small version of both types of pickers // (that's in order to make it more visible in the demo to people who are skimming quickly through it) ImGui::Text("Both types:"); @@ -1159,6 +1328,7 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::SetNextItemWidth(w); ImGui::ColorPicker3("##MyColor##6", (float*)&color, ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha); ImGui::PopID(); + // HSV encoded support (to avoid RGB<>HSV round trips and singularities when S==0 or V==0) static ImVec4 color_hsv(0.23f, 1.0f, 1.0f, 1.0f); // Stored as HSV! ImGui::Spacing(); @@ -1171,12 +1341,15 @@ static void DemoWindowWidgetsColorAndPickers() ImGui::ColorEdit4("HSV shown as RGB##1", (float*)&color_hsv, ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_InputHSV | ImGuiColorEditFlags_Float); ImGui::ColorEdit4("HSV shown as HSV##1", (float*)&color_hsv, ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_InputHSV | ImGuiColorEditFlags_Float); ImGui::DragFloat4("Raw HSV values", (float*)&color_hsv, 0.01f, 0.0f, 1.0f); + ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsComboBoxes() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsComboBoxes() { IMGUI_DEMO_MARKER("Widgets/Combo"); @@ -1193,6 +1366,7 @@ static void DemoWindowWidgetsComboBoxes() flags &= ~(ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_WidthFitPreview); // Clear incompatible flags if (ImGui::CheckboxFlags("ImGuiComboFlags_WidthFitPreview", &flags, ImGuiComboFlags_WidthFitPreview)) flags &= ~ImGuiComboFlags_NoPreview; + // Override default popup height if (ImGui::CheckboxFlags("ImGuiComboFlags_HeightSmall", &flags, ImGuiComboFlags_HeightSmall)) flags &= ~(ImGuiComboFlags_HeightMask_ & ~ImGuiComboFlags_HeightSmall); @@ -1200,11 +1374,13 @@ static void DemoWindowWidgetsComboBoxes() flags &= ~(ImGuiComboFlags_HeightMask_ & ~ImGuiComboFlags_HeightRegular); if (ImGui::CheckboxFlags("ImGuiComboFlags_HeightLargest", &flags, ImGuiComboFlags_HeightLargest)) flags &= ~(ImGuiComboFlags_HeightMask_ & ~ImGuiComboFlags_HeightLargest); + // Using the generic BeginCombo() API, you have full control over how to display the combo contents. // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively // stored in the object itself, etc.) const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" }; static int item_selected_idx = 0; // Here we store our selection data as an index. + // Pass in the preview value visible before opening the combo (it could technically be different contents or not pulled from items[]) const char* combo_preview_value = items[item_selected_idx]; if (ImGui::BeginCombo("combo 1", combo_preview_value, flags)) @@ -1214,12 +1390,14 @@ static void DemoWindowWidgetsComboBoxes() const bool is_selected = (item_selected_idx == n); if (ImGui::Selectable(items[n], is_selected)) item_selected_idx = n; + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) if (is_selected) ImGui::SetItemDefaultFocus(); } ImGui::EndCombo(); } + // Show case embedding a filter using a simple trick: displaying the filter inside combo contents. // See https://github.com/ocornut/imgui/issues/718 for advanced/esoteric alternatives. if (ImGui::BeginCombo("combo 2 (w/ filter)", combo_preview_value, flags)) @@ -1232,6 +1410,7 @@ static void DemoWindowWidgetsComboBoxes() } ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F); filter.Draw("##Filter", -FLT_MIN); + for (int n = 0; n < IM_ARRAYSIZE(items); n++) { const bool is_selected = (item_selected_idx == n); @@ -1241,26 +1420,33 @@ static void DemoWindowWidgetsComboBoxes() } ImGui::EndCombo(); } + ImGui::Spacing(); ImGui::SeparatorText("One-liner variants"); HelpMarker("The Combo() function is not greatly useful apart from cases were you want to embed all options in a single strings.\nFlags above don't apply to this section."); + // Simplified one-liner Combo() API, using values packed in a single constant string // This is a convenience for when the selection set is small and known at compile-time. static int item_current_2 = 0; ImGui::Combo("combo 3 (one-liner)", &item_current_2, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); + // Simplified one-liner Combo() using an array of const char* // This is not very useful (may obsolete): prefer using BeginCombo()/EndCombo() for full control. static int item_current_3 = -1; // If the selection isn't within 0..count, Combo won't display a preview ImGui::Combo("combo 4 (array)", &item_current_3, items, IM_ARRAYSIZE(items)); + // Simplified one-liner Combo() using an accessor function static int item_current_4 = 0; ImGui::Combo("combo 5 (function)", &item_current_4, [](void* data, int n) { return ((const char**)data)[n]; }, items, IM_ARRAYSIZE(items)); + ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsDataTypes() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsDataTypes() { IMGUI_DEMO_MARKER("Widgets/Data Types"); @@ -1280,6 +1466,7 @@ static void DemoWindowWidgetsDataTypes() // { // return SliderScalar(label, ImGuiDataType_U64, value, &min, &max, format); // } + // Setup limits (as helper variables so we can take their address, as explained above) // Note: SliderScalar() functions have a maximum usable range of half the natural type maximum, hence the /2. #ifndef LLONG_MIN @@ -1297,6 +1484,7 @@ static void DemoWindowWidgetsDataTypes() const ImU64 u64_zero = 0, u64_one = 1, u64_fifty = 50, u64_min = 0, u64_max = ULLONG_MAX/2, u64_hi_a = ULLONG_MAX/2 - 100, u64_hi_b = ULLONG_MAX/2; const float f32_zero = 0.f, f32_one = 1.f, f32_lo_a = -10000000000.0f, f32_hi_a = +10000000000.0f; const double f64_zero = 0., f64_one = 1., f64_lo_a = -1000000000000000.0, f64_hi_a = +1000000000000000.0; + // State static char s8_v = 127; static ImU8 u8_v = 255; @@ -1308,6 +1496,7 @@ static void DemoWindowWidgetsDataTypes() static ImU64 u64_v = (ImU64)-1; static float f32_v = 0.123f; static double f64_v = 90000.01234567890123456789; + const float drag_speed = 0.2f; static bool drag_clamp = false; IMGUI_DEMO_MARKER("Widgets/Data Types/Drags"); @@ -1329,6 +1518,7 @@ static void DemoWindowWidgetsDataTypes() ImGui::DragScalar("drag float log", ImGuiDataType_Float, &f32_v, 0.005f, &f32_zero, &f32_one, "%f", ImGuiSliderFlags_Logarithmic); ImGui::DragScalar("drag double", ImGuiDataType_Double, &f64_v, 0.0005f, &f64_zero, NULL, "%.10f grams"); ImGui::DragScalar("drag double log",ImGuiDataType_Double, &f64_v, 0.0005f, &f64_zero, &f64_one, "0 < %.10f < 1", ImGuiSliderFlags_Logarithmic); + IMGUI_DEMO_MARKER("Widgets/Data Types/Sliders"); ImGui::SeparatorText("Sliders"); ImGui::SliderScalar("slider s8 full", ImGuiDataType_S8, &s8_v, &s8_min, &s8_max, "%d"); @@ -1354,6 +1544,7 @@ static void DemoWindowWidgetsDataTypes() ImGui::SliderScalar("slider double low", ImGuiDataType_Double, &f64_v, &f64_zero, &f64_one, "%.10f grams"); ImGui::SliderScalar("slider double low log",ImGuiDataType_Double, &f64_v, &f64_zero, &f64_one, "%.10f", ImGuiSliderFlags_Logarithmic); ImGui::SliderScalar("slider double high", ImGuiDataType_Double, &f64_v, &f64_lo_a, &f64_hi_a, "%e grams"); + ImGui::SeparatorText("Sliders (reverse)"); ImGui::SliderScalar("slider s8 reverse", ImGuiDataType_S8, &s8_v, &s8_max, &s8_min, "%d"); ImGui::SliderScalar("slider u8 reverse", ImGuiDataType_U8, &u8_v, &u8_max, &u8_min, "%u"); @@ -1361,6 +1552,7 @@ static void DemoWindowWidgetsDataTypes() ImGui::SliderScalar("slider u32 reverse", ImGuiDataType_U32, &u32_v, &u32_fifty, &u32_zero, "%u"); ImGui::SliderScalar("slider s64 reverse", ImGuiDataType_S64, &s64_v, &s64_fifty, &s64_zero, "%" PRId64); ImGui::SliderScalar("slider u64 reverse", ImGuiDataType_U64, &u64_v, &u64_fifty, &u64_zero, "%" PRIu64 " ms"); + IMGUI_DEMO_MARKER("Widgets/Data Types/Inputs"); static bool inputs_step = true; static ImGuiInputTextFlags flags = ImGuiInputTextFlags_None; @@ -1381,12 +1573,15 @@ static void DemoWindowWidgetsDataTypes() ImGui::InputScalar("input u64", ImGuiDataType_U64, &u64_v, inputs_step ? &u64_one : NULL, NULL, NULL, flags); ImGui::InputScalar("input float", ImGuiDataType_Float, &f32_v, inputs_step ? &f32_one : NULL, NULL, NULL, flags); ImGui::InputScalar("input double", ImGuiDataType_Double, &f64_v, inputs_step ? &f64_one : NULL, NULL, NULL, flags); + ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsDisableBlocks() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsDisableBlocks(ImGuiDemoWindowData* demo_data) { IMGUI_DEMO_MARKER("Widgets/Disable Blocks"); @@ -1397,9 +1592,11 @@ static void DemoWindowWidgetsDisableBlocks(ImGuiDemoWindowData* demo_data) ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsDragAndDrop() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsDragAndDrop() { IMGUI_DEMO_MARKER("Widgets/Drag and drop"); @@ -1419,6 +1616,7 @@ static void DemoWindowWidgetsDragAndDrop() ImGui::ColorEdit4("color 2", col2); ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Drag and drop/Copy-swap items"); if (ImGui::TreeNode("Drag and drop to copy/swap items")) { @@ -1444,11 +1642,13 @@ static void DemoWindowWidgetsDragAndDrop() if ((n % 3) != 0) ImGui::SameLine(); ImGui::Button(names[n], ImVec2(60, 60)); + // Our buttons are both drag sources and drag targets here! if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { // Set payload to carry the index of our item (could be anything) ImGui::SetDragDropPayload("DND_DEMO_CELL", &n, sizeof(int)); + // Display preview (could be anything, e.g. when dragging an image we could decide to display // the filename and a small preview of the image, etc.) if (mode == Mode_Copy) { ImGui::Text("Copy %s", names[n]); } @@ -1484,6 +1684,7 @@ static void DemoWindowWidgetsDragAndDrop() } ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Drag and Drop/Drag to reorder items (simple)"); if (ImGui::TreeNode("Drag to reorder items (simple)")) { @@ -1491,6 +1692,7 @@ static void DemoWindowWidgetsDragAndDrop() // This code was always slightly faulty but in a way which was not easily noticeable. // Until we fix this, enable ImGuiItemFlags_AllowDuplicateId to disable detecting the issue. ImGui::PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); + // Simple reordering HelpMarker( "We don't use the drag and drop api at all here! " @@ -1500,6 +1702,7 @@ static void DemoWindowWidgetsDragAndDrop() { const char* item = item_names[n]; ImGui::Selectable(item); + if (ImGui::IsItemActive() && !ImGui::IsItemHovered()) { int n_next = n + (ImGui::GetMouseDragDelta(0).y < 0.f ? -1 : 1); @@ -1511,9 +1714,11 @@ static void DemoWindowWidgetsDragAndDrop() } } } + ImGui::PopItemFlag(); ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Drag and Drop/Tooltip at target location"); if (ImGui::TreeNode("Tooltip at target location")) { @@ -1532,19 +1737,24 @@ static void DemoWindowWidgetsDragAndDrop() } ImGui::EndDragDropTarget(); } + // Drop source static ImVec4 col4 = { 1.0f, 0.0f, 0.2f, 1.0f }; if (n == 0) ImGui::ColorButton("drag me", col4); + } ImGui::TreePop(); } + ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsDragsAndSliders() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsDragsAndSliders() { IMGUI_DEMO_MARKER("Widgets/Drag and Slider Flags"); @@ -1567,6 +1777,7 @@ static void DemoWindowWidgetsDragsAndSliders() ImGui::SameLine(); HelpMarker("Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic."); ImGui::CheckboxFlags("ImGuiSliderFlags_WrapAround", &flags, ImGuiSliderFlags_WrapAround); ImGui::SameLine(); HelpMarker("Enable wrapping around from max to min and from min to max (only supported by DragXXX() functions)"); + // Drags static float drag_f = 0.5f; static int drag_i = 50; @@ -1578,6 +1789,7 @@ static void DemoWindowWidgetsDragsAndSliders() //ImGui::DragFloat("DragFloat (0 -> 0)", &drag_f, 0.005f, 0.0f, 0.0f, "%.3f", flags); // To test ClampZeroRange //ImGui::DragFloat("DragFloat (100 -> 100)", &drag_f, 0.005f, 100.0f, 100.0f, "%.3f", flags); ImGui::DragInt("DragInt (0 -> 100)", &drag_i, 0.5f, 0, 100, "%d", flags); + // Sliders static float slider_f = 0.5f; static int slider_i = 50; @@ -1585,12 +1797,34 @@ static void DemoWindowWidgetsDragsAndSliders() ImGui::Text("Underlying float value: %f", slider_f); ImGui::SliderFloat("SliderFloat (0 -> 1)", &slider_f, 0.0f, 1.0f, "%.3f", flags_for_sliders); ImGui::SliderInt("SliderInt (0 -> 100)", &slider_i, 0, 100, "%d", flags_for_sliders); + ImGui::TreePop(); } } + +//----------------------------------------------------------------------------- +// [SECTION] DemoWindowWidgetsFonts() +//----------------------------------------------------------------------------- + +// Forward declare ShowFontAtlas() which isn't worth putting in public API yet +namespace ImGui { IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas); } + +static void DemoWindowWidgetsFonts() +{ + IMGUI_DEMO_MARKER("Widgets/Fonts"); + if (ImGui::TreeNode("Fonts")) + { + ImFontAtlas* atlas = ImGui::GetIO().Fonts; + ImGui::ShowFontAtlas(atlas); + // FIXME-NEWATLAS: Provide a demo to add/create a procedural font? + ImGui::TreePop(); + } +} + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsImages() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsImages() { IMGUI_DEMO_MARKER("Widgets/Images"); @@ -1601,14 +1835,14 @@ static void DemoWindowWidgetsImages() "Below we are displaying the font texture (which is the only texture we have access to in this demo). " "Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. " "Hover the texture for a zoomed view!"); + // Below we are displaying the font texture because it is the only texture we have access to inside the demo! - // Remember that ImTextureID is just storage for whatever you want it to be. It is essentially a value that - // will be passed to the rendering backend via the ImDrawCmd structure. + // Read description about ImTextureID/ImTextureRef and FAQ for details about texture identifiers. // If you use one of the default imgui_impl_XXXX.cpp rendering backend, they all have comments at the top - // of their respective source file to specify what they expect to be stored in ImTextureID, for example: - // - The imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer + // of their respective source file to specify what they are using as texture identifier, for example: + // - The imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer. // - The imgui_impl_opengl3.cpp renderer expect a GLuint OpenGL texture identifier, etc. - // More: + // So with the DirectX11 backend, you call ImGui::Image() with a 'ID3D11ShaderResourceView*' cast to ImTextureID. // - If you decided that ImTextureID = MyEngineTexture*, then you can pass your MyEngineTexture* pointers // to ImGui::Image(), and gather width/height through your own functions, etc. // - You can use ShowMetricsWindow() to inspect the draw data that are being passed to your renderer, @@ -1616,14 +1850,19 @@ static void DemoWindowWidgetsImages() // - Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage(). // - Read https://github.com/ocornut/imgui/blob/master/docs/FAQ.md // - Read https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples - ImTextureID my_tex_id = io.Fonts->TexID; - float my_tex_w = (float)io.Fonts->TexWidth; - float my_tex_h = (float)io.Fonts->TexHeight; + + // Grab the current texture identifier used by the font atlas. + ImTextureRef my_tex_id = io.Fonts->TexRef; + + // Regular user code should never have to care about TexData-> fields, but since we want to display the entire texture here, we pull Width/Height from it. + float my_tex_w = (float)io.Fonts->TexData->Width; + float my_tex_h = (float)io.Fonts->TexData->Height; + { ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); ImVec2 pos = ImGui::GetCursorScreenPos(); - ImVec2 uv_min = ImVec2(0.0f, 0.0f); // Top-left - ImVec2 uv_max = ImVec2(1.0f, 1.0f); // Lower-right + ImVec2 uv_min = ImVec2(0.0f, 0.0f); // Top-left + ImVec2 uv_max = ImVec2(1.0f, 1.0f); // Lower-right ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, IM_MAX(1.0f, ImGui::GetStyle().ImageBorderSize)); ImGui::ImageWithBg(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); if (ImGui::BeginItemTooltip()) @@ -1645,6 +1884,7 @@ static void DemoWindowWidgetsImages() } ImGui::PopStyleVar(); } + IMGUI_DEMO_MARKER("Widgets/Images/Textured buttons"); ImGui::TextWrapped("And now some textured buttons.."); static int pressed_count = 0; @@ -1673,9 +1913,11 @@ static void DemoWindowWidgetsImages() ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsListBoxes() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsListBoxes() { IMGUI_DEMO_MARKER("Widgets/List Boxes"); @@ -1685,14 +1927,17 @@ static void DemoWindowWidgetsListBoxes() // using the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label. // You may be tempted to simply use BeginChild() directly. However note that BeginChild() requires EndChild() // to always be called (inconsistent with BeginListBox()/EndListBox()). + // Using the generic BeginListBox() API, you have full control over how to display the combo contents. // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively // stored in the object itself, etc.) const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" }; static int item_selected_idx = 0; // Here we store our selected data as an index. + static bool item_highlight = false; int item_highlighted_idx = -1; // Here we store our highlighted data as an index. ImGui::Checkbox("Highlight hovered item in second listbox", &item_highlight); + if (ImGui::BeginListBox("listbox 1")) { for (int n = 0; n < IM_ARRAYSIZE(items); n++) @@ -1700,8 +1945,10 @@ static void DemoWindowWidgetsListBoxes() const bool is_selected = (item_selected_idx == n); if (ImGui::Selectable(items[n], is_selected)) item_selected_idx = n; + if (item_highlight && ImGui::IsItemHovered()) item_highlighted_idx = n; + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) if (is_selected) ImGui::SetItemDefaultFocus(); @@ -1709,6 +1956,7 @@ static void DemoWindowWidgetsListBoxes() ImGui::EndListBox(); } ImGui::SameLine(); HelpMarker("Here we are sharing selection state between both boxes."); + // Custom size: use all width, 5 items tall ImGui::Text("Full-width:"); if (ImGui::BeginListBox("##listbox 2", ImVec2(-FLT_MIN, 5 * ImGui::GetTextLineHeightWithSpacing()))) @@ -1719,18 +1967,22 @@ static void DemoWindowWidgetsListBoxes() ImGuiSelectableFlags flags = (item_highlighted_idx == n) ? ImGuiSelectableFlags_Highlight : 0; if (ImGui::Selectable(items[n], is_selected, flags)) item_selected_idx = n; + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) if (is_selected) ImGui::SetItemDefaultFocus(); } ImGui::EndListBox(); } + ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsMultiComponents() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsMultiComponents() { IMGUI_DEMO_MARKER("Widgets/Multi-component Widgets"); @@ -1738,6 +1990,7 @@ static void DemoWindowWidgetsMultiComponents() { static float vec4f[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; static int vec4i[4] = { 1, 5, 100, 255 }; + ImGui::SeparatorText("2-wide"); ImGui::InputFloat2("input float2", vec4f); ImGui::DragFloat2("drag float2", vec4f, 0.01f, 0.0f, 1.0f); @@ -1745,6 +1998,7 @@ static void DemoWindowWidgetsMultiComponents() ImGui::InputInt2("input int2", vec4i); ImGui::DragInt2("drag int2", vec4i, 1, 0, 255); ImGui::SliderInt2("slider int2", vec4i, 0, 255); + ImGui::SeparatorText("3-wide"); ImGui::InputFloat3("input float3", vec4f); ImGui::DragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f); @@ -1752,6 +2006,7 @@ static void DemoWindowWidgetsMultiComponents() ImGui::InputInt3("input int3", vec4i); ImGui::DragInt3("drag int3", vec4i, 1, 0, 255); ImGui::SliderInt3("slider int3", vec4i, 0, 255); + ImGui::SeparatorText("4-wide"); ImGui::InputFloat4("input float4", vec4f); ImGui::DragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f); @@ -1759,18 +2014,22 @@ static void DemoWindowWidgetsMultiComponents() ImGui::InputInt4("input int4", vec4i); ImGui::DragInt4("drag int4", vec4i, 1, 0, 255); ImGui::SliderInt4("slider int4", vec4i, 0, 255); + ImGui::SeparatorText("Ranges"); static float begin = 10, end = 90; static int begin_i = 100, end_i = 1000; ImGui::DragFloatRange2("range float", &begin, &end, 0.25f, 0.0f, 100.0f, "Min: %.1f %%", "Max: %.1f %%", ImGuiSliderFlags_AlwaysClamp); ImGui::DragIntRange2("range int", &begin_i, &end_i, 5, 0, 1000, "Min: %d units", "Max: %d units"); ImGui::DragIntRange2("range int (no bounds)", &begin_i, &end_i, 5, 0, 0, "Min: %d units", "Max: %d units"); + ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsPlotting() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsPlotting() { // Plot/Graph widgets are not very good. @@ -1782,13 +2041,16 @@ static void DemoWindowWidgetsPlotting() ImGui::Text("Need better plotting and graphing? Consider using ImPlot:"); ImGui::TextLinkOpenURL("https://github.com/epezent/implot"); ImGui::Separator(); + static bool animate = true; ImGui::Checkbox("Animate", &animate); + // Plot as lines and plot as histogram static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0, 80.0f)); //ImGui::SameLine(); HelpMarker("Consider using ImPlot instead!"); + // Fill an array of contiguous float values to plot // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float // and the sizeof() of your structure in the "stride" parameter. @@ -1805,6 +2067,7 @@ static void DemoWindowWidgetsPlotting() phase += 0.10f * values_offset; refresh_time += 1.0f / 60.0f; } + // Plots can display overlay texts // (in this example, we will display an average value) { @@ -1816,6 +2079,7 @@ static void DemoWindowWidgetsPlotting() sprintf(overlay, "avg %f", average); ImGui::PlotLines("Lines", values, IM_ARRAYSIZE(values), values_offset, overlay, -1.0f, 1.0f, ImVec2(0, 80.0f)); } + // Use functions to generate output // FIXME: This is actually VERY awkward because current plot API only pass in indices. // We probably want an API passing floats and user provide sample rate/count. @@ -1833,12 +2097,15 @@ static void DemoWindowWidgetsPlotting() float (*func)(void*, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw; ImGui::PlotLines("Lines##2", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); ImGui::PlotHistogram("Histogram##2", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); + ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsProgressBars() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsProgressBars() { IMGUI_DEMO_MARKER("Widgets/Progress Bars"); @@ -1849,26 +2116,32 @@ static void DemoWindowWidgetsProgressBars() progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } + // Typically we would use ImVec2(-1.0f,0.0f) or ImVec2(-FLT_MIN,0.0f) to use all available width, // or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth. ImGui::ProgressBar(progress, ImVec2(0.0f, 0.0f)); ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); ImGui::Text("Progress Bar"); + float progress_saturated = IM_CLAMP(progress, 0.0f, 1.0f); char buf[32]; sprintf(buf, "%d/%d", (int)(progress_saturated * 1753), 1753); ImGui::ProgressBar(progress, ImVec2(0.f, 0.f), buf); + // Pass an animated negative value, e.g. -1.0f * (float)ImGui::GetTime() is the recommended value. // Adjust the factor if you want to adjust the animation speed. ImGui::ProgressBar(-1.0f * (float)ImGui::GetTime(), ImVec2(0.0f, 0.0f), "Searching.."); ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); ImGui::Text("Indeterminate"); + ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsQueryingStatuses() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsQueryingStatuses() { IMGUI_DEMO_MARKER("Widgets/Querying Item Status (Edited,Active,Hovered etc.)"); @@ -1886,6 +2159,7 @@ static void DemoWindowWidgetsQueryingStatuses() ImGui::SameLine(); HelpMarker("Testing how various types of items are interacting with the IsItemXXX functions. Note that the bool return value of most ImGui function is generally equivalent to calling ImGui::IsItemHovered()."); ImGui::Checkbox("Item Disabled", &item_disabled); + // Submit selected items so we can query their status in the code following it. bool ret = false; static bool b = false; @@ -1909,11 +2183,13 @@ static void DemoWindowWidgetsQueryingStatuses() if (item_type == 13) { ret = ImGui::TreeNodeEx("ITEM: TreeNode w/ ImGuiTreeNodeFlags_OpenOnDoubleClick", ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_NoTreePushOnOpen); } // Testing tree node with ImGuiButtonFlags_PressedOnDoubleClick button policy. if (item_type == 14) { const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::Combo("ITEM: Combo", ¤t, items, IM_ARRAYSIZE(items)); } if (item_type == 15) { const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", ¤t, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); } + bool hovered_delay_none = ImGui::IsItemHovered(); bool hovered_delay_stationary = ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary); bool hovered_delay_short = ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort); bool hovered_delay_normal = ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal); bool hovered_delay_tooltip = ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip); // = Normal + Stationary + // Display the values of IsItemHovered() and other common item state functions. // Note that the ImGuiHoveredFlags_XXX flags can be combined. // Because BulletText is an item itself and that would affect the output of IsItemXXX functions, @@ -1962,20 +2238,24 @@ static void DemoWindowWidgetsQueryingStatuses() ); ImGui::BulletText( "with Hovering Delay or Stationary test:\n" - "IsItemHovered() = = %d\n" + "IsItemHovered() = %d\n" "IsItemHovered(_Stationary) = %d\n" "IsItemHovered(_DelayShort) = %d\n" "IsItemHovered(_DelayNormal) = %d\n" "IsItemHovered(_Tooltip) = %d", hovered_delay_none, hovered_delay_stationary, hovered_delay_short, hovered_delay_normal, hovered_delay_tooltip); + if (item_disabled) ImGui::EndDisabled(); + char buf[1] = ""; ImGui::InputText("unused", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_ReadOnly); ImGui::SameLine(); HelpMarker("This widget is only here to be able to tab-out of the widgets above and see e.g. Deactivated() status."); + ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Querying Window Status (Focused,Hovered etc.)"); if (ImGui::TreeNode("Querying Window Status (Focused/Hovered etc.)")) { @@ -1983,6 +2263,7 @@ static void DemoWindowWidgetsQueryingStatuses() ImGui::Checkbox("Embed everything inside a child window for testing _RootWindow flag.", &embed_all_inside_a_child_window); if (embed_all_inside_a_child_window) ImGui::BeginChild("outer_child", ImVec2(0, ImGui::GetFontSize() * 20.0f), ImGuiChildFlags_Borders); + // Testing IsWindowFocused() function with its various flags. ImGui::BulletText( "IsWindowFocused() = %d\n" @@ -2007,6 +2288,7 @@ static void DemoWindowWidgetsQueryingStatuses() ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_NoPopupHierarchy), ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_DockHierarchy), ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)); + // Testing IsWindowHovered() function with its various flags. ImGui::BulletText( "IsWindowHovered() = %d\n" @@ -2039,11 +2321,13 @@ static void DemoWindowWidgetsQueryingStatuses() ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_AllowWhenBlockedByPopup), ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow), ImGui::IsWindowHovered(ImGuiHoveredFlags_Stationary)); + ImGui::BeginChild("child", ImVec2(0, 50), ImGuiChildFlags_Borders); ImGui::Text("This is another child window for testing the _ChildWindows flag."); ImGui::EndChild(); if (embed_all_inside_a_child_window) ImGui::EndChild(); + // Calling IsItemHovered() after begin returns the hovered status of the title bar. // This is useful in particular if you want to create a context menu associated to the title bar of a window. // This will also work when docked into a Tab (the Tab replace the Title Bar and guarantee the same properties). @@ -2065,12 +2349,15 @@ static void DemoWindowWidgetsQueryingStatuses() ImGui::IsItemHovered(), ImGui::IsItemActive()); ImGui::End(); } + ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsSelectables() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsSelectables() { IMGUI_DEMO_MARKER("Widgets/Selectables"); @@ -2095,6 +2382,7 @@ static void DemoWindowWidgetsSelectables() selection[3] = !selection[3]; ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Selectables/Rendering more items on the same line"); if (ImGui::TreeNode("Rendering more items on the same line")) { @@ -2106,10 +2394,12 @@ static void DemoWindowWidgetsSelectables() ImGui::SetNextItemAllowOverlap(); ImGui::Selectable("Hello.h", &selected[2]); ImGui::SameLine(); ImGui::SmallButton("Link 3"); ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Selectables/In Tables"); if (ImGui::TreeNode("In Tables")) { static bool selected[10] = {}; + if (ImGui::BeginTable("split1", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders)) { for (int i = 0; i < 10; i++) @@ -2140,15 +2430,18 @@ static void DemoWindowWidgetsSelectables() } ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Selectables/Grid"); if (ImGui::TreeNode("Grid")) { static char selected[4][4] = { { 1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 1 } }; + // Add in a bit of silly fun... const float time = (float)ImGui::GetTime(); const bool winning_state = memchr(selected, 0, sizeof(selected)) == NULL; // If all cells are selected... if (winning_state) ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f + 0.5f * cosf(time * 2.0f), 0.5f + 0.5f * sinf(time * 3.0f))); + for (int y = 0; y < 4; y++) for (int x = 0; x < 4; x++) { @@ -2166,6 +2459,7 @@ static void DemoWindowWidgetsSelectables() } ImGui::PopID(); } + if (winning_state) ImGui::PopStyleVar(); ImGui::TreePop(); @@ -2196,23 +2490,26 @@ static void DemoWindowWidgetsSelectables() ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsSelectionAndMultiSelect() //----------------------------------------------------------------------------- // Multi-selection demos // Also read: https://github.com/ocornut/imgui/wiki/Multi-Select //----------------------------------------------------------------------------- + static const char* ExampleNames[] = { "Artichoke", "Arugula", "Asparagus", "Avocado", "Bamboo Shoots", "Bean Sprouts", "Beans", "Beet", "Belgian Endive", "Bell Pepper", "Bitter Gourd", "Bok Choy", "Broccoli", "Brussels Sprouts", "Burdock Root", "Cabbage", "Calabash", "Capers", "Carrot", "Cassava", "Cauliflower", "Celery", "Celery Root", "Celcuce", "Chayote", "Chinese Broccoli", "Corn", "Cucumber" }; + // Extra functions to add deletion support to ImGuiSelectionBasicStorage struct ExampleSelectionWithDeletion : ImGuiSelectionBasicStorage { // Find which item should be Focused after deletion. - // Call _before_ item submission. Retunr an index in the before-deletion item list, your item loop should call SetKeyboardFocusHere() on it. + // Call _before_ item submission. Return an index in the before-deletion item list, your item loop should call SetKeyboardFocusHere() on it. // The subsequent ApplyDeletionPostLoop() code will use it to apply Selection. // - We cannot provide this logic in core Dear ImGui because we don't have access to selection data. // - We don't actually manipulate the ImVector<> here, only in ApplyDeletionPostLoop(), but using similar API for consistency and flexibility. @@ -2222,6 +2519,7 @@ struct ExampleSelectionWithDeletion : ImGuiSelectionBasicStorage { if (Size == 0) return -1; + // If focused item is not selected... const int focused_idx = (int)ms_io->NavIdItem; // Index of currently focused item if (ms_io->NavIdSelected == false) // This is merely a shortcut, == Contains(adapter->IndexToStorage(items, focused_idx)) @@ -2229,16 +2527,20 @@ struct ExampleSelectionWithDeletion : ImGuiSelectionBasicStorage ms_io->RangeSrcReset = true; // Request to recover RangeSrc from NavId next frame. Would be ok to reset even when NavIdSelected==true, but it would take an extra frame to recover RangeSrc when deleting a selected item. return focused_idx; // Request to focus same item after deletion. } + // If focused item is selected: land on first unselected item after focused item. for (int idx = focused_idx + 1; idx < items_count; idx++) if (!Contains(GetStorageIdFromIndex(idx))) return idx; + // If focused item is selected: otherwise return last unselected item before focused item. for (int idx = IM_MIN(focused_idx, items_count) - 1; idx >= 0; idx--) if (!Contains(GetStorageIdFromIndex(idx))) return idx; + return -1; } + // Rewrite item list (delete items) + update selection. // - Call after EndMultiSelect() // - We cannot provide this logic in core Dear ImGui because we don't have access to your items, nor to selection data. @@ -2258,18 +2560,21 @@ struct ExampleSelectionWithDeletion : ImGuiSelectionBasicStorage item_next_idx_to_select = new_items.Size - 1; } items.swap(new_items); + // Update selection Clear(); if (item_next_idx_to_select != -1 && ms_io->NavIdSelected) SetItemSelected(GetStorageIdFromIndex(item_next_idx_to_select), true); } }; + // Example: Implement dual list box storage and interface struct ExampleDualListBox { ImVector Items[2]; // ID is index into ExampleName[] ImGuiSelectionBasicStorage Selections[2]; // Store ExampleItemId into selection bool OptKeepSorted = true; + void MoveAll(int src, int dst) { IM_ASSERT((src == 0 && dst == 1) || (src == 1 && dst == 0)); @@ -2307,7 +2612,7 @@ struct ExampleDualListBox { const int* a = (const int*)lhs; const int* b = (const int*)rhs; - return (*a - *b) > 0 ? +1 : -1; + return (*a - *b); } void SortItems(int n) { @@ -2315,13 +2620,14 @@ struct ExampleDualListBox } void Show() { - //ImGui::Checkbox("Sorted", &OptKeepSorted); + //if (ImGui::Checkbox("Sorted", &OptKeepSorted) && OptKeepSorted) { SortItems(0); SortItems(1); } if (ImGui::BeginTable("split", 3, ImGuiTableFlags_None)) { ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); // Left side ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); // Buttons ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); // Right side ImGui::TableNextRow(); + int request_move_selected = -1; int request_move_all = -1; float child_height_0 = 0.0f; @@ -2331,11 +2637,14 @@ struct ExampleDualListBox // FIXME-NAV: Using ImGuiWindowFlags_NavFlattened exhibit many issues. ImVector& items = Items[side]; ImGuiSelectionBasicStorage& selection = Selections[side]; + ImGui::TableSetColumnIndex((side == 0) ? 0 : 2); ImGui::Text("%s (%d)", (side == 0) ? "Available" : "Basket", items.Size); + // Submit scrolling range to avoid glitches on moving/deletion const float items_height = ImGui::GetTextLineHeightWithSpacing(); ImGui::SetNextWindowContentSize(ImVec2(0.0f, items.Size * items_height)); + bool child_visible; if (side == 0) { @@ -2354,6 +2663,7 @@ struct ExampleDualListBox ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_None; ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size); ApplySelectionRequests(ms_io, side); + for (int item_n = 0; item_n < items.Size; item_n++) { ImGuiID item_id = items[item_n]; @@ -2369,16 +2679,19 @@ struct ExampleDualListBox request_move_selected = side; } } + ms_io = ImGui::EndMultiSelect(); ApplySelectionRequests(ms_io, side); } ImGui::EndChild(); } + // Buttons columns ImGui::TableSetColumnIndex(1); ImGui::NewLine(); //ImVec2 button_sz = { ImGui::CalcTextSize(">>").x + ImGui::GetStyle().FramePadding.x * 2.0f, ImGui::GetFrameHeight() + padding.y * 2.0f }; ImVec2 button_sz = { ImGui::GetFrameHeight(), ImGui::GetFrameHeight() }; + // (Using BeginDisabled()/EndDisabled() works but feels distracting given how it is currently visualized) if (ImGui::Button(">>", button_sz)) request_move_all = 0; @@ -2388,11 +2701,13 @@ struct ExampleDualListBox request_move_selected = 1; if (ImGui::Button("<<", button_sz)) request_move_all = 1; + // Process requests if (request_move_all != -1) MoveAll(request_move_all, request_move_all ^ 1); if (request_move_selected != -1) MoveSelected(request_move_selected, request_move_selected ^ 1); + // FIXME-MULTISELECT: Support action from outside /* if (OptKeepSorted == false) @@ -2402,16 +2717,19 @@ struct ExampleDualListBox if (ImGui::ArrowButton("MoveDown", ImGuiDir_Down)) {} } */ + ImGui::EndTable(); } } }; + static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_data) { IMGUI_DEMO_MARKER("Widgets/Selection State & Multi-Select"); if (ImGui::TreeNode("Selection State & Multi-Select")) { HelpMarker("Selections can be built using Selectable(), TreeNode() or other widgets. Selection state is owned by application code/data."); + // Without any fancy API: manage single-selection yourself. IMGUI_DEMO_MARKER("Widgets/Selection State/Single-Select"); if (ImGui::TreeNode("Single-Select")) @@ -2426,6 +2744,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d } ImGui::TreePop(); } + // Demonstrate implementation a most-basic form of multi-selection manually // This doesn't support the SHIFT modifier which requires BeginMultiSelect()! IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (manual/simplified, without BeginMultiSelect)"); @@ -2446,6 +2765,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d } ImGui::TreePop(); } + // Demonstrate handling proper multi-selection using the BeginMultiSelect/EndMultiSelect API. // SHIFT+Click w/ CTRL and other standard features are supported. // We use the ImGuiSelectionBasicStorage helper which you may freely reimplement. @@ -2460,16 +2780,19 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::BulletText("Escape to clear selection."); ImGui::BulletText("Click and drag to box-select."); ImGui::Text("Tip: Use 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen."); + // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection const int ITEMS_COUNT = 50; static ImGuiSelectionBasicStorage selection; ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT); + // The BeginChild() has no purpose for selection logic, other that offering a scrolling region. if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) { ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT); selection.ApplyRequests(ms_io); + for (int n = 0; n < ITEMS_COUNT; n++) { char label[64]; @@ -2478,20 +2801,24 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::SetNextItemSelectionUserData(n); ImGui::Selectable(label, item_is_selected); } + ms_io = ImGui::EndMultiSelect(); selection.ApplyRequests(ms_io); } ImGui::EndChild(); ImGui::TreePop(); } + // Demonstrate using the clipper with BeginMultiSelect()/EndMultiSelect() IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (with clipper)"); if (ImGui::TreeNode("Multi-Select (with clipper)")) { // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection static ImGuiSelectionBasicStorage selection; + ImGui::Text("Added features:"); ImGui::BulletText("Using ImGuiListClipper."); + const int ITEMS_COUNT = 10000; ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT); if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) @@ -2499,6 +2826,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT); selection.ApplyRequests(ms_io); + ImGuiListClipper clipper; clipper.Begin(ITEMS_COUNT); if (ms_io->RangeSrcItem != -1) @@ -2514,12 +2842,14 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::Selectable(label, item_is_selected); } } + ms_io = ImGui::EndMultiSelect(); selection.ApplyRequests(ms_io); } ImGui::EndChild(); ImGui::TreePop(); } + // Demonstrate dynamic item list + deletion support using the BeginMultiSelect/EndMultiSelect API. // In order to support Deletion without any glitches you need to: // - (1) If items are submitted in their own scrolling area, submit contents size SetNextWindowContentSize() ahead of time to prevent one-frame readjustment of scrolling. @@ -2537,9 +2867,11 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d static ExampleSelectionWithDeletion selection; selection.UserData = (void*)&items; selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { ImVector* p_items = (ImVector*)self->UserData; return (*p_items)[idx]; }; // Index -> ID + ImGui::Text("Added features:"); ImGui::BulletText("Dynamic list with Delete key support."); ImGui::Text("Selection size: %d/%d", selection.Size, items.Size); + // Initialize default list with 50 items + button to add/remove items. static ImGuiID items_next_id = 0; if (items_next_id == 0) @@ -2548,27 +2880,33 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d if (ImGui::SmallButton("Add 20 items")) { for (int n = 0; n < 20; n++) { items.push_back(items_next_id++); } } ImGui::SameLine(); if (ImGui::SmallButton("Remove 20 items")) { for (int n = IM_MIN(20, items.Size); n > 0; n--) { selection.SetItemSelected(items.back(), false); items.pop_back(); } } + // (1) Extra to support deletion: Submit scrolling range to avoid glitches on deletion const float items_height = ImGui::GetTextLineHeightWithSpacing(); ImGui::SetNextWindowContentSize(ImVec2(0.0f, items.Size * items_height)); + if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) { ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size); selection.ApplyRequests(ms_io); + const bool want_delete = ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (selection.Size > 0); const int item_curr_idx_to_focus = want_delete ? selection.ApplyDeletionPreLoop(ms_io, items.Size) : -1; + for (int n = 0; n < items.Size; n++) { const ImGuiID item_id = items[n]; char label[64]; sprintf(label, "Object %05u: %s", item_id, ExampleNames[item_id % IM_ARRAYSIZE(ExampleNames)]); + bool item_is_selected = selection.Contains(item_id); ImGui::SetNextItemSelectionUserData(n); ImGui::Selectable(label, item_is_selected); if (item_curr_idx_to_focus == n) ImGui::SetKeyboardFocusHere(-1); } + // Apply multi-select requests ms_io = ImGui::EndMultiSelect(); selection.ApplyRequests(ms_io); @@ -2578,6 +2916,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::EndChild(); ImGui::TreePop(); } + // Implement a Dual List Box (#6648) IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (dual list box)"); if (ImGui::TreeNode("Multi-Select (dual list box)")) @@ -2587,15 +2926,19 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d if (dlb.Items[0].Size == 0 && dlb.Items[1].Size == 0) for (int item_id = 0; item_id < IM_ARRAYSIZE(ExampleNames); item_id++) dlb.Items[0].push_back((ImGuiID)item_id); + // Show dlb.Show(); + ImGui::TreePop(); } + // Demonstrate using the clipper with BeginMultiSelect()/EndMultiSelect() IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (in a table)"); if (ImGui::TreeNode("Multi-Select (in a table)")) { static ImGuiSelectionBasicStorage selection; + const int ITEMS_COUNT = 10000; ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT); if (ImGui::BeginTable("##Basket", 2, ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter)) @@ -2604,9 +2947,11 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::TableSetupColumn("Action"); ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableHeadersRow(); + ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT); selection.ApplyRequests(ms_io); + ImGuiListClipper clipper; clipper.Begin(ITEMS_COUNT); if (ms_io->RangeSrcItem != -1) @@ -2626,12 +2971,14 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::SmallButton("hello"); } } + ms_io = ImGui::EndMultiSelect(); selection.ApplyRequests(ms_io); ImGui::EndTable(); } ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (checkboxes)"); if (ImGui::TreeNode("Multi-Select (checkboxes)")) { @@ -2639,12 +2986,14 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::BulletText("Using _NoAutoSelect + _NoAutoClear flags."); ImGui::BulletText("Shift+Click to check multiple boxes."); ImGui::BulletText("Shift+Keyboard to copy current value to other boxes."); + // If you have an array of checkboxes, you may want to use NoAutoSelect + NoAutoClear and the ImGuiSelectionExternalStorage helper. static bool items[20] = {}; static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_NoAutoSelect | ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_ClearOnEscape; ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoSelect", &flags, ImGuiMultiSelectFlags_NoAutoSelect); ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoClear", &flags, ImGuiMultiSelectFlags_NoAutoClear); ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect2d", &flags, ImGuiMultiSelectFlags_BoxSelect2d); // Cannot use ImGuiMultiSelectFlags_BoxSelect1d as checkboxes are varying width. + if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY)) { ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, -1, IM_ARRAYSIZE(items)); @@ -2663,8 +3012,10 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d storage_wrapper.ApplyRequests(ms_io); } ImGui::EndChild(); + ImGui::TreePop(); } + // Demonstrate individual selection scopes in same window IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (multiple scopes)"); if (ImGui::TreeNode("Multi-Select (multiple scopes)")) @@ -2673,6 +3024,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d const int SCOPES_COUNT = 3; const int ITEMS_COUNT = 8; // Per scope static ImGuiSelectionBasicStorage selections_data[SCOPES_COUNT]; + // Use ImGuiMultiSelectFlags_ScopeRect to not affect other selections in same window. static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ScopeRect | ImGuiMultiSelectFlags_ClearOnEscape;// | ImGuiMultiSelectFlags_ClearOnClickVoid; if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeWindow", &flags, ImGuiMultiSelectFlags_ScopeWindow) && (flags & ImGuiMultiSelectFlags_ScopeWindow)) @@ -2681,14 +3033,17 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d flags &= ~ImGuiMultiSelectFlags_ScopeWindow; ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnClickVoid", &flags, ImGuiMultiSelectFlags_ClearOnClickVoid); ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect1d", &flags, ImGuiMultiSelectFlags_BoxSelect1d); + for (int selection_scope_n = 0; selection_scope_n < SCOPES_COUNT; selection_scope_n++) { ImGui::PushID(selection_scope_n); ImGuiSelectionBasicStorage* selection = &selections_data[selection_scope_n]; ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection->Size, ITEMS_COUNT); selection->ApplyRequests(ms_io); + ImGui::SeparatorText("Selection scope"); ImGui::Text("Selection size: %d/%d", selection->Size, ITEMS_COUNT); + for (int n = 0; n < ITEMS_COUNT; n++) { char label[64]; @@ -2697,6 +3052,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::SetNextItemSelectionUserData(n); ImGui::Selectable(label, item_is_selected); } + // Apply multi-select requests ms_io = ImGui::EndMultiSelect(); selection->ApplyRequests(ms_io); @@ -2704,6 +3060,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d } ImGui::TreePop(); } + // See ShowExampleAppAssetsBrowser() if (ImGui::TreeNode("Multi-Select (tiled assets browser)")) { @@ -2711,6 +3068,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::Text("(also access from 'Examples->Assets Browser' in menu)"); ImGui::TreePop(); } + // Demonstrate supporting multiple-selection in a tree. // - We don't use linear indices for selection user data, but our ExampleTreeNode* pointer directly! // This showcase how SetNextItemSelectionUserData() never assume indices! @@ -2728,16 +3086,18 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d "This is rather advanced and experimental. If you are getting started with multi-select, " "please don't start by looking at how to use it for a tree!\n\n" "Future versions will try to simplify and formalize some of this."); + struct ExampleTreeFuncs { static void DrawNode(ExampleTreeNode* node, ImGuiSelectionBasicStorage* selection) { ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; - tree_node_flags |= ImGuiTreeNodeFlags_NavLeftJumpsBackHere; // Enable pressing left to jump to parent + tree_node_flags |= ImGuiTreeNodeFlags_NavLeftJumpsToParent; // Enable pressing left to jump to parent if (node->Childs.Size == 0) tree_node_flags |= ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_Leaf; if (selection->Contains((ImGuiID)node->UID)) tree_node_flags |= ImGuiTreeNodeFlags_Selected; + // Using SetNextItemStorageID() to specify storage id, so we can easily peek into // the storage holding open/close stage, using our TreeNodeGetOpen/TreeNodeSetOpen() functions. ImGui::SetNextItemSelectionUserData((ImGuiSelectionUserData)(intptr_t)node); @@ -2753,14 +3113,17 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d TreeCloseAndUnselectChildNodes(node, selection); } } + static bool TreeNodeGetOpen(ExampleTreeNode* node) { return ImGui::GetStateStorage()->GetBool((ImGuiID)node->UID); } + static void TreeNodeSetOpen(ExampleTreeNode* node, bool open) { ImGui::GetStateStorage()->SetBool((ImGuiID)node->UID, open); } + // When closing a node: 1) close and unselect all child nodes, 2) select parent if any child was selected. // FIXME: This is currently handled by user logic but I'm hoping to eventually provide tree node // features to do this automatically, e.g. a ImGuiTreeNodeFlags_AutoCloseChildNodes etc. @@ -2774,10 +3137,12 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d unselected_count += TreeCloseAndUnselectChildNodes(child, selection, depth + 1); TreeNodeSetOpen(node, false); } + // Select root node if any of its child was selected, otherwise unselect selection->SetItemSelected((ImGuiID)node->UID, (depth == 0 && unselected_count > 0)); return unselected_count; } + // Apply multi-selection requests static void ApplySelectionRequests(ImGuiMultiSelectIO* ms_io, ExampleTreeNode* tree, ImGuiSelectionBasicStorage* selection) { @@ -2799,6 +3164,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d } } } + static void TreeSetAllInOpenNodes(ExampleTreeNode* node, ImGuiSelectionBasicStorage* selection, bool selected) { if (node->Parent != NULL) // Root node isn't visible nor selectable in our scheme @@ -2807,6 +3173,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d for (ExampleTreeNode* child : node->Childs) TreeSetAllInOpenNodes(child, selection, selected); } + // Interpolate in *user-visible order* AND only *over opened nodes*. // If you have a sequential mapping tables (e.g. generated after a filter/search pass) this would be simpler. // Here the tricks are that: @@ -2820,9 +3187,11 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d // Reached last node if (curr_node == last_node) return NULL; + // Recurse into childs. Query storage to tell if the node is open. if (curr_node->Childs.Size > 0 && TreeNodeGetOpen(curr_node)) return curr_node->Childs[0]; + // Next sibling, then into our own parent while (curr_node->Parent != NULL) { @@ -2832,11 +3201,14 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d } return NULL; } + }; // ExampleTreeFuncs + static ImGuiSelectionBasicStorage selection; if (demo_data->DemoTree == NULL) demo_data->DemoTree = ExampleTree_CreateDemoTree(); // Create tree once ImGui::Text("Selection size: %d", selection.Size); + if (ImGui::BeginChild("##Tree", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) { ExampleTreeNode* tree = demo_data->DemoTree; @@ -2849,8 +3221,10 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ExampleTreeFuncs::ApplySelectionRequests(ms_io, tree, &selection); } ImGui::EndChild(); + ImGui::TreePop(); } + // Advanced demonstration of BeginMultiSelect() // - Showcase clipping. // - Showcase deletion. @@ -2870,6 +3244,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d static bool show_color_button = true; static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; static WidgetType widget_type = WidgetType_Selectable; + if (ImGui::TreeNode("Options")) { if (ImGui::RadioButton("Selectables", widget_type == WidgetType_Selectable)) { widget_type = WidgetType_Selectable; } @@ -2904,6 +3279,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::SameLine(); HelpMarker("Allow dragging an unselected item without altering selection."); ImGui::TreePop(); } + // Initialize default list with 1000 items. // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection static ImVector items; @@ -2911,7 +3287,9 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d if (items_next_id == 0) { for (int n = 0; n < 1000; n++) { items.push_back(items_next_id++); } } static ExampleSelectionWithDeletion selection; static bool request_deletion_from_menu = false; // Queue deletion triggered from context menu + ImGui::Text("Selection size: %d/%d", selection.Size, items.Size); + const float items_height = (widget_type == WidgetType_TreeNode) ? ImGui::GetTextLineHeight() : ImGui::GetTextLineHeightWithSpacing(); ImGui::SetNextWindowContentSize(ImVec2(0.0f, items.Size * items_height)); if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) @@ -2919,11 +3297,14 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImVec2 color_button_sz(ImGui::GetFontSize(), ImGui::GetFontSize()); if (widget_type == WidgetType_TreeNode) ImGui::PushStyleVarY(ImGuiStyleVar_ItemSpacing, 0.0f); + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size); selection.ApplyRequests(ms_io); + const bool want_delete = (ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (selection.Size > 0)) || request_deletion_from_menu; const int item_curr_idx_to_focus = want_delete ? selection.ApplyDeletionPreLoop(ms_io, items.Size) : -1; request_deletion_from_menu = false; + if (show_in_table) { if (widget_type == WidgetType_TreeNode) @@ -2933,6 +3314,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.30f); //ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacingY, 0.0f); } + ImGuiListClipper clipper; if (use_clipper) { @@ -2942,6 +3324,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d if (ms_io->RangeSrcItem != -1) clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped. } + while (!use_clipper || clipper.Step()) { const int item_begin = use_clipper ? clipper.DisplayStart : 0; @@ -2950,15 +3333,18 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d { if (show_in_table) ImGui::TableNextColumn(); + const int item_id = items[n]; const char* item_category = ExampleNames[item_id % IM_ARRAYSIZE(ExampleNames)]; char label[64]; sprintf(label, "Object %05d: %s", item_id, item_category); + // IMPORTANT: for deletion refocus to work we need object ID to be stable, // aka not depend on their index in the list. Here we use our persistent item_id // instead of index to build a unique ID that will persist. // (If we used PushID(index) instead, focus wouldn't be restored correctly after deletion). ImGui::PushID(item_id); + // Emit a color button, to test that Shift+LeftArrow landing on an item that is not part // of the selection scope doesn't erroneously alter our selection. if (show_color_button) @@ -2967,6 +3353,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::ColorButton("##", ImColor(dummy_col), ImGuiColorEditFlags_NoTooltip, color_button_sz); ImGui::SameLine(); } + // Submit item bool item_is_selected = selection.Contains((ImGuiID)n); bool item_is_open = false; @@ -2982,9 +3369,11 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d tree_node_flags |= ImGuiTreeNodeFlags_Selected; item_is_open = ImGui::TreeNodeEx(label, tree_node_flags); } + // Focus (for after deletion) if (item_curr_idx_to_focus == n) ImGui::SetKeyboardFocusHere(-1); + // Drag and Drop if (use_drag_drop && ImGui::BeginDragDropSource()) { @@ -3002,6 +3391,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d payload_items.push_back((int)id); ImGui::SetDragDropPayload("MULTISELECT_DEMO_ITEMS", payload_items.Data, (size_t)payload_items.size_in_bytes()); } + // Display payload content in tooltip const ImGuiPayload* payload = ImGui::GetDragDropPayload(); const int* payload_items = (int*)payload->Data; @@ -3010,10 +3400,13 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::Text("Object %05d: %s", payload_items[0], ExampleNames[payload_items[0] % IM_ARRAYSIZE(ExampleNames)]); else ImGui::Text("Dragging %d objects", payload_count); + ImGui::EndDragDropSource(); } + if (widget_type == WidgetType_TreeNode && item_is_open) ImGui::TreePop(); + // Right-click: context menu if (ImGui::BeginPopupContextItem()) { @@ -3025,6 +3418,7 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::Selectable("Close"); ImGui::EndPopup(); } + // Demo content within a table if (show_in_table) { @@ -3034,22 +3428,26 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::InputText("###NoLabel", (char*)(void*)item_category, strlen(item_category), ImGuiInputTextFlags_ReadOnly); ImGui::PopStyleVar(); } + ImGui::PopID(); } if (!use_clipper) break; } + if (show_in_table) { ImGui::EndTable(); if (widget_type == WidgetType_TreeNode) ImGui::PopStyleVar(); } + // Apply multi-select requests ms_io = ImGui::EndMultiSelect(); selection.ApplyRequests(ms_io); if (want_delete) selection.ApplyDeletionPostLoop(ms_io, items, item_curr_idx_to_focus); + if (widget_type == WidgetType_TreeNode) ImGui::PopStyleVar(); } @@ -3059,9 +3457,23 @@ static void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_d ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsTabs() //----------------------------------------------------------------------------- + +static void EditTabBarFittingPolicyFlags(ImGuiTabBarFlags* p_flags) +{ + if ((*p_flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0) + *p_flags |= ImGuiTabBarFlags_FittingPolicyDefault_; + if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyMixed", p_flags, ImGuiTabBarFlags_FittingPolicyMixed)) + *p_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyMixed); + if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyShrink", p_flags, ImGuiTabBarFlags_FittingPolicyShrink)) + *p_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyShrink); + if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", p_flags, ImGuiTabBarFlags_FittingPolicyScroll)) + *p_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll); +} + static void DemoWindowWidgetsTabs() { IMGUI_DEMO_MARKER("Widgets/Tabs"); @@ -3093,6 +3505,7 @@ static void DemoWindowWidgetsTabs() ImGui::Separator(); ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Tabs/Advanced & Close Button"); if (ImGui::TreeNode("Advanced & Close Button")) { @@ -3103,12 +3516,8 @@ static void DemoWindowWidgetsTabs() ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", &tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton); ImGui::CheckboxFlags("ImGuiTabBarFlags_NoCloseWithMiddleMouseButton", &tab_bar_flags, ImGuiTabBarFlags_NoCloseWithMiddleMouseButton); ImGui::CheckboxFlags("ImGuiTabBarFlags_DrawSelectedOverline", &tab_bar_flags, ImGuiTabBarFlags_DrawSelectedOverline); - if ((tab_bar_flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0) - tab_bar_flags |= ImGuiTabBarFlags_FittingPolicyDefault_; - if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown)) - tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown); - if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll)) - tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll); + EditTabBarFittingPolicyFlags(&tab_bar_flags); + // Tab Bar ImGui::AlignTextToFramePadding(); ImGui::Text("Opened:"); @@ -3119,6 +3528,7 @@ static void DemoWindowWidgetsTabs() ImGui::SameLine(); ImGui::Checkbox(names[n], &opened[n]); } + // Passing a bool* to BeginTabItem() is similar to passing one to Begin(): // the underlying bool will be set to false when the tab is closed. if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) @@ -3136,6 +3546,7 @@ static void DemoWindowWidgetsTabs() ImGui::Separator(); ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Tabs/TabItemButton & Leading-Trailing flags"); if (ImGui::TreeNode("TabItemButton & Leading/Trailing flags")) { @@ -3144,6 +3555,7 @@ static void DemoWindowWidgetsTabs() if (next_tab_id == 0) // Initialize with some default tabs for (int i = 0; i < 3; i++) active_tabs.push_back(next_tab_id++); + // TabItemButton() and Leading/Trailing flags are distinct features which we will demo together. // (It is possible to submit regular tabs with Leading/Trailing flags, or TabItemButton tabs without Leading/Trailing flags... // but they tend to make more sense together) @@ -3151,13 +3563,11 @@ static void DemoWindowWidgetsTabs() static bool show_trailing_button = true; ImGui::Checkbox("Show Leading TabItemButton()", &show_leading_button); ImGui::Checkbox("Show Trailing TabItemButton()", &show_trailing_button); + // Expose some other flags which are useful to showcase how they interact with Leading/Trailing tabs - static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyResizeDown; - ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", &tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton); - if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown)) - tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown); - if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll)) - tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll); + static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyShrink; + EditTabBarFittingPolicyFlags(&tab_bar_flags); + if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) { // Demo a Leading TabItemButton(): click the "?" button to open a menu @@ -3169,12 +3579,14 @@ static void DemoWindowWidgetsTabs() ImGui::Selectable("Hello!"); ImGui::EndPopup(); } + // Demo Trailing Tabs: click the "+" button to add a new tab. // (In your app you may want to use a font icon instead of the "+") // We submit it before the regular tabs, but thanks to the ImGuiTabItemFlags_Trailing flag it will always appear at the end. if (show_trailing_button) if (ImGui::TabItemButton("+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) active_tabs.push_back(next_tab_id++); // Add new tab + // Submit our regular tabs for (int n = 0; n < active_tabs.Size; ) { @@ -3186,11 +3598,13 @@ static void DemoWindowWidgetsTabs() ImGui::Text("This is the %s tab!", name); ImGui::EndTabItem(); } + if (!open) active_tabs.erase(active_tabs.Data + n); else n++; } + ImGui::EndTabBar(); } ImGui::Separator(); @@ -3199,9 +3613,11 @@ static void DemoWindowWidgetsTabs() ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsText() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsText() { IMGUI_DEMO_MARKER("Widgets/Text"); @@ -3217,6 +3633,44 @@ static void DemoWindowWidgetsText() ImGui::SameLine(); HelpMarker("The TextDisabled color is stored in ImGuiStyle."); ImGui::TreePop(); } + + IMGUI_DEMO_MARKER("Widgets/Text/Font Size"); + if (ImGui::TreeNode("Font Size")) + { + ImGuiStyle& style = ImGui::GetStyle(); + const float global_scale = style.FontScaleMain * style.FontScaleDpi; + ImGui::Text("style.FontScaleMain = %0.2f", style.FontScaleMain); + ImGui::Text("style.FontScaleDpi = %0.2f", style.FontScaleDpi); + ImGui::Text("global_scale = ~%0.2f", global_scale); // This is not technically accurate as internal scales may apply, but conceptually let's pretend it is. + ImGui::Text("FontSize = %0.2f", ImGui::GetFontSize()); + + ImGui::SeparatorText(""); + static float custom_size = 16.0f; + ImGui::SliderFloat("custom_size", &custom_size, 10.0f, 100.0f, "%.0f"); + ImGui::Text("ImGui::PushFont(nullptr, custom_size);"); + ImGui::PushFont(NULL, custom_size); + ImGui::Text("FontSize = %.2f (== %.2f * global_scale)", ImGui::GetFontSize(), custom_size); + ImGui::PopFont(); + + ImGui::SeparatorText(""); + static float custom_scale = 1.0f; + ImGui::SliderFloat("custom_scale", &custom_scale, 0.5f, 4.0f, "%.2f"); + ImGui::Text("ImGui::PushFont(nullptr, style.FontSizeBase * custom_scale);"); + ImGui::PushFont(NULL, style.FontSizeBase * custom_scale); + ImGui::Text("FontSize = %.2f (== style.FontSizeBase * %.2f * global_scale)", ImGui::GetFontSize(), custom_scale); + ImGui::PopFont(); + + ImGui::SeparatorText(""); + for (float scaling = 0.5f; scaling <= 4.0f; scaling += 0.5f) + { + ImGui::PushFont(NULL, style.FontSizeBase * scaling); + ImGui::Text("FontSize = %.2f (== style.FontSizeBase * %.2f * global_scale)", ImGui::GetFontSize(), scaling); + ImGui::PopFont(); + } + + ImGui::TreePop(); + } + IMGUI_DEMO_MARKER("Widgets/Text/Word Wrapping"); if (ImGui::TreeNode("Word Wrapping")) { @@ -3225,8 +3679,10 @@ static void DemoWindowWidgetsText() "This text should automatically wrap on the edge of the window. The current implementation " "for text wrapping follows simple rules suitable for English and possibly other languages."); ImGui::Spacing(); + static float wrap_width = 200.0f; ImGui::SliderFloat("Wrap width", &wrap_width, -20, 600, "%.0f"); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); for (int n = 0; n < 2; n++) { @@ -3239,13 +3695,16 @@ static void DemoWindowWidgetsText() ImGui::Text("The lazy dog is a good dog. This paragraph should fit within %.0f pixels. Testing a 1 character word. The quick brown fox jumps over the lazy dog.", wrap_width); else ImGui::Text("aaaaaaaa bbbbbbbb, c cccccccc,dddddddd. d eeeeeeee ffffffff. gggggggg!hhhhhhhh"); + // Draw actual text bounding box, following by marker of our expected limit (should not overlap!) draw_list->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255, 255, 0, 255)); draw_list->AddRectFilled(marker_min, marker_max, IM_COL32(255, 0, 255, 255)); ImGui::PopTextWrapPos(); } + ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Text/UTF-8 Text"); if (ImGui::TreeNode("UTF-8 Text")) { @@ -3273,9 +3732,11 @@ static void DemoWindowWidgetsText() ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsTextFilter() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsTextFilter() { IMGUI_DEMO_MARKER("Widgets/Text Filter"); @@ -3298,9 +3759,11 @@ static void DemoWindowWidgetsTextFilter() ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsTextInput() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsTextInput() { // To wire InputText() with std::string or any other custom string type, @@ -3324,6 +3787,7 @@ static void DemoWindowWidgetsTextInput() "*/\n\n" "label:\n" "\tlock cmpxchg8b eax\n"; + static ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput; HelpMarker("You can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputTextMultiline() to a dynamic string type. See misc/cpp/imgui_stdlib.h for an example. (This is not demonstrated in imgui_demo.cpp because we don't want to include in here)"); ImGui::CheckboxFlags("ImGuiInputTextFlags_ReadOnly", &flags, ImGuiInputTextFlags_ReadOnly); @@ -3333,6 +3797,7 @@ static void DemoWindowWidgetsTextInput() ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 16), flags); ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Text Input/Filtered Text Input"); if (ImGui::TreeNode("Filtered Text Input")) { @@ -3345,6 +3810,7 @@ static void DemoWindowWidgetsTextInput() else if (data->EventChar >= 'A' && data->EventChar <= 'Z') { data->EventChar += 'a' - 'A'; } // Uppercase becomes lowercase return 0; } + // Return 0 (pass) if the character is 'i' or 'm' or 'g' or 'u' or 'i', otherwise return 1 (filter out) static int FilterImGuiLetters(ImGuiInputTextCallbackData* data) { @@ -3353,15 +3819,17 @@ static void DemoWindowWidgetsTextInput() return 1; } }; - static char buf1[32] = ""; ImGui::InputText("default", buf1, 32); - static char buf2[32] = ""; ImGui::InputText("decimal", buf2, 32, ImGuiInputTextFlags_CharsDecimal); - static char buf3[32] = ""; ImGui::InputText("hexadecimal", buf3, 32, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); - static char buf4[32] = ""; ImGui::InputText("uppercase", buf4, 32, ImGuiInputTextFlags_CharsUppercase); - static char buf5[32] = ""; ImGui::InputText("no blank", buf5, 32, ImGuiInputTextFlags_CharsNoBlank); - static char buf6[32] = ""; ImGui::InputText("casing swap", buf6, 32, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterCasingSwap); // Use CharFilter callback to replace characters. - static char buf7[32] = ""; ImGui::InputText("\"imgui\"", buf7, 32, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters); // Use CharFilter callback to disable some characters. + + static char buf1[32] = ""; ImGui::InputText("default", buf1, IM_ARRAYSIZE(buf1)); + static char buf2[32] = ""; ImGui::InputText("decimal", buf2, IM_ARRAYSIZE(buf2), ImGuiInputTextFlags_CharsDecimal); + static char buf3[32] = ""; ImGui::InputText("hexadecimal", buf3, IM_ARRAYSIZE(buf3), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); + static char buf4[32] = ""; ImGui::InputText("uppercase", buf4, IM_ARRAYSIZE(buf4), ImGuiInputTextFlags_CharsUppercase); + static char buf5[32] = ""; ImGui::InputText("no blank", buf5, IM_ARRAYSIZE(buf5), ImGuiInputTextFlags_CharsNoBlank); + static char buf6[32] = ""; ImGui::InputText("casing swap", buf6, IM_ARRAYSIZE(buf6), ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterCasingSwap); // Use CharFilter callback to replace characters. + static char buf7[32] = ""; ImGui::InputText("\"imgui\"", buf7, IM_ARRAYSIZE(buf7), ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters); // Use CharFilter callback to disable some characters. ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Text Input/Password input"); if (ImGui::TreeNode("Password Input")) { @@ -3372,6 +3840,7 @@ static void DemoWindowWidgetsTextInput() ImGui::InputText("password (clear)", password, IM_ARRAYSIZE(password)); ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Text Input/Completion, History, Edit Callbacks"); if (ImGui::TreeNode("Completion, History, Edit Callbacks")) { @@ -3404,6 +3873,7 @@ static void DemoWindowWidgetsTextInput() char c = data->Buf[0]; if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) data->Buf[0] ^= 32; data->BufDirty = true; + // Increment a counter int* p_int = (int*)data->UserData; *p_int = *p_int + 1; @@ -3412,23 +3882,27 @@ static void DemoWindowWidgetsTextInput() } }; static char buf1[64]; - ImGui::InputText("Completion", buf1, 64, ImGuiInputTextFlags_CallbackCompletion, Funcs::MyCallback); + ImGui::InputText("Completion", buf1, IM_ARRAYSIZE(buf1), ImGuiInputTextFlags_CallbackCompletion, Funcs::MyCallback); ImGui::SameLine(); HelpMarker( "Here we append \"..\" each time Tab is pressed. " "See 'Examples>Console' for a more meaningful demonstration of using this callback."); + static char buf2[64]; - ImGui::InputText("History", buf2, 64, ImGuiInputTextFlags_CallbackHistory, Funcs::MyCallback); + ImGui::InputText("History", buf2, IM_ARRAYSIZE(buf2), ImGuiInputTextFlags_CallbackHistory, Funcs::MyCallback); ImGui::SameLine(); HelpMarker( "Here we replace and select text each time Up/Down are pressed. " "See 'Examples>Console' for a more meaningful demonstration of using this callback."); + static char buf3[64]; static int edit_count = 0; - ImGui::InputText("Edit", buf3, 64, ImGuiInputTextFlags_CallbackEdit, Funcs::MyCallback, (void*)&edit_count); + ImGui::InputText("Edit", buf3, IM_ARRAYSIZE(buf3), ImGuiInputTextFlags_CallbackEdit, Funcs::MyCallback, (void*)&edit_count); ImGui::SameLine(); HelpMarker( "Here we toggle the casing of the first character on every edit + count edits."); ImGui::SameLine(); ImGui::Text("(%d)", edit_count); + ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Text Input/Resize Callback"); if (ImGui::TreeNode("Resize Callback")) { @@ -3451,6 +3925,7 @@ static void DemoWindowWidgetsTextInput() } return 0; } + // Note: Because ImGui:: is a namespace you would typically add your own function into the namespace. // For example, you code may declare a function 'ImGui::InputText(const char* label, MyString* my_str)' static bool MyInputTextMultiline(const char* label, ImVector* my_str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0) @@ -3459,6 +3934,7 @@ static void DemoWindowWidgetsTextInput() return ImGui::InputTextMultiline(label, my_str->begin(), (size_t)my_str->size(), size, flags | ImGuiInputTextFlags_CallbackResize, Funcs::MyResizeCallback, (void*)my_str); } }; + // For this demo we are using ImVector as a string container. // Note that because we need to store a terminating zero character, our size/capacity are 1 more // than usually reported by a typical string class. @@ -3469,6 +3945,7 @@ static void DemoWindowWidgetsTextInput() ImGui::Text("Data: %p\nSize: %d\nCapacity: %d", (void*)my_str.begin(), my_str.size(), my_str.capacity()); ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Text Input/Eliding, Alignment"); if (ImGui::TreeNode("Eliding, Alignment")) { @@ -3478,6 +3955,7 @@ static void DemoWindowWidgetsTextInput() ImGui::InputText("Path", buf1, IM_ARRAYSIZE(buf1), flags); ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Widgets/Text Input/Miscellaneous"); if (ImGui::TreeNode("Miscellaneous")) { @@ -3489,12 +3967,16 @@ static void DemoWindowWidgetsTextInput() ImGui::InputText("Hello", buf1, IM_ARRAYSIZE(buf1), flags); ImGui::TreePop(); } + ImGui::TreePop(); } + } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsTooltips() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsTooltips() { IMGUI_DEMO_MARKER("Widgets/Tooltips"); @@ -3502,17 +3984,23 @@ static void DemoWindowWidgetsTooltips() { // Tooltips are windows following the mouse. They do not take focus away. ImGui::SeparatorText("General"); + // Typical use cases: // - Short-form (text only): SetItemTooltip("Hello"); // - Short-form (any contents): if (BeginItemTooltip()) { Text("Hello"); EndTooltip(); } + // - Full-form (text only): if (IsItemHovered(...)) { SetTooltip("Hello"); } // - Full-form (any contents): if (IsItemHovered(...) && BeginTooltip()) { Text("Hello"); EndTooltip(); } + HelpMarker( "Tooltip are typically created by using a IsItemHovered() + SetTooltip() sequence.\n\n" "We provide a helper SetItemTooltip() function to perform the two with standards flags."); + ImVec2 sz = ImVec2(-FLT_MIN, 0.0f); + ImGui::Button("Basic", sz); ImGui::SetItemTooltip("I am a tooltip"); + ImGui::Button("Fancy", sz); if (ImGui::BeginItemTooltip()) { @@ -3522,7 +4010,9 @@ static void DemoWindowWidgetsTooltips() ImGui::Text("Sin(time) = %f", sinf((float)ImGui::GetTime())); ImGui::EndTooltip(); } + ImGui::SeparatorText("Always On"); + // Showcase NOT relying on a IsItemHovered() to emit a tooltip. // Here the tooltip is always emitted when 'always_on == true'. static int always_on = 0; @@ -3538,11 +4028,14 @@ static void DemoWindowWidgetsTooltips() ImGui::ProgressBar(sinf((float)ImGui::GetTime()) * 0.5f + 0.5f, ImVec2(ImGui::GetFontSize() * 25, 0.0f)); ImGui::EndTooltip(); } + ImGui::SeparatorText("Custom"); + HelpMarker( "Passing ImGuiHoveredFlags_ForTooltip to IsItemHovered() is the preferred way to standardize " "tooltip activation details across your application. You may however decide to use custom " "flags for a specific tooltip instance."); + // The following examples are passed for documentation purpose but may not be useful to most users. // Passing ImGuiHoveredFlags_ForTooltip to IsItemHovered() will pull ImGuiHoveredFlags flags values from // 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav' depending on whether mouse or keyboard/gamepad is being used. @@ -3550,18 +4043,23 @@ static void DemoWindowWidgetsTooltips() ImGui::Button("Manual", sz); if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) ImGui::SetTooltip("I am a manually emitted tooltip."); + ImGui::Button("DelayNone", sz); if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNone)) ImGui::SetTooltip("I am a tooltip with no delay."); + ImGui::Button("DelayShort", sz); if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_NoSharedDelay)) ImGui::SetTooltip("I am a tooltip with a short delay (%0.2f sec).", ImGui::GetStyle().HoverDelayShort); + ImGui::Button("DelayLong", sz); if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay)) ImGui::SetTooltip("I am a tooltip with a long delay (%0.2f sec).", ImGui::GetStyle().HoverDelayNormal); + ImGui::Button("Stationary", sz); if (ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary)) ImGui::SetTooltip("I am a tooltip requiring mouse to be stationary before activating."); + // Using ImGuiHoveredFlags_ForTooltip will pull flags from 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav', // which default value include the ImGuiHoveredFlags_AllowWhenDisabled flag. ImGui::BeginDisabled(); @@ -3569,17 +4067,21 @@ static void DemoWindowWidgetsTooltips() if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) ImGui::SetTooltip("I am a a tooltip for a disabled item."); ImGui::EndDisabled(); + ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsTreeNodes() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsTreeNodes() { IMGUI_DEMO_MARKER("Widgets/Tree Nodes"); if (ImGui::TreeNode("Tree Nodes")) { + // See see "Examples -> Property Editor" (ShowExampleAppPropertyEditor() function) for a fancier, data-driven tree. IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Basic trees"); if (ImGui::TreeNode("Basic trees")) { @@ -3589,6 +4091,7 @@ static void DemoWindowWidgetsTreeNodes() // also use TreeNodeEx() with the ImGuiTreeNodeFlags_DefaultOpen flag to achieve the same thing! if (i == 0) ImGui::SetNextItemOpen(true, ImGuiCond_Once); + // Here we use PushID() to generate a unique base ID, and then the "" used as TreeNode id won't conflict. // An alternative to using 'PushID() + TreeNode("", ...)' to generate a unique ID is to use 'TreeNode((void*)(intptr_t)i, ...)', // aka generate a dummy pointer-sized value to be hashed. The demo below uses that technique. Both are fine. @@ -3604,6 +4107,36 @@ static void DemoWindowWidgetsTreeNodes() } ImGui::TreePop(); } + + IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Hierarchy lines"); + if (ImGui::TreeNode("Hierarchy lines")) + { + static ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DefaultOpen; + HelpMarker("Default option for DrawLinesXXX is stored in style.TreeLinesFlags"); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_DrawLinesNone", &base_flags, ImGuiTreeNodeFlags_DrawLinesNone); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_DrawLinesFull", &base_flags, ImGuiTreeNodeFlags_DrawLinesFull); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_DrawLinesToNodes", &base_flags, ImGuiTreeNodeFlags_DrawLinesToNodes); + + if (ImGui::TreeNodeEx("Parent", base_flags)) + { + if (ImGui::TreeNodeEx("Child 1", base_flags)) + { + ImGui::Button("Button for Child 1"); + ImGui::TreePop(); + } + if (ImGui::TreeNodeEx("Child 2", base_flags)) + { + ImGui::Button("Button for Child 2"); + ImGui::TreePop(); + } + ImGui::Text("Remaining contents"); + ImGui::Text("Remaining contents"); + ImGui::TreePop(); + } + + ImGui::TreePop(); + } + IMGUI_DEMO_MARKER("Widgets/Tree Nodes/Advanced, with Selectable nodes"); if (ImGui::TreeNode("Advanced, with Selectable nodes")) { @@ -3621,12 +4154,19 @@ static void DemoWindowWidgetsTreeNodes() ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAllColumns", &base_flags, ImGuiTreeNodeFlags_SpanAllColumns); ImGui::SameLine(); HelpMarker("For use in Tables only."); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_AllowOverlap", &base_flags, ImGuiTreeNodeFlags_AllowOverlap); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_Framed", &base_flags, ImGuiTreeNodeFlags_Framed); ImGui::SameLine(); HelpMarker("Draw frame with background (e.g. for CollapsingHeader)"); - ImGui::CheckboxFlags("ImGuiTreeNodeFlags_NavLeftJumpsBackHere", &base_flags, ImGuiTreeNodeFlags_NavLeftJumpsBackHere); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_NavLeftJumpsToParent", &base_flags, ImGuiTreeNodeFlags_NavLeftJumpsToParent); + + HelpMarker("Default option for DrawLinesXXX is stored in style.TreeLinesFlags"); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_DrawLinesNone", &base_flags, ImGuiTreeNodeFlags_DrawLinesNone); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_DrawLinesFull", &base_flags, ImGuiTreeNodeFlags_DrawLinesFull); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_DrawLinesToNodes", &base_flags, ImGuiTreeNodeFlags_DrawLinesToNodes); + ImGui::Checkbox("Align label with current X position", &align_label_with_current_x_position); ImGui::Checkbox("Test tree node as drag source", &test_drag_and_drop); ImGui::Text("Hello!"); if (align_label_with_current_x_position) ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing()); + // 'selection_mask' is dumb representation of what may be user-side selection state. // You may retain selection state inside or outside your objects in whatever format you see fit. // 'node_clicked' is temporary storage of what node we have clicked to process selection at the end @@ -3700,9 +4240,11 @@ static void DemoWindowWidgetsTreeNodes() ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgetsVerticalSliders() //----------------------------------------------------------------------------- + static void DemoWindowWidgetsVerticalSliders() { IMGUI_DEMO_MARKER("Widgets/Vertical Sliders"); @@ -3710,9 +4252,11 @@ static void DemoWindowWidgetsVerticalSliders() { const float spacing = 4; ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); + static int int_value = 0; ImGui::VSliderInt("##int", ImVec2(18, 160), &int_value, 0, 5); ImGui::SameLine(); + static float values[7] = { 0.0f, 0.60f, 0.35f, 0.9f, 0.70f, 0.20f, 0.0f }; ImGui::PushID("set1"); for (int i = 0; i < 7; i++) @@ -3730,6 +4274,7 @@ static void DemoWindowWidgetsVerticalSliders() ImGui::PopID(); } ImGui::PopID(); + ImGui::SameLine(); ImGui::PushID("set2"); static float values2[4] = { 0.20f, 0.80f, 0.40f, 0.25f }; @@ -3750,6 +4295,7 @@ static void DemoWindowWidgetsVerticalSliders() ImGui::EndGroup(); } ImGui::PopID(); + ImGui::SameLine(); ImGui::PushID("set3"); for (int i = 0; i < 4; i++) @@ -3766,31 +4312,38 @@ static void DemoWindowWidgetsVerticalSliders() ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowWidgets() //----------------------------------------------------------------------------- + static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data) { IMGUI_DEMO_MARKER("Widgets"); //ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (!ImGui::CollapsingHeader("Widgets")) return; + const bool disable_all = demo_data->DisableSections; // The Checkbox for that is inside the "Disabled" section at the bottom if (disable_all) ImGui::BeginDisabled(); + DemoWindowWidgetsBasic(); DemoWindowWidgetsBullets(); DemoWindowWidgetsCollapsingHeaders(); DemoWindowWidgetsComboBoxes(); DemoWindowWidgetsColorAndPickers(); DemoWindowWidgetsDataTypes(); + if (disable_all) ImGui::EndDisabled(); DemoWindowWidgetsDisableBlocks(demo_data); if (disable_all) ImGui::BeginDisabled(); + DemoWindowWidgetsDragAndDrop(); DemoWindowWidgetsDragsAndSliders(); + DemoWindowWidgetsFonts(); DemoWindowWidgetsImages(); DemoWindowWidgetsListBoxes(); DemoWindowWidgetsMultiComponents(); @@ -3806,26 +4359,32 @@ static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data) DemoWindowWidgetsTooltips(); DemoWindowWidgetsTreeNodes(); DemoWindowWidgetsVerticalSliders(); + if (disable_all) ImGui::EndDisabled(); } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowLayout() //----------------------------------------------------------------------------- + static void DemoWindowLayout() { IMGUI_DEMO_MARKER("Layout"); if (!ImGui::CollapsingHeader("Layout & Scrolling")) return; + IMGUI_DEMO_MARKER("Layout/Child windows"); if (ImGui::TreeNode("Child windows")) { ImGui::SeparatorText("Child windows"); + HelpMarker("Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window."); static bool disable_mouse_wheel = false; static bool disable_menu = false; ImGui::Checkbox("Disable Mouse Wheel", &disable_mouse_wheel); ImGui::Checkbox("Disable Menu", &disable_menu); + // Child 1: no border, enable horizontal scrollbar { ImGuiWindowFlags window_flags = ImGuiWindowFlags_HorizontalScrollbar; @@ -3836,7 +4395,9 @@ static void DemoWindowLayout() ImGui::Text("%04d: scrollable region", i); ImGui::EndChild(); } + ImGui::SameLine(); + // Child 2: rounded border { ImGuiWindowFlags window_flags = ImGuiWindowFlags_None; @@ -3869,12 +4430,14 @@ static void DemoWindowLayout() ImGui::EndChild(); ImGui::PopStyleVar(); } + // Child 3: manual-resize ImGui::SeparatorText("Manual-resize"); { HelpMarker("Drag bottom border to resize. Double-click bottom border to auto-fit to vertical contents."); //if (ImGui::Button("Set Height to 200")) // ImGui::SetNextWindowSize(ImVec2(-FLT_MIN, 200.0f)); + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg)); if (ImGui::BeginChild("ResizableChild", ImVec2(-FLT_MIN, ImGui::GetTextLineHeightWithSpacing() * 8), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY)) for (int n = 0; n < 10; n++) @@ -3882,6 +4445,7 @@ static void DemoWindowLayout() ImGui::PopStyleColor(); ImGui::EndChild(); } + // Child 4: auto-resizing height with a limit ImGui::SeparatorText("Auto-resize with constraints"); { @@ -3891,13 +4455,16 @@ static void DemoWindowLayout() ImGui::DragInt("Lines Count", &draw_lines, 0.2f); ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); ImGui::DragInt("Max Height (in Lines)", &max_height_in_lines, 0.2f); + ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 1), ImVec2(FLT_MAX, ImGui::GetTextLineHeightWithSpacing() * max_height_in_lines)); if (ImGui::BeginChild("ConstrainedChild", ImVec2(-FLT_MIN, 0.0f), ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeY)) for (int n = 0; n < draw_lines; n++) ImGui::Text("Line %04d", n); ImGui::EndChild(); } + ImGui::SeparatorText("Misc/Advanced"); + // Demonstrate a few extra things // - Changing ImGuiCol_ChildBg (which is transparent black in default styles) // - Using SetCursorPos() to position child window (the child window is an item from the POV of parent window) @@ -3920,12 +4487,14 @@ static void DemoWindowLayout() ImGui::SameLine(); HelpMarker("Style the child window like a framed item: use FrameBg, FrameRounding, FrameBorderSize, FramePadding instead of ChildBg, ChildRounding, ChildBorderSize, WindowPadding."); if (child_flags & ImGuiChildFlags_FrameStyle) override_bg_color = false; + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (float)offset_x); if (override_bg_color) ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(255, 0, 0, 100)); ImGui::BeginChild("Red", ImVec2(200, 100), child_flags, ImGuiWindowFlags_None); if (override_bg_color) ImGui::PopStyleColor(); + for (int n = 0; n < 50; n++) ImGui::Text("Some test %d", n); ImGui::EndChild(); @@ -3935,18 +4504,22 @@ static void DemoWindowLayout() ImGui::Text("Hovered: %d", child_is_hovered); ImGui::Text("Rect of child window is: (%.0f,%.0f) (%.0f,%.0f)", child_rect_min.x, child_rect_min.y, child_rect_max.x, child_rect_max.y); } + ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Layout/Widgets Width"); if (ImGui::TreeNode("Widgets Width")) { static float f = 0.0f; static bool show_indented_items = true; ImGui::Checkbox("Show indented items", &show_indented_items); + // Use SetNextItemWidth() to set the width of a single upcoming item. // Use PushItemWidth()/PopItemWidth() to set the width of a group of items. // In real code use you'll probably want to choose width values that are proportional to your font size // e.g. Using '20.0f * GetFontSize()' as width instead of '200.0f', etc. + ImGui::Text("SetNextItemWidth/PushItemWidth(100)"); ImGui::SameLine(); HelpMarker("Fixed width."); ImGui::PushItemWidth(100); @@ -3958,6 +4531,7 @@ static void DemoWindowLayout() ImGui::Unindent(); } ImGui::PopItemWidth(); + ImGui::Text("SetNextItemWidth/PushItemWidth(-100)"); ImGui::SameLine(); HelpMarker("Align to right edge minus 100"); ImGui::PushItemWidth(-100); @@ -3969,6 +4543,7 @@ static void DemoWindowLayout() ImGui::Unindent(); } ImGui::PopItemWidth(); + ImGui::Text("SetNextItemWidth/PushItemWidth(GetContentRegionAvail().x * 0.5f)"); ImGui::SameLine(); HelpMarker("Half of available width.\n(~ right-cursor_pos)\n(works within a column set)"); ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.5f); @@ -3980,6 +4555,7 @@ static void DemoWindowLayout() ImGui::Unindent(); } ImGui::PopItemWidth(); + ImGui::Text("SetNextItemWidth/PushItemWidth(-GetContentRegionAvail().x * 0.5f)"); ImGui::SameLine(); HelpMarker("Align to right edge minus half"); ImGui::PushItemWidth(-ImGui::GetContentRegionAvail().x * 0.5f); @@ -3991,12 +4567,10 @@ static void DemoWindowLayout() ImGui::Unindent(); } ImGui::PopItemWidth(); - // Demonstrate using PushItemWidth to surround three items. - // Calling SetNextItemWidth() before each of them would have the same effect. - ImGui::Text("SetNextItemWidth/PushItemWidth(-FLT_MIN)"); - ImGui::SameLine(); HelpMarker("Align to right edge"); - ImGui::PushItemWidth(-FLT_MIN); - ImGui::DragFloat("##float5a", &f); + + ImGui::Text("SetNextItemWidth/PushItemWidth(-Min(GetContentRegionAvail().x * 0.40f, GetFontSize() * 12))"); + ImGui::PushItemWidth(-IM_MIN(ImGui::GetFontSize() * 12, ImGui::GetContentRegionAvail().x * 0.40f)); + ImGui::DragFloat("float##5a", &f); if (show_indented_items) { ImGui::Indent(); @@ -4004,29 +4578,50 @@ static void DemoWindowLayout() ImGui::Unindent(); } ImGui::PopItemWidth(); + + // Demonstrate using PushItemWidth to surround three items. + // Calling SetNextItemWidth() before each of them would have the same effect. + ImGui::Text("SetNextItemWidth/PushItemWidth(-FLT_MIN)"); + ImGui::SameLine(); HelpMarker("Align to right edge"); + ImGui::PushItemWidth(-FLT_MIN); + ImGui::DragFloat("##float6a", &f); + if (show_indented_items) + { + ImGui::Indent(); + ImGui::DragFloat("float (indented)##6b", &f); + ImGui::Unindent(); + } + ImGui::PopItemWidth(); + ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Layout/Basic Horizontal Layout"); if (ImGui::TreeNode("Basic Horizontal Layout")) { ImGui::TextWrapped("(Use ImGui::SameLine() to keep adding items to the right of the preceding item)"); + // Text IMGUI_DEMO_MARKER("Layout/Basic Horizontal Layout/SameLine"); ImGui::Text("Two items: Hello"); ImGui::SameLine(); ImGui::TextColored(ImVec4(1, 1, 0, 1), "Sailor"); + // Adjust spacing ImGui::Text("More spacing: Hello"); ImGui::SameLine(0, 20); ImGui::TextColored(ImVec4(1, 1, 0, 1), "Sailor"); + // Button ImGui::AlignTextToFramePadding(); ImGui::Text("Normal buttons"); ImGui::SameLine(); ImGui::Button("Banana"); ImGui::SameLine(); ImGui::Button("Apple"); ImGui::SameLine(); ImGui::Button("Corniflower"); + // Button ImGui::Text("Small buttons"); ImGui::SameLine(); ImGui::SmallButton("Like this one"); ImGui::SameLine(); ImGui::Text("can fit within a text block."); + // Aligned to arbitrary position. Easy/cheap column. IMGUI_DEMO_MARKER("Layout/Basic Horizontal Layout/SameLine (with offset)"); ImGui::Text("Aligned"); @@ -4035,6 +4630,7 @@ static void DemoWindowLayout() ImGui::Text("Aligned"); ImGui::SameLine(150); ImGui::SmallButton("x=150"); ImGui::SameLine(300); ImGui::SmallButton("x=300"); + // Checkbox IMGUI_DEMO_MARKER("Layout/Basic Horizontal Layout/SameLine (more)"); static bool c1 = false, c2 = false, c3 = false, c4 = false; @@ -4042,6 +4638,7 @@ static void DemoWindowLayout() ImGui::Checkbox("Tailor", &c2); ImGui::SameLine(); ImGui::Checkbox("Is", &c3); ImGui::SameLine(); ImGui::Checkbox("Rich", &c4); + // Various static float f0 = 1.0f, f1 = 2.0f, f2 = 3.0f; ImGui::PushItemWidth(80); @@ -4052,6 +4649,7 @@ static void DemoWindowLayout() ImGui::SliderFloat("Y", &f1, 0.0f, 5.0f); ImGui::SameLine(); ImGui::SliderFloat("Z", &f2, 0.0f, 5.0f); ImGui::PopItemWidth(); + ImGui::PushItemWidth(80); ImGui::Text("Lists:"); static int selection[4] = { 0, 1, 2, 3 }; @@ -4064,12 +4662,14 @@ static void DemoWindowLayout() //ImGui::SetItemTooltip("ListBox %d hovered", i); } ImGui::PopItemWidth(); + // Dummy IMGUI_DEMO_MARKER("Layout/Basic Horizontal Layout/Dummy"); ImVec2 button_sz(40, 40); ImGui::Button("A", button_sz); ImGui::SameLine(); ImGui::Dummy(button_sz); ImGui::SameLine(); ImGui::Button("B", button_sz); + // Manually wrapping // (we should eventually provide this as an automatic layout feature, but for now you can do it manually) IMGUI_DEMO_MARKER("Layout/Basic Horizontal Layout/Manual wrapping"); @@ -4087,8 +4687,10 @@ static void DemoWindowLayout() ImGui::SameLine(); ImGui::PopID(); } + ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Layout/Groups"); if (ImGui::TreeNode("Groups")) { @@ -4116,21 +4718,26 @@ static void DemoWindowLayout() ImVec2 size = ImGui::GetItemRectSize(); const float values[5] = { 0.5f, 0.20f, 0.80f, 0.60f, 0.25f }; ImGui::PlotHistogram("##values", values, IM_ARRAYSIZE(values), 0, NULL, 0.0f, 1.0f, size); + ImGui::Button("ACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x) * 0.5f, size.y)); ImGui::SameLine(); ImGui::Button("REACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x) * 0.5f, size.y)); ImGui::EndGroup(); ImGui::SameLine(); + ImGui::Button("LEVERAGE\nBUZZWORD", size); ImGui::SameLine(); + if (ImGui::BeginListBox("List", size)) { ImGui::Selectable("Selected", true); ImGui::Selectable("Not Selected", false); ImGui::EndListBox(); } + ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Layout/Text Baseline Alignment"); if (ImGui::TreeNode("Text Baseline Alignment")) { @@ -4140,9 +4747,11 @@ static void DemoWindowLayout() "This is testing the vertical alignment that gets applied on text to keep it aligned with widgets. " "Lines only composed of text or \"small\" widgets use less vertical space than lines with framed widgets."); ImGui::Indent(); + ImGui::Text("KO Blahblah"); ImGui::SameLine(); ImGui::Button("Some framed item"); ImGui::SameLine(); HelpMarker("Baseline of button will look misaligned with text.."); + // If your line starts with text, call AlignTextToFramePadding() to align text to upcoming widgets. // (because we don't know what's coming after the Text() statement, we need to move the text baseline // down by FramePadding.y ahead of time) @@ -4150,10 +4759,12 @@ static void DemoWindowLayout() ImGui::Text("OK Blahblah"); ImGui::SameLine(); ImGui::Button("Some framed item##2"); ImGui::SameLine(); HelpMarker("We call AlignTextToFramePadding() to vertically align the text baseline by +FramePadding.y"); + // SmallButton() uses the same vertical padding as Text ImGui::Button("TEST##1"); ImGui::SameLine(); ImGui::Text("TEST"); ImGui::SameLine(); ImGui::SmallButton("TEST##2"); + // If your line starts with text, call AlignTextToFramePadding() to align text to upcoming widgets. ImGui::AlignTextToFramePadding(); ImGui::Text("Text aligned to framed item"); ImGui::SameLine(); @@ -4161,31 +4772,40 @@ static void DemoWindowLayout() ImGui::Text("Item"); ImGui::SameLine(); ImGui::SmallButton("Item##2"); ImGui::SameLine(); ImGui::Button("Item##3"); + ImGui::Unindent(); } + ImGui::Spacing(); + { ImGui::BulletText("Multi-line text:"); ImGui::Indent(); ImGui::Text("One\nTwo\nThree"); ImGui::SameLine(); ImGui::Text("Hello\nWorld"); ImGui::SameLine(); ImGui::Text("Banana"); + ImGui::Text("Banana"); ImGui::SameLine(); ImGui::Text("Hello\nWorld"); ImGui::SameLine(); ImGui::Text("One\nTwo\nThree"); + ImGui::Button("HOP##1"); ImGui::SameLine(); ImGui::Text("Banana"); ImGui::SameLine(); ImGui::Text("Hello\nWorld"); ImGui::SameLine(); ImGui::Text("Banana"); + ImGui::Button("HOP##2"); ImGui::SameLine(); ImGui::Text("Hello\nWorld"); ImGui::SameLine(); ImGui::Text("Banana"); ImGui::Unindent(); } + ImGui::Spacing(); + { ImGui::BulletText("Misc items:"); ImGui::Indent(); + // SmallButton() sets FramePadding to zero. Text baseline is aligned to match baseline of previous Button. ImGui::Button("80x80", ImVec2(80, 80)); ImGui::SameLine(); @@ -4194,20 +4814,24 @@ static void DemoWindowLayout() ImGui::Button("Button()"); ImGui::SameLine(); ImGui::SmallButton("SmallButton()"); + // Tree + // (here the node appears after a button and has odd intent, so we use ImGuiTreeNodeFlags_DrawLinesNone to disable hierarchy outline) const float spacing = ImGui::GetStyle().ItemInnerSpacing.x; ImGui::Button("Button##1"); ImGui::SameLine(0.0f, spacing); - if (ImGui::TreeNode("Node##1")) + if (ImGui::TreeNodeEx("Node##1", ImGuiTreeNodeFlags_DrawLinesNone)) { // Placeholder tree data for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } + // Vertically align text node a bit lower so it'll be vertically centered with upcoming widget. // Otherwise you can use SmallButton() (smaller fit). ImGui::AlignTextToFramePadding(); + // Common mistake to avoid: if we want to SameLine after TreeNode we need to do it before we add // other contents below the node. bool node_open = ImGui::TreeNode("Node##2"); @@ -4219,39 +4843,50 @@ static void DemoWindowLayout() ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } + // Bullet ImGui::Button("Button##3"); ImGui::SameLine(0.0f, spacing); ImGui::BulletText("Bullet text"); + ImGui::AlignTextToFramePadding(); ImGui::BulletText("Node"); ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##4"); ImGui::Unindent(); } + ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Layout/Scrolling"); if (ImGui::TreeNode("Scrolling")) { // Vertical scroll functions IMGUI_DEMO_MARKER("Layout/Scrolling/Vertical"); HelpMarker("Use SetScrollHereY() or SetScrollFromPosY() to scroll to a given vertical position."); + static int track_item = 50; static bool enable_track = true; static bool enable_extra_decorations = false; static float scroll_to_off_px = 0.0f; static float scroll_to_pos_px = 200.0f; + ImGui::Checkbox("Decoration", &enable_extra_decorations); + ImGui::Checkbox("Track", &enable_track); ImGui::PushItemWidth(100); ImGui::SameLine(140); enable_track |= ImGui::DragInt("##item", &track_item, 0.25f, 0, 99, "Item = %d"); + bool scroll_to_off = ImGui::Button("Scroll Offset"); ImGui::SameLine(140); scroll_to_off |= ImGui::DragFloat("##off", &scroll_to_off_px, 1.00f, 0, FLT_MAX, "+%.0f px"); + bool scroll_to_pos = ImGui::Button("Scroll To Pos"); ImGui::SameLine(140); scroll_to_pos |= ImGui::DragFloat("##pos", &scroll_to_pos_px, 1.00f, -10, FLT_MAX, "X/Y = %.0f px"); ImGui::PopItemWidth(); + if (scroll_to_off || scroll_to_pos) enable_track = false; + ImGuiStyle& style = ImGui::GetStyle(); float child_w = (ImGui::GetContentRegionAvail().x - 4 * style.ItemSpacing.x) / 5; if (child_w < 1.0f) @@ -4263,6 +4898,7 @@ static void DemoWindowLayout() ImGui::BeginGroup(); const char* names[] = { "Top", "25%", "Center", "75%", "Bottom" }; ImGui::TextUnformatted(names[i]); + const ImGuiWindowFlags child_flags = enable_extra_decorations ? ImGuiWindowFlags_MenuBar : 0; const ImGuiID child_id = ImGui::GetID((void*)(intptr_t)i); const bool child_is_visible = ImGui::BeginChild(child_id, ImVec2(child_w, 200.0f), ImGuiChildFlags_Borders, child_flags); @@ -4297,6 +4933,7 @@ static void DemoWindowLayout() ImGui::EndGroup(); } ImGui::PopID(); + // Horizontal scroll functions IMGUI_DEMO_MARKER("Layout/Scrolling/Horizontal"); ImGui::Spacing(); @@ -4342,6 +4979,7 @@ static void DemoWindowLayout() ImGui::Spacing(); } ImGui::PopID(); + // Miscellaneous Horizontal Scrolling Demo IMGUI_DEMO_MARKER("Layout/Scrolling/Horizontal (more)"); HelpMarker( @@ -4400,8 +5038,10 @@ static void DemoWindowLayout() ImGui::EndChild(); } ImGui::Spacing(); + static bool show_horizontal_contents_size_demo_window = false; ImGui::Checkbox("Show Horizontal contents size demo window", &show_horizontal_contents_size_demo_window); + if (show_horizontal_contents_size_demo_window) { static bool show_h_scrollbar = true; @@ -4501,8 +5141,10 @@ static void DemoWindowLayout() } ImGui::End(); } + ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Layout/Text Clipping"); if (ImGui::TreeNode("Text Clipping")) { @@ -4510,6 +5152,7 @@ static void DemoWindowLayout() static ImVec2 offset(30.0f, 30.0f); ImGui::DragFloat2("size", (float*)&size, 0.5f, 1.0f, 200.0f, "%.0f"); ImGui::TextWrapped("(Click and drag to scroll)"); + HelpMarker( "(Left) Using ImGui::PushClipRect():\n" "Will alter ImGui hit-testing logic + ImDrawList rendering.\n" @@ -4520,10 +5163,12 @@ static void DemoWindowLayout() "(Right) Using ImDrawList::AddText() with a fine ClipRect:\n" "Will alter only this specific ImDrawList::AddText() rendering.\n" "This is often used internally to avoid altering the clipping rectangle and minimize draw calls."); + for (int n = 0; n < 3; n++) { if (n > 0) ImGui::SameLine(); + ImGui::PushID(n); ImGui::InvisibleButton("##canvas", size); if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) @@ -4534,6 +5179,7 @@ static void DemoWindowLayout() ImGui::PopID(); if (!ImGui::IsItemVisible()) // Skip rendering as ImDrawList elements are not clipped. continue; + const ImVec2 p0 = ImGui::GetItemRectMin(); const ImVec2 p1 = ImGui::GetItemRectMax(); const char* text_str = "Line 1 hello\nLine 2 clip me!"; @@ -4560,17 +5206,21 @@ static void DemoWindowLayout() break; } } + ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Layout/Overlap Mode"); if (ImGui::TreeNode("Overlap Mode")) { static bool enable_allow_overlap = true; + HelpMarker( "Hit-testing is by default performed in item submission order, which generally is perceived as 'back-to-front'.\n\n" "By using SetNextItemAllowOverlap() you can notify that an item may be overlapped by another. " "Doing so alters the hovering logic: items using AllowOverlap mode requires an extra frame to accept hovered state."); ImGui::Checkbox("Enable AllowOverlap", &enable_allow_overlap); + ImVec2 button1_pos = ImGui::GetCursorScreenPos(); ImVec2 button2_pos = ImVec2(button1_pos.x + 50.0f, button1_pos.y + 50.0f); if (enable_allow_overlap) @@ -4578,6 +5228,7 @@ static void DemoWindowLayout() ImGui::Button("Button 1", ImVec2(80, 80)); ImGui::SetCursorScreenPos(button2_pos); ImGui::Button("Button 2", ImVec2(80, 80)); + // This is typically used with width-spanning items. // (note that Selectable() has a dedicated flag ImGuiSelectableFlags_AllowOverlap, which is a shortcut // for using SetNextItemAllowOverlap(). For demo purpose we use SetNextItemAllowOverlap() here.) @@ -4586,17 +5237,21 @@ static void DemoWindowLayout() ImGui::Selectable("Some Selectable", false); ImGui::SameLine(); ImGui::SmallButton("++"); + ImGui::TreePop(); } } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowPopups() //----------------------------------------------------------------------------- + static void DemoWindowPopups() { IMGUI_DEMO_MARKER("Popups"); if (!ImGui::CollapsingHeader("Popups & Modal windows")) return; + // The properties of popups windows are: // - They block normal mouse hovering detection outside them. (*) // - Unless modal, they can be closed by clicking anywhere outside them, or by pressing ESCAPE. @@ -4606,21 +5261,26 @@ static void DemoWindowPopups() // when normally blocked by a popup. // Those three properties are connected. The library needs to hold their visibility state BECAUSE it can close // popups at any time. + // Typical use for regular windows: // bool my_tool_is_active = false; if (ImGui::Button("Open")) my_tool_is_active = true; [...] if (my_tool_is_active) Begin("My Tool", &my_tool_is_active) { [...] } End(); // Typical use for popups: // if (ImGui::Button("Open")) ImGui::OpenPopup("MyPopup"); if (ImGui::BeginPopup("MyPopup") { [...] EndPopup(); } + // With popups we have to go through a library call (here OpenPopup) to manipulate the visibility state. // This may be a bit confusing at first but it should quickly make sense. Follow on the examples below. + IMGUI_DEMO_MARKER("Popups/Popups"); if (ImGui::TreeNode("Popups")) { ImGui::TextWrapped( "When a popup is active, it inhibits interacting with windows that are behind the popup. " "Clicking outside the popup closes it."); + static int selected_fish = -1; const char* names[] = { "Bream", "Haddock", "Mackerel", "Pollock", "Tilefish" }; static bool toggles[] = { true, false, false, false, false }; + // Simple selection popup (if you want to show the current selection inside the Button itself, // you may want to build a string using the "###" operator to preserve a constant ID with a variable label) if (ImGui::Button("Select..")) @@ -4635,6 +5295,7 @@ static void DemoWindowPopups() selected_fish = i; ImGui::EndPopup(); } + // Showing a menu with toggles if (ImGui::Button("Toggle..")) ImGui::OpenPopup("my_toggle_popup"); @@ -4647,9 +5308,11 @@ static void DemoWindowPopups() ImGui::MenuItem("Click me"); ImGui::EndMenu(); } + ImGui::Separator(); ImGui::Text("Tooltip here"); ImGui::SetItemTooltip("I am a tooltip over a popup"); + if (ImGui::Button("Stacked Popup")) ImGui::OpenPopup("another popup"); if (ImGui::BeginPopup("another popup")) @@ -4672,6 +5335,7 @@ static void DemoWindowPopups() } ImGui::EndPopup(); } + // Call the more complete ShowExampleMenuFile which we use in various places of this demo if (ImGui::Button("With a menu..")) ImGui::OpenPopup("my_file_popup"); @@ -4695,12 +5359,15 @@ static void DemoWindowPopups() ImGui::Button("This is a dummy button.."); ImGui::EndPopup(); } + ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Popups/Context menus"); if (ImGui::TreeNode("Context menus")) { HelpMarker("\"Context\" functions are simple helpers to associate a Popup to a given Item or Window identifier."); + // BeginPopupContextItem() is a helper to provide common/simple popup behavior of essentially doing: // if (id == 0) // id = GetItemID(); // Use last item id @@ -4709,6 +5376,7 @@ static void DemoWindowPopups() // return BeginPopup(id); // For advanced uses you may want to replicate and customize this code. // See more details in BeginPopupContextItem(). + // Example 1 // When used after an item that has an ID (e.g. Button), we can skip providing an ID to BeginPopupContextItem(), // and BeginPopupContextItem() will use the last item ID as the popup ID. @@ -4730,6 +5398,7 @@ static void DemoWindowPopups() ImGui::SetItemTooltip("Right-click to open popup"); } } + // Example 2 // Popup on a Text() element which doesn't have an identifier: we need to provide an identifier to BeginPopupContextItem(). // Using an explicit identifier is also convenient if you want to activate the popups from different locations. @@ -4745,15 +5414,18 @@ static void DemoWindowPopups() ImGui::DragFloat("##Value", &value, 0.1f, 0.0f, 0.0f); ImGui::EndPopup(); } + // We can also use OpenPopupOnItemClick() to toggle the visibility of a given popup. // Here we make it that right-clicking this other text element opens the same popup as above. // The popup itself will be submitted by the code above. ImGui::Text("(2) Or right-click this text"); ImGui::OpenPopupOnItemClick("my popup", ImGuiPopupFlags_MouseButtonRight); + // Back to square one: manually open the same popup. if (ImGui::Button("(3) Or click this button")) ImGui::OpenPopup("my popup"); } + // Example 3 // When using BeginPopupContextItem() with an implicit identifier (NULL == use last item ID), // we need to make sure your item identifier is stable. @@ -4774,33 +5446,42 @@ static void DemoWindowPopups() } ImGui::SameLine(); ImGui::Text("(<-- right-click here)"); } + ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Popups/Modals"); if (ImGui::TreeNode("Modals")) { ImGui::TextWrapped("Modal windows are like popups but the user cannot close them by clicking outside."); + if (ImGui::Button("Delete..")) ImGui::OpenPopup("Delete?"); + // Always center this window when appearing ImVec2 center = ImGui::GetMainViewport()->GetCenter(); ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + if (ImGui::BeginPopupModal("Delete?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("All those beautiful files will be deleted.\nThis operation cannot be undone!"); ImGui::Separator(); + //static int unused_i = 0; //ImGui::Combo("Combo", &unused_i, "Delete\0Delete harder\0"); + static bool dont_ask_me_next_time = false; ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); ImGui::Checkbox("Don't ask me next time", &dont_ask_me_next_time); ImGui::PopStyleVar(); + if (ImGui::Button("OK", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } ImGui::SetItemDefaultFocus(); ImGui::SameLine(); if (ImGui::Button("Cancel", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } + if (ImGui::Button("Stacked modals..")) ImGui::OpenPopup("Stacked 1"); if (ImGui::BeginPopupModal("Stacked 1", NULL, ImGuiWindowFlags_MenuBar)) @@ -4815,13 +5496,16 @@ static void DemoWindowPopups() ImGui::EndMenuBar(); } ImGui::Text("Hello from Stacked The First\nUsing style.Colors[ImGuiCol_ModalWindowDimBg] behind it."); + // Testing behavior of widgets stacking their own regular popups over the modal. static int item = 1; static float color[4] = { 0.4f, 0.7f, 0.0f, 0.5f }; ImGui::Combo("Combo", &item, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); ImGui::ColorEdit4("Color", color); + if (ImGui::Button("Add another modal..")) ImGui::OpenPopup("Stacked 2"); + // Also demonstrate passing a bool* to BeginPopupModal(), this will create a regular close button which // will close the popup. Note that the visibility state of popups is owned by imgui, so the input value // of the bool actually doesn't matter here. @@ -4834,17 +5518,21 @@ static void DemoWindowPopups() ImGui::CloseCurrentPopup(); ImGui::EndPopup(); } + if (ImGui::Button("Close")) ImGui::CloseCurrentPopup(); ImGui::EndPopup(); } + ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Popups/Menus inside a regular window"); if (ImGui::TreeNode("Menus inside a regular window")) { ImGui::TextWrapped("Below we are testing adding menu items to a regular window. It's rather unusual but should work!"); ImGui::Separator(); + ImGui::MenuItem("Menu item", "CTRL+M"); if (ImGui::BeginMenu("Menu inside a regular window")) { @@ -4855,6 +5543,7 @@ static void DemoWindowPopups() ImGui::TreePop(); } } + // Dummy data structure that we use for the Table demo. // (pre-C++11 doesn't allow us to instantiate ImVector template if this structure is defined inside the demo function) namespace @@ -4871,11 +5560,13 @@ enum MyItemColumnID MyItemColumnID_Quantity, MyItemColumnID_Description }; + struct MyItem { int ID; const char* Name; int Quantity; + // We have a problem which is affecting _only this demo_ and should not affect your code: // As we don't rely on std:: or other third-party library to compile dear imgui, we only have reliable access to qsort(), // however qsort doesn't allow passing user data to comparing function. @@ -4884,6 +5575,7 @@ struct MyItem // We could technically call ImGui::TableGetSortSpecs() in CompareWithSortSpecs(), but considering that this function is called // very often by the sorting algorithm it would be a little wasteful. static const ImGuiTableSortSpecs* s_current_sort_specs; + static void SortWithSortSpecs(ImGuiTableSortSpecs* sort_specs, MyItem* items, int items_count) { s_current_sort_specs = sort_specs; // Store in variable accessible by the sort function. @@ -4891,6 +5583,7 @@ struct MyItem qsort(items, (size_t)items_count, sizeof(items[0]), MyItem::CompareWithSortSpecs); s_current_sort_specs = NULL; } + // Compare function to be used by qsort() static int IMGUI_CDECL CompareWithSortSpecs(const void* lhs, const void* rhs) { @@ -4915,7 +5608,8 @@ struct MyItem if (delta < 0) return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? -1 : +1; } - // qsort() is instable so always return a way to differenciate items. + + // qsort() is instable so always return a way to differentiate items. // Your own compare function may want to avoid fallback on implicit sort specs. // e.g. a Name compare if it wasn't already part of the sort specs. return (a->ID - b->ID); @@ -4923,6 +5617,7 @@ struct MyItem }; const ImGuiTableSortSpecs* MyItem::s_current_sort_specs = NULL; } + // Make the UI compact because there are so many fields static void PushStyleCompact() { @@ -4930,10 +5625,12 @@ static void PushStyleCompact() ImGui::PushStyleVarY(ImGuiStyleVar_FramePadding, (float)(int)(style.FramePadding.y * 0.60f)); ImGui::PushStyleVarY(ImGuiStyleVar_ItemSpacing, (float)(int)(style.ItemSpacing.y * 0.60f)); } + static void PopStyleCompact() { ImGui::PopStyleVar(2); } + // Show a combo box with a choice of sizing policies static void EditTableSizingFlags(ImGuiTableFlags* p_flags) { @@ -4975,6 +5672,7 @@ static void EditTableSizingFlags(ImGuiTableFlags* p_flags) ImGui::EndTooltip(); } } + static void EditTableColumnsFlags(ImGuiTableColumnFlags* p_flags) { ImGui::CheckboxFlags("_Disabled", p_flags, ImGuiTableColumnFlags_Disabled); ImGui::SameLine(); HelpMarker("Master disable flag (also hide from context menu)"); @@ -4999,6 +5697,7 @@ static void EditTableColumnsFlags(ImGuiTableColumnFlags* p_flags) ImGui::CheckboxFlags("_IndentDisable", p_flags, ImGuiTableColumnFlags_IndentDisable); ImGui::SameLine(); HelpMarker("Default for column >0"); ImGui::CheckboxFlags("_AngledHeader", p_flags, ImGuiTableColumnFlags_AngledHeader); } + static void ShowTableColumnsStatusFlags(ImGuiTableColumnFlags flags) { ImGui::CheckboxFlags("_IsEnabled", &flags, ImGuiTableColumnFlags_IsEnabled); @@ -5006,19 +5705,24 @@ static void ShowTableColumnsStatusFlags(ImGuiTableColumnFlags flags) ImGui::CheckboxFlags("_IsSorted", &flags, ImGuiTableColumnFlags_IsSorted); ImGui::CheckboxFlags("_IsHovered", &flags, ImGuiTableColumnFlags_IsHovered); } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowTables() //----------------------------------------------------------------------------- + static void DemoWindowTables() { //ImGui::SetNextItemOpen(true, ImGuiCond_Once); IMGUI_DEMO_MARKER("Tables"); if (!ImGui::CollapsingHeader("Tables & Columns")) return; + // Using those as a base value to create width/height that are factor of the size of our font const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); + ImGui::PushID("Tables"); + int open_action = -1; if (ImGui::Button("Expand all")) open_action = 1; @@ -5026,6 +5730,7 @@ static void DemoWindowTables() if (ImGui::Button("Collapse all")) open_action = 0; ImGui::SameLine(); + // Options static bool disable_indent = false; ImGui::Checkbox("Disable tree indentation", &disable_indent); @@ -5034,6 +5739,7 @@ static void DemoWindowTables() ImGui::Separator(); if (disable_indent) ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, 0.0f); + // About Styling of tables // Most settings are configured on a per-table basis via the flags passed to BeginTable() and TableSetupColumns APIs. // There are however a few settings that a shared and part of the ImGuiStyle structure: @@ -5043,6 +5749,7 @@ static void DemoWindowTables() // style.Colors[ImGuiCol_TableBorderLight] // Table inner borders // style.Colors[ImGuiCol_TableRowBg] // Table row background when ImGuiTableFlags_RowBg is enabled (even rows) // style.Colors[ImGuiCol_TableRowBgAlt] // Table row background when ImGuiTableFlags_RowBg is enabled (odds rows) + // Demos if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); @@ -5051,6 +5758,7 @@ static void DemoWindowTables() { // Here we will showcase three different ways to output a table. // They are very simple variations of a same thing! + // [Method 1] Using TableNextRow() to create a new row, and TableSetColumnIndex() to select the column. // In many situations, this is the most flexible and easy to use pattern. HelpMarker("Using TableNextRow() + calling TableSetColumnIndex() _before_ each cell, in a loop."); @@ -5067,6 +5775,7 @@ static void DemoWindowTables() } ImGui::EndTable(); } + // [Method 2] Using TableNextColumn() called multiple times, instead of using a for loop + TableSetColumnIndex(). // This is generally more convenient when you have code manually submitting the contents of each column. HelpMarker("Using TableNextRow() + calling TableNextColumn() _before_ each cell, manually."); @@ -5084,6 +5793,7 @@ static void DemoWindowTables() } ImGui::EndTable(); } + // [Method 3] We call TableNextColumn() _before_ each cell. We never call TableNextRow(), // as TableNextColumn() will automatically wrap around and create new rows as needed. // This is generally more convenient when your cells all contains the same type of data. @@ -5100,8 +5810,10 @@ static void DemoWindowTables() } ImGui::EndTable(); } + ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Borders, background"); @@ -5112,30 +5824,36 @@ static void DemoWindowTables() static ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg; static bool display_headers = false; static int contents_type = CT_Text; + PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", &flags, ImGuiTableFlags_RowBg); ImGui::CheckboxFlags("ImGuiTableFlags_Borders", &flags, ImGuiTableFlags_Borders); ImGui::SameLine(); HelpMarker("ImGuiTableFlags_Borders\n = ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterV\n | ImGuiTableFlags_BordersInnerH\n | ImGuiTableFlags_BordersOuterH"); ImGui::Indent(); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", &flags, ImGuiTableFlags_BordersH); ImGui::Indent(); ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterH", &flags, ImGuiTableFlags_BordersOuterH); ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerH", &flags, ImGuiTableFlags_BordersInnerH); ImGui::Unindent(); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", &flags, ImGuiTableFlags_BordersV); ImGui::Indent(); ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterV", &flags, ImGuiTableFlags_BordersOuterV); ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", &flags, ImGuiTableFlags_BordersInnerV); ImGui::Unindent(); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuter", &flags, ImGuiTableFlags_BordersOuter); ImGui::CheckboxFlags("ImGuiTableFlags_BordersInner", &flags, ImGuiTableFlags_BordersInner); ImGui::Unindent(); + ImGui::AlignTextToFramePadding(); ImGui::Text("Cell contents:"); ImGui::SameLine(); ImGui::RadioButton("Text", &contents_type, CT_Text); ImGui::SameLine(); ImGui::RadioButton("FillButton", &contents_type, CT_FillButton); ImGui::Checkbox("Display headers", &display_headers); ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", &flags, ImGuiTableFlags_NoBordersInBody); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body (borders will always appear in Headers"); PopStyleCompact(); + if (ImGui::BeginTable("table1", 3, flags)) { // Display headers so we can inspect their interaction with borders @@ -5147,6 +5865,7 @@ static void DemoWindowTables() ImGui::TableSetupColumn("Three"); ImGui::TableHeadersRow(); } + for (int row = 0; row < 5; row++) { ImGui::TableNextRow(); @@ -5165,6 +5884,7 @@ static void DemoWindowTables() } ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Resizable, stretch"); @@ -5180,6 +5900,7 @@ static void DemoWindowTables() "Using the _Resizable flag automatically enables the _BordersInnerV flag as well, " "this is why the resize borders are still showing when unchecking this."); PopStyleCompact(); + if (ImGui::BeginTable("table1", 3, flags)) { for (int row = 0; row < 5; row++) @@ -5195,6 +5916,7 @@ static void DemoWindowTables() } ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Resizable, fixed"); @@ -5212,6 +5934,7 @@ static void DemoWindowTables() static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody; ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendX", &flags, ImGuiTableFlags_NoHostExtendX); PopStyleCompact(); + if (ImGui::BeginTable("table1", 3, flags)) { for (int row = 0; row < 5; row++) @@ -5227,6 +5950,7 @@ static void DemoWindowTables() } ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Resizable, mixed"); @@ -5236,6 +5960,7 @@ static void DemoWindowTables() "Using TableSetupColumn() to alter resizing policy on a per-column basis.\n\n" "When combining Fixed and Stretch columns, generally you only want one, maybe two trailing columns to use _WidthStretch."); static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + if (ImGui::BeginTable("table1", 3, flags)) { ImGui::TableSetupColumn("AAA", ImGuiTableColumnFlags_WidthFixed); @@ -5275,6 +6000,7 @@ static void DemoWindowTables() } ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Reorderable, hideable, with headers"); @@ -5292,6 +6018,7 @@ static void DemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", &flags, ImGuiTableFlags_NoBordersInBodyUntilResize); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appear in Headers)"); ImGui::CheckboxFlags("ImGuiTableFlags_HighlightHoveredColumn", &flags, ImGuiTableFlags_HighlightHoveredColumn); PopStyleCompact(); + if (ImGui::BeginTable("table1", 3, flags)) { // Submit columns name with TableSetupColumn() and call TableHeadersRow() to create a row with a header in each column. @@ -5311,6 +6038,7 @@ static void DemoWindowTables() } ImGui::EndTable(); } + // Use outer_size.x == 0.0f instead of default to make the table as tight as possible // (only valid when no scrolling and no stretch column) if (ImGui::BeginTable("table2", 3, flags | ImGuiTableFlags_SizingFixedFit, ImVec2(0.0f, 0.0f))) @@ -5332,6 +6060,7 @@ static void DemoWindowTables() } ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Padding"); @@ -5348,6 +6077,7 @@ static void DemoWindowTables() "Using PadOuterX or NoPadOuterX you can override the default.\n\n" "Actual padding values are using style.CellPadding.\n\n" "In this demo we don't show horizontal borders to emphasize how they don't affect default horizontal padding."); + static ImGuiTableFlags flags1 = ImGuiTableFlags_BordersV; PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_PadOuterX", &flags1, ImGuiTableFlags_PadOuterX); @@ -5361,6 +6091,7 @@ static void DemoWindowTables() static bool show_headers = false; ImGui::Checkbox("show_headers", &show_headers); PopStyleCompact(); + if (ImGui::BeginTable("table_padding", 3, flags1)) { if (show_headers) @@ -5370,6 +6101,7 @@ static void DemoWindowTables() ImGui::TableSetupColumn("Three"); ImGui::TableHeadersRow(); } + for (int row = 0; row < 5; row++) { ImGui::TableNextRow(); @@ -5392,12 +6124,14 @@ static void DemoWindowTables() } ImGui::EndTable(); } + // Second example: set style.CellPadding to (0.0) or a custom value. // FIXME-TABLE: Vertical border effectively not displayed the same way as horizontal one... HelpMarker("Setting style.CellPadding to (0,0) or a custom value."); static ImGuiTableFlags flags2 = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg; static ImVec2 cell_padding(0.0f, 0.0f); static bool show_widget_frame_bg = true; + PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_Borders", &flags2, ImGuiTableFlags_Borders); ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", &flags2, ImGuiTableFlags_BordersH); @@ -5409,6 +6143,7 @@ static void DemoWindowTables() ImGui::Checkbox("show_widget_frame_bg", &show_widget_frame_bg); ImGui::SliderFloat2("CellPadding", &cell_padding.x, 0.0f, 10.0f, "%.0f"); PopStyleCompact(); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cell_padding); if (ImGui::BeginTable("table_padding_2", 3, flags2)) { @@ -5432,8 +6167,10 @@ static void DemoWindowTables() ImGui::EndTable(); } ImGui::PopStyleVar(); + ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Explicit widths"); @@ -5444,12 +6181,14 @@ static void DemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags1, ImGuiTableFlags_Resizable); ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendX", &flags1, ImGuiTableFlags_NoHostExtendX); PopStyleCompact(); + static ImGuiTableFlags sizing_policy_flags[4] = { ImGuiTableFlags_SizingFixedFit, ImGuiTableFlags_SizingFixedSame, ImGuiTableFlags_SizingStretchProp, ImGuiTableFlags_SizingStretchSame }; for (int table_n = 0; table_n < 4; table_n++) { ImGui::PushID(table_n); ImGui::SetNextItemWidth(TEXT_BASE_WIDTH * 30); EditTableSizingFlags(&sizing_policy_flags[table_n]); + // To make it easier to understand the different sizing policy, // For each policy: we display one table where the columns have equal contents width, // and one where the columns have different contents width. @@ -5477,16 +6216,19 @@ static void DemoWindowTables() } ImGui::PopID(); } + ImGui::Spacing(); ImGui::TextUnformatted("Advanced"); ImGui::SameLine(); HelpMarker( "This section allows you to interact and see the effect of various sizing policies " "depending on whether Scroll is enabled and the contents of your columns."); + enum ContentsType { CT_ShowWidth, CT_ShortText, CT_LongText, CT_Button, CT_FillButton, CT_InputText }; static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable; static int contents_type = CT_ShowWidth; static int column_count = 3; + PushStyleCompact(); ImGui::PushID("Advanced"); ImGui::PushItemWidth(TEXT_BASE_WIDTH * 30); @@ -5509,6 +6251,7 @@ static void DemoWindowTables() ImGui::PopItemWidth(); ImGui::PopID(); PopStyleCompact(); + if (ImGui::BeginTable("table2", column_count, flags, ImVec2(0.0f, TEXT_BASE_HEIGHT * 7))) { for (int cell = 0; cell < 10 * column_count; cell++) @@ -5516,6 +6259,7 @@ static void DemoWindowTables() ImGui::TableNextColumn(); int column = ImGui::TableGetColumnIndex(); int row = ImGui::TableGetRowIndex(); + ImGui::PushID(cell); char label[32]; static char text_buf[32] = ""; @@ -5535,6 +6279,7 @@ static void DemoWindowTables() } ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Vertical scrolling, with clipping"); @@ -5544,9 +6289,11 @@ static void DemoWindowTables() "Here we activate ScrollY, which will create a child window container to allow hosting scrollable contents.\n\n" "We also demonstrate using ImGuiListClipper to virtualize the submission of many items."); static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; + PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", &flags, ImGuiTableFlags_ScrollY); PopStyleCompact(); + // When using ScrollX or ScrollY we need to specify a size for our table container! // Otherwise by default the table will fit all available space, like a BeginChild() call. ImVec2 outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 8); @@ -5557,6 +6304,7 @@ static void DemoWindowTables() ImGui::TableSetupColumn("Two", ImGuiTableColumnFlags_None); ImGui::TableSetupColumn("Three", ImGuiTableColumnFlags_None); ImGui::TableHeadersRow(); + // Demonstrate using clipper for large vertical lists ImGuiListClipper clipper; clipper.Begin(1000); @@ -5576,6 +6324,7 @@ static void DemoWindowTables() } ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Horizontal scrolling"); @@ -5590,6 +6339,7 @@ static void DemoWindowTables() static ImGuiTableFlags flags = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable; static int freeze_cols = 1; static int freeze_rows = 1; + PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", &flags, ImGuiTableFlags_ScrollX); @@ -5599,6 +6349,7 @@ static void DemoWindowTables() ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); ImGui::DragInt("freeze_rows", &freeze_rows, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); PopStyleCompact(); + // When using ScrollX or ScrollY we need to specify a size for our table container! // Otherwise by default the table will fit all available space, like a BeginChild() call. ImVec2 outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 8); @@ -5634,6 +6385,7 @@ static void DemoWindowTables() } ImGui::EndTable(); } + ImGui::Spacing(); ImGui::TextUnformatted("Stretch + ScrollX"); ImGui::SameLine(); @@ -5663,6 +6415,7 @@ static void DemoWindowTables() } ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Columns flags"); @@ -5673,6 +6426,7 @@ static void DemoWindowTables() const char* column_names[column_count] = { "One", "Two", "Three" }; static ImGuiTableColumnFlags column_flags[column_count] = { ImGuiTableColumnFlags_DefaultSort, ImGuiTableColumnFlags_None, ImGuiTableColumnFlags_DefaultHide }; static ImGuiTableColumnFlags column_flags_out[column_count] = { 0, 0, 0 }; // Output from TableGetColumnFlags() + if (ImGui::BeginTable("table_columns_flags_checkboxes", column_count, ImGuiTableFlags_None)) { PushStyleCompact(); @@ -5695,6 +6449,7 @@ static void DemoWindowTables() PopStyleCompact(); ImGui::EndTable(); } + // Create the real table we care about for the example! // We use a scrolling table to be able to showcase the difference between the _IsEnabled and _IsVisible flags above, // otherwise in a non-scrolling table columns are always visible (unless using ImGuiTableFlags_NoKeepColumnsVisible @@ -5730,16 +6485,19 @@ static void DemoWindowTables() } } ImGui::Unindent(indent_step * 8.0f); + ImGui::EndTable(); } ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Columns widths"); if (ImGui::TreeNode("Columns widths")) { HelpMarker("Using TableSetupColumn() to setup default width."); + static ImGuiTableFlags flags1 = ImGuiTableFlags_Borders | ImGuiTableFlags_NoBordersInBodyUntilResize; PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags1, ImGuiTableFlags_Resizable); @@ -5766,9 +6524,11 @@ static void DemoWindowTables() } ImGui::EndTable(); } + HelpMarker( "Using TableSetupColumn() to setup explicit width.\n\nUnless _NoKeepColumnsVisible is set, " "fixed columns with set width may still be shrunk down if there's not enough space in the host."); + static ImGuiTableFlags flags2 = ImGuiTableFlags_None; PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_NoKeepColumnsVisible", &flags2, ImGuiTableFlags_NoKeepColumnsVisible); @@ -5799,17 +6559,20 @@ static void DemoWindowTables() } ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Nested tables"); if (ImGui::TreeNode("Nested tables")) { HelpMarker("This demonstrates embedding a table into another table cell."); + if (ImGui::BeginTable("table_nested1", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) { ImGui::TableSetupColumn("A0"); ImGui::TableSetupColumn("A1"); ImGui::TableHeadersRow(); + ImGui::TableNextColumn(); ImGui::Text("A0 Row 0"); { @@ -5819,6 +6582,7 @@ static void DemoWindowTables() ImGui::TableSetupColumn("B0"); ImGui::TableSetupColumn("B1"); ImGui::TableHeadersRow(); + ImGui::TableNextRow(ImGuiTableRowFlags_None, rows_height); ImGui::TableNextColumn(); ImGui::Text("B0 Row 0"); @@ -5829,6 +6593,7 @@ static void DemoWindowTables() ImGui::Text("B0 Row 1"); ImGui::TableNextColumn(); ImGui::Text("B1 Row 1"); + ImGui::EndTable(); } } @@ -5839,6 +6604,7 @@ static void DemoWindowTables() } ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Row height"); @@ -5859,6 +6625,7 @@ static void DemoWindowTables() } ImGui::EndTable(); } + HelpMarker( "Showcase using SameLine(0,0) to share Current Line Height between cells.\n\n" "Please note that Tables Row Height is not the same thing as Current Line Height, " @@ -5871,6 +6638,7 @@ static void DemoWindowTables() ImGui::TableNextColumn(); ImGui::Text("Line 1"); ImGui::Text("Line 2"); + ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::ColorButton("##2", ImVec4(0.13f, 0.26f, 0.40f, 1.0f), ImGuiColorEditFlags_None, ImVec2(40, 40)); @@ -5878,8 +6646,10 @@ static void DemoWindowTables() ImGui::SameLine(0.0f, 0.0f); // Reuse line height from previous column ImGui::Text("Line 1, with SameLine(0,0)"); ImGui::Text("Line 2"); + ImGui::EndTable(); } + HelpMarker("Showcase altering CellPadding.y between rows. Note that CellPadding.x is locked for the entire table."); if (ImGui::BeginTable("table_changing_cellpadding_y", 1, ImGuiTableFlags_Borders)) { @@ -5896,8 +6666,10 @@ static void DemoWindowTables() } ImGui::EndTable(); } + ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Outer size"); @@ -5913,6 +6685,7 @@ static void DemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendY", &flags, ImGuiTableFlags_NoHostExtendY); ImGui::SameLine(); HelpMarker("Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit).\n\nOnly available when ScrollX/ScrollY are disabled. Data below the limit will be clipped and not visible."); PopStyleCompact(); + ImVec2 outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 5.5f); if (ImGui::BeginTable("table1", 3, flags, outer_size)) { @@ -5929,7 +6702,9 @@ static void DemoWindowTables() } ImGui::SameLine(); ImGui::Text("Hello!"); + ImGui::Spacing(); + ImGui::Text("Using explicit size:"); if (ImGui::BeginTable("table2", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg, ImVec2(TEXT_BASE_WIDTH * 30, 0.0f))) { @@ -5958,8 +6733,10 @@ static void DemoWindowTables() } ImGui::EndTable(); } + ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Background color"); @@ -5969,6 +6746,7 @@ static void DemoWindowTables() static int row_bg_type = 1; static int row_bg_target = 1; static int cell_bg_type = 1; + PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_Borders", &flags, ImGuiTableFlags_Borders); ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", &flags, ImGuiTableFlags_RowBg); @@ -5980,11 +6758,13 @@ static void DemoWindowTables() IM_ASSERT(row_bg_target >= 0 && row_bg_target <= 1); IM_ASSERT(cell_bg_type >= 0 && cell_bg_type <= 1); PopStyleCompact(); + if (ImGui::BeginTable("table1", 5, flags)) { for (int row = 0; row < 6; row++) { ImGui::TableNextRow(); + // Demonstrate setting a row background color with 'ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBgX, ...)' // We use a transparent color so we can see the one behind in case our target is RowBg1 and RowBg0 was already targeted by the ImGuiTableFlags_RowBg flag. if (row_bg_type != 0) @@ -5992,11 +6772,13 @@ static void DemoWindowTables() ImU32 row_bg_color = ImGui::GetColorU32(row_bg_type == 1 ? ImVec4(0.7f, 0.3f, 0.3f, 0.65f) : ImVec4(0.2f + row * 0.1f, 0.2f, 0.2f, 0.65f)); // Flat or Gradient? ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0 + row_bg_target, row_bg_color); } + // Fill cells for (int column = 0; column < 5; column++) { ImGui::TableSetColumnIndex(column); ImGui::Text("%c%c", 'A' + row, '0' + column); + // Change background of Cells B1->C2 // Demonstrate setting a cell background color with 'ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ...)' // (the CellBg color will be blended over the RowBg and ColumnBg colors) @@ -6012,18 +6794,21 @@ static void DemoWindowTables() } ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Tree view"); if (ImGui::TreeNode("Tree view")) { static ImGuiTableFlags table_flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; - static ImGuiTreeNodeFlags tree_node_flags_base = ImGuiTreeNodeFlags_SpanAllColumns; + + static ImGuiTreeNodeFlags tree_node_flags_base = ImGuiTreeNodeFlags_SpanAllColumns | ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_DrawLinesFull; ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanFullWidth", &tree_node_flags_base, ImGuiTreeNodeFlags_SpanFullWidth); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanLabelWidth", &tree_node_flags_base, ImGuiTreeNodeFlags_SpanLabelWidth); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAllColumns", &tree_node_flags_base, ImGuiTreeNodeFlags_SpanAllColumns); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_LabelSpanAllColumns", &tree_node_flags_base, ImGuiTreeNodeFlags_LabelSpanAllColumns); ImGui::SameLine(); HelpMarker("Useful if you know that you aren't displaying contents in other columns"); + HelpMarker("See \"Columns flags\" section to configure how indentation is applied to individual columns."); if (ImGui::BeginTable("3ways", 3, table_flags)) { @@ -6032,6 +6817,7 @@ static void DemoWindowTables() ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f); ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 18.0f); ImGui::TableHeadersRow(); + // Simple storage to output a dummy file-system. struct MyTreeNode { @@ -6045,9 +6831,11 @@ static void DemoWindowTables() ImGui::TableNextRow(); ImGui::TableNextColumn(); const bool is_folder = (node->ChildCount > 0); + ImGuiTreeNodeFlags node_flags = tree_node_flags_base; if (node != &all_nodes[0]) node_flags &= ~ImGuiTreeNodeFlags_LabelSpanAllColumns; // Only demonstrate this on the root node. + if (is_folder) { bool open = ImGui::TreeNodeEx(node->Name, node_flags); @@ -6087,11 +6875,14 @@ static void DemoWindowTables() { "Copy of Image001.png", "Image file", 203256, -1,-1 }, // 7 { "Copy of Image001 (Final2).png","Image file", 203512, -1,-1 }, // 8 }; + MyTreeNode::DisplayNode(&nodes[0], nodes); + ImGui::EndTable(); } ImGui::TreePop(); } + if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); IMGUI_DEMO_MARKER("Tables/Item width"); @@ -6107,6 +6898,7 @@ static void DemoWindowTables() ImGui::TableSetupColumn("half"); ImGui::TableSetupColumn("right-align"); ImGui::TableHeadersRow(); + for (int row = 0; row < 3; row++) { ImGui::TableNextRow(); @@ -6120,6 +6912,7 @@ static void DemoWindowTables() ImGui::TableSetColumnIndex(2); ImGui::PushItemWidth(-FLT_MIN); // Right-aligned } + // Draw our contents static float dummy_f = 0.0f; ImGui::PushID(row); @@ -6135,6 +6928,7 @@ static void DemoWindowTables() } ImGui::TreePop(); } + // Demonstrate using TableHeader() calls instead of TableHeadersRow() if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); @@ -6147,9 +6941,11 @@ static void DemoWindowTables() ImGui::TableSetupColumn("Apricot"); ImGui::TableSetupColumn("Banana"); ImGui::TableSetupColumn("Cherry"); + // Dummy entire-column selection storage // FIXME: It would be nice to actually demonstrate full-featured selection using those checkbox. static bool column_selected[3] = {}; + // Instead of calling TableHeadersRow() we'll submit custom headers ourselves. // (A different approach is also possible: // - Specify ImGuiTableColumnFlags_NoHeaderLabel in some TableSetupColumn() call. @@ -6168,6 +6964,7 @@ static void DemoWindowTables() ImGui::TableHeader(column_name); ImGui::PopID(); } + // Submit table contents for (int row = 0; row < 5; row++) { @@ -6184,6 +6981,7 @@ static void DemoWindowTables() } ImGui::TreePop(); } + // Demonstrate using ImGuiTableColumnFlags_AngledHeader flag to create angled headers if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); @@ -6193,6 +6991,7 @@ static void DemoWindowTables() const char* column_names[] = { "Track", "cabasa", "ride", "smash", "tom-hi", "tom-mid", "tom-low", "hihat-o", "hihat-c", "snare-s", "snare-c", "clap", "rim", "kick" }; const int columns_count = IM_ARRAYSIZE(column_names); const int rows_count = 12; + static ImGuiTableFlags table_flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_Hideable | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_HighlightHoveredColumn; static ImGuiTableColumnFlags column_flags = ImGuiTableColumnFlags_AngledHeader | ImGuiTableColumnFlags_WidthFixed; static bool bools[columns_count * rows_count] = {}; // Dummy storage selection storage @@ -6209,6 +7008,7 @@ static void DemoWindowTables() ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); ImGui::SliderInt("Frozen rows", &frozen_rows, 0, 2); ImGui::CheckboxFlags("Disable header contributing to column width", &column_flags, ImGuiTableColumnFlags_NoHeaderWidth); + if (ImGui::TreeNode("Style settings")) { ImGui::SameLine(); @@ -6219,12 +7019,14 @@ static void DemoWindowTables() ImGui::SliderFloat2("style.TableAngledHeadersTextAlign", (float*)&ImGui::GetStyle().TableAngledHeadersTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::TreePop(); } + if (ImGui::BeginTable("table_angled_headers", columns_count, table_flags, ImVec2(0.0f, TEXT_BASE_HEIGHT * 12))) { ImGui::TableSetupColumn(column_names[0], ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_NoReorder); for (int n = 1; n < columns_count; n++) ImGui::TableSetupColumn(column_names[n], column_flags); ImGui::TableSetupScrollFreeze(frozen_cols, frozen_rows); + ImGui::TableAngledHeadersRow(); // Draw angled headers for all columns with the ImGuiTableColumnFlags_AngledHeader flag. ImGui::TableHeadersRow(); // Draw remaining headers and allow access to context-menu and other functions. for (int row = 0; row < rows_count; row++) @@ -6247,6 +7049,7 @@ static void DemoWindowTables() } ImGui::TreePop(); } + // Demonstrate creating custom context menus inside columns, // while playing it nice with context menus provided by TableHeadersRow()/TableHeader() if (open_action != -1) @@ -6258,9 +7061,11 @@ static void DemoWindowTables() "By default, right-clicking over a TableHeadersRow()/TableHeader() line will open the default context-menu.\n" "Using ImGuiTableFlags_ContextMenuInBody we also allow right-clicking over columns body."); static ImGuiTableFlags flags1 = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_ContextMenuInBody; + PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_ContextMenuInBody", &flags1, ImGuiTableFlags_ContextMenuInBody); PopStyleCompact(); + // Context Menus: first example // [1.1] Right-click on the TableHeadersRow() line to open the default table context menu. // [1.2] Right-click in columns also open the default table context menu (if ImGuiTableFlags_ContextMenuInBody is set) @@ -6270,8 +7075,10 @@ static void DemoWindowTables() ImGui::TableSetupColumn("One"); ImGui::TableSetupColumn("Two"); ImGui::TableSetupColumn("Three"); + // [1.1]] Right-click on the TableHeadersRow() line to open the default table context menu. ImGui::TableHeadersRow(); + // Submit dummy contents for (int row = 0; row < 4; row++) { @@ -6284,6 +7091,7 @@ static void DemoWindowTables() } ImGui::EndTable(); } + // Context Menus: second example // [2.1] Right-click on the TableHeadersRow() line to open the default table context menu. // [2.2] Right-click on the ".." to open a custom popup @@ -6297,6 +7105,7 @@ static void DemoWindowTables() ImGui::TableSetupColumn("One"); ImGui::TableSetupColumn("Two"); ImGui::TableSetupColumn("Three"); + // [2.1] Right-click on the TableHeadersRow() line to open the default table context menu. ImGui::TableHeadersRow(); for (int row = 0; row < 4; row++) @@ -6308,6 +7117,7 @@ static void DemoWindowTables() ImGui::TableSetColumnIndex(column); ImGui::Text("Cell %d,%d", column, row); ImGui::SameLine(); + // [2.2] Right-click on the ".." to open a custom popup ImGui::PushID(row * COLUMNS_COUNT + column); ImGui::SmallButton(".."); @@ -6321,6 +7131,7 @@ static void DemoWindowTables() ImGui::PopID(); } } + // [2.3] Right-click anywhere in columns to open another custom popup // (instead of testing for !IsAnyItemHovered() we could also call OpenPopup() with ImGuiPopupFlags_NoOpenOverExistingPopup // to manage popup priority as the popups triggers, here "are we hovering a column" are overlapping) @@ -6344,11 +7155,13 @@ static void DemoWindowTables() } ImGui::PopID(); } + ImGui::EndTable(); ImGui::Text("Hovered column: %d", hovered_column); } ImGui::TreePop(); } + // Demonstrate creating multiple tables with the same ID if (open_action != -1) ImGui::SetNextItemOpen(open_action != 0); @@ -6356,6 +7169,7 @@ static void DemoWindowTables() if (ImGui::TreeNode("Synced instances")) { HelpMarker("Multiple tables with the same identifier will share their settings, width, visibility, order etc."); + static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings; ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", &flags, ImGuiTableFlags_ScrollY); @@ -6383,6 +7197,7 @@ static void DemoWindowTables() } ImGui::TreePop(); } + // Demonstrate using Sorting facilities // This is a simplified version of the "Advanced" example, where we mostly focus on the code necessary to handle sorting. // Note that the "Advanced" example also showcase manually triggering a sort (e.g. if item quantities have been modified) @@ -6410,6 +7225,7 @@ static void DemoWindowTables() item.Quantity = (n * n - n) % 20; // Assign default quantities } } + // Options static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti @@ -6421,6 +7237,7 @@ static void DemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_SortTristate", &flags, ImGuiTableFlags_SortTristate); ImGui::SameLine(); HelpMarker("When sorting is enabled: allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0)."); PopStyleCompact(); + if (ImGui::BeginTable("table_sorting", 4, flags, ImVec2(0.0f, TEXT_BASE_HEIGHT * 15), 0.0f)) { // Declare columns @@ -6436,6 +7253,7 @@ static void DemoWindowTables() ImGui::TableSetupColumn("Quantity", ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_WidthStretch, 0.0f, MyItemColumnID_Quantity); ImGui::TableSetupScrollFreeze(0, 1); // Make row always visible ImGui::TableHeadersRow(); + // Sort our data if sort specs have been changed! if (ImGuiTableSortSpecs* sort_specs = ImGui::TableGetSortSpecs()) if (sort_specs->SpecsDirty) @@ -6443,6 +7261,7 @@ static void DemoWindowTables() MyItem::SortWithSortSpecs(sort_specs, items.Data, items.Size); sort_specs->SpecsDirty = false; } + // Demonstrate using clipper for large vertical lists ImGuiListClipper clipper; clipper.Begin(items.Size); @@ -6467,6 +7286,7 @@ static void DemoWindowTables() } ImGui::TreePop(); } + // In this example we'll expose most table flags and settings. // For specific flags and settings refer to the corresponding section for more detailed explanation. // This section is mostly useful to experiment with combining certain flags or settings with each others. @@ -6483,6 +7303,7 @@ static void DemoWindowTables() | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingFixedFit; static ImGuiTableColumnFlags columns_base_flags = ImGuiTableColumnFlags_None; + enum ContentsType { CT_Text, CT_Button, CT_SmallButton, CT_FillButton, CT_Selectable, CT_SelectableSpanRow }; static int contents_type = CT_SelectableSpanRow; const char* contents_type_names[] = { "Text", "Button", "SmallButton", "FillButton", "Selectable", "Selectable (span row)" }; @@ -6502,6 +7323,7 @@ static void DemoWindowTables() // Make the UI compact because there are so many fields PushStyleCompact(); ImGui::PushItemWidth(TEXT_BASE_WIDTH * 28.0f); + if (ImGui::TreeNodeEx("Features:", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); @@ -6512,6 +7334,7 @@ static void DemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_ContextMenuInBody", &flags, ImGuiTableFlags_ContextMenuInBody); ImGui::TreePop(); } + if (ImGui::TreeNodeEx("Decorations:", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", &flags, ImGuiTableFlags_RowBg); @@ -6525,6 +7348,7 @@ static void DemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", &flags, ImGuiTableFlags_NoBordersInBodyUntilResize); ImGui::SameLine(); HelpMarker("Disable vertical borders in columns Body until hovered for resize (borders will always appear in Headers)"); ImGui::TreePop(); } + if (ImGui::TreeNodeEx("Sizing:", ImGuiTreeNodeFlags_DefaultOpen)) { EditTableSizingFlags(&flags); @@ -6541,6 +7365,7 @@ static void DemoWindowTables() ImGui::SameLine(); HelpMarker("Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with ScrollFreeze options."); ImGui::TreePop(); } + if (ImGui::TreeNodeEx("Padding:", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::CheckboxFlags("ImGuiTableFlags_PadOuterX", &flags, ImGuiTableFlags_PadOuterX); @@ -6548,6 +7373,7 @@ static void DemoWindowTables() ImGui::CheckboxFlags("ImGuiTableFlags_NoPadInnerX", &flags, ImGuiTableFlags_NoPadInnerX); ImGui::TreePop(); } + if (ImGui::TreeNodeEx("Scrolling:", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", &flags, ImGuiTableFlags_ScrollX); @@ -6560,6 +7386,7 @@ static void DemoWindowTables() ImGui::DragInt("freeze_rows", &freeze_rows, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); ImGui::TreePop(); } + if (ImGui::TreeNodeEx("Sorting:", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::CheckboxFlags("ImGuiTableFlags_SortMulti", &flags, ImGuiTableFlags_SortMulti); @@ -6568,6 +7395,7 @@ static void DemoWindowTables() ImGui::SameLine(); HelpMarker("When sorting is enabled: allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0)."); ImGui::TreePop(); } + if (ImGui::TreeNodeEx("Headers:", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Checkbox("show_headers", &show_headers); @@ -6576,9 +7404,11 @@ static void DemoWindowTables() ImGui::SameLine(); HelpMarker("Enable AngledHeader on all columns. Best enabled on selected narrow columns (see \"Angled headers\" section of the demo)."); ImGui::TreePop(); } + if (ImGui::TreeNodeEx("Other:", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Checkbox("show_wrapped_text", &show_wrapped_text); + ImGui::DragFloat2("##OuterSize", &outer_size_value.x); ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); ImGui::Checkbox("outer_size", &outer_size_enabled); @@ -6588,21 +7418,26 @@ static void DemoWindowTables() "- OuterSize.x < 0.0f will right-align the table.\n" "- OuterSize.x = 0.0f will narrow fit the table unless there are any Stretch columns.\n" "- OuterSize.y then becomes the minimum size for the table, which will extend vertically if there are more rows (unless NoHostExtendY is set)."); + // From a user point of view we will tend to use 'inner_width' differently depending on whether our table is embedding scrolling. // To facilitate toying with this demo we will actually pass 0.0f to the BeginTable() when ScrollX is disabled. ImGui::DragFloat("inner_width (when ScrollX active)", &inner_width_with_scroll, 1.0f, 0.0f, FLT_MAX); + ImGui::DragFloat("row_min_height", &row_min_height, 1.0f, 0.0f, FLT_MAX); ImGui::SameLine(); HelpMarker("Specify height of the Selectable item."); + ImGui::DragInt("items_count", &items_count, 0.1f, 0, 9999); ImGui::Combo("items_type (first column)", &contents_type, contents_type_names, IM_ARRAYSIZE(contents_type_names)); //filter.Draw("filter"); ImGui::TreePop(); } + ImGui::PopItemWidth(); PopStyleCompact(); ImGui::Spacing(); ImGui::TreePop(); } + // Update item list if we changed the number of items static ImVector items; static ImVector selection; @@ -6619,10 +7454,12 @@ static void DemoWindowTables() item.Quantity = (template_n == 3) ? 10 : (template_n == 4) ? 20 : 0; // Assign default quantities } } + const ImDrawList* parent_draw_list = ImGui::GetWindowDrawList(); const int parent_draw_list_draw_cmd_count = parent_draw_list->CmdBuffer.Size; ImVec2 table_scroll_cur, table_scroll_max; // For debug display const ImDrawList* table_draw_list = NULL; // " + // Submit table const float inner_width_to_use = (flags & ImGuiTableFlags_ScrollX) ? inner_width_with_scroll : 0.0f; if (ImGui::BeginTable("table_advanced", 6, flags, outer_size_enabled ? outer_size_value : ImVec2(0, 0), inner_width_to_use)) @@ -6637,6 +7474,7 @@ static void DemoWindowTables() ImGui::TableSetupColumn("Description", columns_base_flags | ((flags & ImGuiTableFlags_NoHostExtendX) ? 0 : ImGuiTableColumnFlags_WidthStretch), 0.0f, MyItemColumnID_Description); ImGui::TableSetupColumn("Hidden", columns_base_flags | ImGuiTableColumnFlags_DefaultHide | ImGuiTableColumnFlags_NoSort); ImGui::TableSetupScrollFreeze(freeze_cols, freeze_rows); + // Sort our data if sort specs have been changed! ImGuiTableSortSpecs* sort_specs = ImGui::TableGetSortSpecs(); if (sort_specs && sort_specs->SpecsDirty) @@ -6647,14 +7485,17 @@ static void DemoWindowTables() sort_specs->SpecsDirty = false; } items_need_sort = false; + // Take note of whether we are currently sorting based on the Quantity field, // we will use this to trigger sorting when we know the data of this column has been modified. const bool sorts_specs_using_quantity = (ImGui::TableGetColumnFlags(3) & ImGuiTableColumnFlags_IsSorted) != 0; + // Show headers if (show_headers && (columns_base_flags & ImGuiTableColumnFlags_AngledHeader) != 0) ImGui::TableAngledHeadersRow(); if (show_headers) ImGui::TableHeadersRow(); + // Show data // FIXME-TABLE FIXME-NAV: How we can get decent up/down even though we have the buttons here? #if 1 @@ -6673,9 +7514,11 @@ static void DemoWindowTables() MyItem* item = &items[row_n]; //if (!filter.PassFilter(item->Name)) // continue; + const bool item_is_selected = selection.contains(item->ID); ImGui::PushID(item->ID); ImGui::TableNextRow(ImGuiTableRowFlags_None, row_min_height); + // For the demo purpose we can select among different type of items submitted in the first column ImGui::TableSetColumnIndex(0); char label[32]; @@ -6707,8 +7550,10 @@ static void DemoWindowTables() } } } + if (ImGui::TableSetColumnIndex(1)) ImGui::TextUnformatted(item->Name); + // Here we demonstrate marking our data set as needing to be sorted again if we modified a quantity, // and we are currently sorting on the column showing the Quantity. // To avoid triggering a sort while holding the button, we only trigger it when the button has been released. @@ -6721,18 +7566,23 @@ static void DemoWindowTables() if (ImGui::SmallButton("Eat")) { item->Quantity -= 1; } if (sorts_specs_using_quantity && ImGui::IsItemDeactivated()) { items_need_sort = true; } } + if (ImGui::TableSetColumnIndex(3)) ImGui::Text("%d", item->Quantity); + ImGui::TableSetColumnIndex(4); if (show_wrapped_text) ImGui::TextWrapped("Lorem ipsum dolor sit amet"); else ImGui::Text("Lorem ipsum dolor sit amet"); + if (ImGui::TableSetColumnIndex(5)) ImGui::Text("1234"); + ImGui::PopID(); } } + // Store some info to display debug details below table_scroll_cur = ImVec2(ImGui::GetScrollX(), ImGui::GetScrollY()); table_scroll_max = ImVec2(ImGui::GetScrollMaxX(), ImGui::GetScrollMaxY()); @@ -6754,11 +7604,15 @@ static void DemoWindowTables() } ImGui::TreePop(); } + ImGui::PopID(); + DemoWindowColumns(); + if (disable_indent) ImGui::PopStyleVar(); } + // Demonstrate old/legacy Columns API! // [2020: Columns are under-featured and not maintained. Prefer using the more flexible and powerful BeginTable() API!] static void DemoWindowColumns() @@ -6769,6 +7623,7 @@ static void DemoWindowColumns() HelpMarker("Columns() is an old API! Prefer using the more flexible and powerful BeginTable() API!"); if (!open) return; + // Basic columns IMGUI_DEMO_MARKER("Columns (legacy API)/Basic"); if (ImGui::TreeNode("Basic")) @@ -6786,6 +7641,7 @@ static void DemoWindowColumns() } ImGui::Columns(1); ImGui::Separator(); + ImGui::Text("With border:"); ImGui::Columns(4, "mycolumns"); // 4-ways, with border ImGui::Separator(); @@ -6813,6 +7669,7 @@ static void DemoWindowColumns() ImGui::Separator(); ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Columns (legacy API)/Borders"); if (ImGui::TreeNode("Borders")) { @@ -6849,26 +7706,31 @@ static void DemoWindowColumns() ImGui::Separator(); ImGui::TreePop(); } + // Create multiple items in a same cell before switching to next column IMGUI_DEMO_MARKER("Columns (legacy API)/Mixed items"); if (ImGui::TreeNode("Mixed items")) { ImGui::Columns(3, "mixed"); ImGui::Separator(); + ImGui::Text("Hello"); ImGui::Button("Banana"); ImGui::NextColumn(); + ImGui::Text("ImGui"); ImGui::Button("Apple"); static float foo = 1.0f; ImGui::InputFloat("red", &foo, 0.05f, 0, "%.3f"); ImGui::Text("An extra line here."); ImGui::NextColumn(); + ImGui::Text("Sailor"); ImGui::Button("Corniflower"); static float bar = 1.0f; ImGui::InputFloat("blue", &bar, 0.05f, 0, "%.3f"); ImGui::NextColumn(); + if (ImGui::CollapsingHeader("Category A")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); if (ImGui::CollapsingHeader("Category B")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); if (ImGui::CollapsingHeader("Category C")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); @@ -6876,6 +7738,7 @@ static void DemoWindowColumns() ImGui::Separator(); ImGui::TreePop(); } + // Word wrapping IMGUI_DEMO_MARKER("Columns (legacy API)/Word-wrapping"); if (ImGui::TreeNode("Word-wrapping")) @@ -6891,6 +7754,7 @@ static void DemoWindowColumns() ImGui::Separator(); ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Columns (legacy API)/Horizontal Scrolling"); if (ImGui::TreeNode("Horizontal Scrolling")) { @@ -6898,6 +7762,7 @@ static void DemoWindowColumns() ImVec2 child_size = ImVec2(0, ImGui::GetFontSize() * 20.0f); ImGui::BeginChild("##ScrollingRegion", child_size, ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar); ImGui::Columns(10); + // Also demonstrate using clipper for large vertical lists int ITEMS_COUNT = 2000; ImGuiListClipper clipper; @@ -6915,6 +7780,7 @@ static void DemoWindowColumns() ImGui::EndChild(); ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Columns (legacy API)/Tree"); if (ImGui::TreeNode("Tree")) { @@ -6951,17 +7817,21 @@ static void DemoWindowColumns() ImGui::Columns(1); ImGui::TreePop(); } + ImGui::TreePop(); } + //----------------------------------------------------------------------------- // [SECTION] DemoWindowInputs() //----------------------------------------------------------------------------- + static void DemoWindowInputs() { IMGUI_DEMO_MARKER("Inputs & Focus"); if (ImGui::CollapsingHeader("Inputs & Focus")) { ImGuiIO& io = ImGui::GetIO(); + // Display inputs submitted to ImGuiIO IMGUI_DEMO_MARKER("Inputs & Focus/Inputs"); ImGui::SetNextItemOpen(true, ImGuiCond_Once); @@ -6983,6 +7853,7 @@ static void DemoWindowInputs() ImGui::Text("Mouse wheel: %.1f", io.MouseWheel); ImGui::Text("Mouse clicked count:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseClickedCount[i] > 0) { ImGui::SameLine(); ImGui::Text("b%d: %d", i, io.MouseClickedCount[i]); } + // We iterate both legacy native range and named ImGuiKey ranges. This is a little unusual/odd but this allows // displaying the data for old/new backends. // User code should never have to go through such hoops! @@ -6992,8 +7863,10 @@ static void DemoWindowInputs() ImGui::Text("Keys down:"); for (ImGuiKey key = start_key; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !ImGui::IsKeyDown(key)) continue; ImGui::SameLine(); ImGui::Text((key < ImGuiKey_NamedKey_BEGIN) ? "\"%s\"" : "\"%s\" %d", ImGui::GetKeyName(key), key); } ImGui::Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); ImGui::Text("Chars queue:"); for (int i = 0; i < io.InputQueueCharacters.Size; i++) { ImWchar c = io.InputQueueCharacters[i]; ImGui::SameLine(); ImGui::Text("\'%c\' (0x%04X)", (c > ' ' && c <= 255) ? (char)c : '?', c); } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public. + ImGui::TreePop(); } + // Display ImGuiIO output flags IMGUI_DEMO_MARKER("Inputs & Focus/Outputs"); ImGui::SetNextItemOpen(true, ImGuiCond_Once); @@ -7014,6 +7887,7 @@ static void DemoWindowInputs() ImGui::Text("io.WantTextInput: %d", io.WantTextInput); ImGui::Text("io.WantSetMousePos: %d", io.WantSetMousePos); ImGui::Text("io.NavActive: %d, io.NavVisible: %d", io.NavActive, io.NavVisible); + IMGUI_DEMO_MARKER("Inputs & Focus/Outputs/WantCapture override"); if (ImGui::TreeNode("WantCapture override")) { @@ -7028,15 +7902,18 @@ static void DemoWindowInputs() ImGui::SliderInt("SetNextFrameWantCaptureMouse() on hover", &capture_override_mouse, -1, +1, capture_override_desc[capture_override_mouse + 1], ImGuiSliderFlags_AlwaysClamp); ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); ImGui::SliderInt("SetNextFrameWantCaptureKeyboard() on hover", &capture_override_keyboard, -1, +1, capture_override_desc[capture_override_keyboard + 1], ImGuiSliderFlags_AlwaysClamp); + ImGui::ColorButton("##panel", ImVec4(0.7f, 0.1f, 0.7f, 1.0f), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop, ImVec2(128.0f, 96.0f)); // Dummy item if (ImGui::IsItemHovered() && capture_override_mouse != -1) ImGui::SetNextFrameWantCaptureMouse(capture_override_mouse == 1); if (ImGui::IsItemHovered() && capture_override_keyboard != -1) ImGui::SetNextFrameWantCaptureKeyboard(capture_override_keyboard == 1); + ImGui::TreePop(); } ImGui::TreePop(); } + // Demonstrate using Shortcut() and Routing Policies. // The general flow is: // - Code interested in a chord (e.g. "Ctrl+A") declares their intent. @@ -7067,6 +7944,7 @@ static void DemoWindowInputs() ImGuiInputFlags flags = route_type | route_options; // Merged flags if (route_type != ImGuiInputFlags_RouteGlobal) flags &= ~(ImGuiInputFlags_RouteOverFocused | ImGuiInputFlags_RouteOverActive | ImGuiInputFlags_RouteUnlessBgFocused); + ImGui::SeparatorText("Using SetNextItemShortcut()"); ImGui::Text("Ctrl+S"); ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_S, flags | ImGuiInputFlags_Tooltip); @@ -7075,36 +7953,45 @@ static void DemoWindowInputs() ImGui::SetNextItemShortcut(ImGuiMod_Alt | ImGuiKey_F, flags | ImGuiInputFlags_Tooltip); static float f = 0.5f; ImGui::SliderFloat("Factor", &f, 0.0f, 1.0f); + ImGui::SeparatorText("Using Shortcut()"); const float line_height = ImGui::GetTextLineHeightWithSpacing(); const ImGuiKeyChord key_chord = ImGuiMod_Ctrl | ImGuiKey_A; + ImGui::Text("Ctrl+A"); ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags) ? "PRESSED" : "..."); + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 0.0f, 1.0f, 0.1f)); + ImGui::BeginChild("WindowA", ImVec2(-FLT_MIN, line_height * 14), true); ImGui::Text("Press CTRL+A and see who receives it!"); ImGui::Separator(); + // 1: Window polling for CTRL+A ImGui::Text("(in WindowA)"); ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags) ? "PRESSED" : "..."); + // 2: InputText also polling for CTRL+A: it always uses _RouteFocused internally (gets priority when active) - // (Commmented because the owner-aware version of Shortcut() is still in imgui_internal.h) + // (Commented because the owner-aware version of Shortcut() is still in imgui_internal.h) //char str[16] = "Press CTRL+A"; //ImGui::Spacing(); //ImGui::InputText("InputTextB", str, IM_ARRAYSIZE(str), ImGuiInputTextFlags_ReadOnly); //ImGuiID item_id = ImGui::GetItemID(); //ImGui::SameLine(); HelpMarker("Internal widgets always use _RouteFocused"); //ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags, item_id) ? "PRESSED" : "..."); + // 3: Dummy child is not claiming the route: focusing them shouldn't steal route away from WindowA ImGui::BeginChild("ChildD", ImVec2(-FLT_MIN, line_height * 4), true); ImGui::Text("(in ChildD: not using same Shortcut)"); ImGui::Text("IsWindowFocused: %d", ImGui::IsWindowFocused()); ImGui::EndChild(); + // 4: Child window polling for CTRL+A. It is deeper than WindowA and gets priority when focused. ImGui::BeginChild("ChildE", ImVec2(-FLT_MIN, line_height * 4), true); ImGui::Text("(in ChildE: using same Shortcut)"); ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags) ? "PRESSED" : "..."); ImGui::EndChild(); + // 5: In a popup if (ImGui::Button("Open Popup")) ImGui::OpenPopup("PopupF"); @@ -7112,27 +7999,31 @@ static void DemoWindowInputs() { ImGui::Text("(in PopupF)"); ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags) ? "PRESSED" : "..."); - // (Commmented because the owner-aware version of Shortcut() is still in imgui_internal.h) + // (Commented because the owner-aware version of Shortcut() is still in imgui_internal.h) //ImGui::InputText("InputTextG", str, IM_ARRAYSIZE(str), ImGuiInputTextFlags_ReadOnly); //ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags, ImGui::GetItemID()) ? "PRESSED" : "..."); ImGui::EndPopup(); } ImGui::EndChild(); ImGui::PopStyleColor(); + ImGui::TreePop(); } + // Display mouse cursors IMGUI_DEMO_MARKER("Inputs & Focus/Mouse Cursors"); if (ImGui::TreeNode("Mouse Cursors")) { const char* mouse_cursors_names[] = { "Arrow", "TextInput", "ResizeAll", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE", "Hand", "Wait", "Progress", "NotAllowed" }; IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_COUNT); + ImGuiMouseCursor current = ImGui::GetMouseCursor(); const char* cursor_name = (current >= ImGuiMouseCursor_Arrow) && (current < ImGuiMouseCursor_COUNT) ? mouse_cursors_names[current] : "N/A"; ImGui::Text("Current mouse cursor = %d: %s", current, cursor_name); ImGui::BeginDisabled(true); ImGui::CheckboxFlags("io.BackendFlags: HasMouseCursors", &io.BackendFlags, ImGuiBackendFlags_HasMouseCursors); ImGui::EndDisabled(); + ImGui::Text("Hover to see mouse cursors:"); ImGui::SameLine(); HelpMarker( "Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. " @@ -7148,6 +8039,7 @@ static void DemoWindowInputs() } ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Inputs & Focus/Tabbing"); if (ImGui::TreeNode("Tabbing")) { @@ -7163,6 +8055,7 @@ static void DemoWindowInputs() ImGui::InputText("5", buf, IM_ARRAYSIZE(buf)); ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Inputs & Focus/Focus from code"); if (ImGui::TreeNode("Focus from code")) { @@ -7171,22 +8064,27 @@ static void DemoWindowInputs() bool focus_3 = ImGui::Button("Focus on 3"); int has_focus = 0; static char buf[128] = "click on a button to set focus"; + if (focus_1) ImGui::SetKeyboardFocusHere(); ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); if (ImGui::IsItemActive()) has_focus = 1; + if (focus_2) ImGui::SetKeyboardFocusHere(); ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); if (ImGui::IsItemActive()) has_focus = 2; + ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop, true); if (focus_3) ImGui::SetKeyboardFocusHere(); ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf)); if (ImGui::IsItemActive()) has_focus = 3; ImGui::SameLine(); HelpMarker("Item won't be cycled through when using TAB or Shift+Tab."); ImGui::PopItemFlag(); + if (has_focus) ImGui::Text("Item with focus: %d", has_focus); else ImGui::Text("Item with focus: "); + // Use >= 0 parameter to SetKeyboardFocusHere() to focus an upcoming item static float f3[3] = { 0.0f, 0.0f, 0.0f }; int focus_ahead = -1; @@ -7195,9 +8093,11 @@ static void DemoWindowInputs() if (ImGui::Button("Focus on Z")) { focus_ahead = 2; } if (focus_ahead != -1) ImGui::SetKeyboardFocusHere(focus_ahead); ImGui::SliderFloat3("Float3", &f3[0], 0.0f, 1.0f); + ImGui::TextWrapped("NB: Cursor & selection are preserved when refocusing last used item in code."); ImGui::TreePop(); } + IMGUI_DEMO_MARKER("Inputs & Focus/Dragging"); if (ImGui::TreeNode("Dragging")) { @@ -7209,9 +8109,11 @@ static void DemoWindowInputs() ImGui::Text(" w/ zero threshold: %d,", ImGui::IsMouseDragging(button, 0.0f)); ImGui::Text(" w/ large threshold: %d,", ImGui::IsMouseDragging(button, 20.0f)); } + ImGui::Button("Drag Me"); if (ImGui::IsItemActive()) ImGui::GetForegroundDrawList()->AddLine(io.MouseClickedPos[0], io.MousePos, ImGui::GetColorU32(ImGuiCol_Button), 4.0f); // Draw a line between the button and the mouse cursor + // Drag operations gets "unlocked" when the mouse has moved past a certain threshold // (the default threshold is stored in io.MouseDragThreshold). You can request a lower or higher // threshold using the second parameter of IsMouseDragging() and GetMouseDragDelta(). @@ -7226,10 +8128,12 @@ static void DemoWindowInputs() } } } + //----------------------------------------------------------------------------- // [SECTION] About Window / ShowAboutWindow() // Access from Dear ImGui Demo -> Tools -> About //----------------------------------------------------------------------------- + void ImGui::ShowAboutWindow(bool* p_open) { if (!ImGui::Begin("About Dear ImGui", p_open, ImGuiWindowFlags_AlwaysAutoResize)) @@ -7239,38 +8143,48 @@ void ImGui::ShowAboutWindow(bool* p_open) } IMGUI_DEMO_MARKER("Tools/About Dear ImGui"); ImGui::Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); + ImGui::TextLinkOpenURL("Homepage", "https://github.com/ocornut/imgui"); ImGui::SameLine(); ImGui::TextLinkOpenURL("FAQ", "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md"); ImGui::SameLine(); ImGui::TextLinkOpenURL("Wiki", "https://github.com/ocornut/imgui/wiki"); ImGui::SameLine(); + ImGui::TextLinkOpenURL("Extensions", "https://github.com/ocornut/imgui/wiki/Useful-Extensions"); + ImGui::SameLine(); ImGui::TextLinkOpenURL("Releases", "https://github.com/ocornut/imgui/releases"); ImGui::SameLine(); ImGui::TextLinkOpenURL("Funding", "https://github.com/ocornut/imgui/wiki/Funding"); + ImGui::Separator(); ImGui::Text("(c) 2014-2025 Omar Cornut"); ImGui::Text("Developed by Omar Cornut and all Dear ImGui contributors."); ImGui::Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information."); ImGui::Text("If your company uses this, please consider funding the project."); + static bool show_config_info = false; ImGui::Checkbox("Config/Build Information", &show_config_info); if (show_config_info) { ImGuiIO& io = ImGui::GetIO(); ImGuiStyle& style = ImGui::GetStyle(); + bool copy_to_clipboard = ImGui::Button("Copy to clipboard"); ImVec2 child_size = ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 18); ImGui::BeginChild(ImGui::GetID("cfg_infos"), child_size, ImGuiChildFlags_FrameStyle); if (copy_to_clipboard) { ImGui::LogToClipboard(); - ImGui::LogText("```\n"); // Back quotes will make text appears without formatting when pasting on GitHub + ImGui::LogText("```cpp\n"); // Back quotes will make text appears without formatting when pasting on GitHub } + ImGui::Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); ImGui::Separator(); ImGui::Text("sizeof(size_t): %d, sizeof(ImDrawIdx): %d, sizeof(ImDrawVert): %d", (int)sizeof(size_t), (int)sizeof(ImDrawIdx), (int)sizeof(ImDrawVert)); ImGui::Text("define: __cplusplus=%d", (int)__cplusplus); +#ifdef IMGUI_ENABLE_TEST_ENGINE + ImGui::Text("define: IMGUI_ENABLE_TEST_ENGINE"); +#endif #ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGui::Text("define: IMGUI_DISABLE_OBSOLETE_FUNCTIONS"); #endif @@ -7355,9 +8269,9 @@ void ImGui::ShowAboutWindow(bool* p_open) if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) ImGui::Text(" NoKeyboard"); if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) ImGui::Text(" DockingEnable"); if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) ImGui::Text(" ViewportsEnable"); - if (io.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleViewports) ImGui::Text(" DpiEnableScaleViewports"); - if (io.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ImGui::Text(" DpiEnableScaleFonts"); if (io.MouseDrawCursor) ImGui::Text("io.MouseDrawCursor"); + if (io.ConfigDpiScaleFonts) ImGui::Text("io.ConfigDpiScaleFonts"); + if (io.ConfigDpiScaleViewports) ImGui::Text("io.ConfigDpiScaleViewports"); if (io.ConfigViewportsNoAutoMerge) ImGui::Text("io.ConfigViewportsNoAutoMerge"); if (io.ConfigViewportsNoTaskBarIcon) ImGui::Text("io.ConfigViewportsNoTaskBarIcon"); if (io.ConfigViewportsNoDecoration) ImGui::Text("io.ConfigViewportsNoDecoration"); @@ -7380,9 +8294,11 @@ void ImGui::ShowAboutWindow(bool* p_open) if (io.BackendFlags & ImGuiBackendFlags_PlatformHasViewports) ImGui::Text(" PlatformHasViewports"); if (io.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport)ImGui::Text(" HasMouseHoveredViewport"); if (io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) ImGui::Text(" RendererHasVtxOffset"); + if (io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) ImGui::Text(" RendererHasTextures"); if (io.BackendFlags & ImGuiBackendFlags_RendererHasViewports) ImGui::Text(" RendererHasViewports"); ImGui::Separator(); - ImGui::Text("io.Fonts: %d fonts, Flags: 0x%08X, TexSize: %d,%d", io.Fonts->Fonts.Size, io.Fonts->Flags, io.Fonts->TexWidth, io.Fonts->TexHeight); + ImGui::Text("io.Fonts: %d fonts, Flags: 0x%08X, TexSize: %d,%d", io.Fonts->Fonts.Size, io.Fonts->Flags, io.Fonts->TexData->Width, io.Fonts->TexData->Height); + ImGui::Text("io.Fonts->FontLoaderName: %s", io.Fonts->FontLoaderName ? io.Fonts->FontLoaderName : "NULL"); ImGui::Text("io.DisplaySize: %.2f,%.2f", io.DisplaySize.x, io.DisplaySize.y); ImGui::Text("io.DisplayFramebufferScale: %.2f,%.2f", io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); ImGui::Separator(); @@ -7393,6 +8309,7 @@ void ImGui::ShowAboutWindow(bool* p_open) ImGui::Text("style.FrameBorderSize: %.2f", style.FrameBorderSize); ImGui::Text("style.ItemSpacing: %.2f,%.2f", style.ItemSpacing.x, style.ItemSpacing.y); ImGui::Text("style.ItemInnerSpacing: %.2f,%.2f", style.ItemInnerSpacing.x, style.ItemInnerSpacing.y); + if (copy_to_clipboard) { ImGui::LogText("\n```\n"); @@ -7402,41 +8319,14 @@ void ImGui::ShowAboutWindow(bool* p_open) } ImGui::End(); } + //----------------------------------------------------------------------------- // [SECTION] Style Editor / ShowStyleEditor() //----------------------------------------------------------------------------- -// - ShowFontSelector() // - ShowStyleSelector() // - ShowStyleEditor() //----------------------------------------------------------------------------- -// Forward declare ShowFontAtlas() which isn't worth putting in public API yet -namespace ImGui { IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas); } -// Demo helper function to select among loaded fonts. -// Here we use the regular BeginCombo()/EndCombo() api which is the more flexible one. -void ImGui::ShowFontSelector(const char* label) -{ - ImGuiIO& io = ImGui::GetIO(); - ImFont* font_current = ImGui::GetFont(); - if (ImGui::BeginCombo(label, font_current->GetDebugName())) - { - for (ImFont* font : io.Fonts->Fonts) - { - ImGui::PushID((void*)font); - if (ImGui::Selectable(font->GetDebugName(), font == font_current)) - io.FontDefault = font; - if (font == font_current) - ImGui::SetItemDefaultFocus(); - ImGui::PopID(); - } - ImGui::EndCombo(); - } - ImGui::SameLine(); - HelpMarker( - "- Load additional fonts with io.Fonts->AddFontFromFileTTF().\n" - "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" - "- Read FAQ and docs/FONTS.md for more details.\n" - "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); -} + // Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options. // Here we use the simplified Combo() api that packs items into a single literal string. // Useful for quick combo boxes where the choices are known locally. @@ -7455,13 +8345,24 @@ bool ImGui::ShowStyleSelector(const char* label) } return false; } + +static const char* GetTreeLinesFlagsName(ImGuiTreeNodeFlags flags) +{ + if (flags == ImGuiTreeNodeFlags_DrawLinesNone) return "DrawLinesNone"; + if (flags == ImGuiTreeNodeFlags_DrawLinesFull) return "DrawLinesFull"; + if (flags == ImGuiTreeNodeFlags_DrawLinesToNodes) return "DrawLinesToNodes"; + return ""; +} + +// We omit the ImGui:: prefix in this function, as we don't expect user to be copy and pasting this code. void ImGui::ShowStyleEditor(ImGuiStyle* ref) { IMGUI_DEMO_MARKER("Tools/Style Editor"); // You can pass in a reference ImGuiStyle structure to compare to, revert to and save to // (without a reference style pointer, we will use one compared locally as a reference) - ImGuiStyle& style = ImGui::GetStyle(); + ImGuiStyle& style = GetStyle(); static ImGuiStyle ref_saved_style; + // Default to using internal storage as reference static bool init = true; if (init && ref == NULL) @@ -7469,175 +8370,235 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) init = false; if (ref == NULL) ref = &ref_saved_style; - ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); - if (ImGui::ShowStyleSelector("Colors##Selector")) - ref_saved_style = style; - ImGui::ShowFontSelector("Fonts##Selector"); - // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f) - if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) - style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding - { bool border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox("WindowBorder", &border)) { style.WindowBorderSize = border ? 1.0f : 0.0f; } } - ImGui::SameLine(); - { bool border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox("FrameBorder", &border)) { style.FrameBorderSize = border ? 1.0f : 0.0f; } } - ImGui::SameLine(); - { bool border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox("PopupBorder", &border)) { style.PopupBorderSize = border ? 1.0f : 0.0f; } } + + PushItemWidth(GetWindowWidth() * 0.50f); + + { + // General + SeparatorText("General"); + if ((GetIO().BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + { + BulletText("Warning: Font scaling will NOT be smooth, because\nImGuiBackendFlags_RendererHasTextures is not set!"); + BulletText("For instructions, see:"); + SameLine(); + TextLinkOpenURL("docs/BACKENDS.md", "https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md"); + } + + if (ShowStyleSelector("Colors##Selector")) + ref_saved_style = style; + ShowFontSelector("Fonts##Selector"); + if (DragFloat("FontSizeBase", &style.FontSizeBase, 0.20f, 5.0f, 100.0f, "%.0f")) + style._NextFrameFontSizeBase = style.FontSizeBase; // FIXME: Temporary hack until we finish remaining work. + SameLine(0.0f, 0.0f); Text(" (out %.2f)", GetFontSize()); + DragFloat("FontScaleMain", &style.FontScaleMain, 0.02f, 0.5f, 4.0f); + //BeginDisabled(GetIO().ConfigDpiScaleFonts); + DragFloat("FontScaleDpi", &style.FontScaleDpi, 0.02f, 0.5f, 4.0f); + //SetItemTooltip("When io.ConfigDpiScaleFonts is set, this value is automatically overwritten."); + //EndDisabled(); + + // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f) + if (SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) + style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding + { bool border = (style.WindowBorderSize > 0.0f); if (Checkbox("WindowBorder", &border)) { style.WindowBorderSize = border ? 1.0f : 0.0f; } } + SameLine(); + { bool border = (style.FrameBorderSize > 0.0f); if (Checkbox("FrameBorder", &border)) { style.FrameBorderSize = border ? 1.0f : 0.0f; } } + SameLine(); + { bool border = (style.PopupBorderSize > 0.0f); if (Checkbox("PopupBorder", &border)) { style.PopupBorderSize = border ? 1.0f : 0.0f; } } + } + // Save/Revert button - if (ImGui::Button("Save Ref")) + if (Button("Save Ref")) *ref = ref_saved_style = style; - ImGui::SameLine(); - if (ImGui::Button("Revert Ref")) + SameLine(); + if (Button("Revert Ref")) style = *ref; - ImGui::SameLine(); + SameLine(); HelpMarker( "Save/Revert in local non-persistent storage. Default Colors definition are not affected. " "Use \"Export\" below to save them somewhere."); - ImGui::Separator(); - if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_None)) + + SeparatorText("Details"); + if (BeginTabBar("##tabs", ImGuiTabBarFlags_None)) { - if (ImGui::BeginTabItem("Sizes")) + if (BeginTabItem("Sizes")) { - ImGui::SeparatorText("Main"); - ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); - ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); - ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); - ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); - ImGui::SeparatorText("Borders"); - ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SeparatorText("Rounding"); - ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SeparatorText("Tabs"); - ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("TabBarBorderSize", &style.TabBarBorderSize, 0.0f, 2.0f, "%.0f"); - ImGui::SliderFloat("TabBarOverlineSize", &style.TabBarOverlineSize, 0.0f, 3.0f, "%.0f"); - ImGui::SameLine(); HelpMarker("Overline is only drawn over the selected tab when ImGuiTabBarFlags_DrawSelectedOverline is set."); - ImGui::DragFloat("TabCloseButtonMinWidthSelected", &style.TabCloseButtonMinWidthSelected, 0.1f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthSelected < 0.0f) ? "%.0f (Always)" : "%.0f"); - ImGui::DragFloat("TabCloseButtonMinWidthUnselected", &style.TabCloseButtonMinWidthUnselected, 0.1f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthUnselected < 0.0f) ? "%.0f (Always)" : "%.0f"); - ImGui::SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SeparatorText("Tables"); - ImGui::SliderFloat2("CellPadding", (float*)&style.CellPadding, 0.0f, 20.0f, "%.0f"); - ImGui::SliderAngle("TableAngledHeadersAngle", &style.TableAngledHeadersAngle, -50.0f, +50.0f); - ImGui::SliderFloat2("TableAngledHeadersTextAlign", (float*)&style.TableAngledHeadersTextAlign, 0.0f, 1.0f, "%.2f"); - ImGui::SeparatorText("Windows"); - ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); - ImGui::SliderFloat("WindowBorderHoverPadding", &style.WindowBorderHoverPadding, 1.0f, 20.0f, "%.0f"); + SeparatorText("Main"); + SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); + SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); + SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); + SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); + SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); + SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); + SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); + SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); + + SeparatorText("Borders"); + SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); + SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); + SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); + SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); + + SeparatorText("Rounding"); + SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); + SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 12.0f, "%.0f"); + SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); + SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 12.0f, "%.0f"); + SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); + SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); + + SeparatorText("Tabs"); + SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); + SliderFloat("TabBarBorderSize", &style.TabBarBorderSize, 0.0f, 2.0f, "%.0f"); + SliderFloat("TabBarOverlineSize", &style.TabBarOverlineSize, 0.0f, 3.0f, "%.0f"); + SameLine(); HelpMarker("Overline is only drawn over the selected tab when ImGuiTabBarFlags_DrawSelectedOverline is set."); + DragFloat("TabMinWidthBase", &style.TabMinWidthBase, 0.5f, 1.0f, 500.0f, "%.0f"); + DragFloat("TabMinWidthShrink", &style.TabMinWidthShrink, 0.5f, 1.0f, 500.0f, "%0.f"); + DragFloat("TabCloseButtonMinWidthSelected", &style.TabCloseButtonMinWidthSelected, 0.5f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthSelected < 0.0f) ? "%.0f (Always)" : "%.0f"); + DragFloat("TabCloseButtonMinWidthUnselected", &style.TabCloseButtonMinWidthUnselected, 0.5f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthUnselected < 0.0f) ? "%.0f (Always)" : "%.0f"); + SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f, "%.0f"); + + SeparatorText("Tables"); + SliderFloat2("CellPadding", (float*)&style.CellPadding, 0.0f, 20.0f, "%.0f"); + SliderAngle("TableAngledHeadersAngle", &style.TableAngledHeadersAngle, -50.0f, +50.0f); + SliderFloat2("TableAngledHeadersTextAlign", (float*)&style.TableAngledHeadersTextAlign, 0.0f, 1.0f, "%.2f"); + + SeparatorText("Trees"); + bool combo_open = BeginCombo("TreeLinesFlags", GetTreeLinesFlagsName(style.TreeLinesFlags)); + SameLine(); + HelpMarker("[Experimental] Tree lines may not work in all situations (e.g. using a clipper) and may incurs slight traversal overhead.\n\nImGuiTreeNodeFlags_DrawLinesFull is faster than ImGuiTreeNodeFlags_DrawLinesToNode."); + if (combo_open) + { + const ImGuiTreeNodeFlags options[] = { ImGuiTreeNodeFlags_DrawLinesNone, ImGuiTreeNodeFlags_DrawLinesFull, ImGuiTreeNodeFlags_DrawLinesToNodes }; + for (ImGuiTreeNodeFlags option : options) + if (Selectable(GetTreeLinesFlagsName(option), style.TreeLinesFlags == option)) + style.TreeLinesFlags = option; + EndCombo(); + } + SliderFloat("TreeLinesSize", &style.TreeLinesSize, 0.0f, 2.0f, "%.0f"); + SliderFloat("TreeLinesRounding", &style.TreeLinesRounding, 0.0f, 12.0f, "%.0f"); + + SeparatorText("Windows"); + SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); + SliderFloat("WindowBorderHoverPadding", &style.WindowBorderHoverPadding, 1.0f, 20.0f, "%.0f"); int window_menu_button_position = style.WindowMenuButtonPosition + 1; - if (ImGui::Combo("WindowMenuButtonPosition", (int*)&window_menu_button_position, "None\0Left\0Right\0")) + if (Combo("WindowMenuButtonPosition", (int*)&window_menu_button_position, "None\0Left\0Right\0")) style.WindowMenuButtonPosition = (ImGuiDir)(window_menu_button_position - 1); - ImGui::SeparatorText("Widgets"); - ImGui::Combo("ColorButtonPosition", (int*)&style.ColorButtonPosition, "Left\0Right\0"); - ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); - ImGui::SameLine(); HelpMarker("Alignment applies when a button is larger than its text content."); - ImGui::SliderFloat2("SelectableTextAlign", (float*)&style.SelectableTextAlign, 0.0f, 1.0f, "%.2f"); - ImGui::SameLine(); HelpMarker("Alignment applies when a selectable is larger than its text content."); - ImGui::SliderFloat("SeparatorTextBorderSize", &style.SeparatorTextBorderSize, 0.0f, 10.0f, "%.0f"); - ImGui::SliderFloat2("SeparatorTextAlign", (float*)&style.SeparatorTextAlign, 0.0f, 1.0f, "%.2f"); - ImGui::SliderFloat2("SeparatorTextPadding", (float*)&style.SeparatorTextPadding, 0.0f, 40.0f, "%.0f"); - ImGui::SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("ImageBorderSize", &style.ImageBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SeparatorText("Docking"); - ImGui::SliderFloat("DockingSplitterSize", &style.DockingSeparatorSize, 0.0f, 12.0f, "%.0f"); - ImGui::SeparatorText("Tooltips"); + + SeparatorText("Widgets"); + Combo("ColorButtonPosition", (int*)&style.ColorButtonPosition, "Left\0Right\0"); + SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); + SameLine(); HelpMarker("Alignment applies when a button is larger than its text content."); + SliderFloat2("SelectableTextAlign", (float*)&style.SelectableTextAlign, 0.0f, 1.0f, "%.2f"); + SameLine(); HelpMarker("Alignment applies when a selectable is larger than its text content."); + SliderFloat("SeparatorTextBorderSize", &style.SeparatorTextBorderSize, 0.0f, 10.0f, "%.0f"); + SliderFloat2("SeparatorTextAlign", (float*)&style.SeparatorTextAlign, 0.0f, 1.0f, "%.2f"); + SliderFloat2("SeparatorTextPadding", (float*)&style.SeparatorTextPadding, 0.0f, 40.0f, "%.0f"); + SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); + SliderFloat("ImageBorderSize", &style.ImageBorderSize, 0.0f, 1.0f, "%.0f"); + + SeparatorText("Docking"); + SliderFloat("DockingSeparatorSize", &style.DockingSeparatorSize, 0.0f, 12.0f, "%.0f"); + + SeparatorText("Tooltips"); for (int n = 0; n < 2; n++) - if (ImGui::TreeNodeEx(n == 0 ? "HoverFlagsForTooltipMouse" : "HoverFlagsForTooltipNav")) + if (TreeNodeEx(n == 0 ? "HoverFlagsForTooltipMouse" : "HoverFlagsForTooltipNav")) { ImGuiHoveredFlags* p = (n == 0) ? &style.HoverFlagsForTooltipMouse : &style.HoverFlagsForTooltipNav; - ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayNone", p, ImGuiHoveredFlags_DelayNone); - ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayShort", p, ImGuiHoveredFlags_DelayShort); - ImGui::CheckboxFlags("ImGuiHoveredFlags_DelayNormal", p, ImGuiHoveredFlags_DelayNormal); - ImGui::CheckboxFlags("ImGuiHoveredFlags_Stationary", p, ImGuiHoveredFlags_Stationary); - ImGui::CheckboxFlags("ImGuiHoveredFlags_NoSharedDelay", p, ImGuiHoveredFlags_NoSharedDelay); - ImGui::TreePop(); + CheckboxFlags("ImGuiHoveredFlags_DelayNone", p, ImGuiHoveredFlags_DelayNone); + CheckboxFlags("ImGuiHoveredFlags_DelayShort", p, ImGuiHoveredFlags_DelayShort); + CheckboxFlags("ImGuiHoveredFlags_DelayNormal", p, ImGuiHoveredFlags_DelayNormal); + CheckboxFlags("ImGuiHoveredFlags_Stationary", p, ImGuiHoveredFlags_Stationary); + CheckboxFlags("ImGuiHoveredFlags_NoSharedDelay", p, ImGuiHoveredFlags_NoSharedDelay); + TreePop(); } - ImGui::SeparatorText("Misc"); - ImGui::SliderFloat2("DisplayWindowPadding", (float*)&style.DisplayWindowPadding, 0.0f, 30.0f, "%.0f"); ImGui::SameLine(); HelpMarker("Apply to regular windows: amount which we enforce to keep visible when moving near edges of your screen."); - ImGui::SliderFloat2("DisplaySafeAreaPadding", (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); ImGui::SameLine(); HelpMarker("Apply to every windows, menus, popups, tooltips: amount where we avoid displaying contents. Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); - ImGui::EndTabItem(); + + SeparatorText("Misc"); + SliderFloat2("DisplayWindowPadding", (float*)&style.DisplayWindowPadding, 0.0f, 30.0f, "%.0f"); SameLine(); HelpMarker("Apply to regular windows: amount which we enforce to keep visible when moving near edges of your screen."); + SliderFloat2("DisplaySafeAreaPadding", (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); SameLine(); HelpMarker("Apply to every windows, menus, popups, tooltips: amount where we avoid displaying contents. Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); + + EndTabItem(); } - if (ImGui::BeginTabItem("Colors")) + + if (BeginTabItem("Colors")) { static int output_dest = 0; static bool output_only_modified = true; - if (ImGui::Button("Export")) + if (Button("Export")) { if (output_dest == 0) - ImGui::LogToClipboard(); + LogToClipboard(); else - ImGui::LogToTTY(); - ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); + LogToTTY(); + LogText("ImVec4* colors = GetStyle().Colors;" IM_NEWLINE); for (int i = 0; i < ImGuiCol_COUNT; i++) { const ImVec4& col = style.Colors[i]; - const char* name = ImGui::GetStyleColorName(i); + const char* name = GetStyleColorName(i); if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) - ImGui::LogText("colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, + LogText("colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, name, 23 - (int)strlen(name), "", col.x, col.y, col.z, col.w); } - ImGui::LogFinish(); + LogFinish(); } - ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); - ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified); + SameLine(); SetNextItemWidth(120); Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); + SameLine(); Checkbox("Only Modified Colors", &output_only_modified); + static ImGuiTextFilter filter; - filter.Draw("Filter colors", ImGui::GetFontSize() * 16); + filter.Draw("Filter colors", GetFontSize() * 16); + static ImGuiColorEditFlags alpha_flags = 0; - if (ImGui::RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_AlphaOpaque)) { alpha_flags = ImGuiColorEditFlags_AlphaOpaque; } ImGui::SameLine(); - if (ImGui::RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } ImGui::SameLine(); - if (ImGui::RadioButton("Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; } ImGui::SameLine(); + if (RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_AlphaOpaque)) { alpha_flags = ImGuiColorEditFlags_AlphaOpaque; } SameLine(); + if (RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } SameLine(); + if (RadioButton("Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; } SameLine(); HelpMarker( "In the color list:\n" "Left-click on color square to open color picker,\n" "Right-click to open edit options menu."); - ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 10), ImVec2(FLT_MAX, FLT_MAX)); - ImGui::BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); - ImGui::PushItemWidth(ImGui::GetFontSize() * -12); + + SetNextWindowSizeConstraints(ImVec2(0.0f, GetTextLineHeightWithSpacing() * 10), ImVec2(FLT_MAX, FLT_MAX)); + BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); + PushItemWidth(GetFontSize() * -12); for (int i = 0; i < ImGuiCol_COUNT; i++) { - const char* name = ImGui::GetStyleColorName(i); + const char* name = GetStyleColorName(i); if (!filter.PassFilter(name)) continue; - ImGui::PushID(i); + PushID(i); #ifndef IMGUI_DISABLE_DEBUG_TOOLS - if (ImGui::Button("?")) - ImGui::DebugFlashStyleColor((ImGuiCol)i); - ImGui::SetItemTooltip("Flash given color to identify places where it is used."); - ImGui::SameLine(); + if (Button("?")) + DebugFlashStyleColor((ImGuiCol)i); + SetItemTooltip("Flash given color to identify places where it is used."); + SameLine(); #endif - ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags); + ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags); if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) { // Tips: in a real user application, you may want to merge and use an icon font into the main font, // so instead of "Save"/"Revert" you'd use icons! // Read the FAQ and docs/FONTS.md about using icon fonts. It's really easy and super convenient! - ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Save")) { ref->Colors[i] = style.Colors[i]; } - ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Revert")) { style.Colors[i] = ref->Colors[i]; } + SameLine(0.0f, style.ItemInnerSpacing.x); if (Button("Save")) { ref->Colors[i] = style.Colors[i]; } + SameLine(0.0f, style.ItemInnerSpacing.x); if (Button("Revert")) { style.Colors[i] = ref->Colors[i]; } } - ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); - ImGui::TextUnformatted(name); - ImGui::PopID(); + SameLine(0.0f, style.ItemInnerSpacing.x); + TextUnformatted(name); + PopID(); } - ImGui::PopItemWidth(); - ImGui::EndChild(); - ImGui::EndTabItem(); + PopItemWidth(); + EndChild(); + + EndTabItem(); } - if (ImGui::BeginTabItem("Fonts")) + + if (BeginTabItem("Fonts")) { - ImGuiIO& io = ImGui::GetIO(); + ImGuiIO& io = GetIO(); ImFontAtlas* atlas = io.Fonts; - HelpMarker("Read FAQ and docs/FONTS.md for details on font loading."); - ImGui::ShowFontAtlas(atlas); + ShowFontAtlas(atlas); + // Post-baking font scaling. Note that this is NOT the nice way of scaling fonts, read below. // (we enforce hard clamping manually as by default DragFloat/SliderFloat allows CTRL+Click text to get out of bounds). + /* + SeparatorText("Legacy Scaling"); const float MIN_SCALE = 0.3f; const float MAX_SCALE = 2.0f; HelpMarker( @@ -7645,110 +8606,130 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) "However, the _correct_ way of scaling your UI is currently to reload your font at the designed size, " "rebuild the font atlas, and call style.ScaleAllSizes() on a reference ImGuiStyle structure.\n" "Using those settings here will give you poor quality results."); - static float window_scale = 1.0f; - ImGui::PushItemWidth(ImGui::GetFontSize() * 8); - if (ImGui::DragFloat("window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp)) // Scale only this window - ImGui::SetWindowFontScale(window_scale); - ImGui::DragFloat("global scale", &io.FontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp); // Scale everything - ImGui::PopItemWidth(); - ImGui::EndTabItem(); + PushItemWidth(GetFontSize() * 8); + DragFloat("global scale", &io.FontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp); // Scale everything + //static float window_scale = 1.0f; + //if (DragFloat("window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp)) // Scale only this window + // SetWindowFontScale(window_scale); + PopItemWidth(); + */ + + EndTabItem(); } - if (ImGui::BeginTabItem("Rendering")) + + if (BeginTabItem("Rendering")) { - ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); - ImGui::SameLine(); + Checkbox("Anti-aliased lines", &style.AntiAliasedLines); + SameLine(); HelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); - ImGui::Checkbox("Anti-aliased lines use texture", &style.AntiAliasedLinesUseTex); - ImGui::SameLine(); + + Checkbox("Anti-aliased lines use texture", &style.AntiAliasedLinesUseTex); + SameLine(); HelpMarker("Faster lines using texture data. Require backend to render with bilinear filtering (not point/nearest filtering)."); - ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); - ImGui::PushItemWidth(ImGui::GetFontSize() * 8); - ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, "%.2f"); + + Checkbox("Anti-aliased fill", &style.AntiAliasedFill); + PushItemWidth(GetFontSize() * 8); + DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, "%.2f"); if (style.CurveTessellationTol < 0.10f) style.CurveTessellationTol = 0.10f; + // When editing the "Circle Segment Max Error" value, draw a preview of its effect on auto-tessellated circles. - ImGui::DragFloat("Circle Tessellation Max Error", &style.CircleTessellationMaxError , 0.005f, 0.10f, 5.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); - const bool show_samples = ImGui::IsItemActive(); + DragFloat("Circle Tessellation Max Error", &style.CircleTessellationMaxError , 0.005f, 0.10f, 5.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); + const bool show_samples = IsItemActive(); if (show_samples) - ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); - if (show_samples && ImGui::BeginTooltip()) + SetNextWindowPos(GetCursorScreenPos()); + if (show_samples && BeginTooltip()) { - ImGui::TextUnformatted("(R = radius, N = approx number of segments)"); - ImGui::Spacing(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - const float min_widget_width = ImGui::CalcTextSize("R: MMM\nN: MMM").x; + TextUnformatted("(R = radius, N = approx number of segments)"); + Spacing(); + ImDrawList* draw_list = GetWindowDrawList(); + const float min_widget_width = CalcTextSize("R: MMM\nN: MMM").x; for (int n = 0; n < 8; n++) { const float RAD_MIN = 5.0f; const float RAD_MAX = 70.0f; const float rad = RAD_MIN + (RAD_MAX - RAD_MIN) * (float)n / (8.0f - 1.0f); - ImGui::BeginGroup(); + + BeginGroup(); + // N is not always exact here due to how PathArcTo() function work internally - ImGui::Text("R: %.f\nN: %d", rad, draw_list->_CalcCircleAutoSegmentCount(rad)); + Text("R: %.f\nN: %d", rad, draw_list->_CalcCircleAutoSegmentCount(rad)); + const float canvas_width = IM_MAX(min_widget_width, rad * 2.0f); const float offset_x = floorf(canvas_width * 0.5f); const float offset_y = floorf(RAD_MAX); - const ImVec2 p1 = ImGui::GetCursorScreenPos(); - draw_list->AddCircle(ImVec2(p1.x + offset_x, p1.y + offset_y), rad, ImGui::GetColorU32(ImGuiCol_Text)); - ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2)); + + const ImVec2 p1 = GetCursorScreenPos(); + draw_list->AddCircle(ImVec2(p1.x + offset_x, p1.y + offset_y), rad, GetColorU32(ImGuiCol_Text)); + Dummy(ImVec2(canvas_width, RAD_MAX * 2)); + /* - const ImVec2 p2 = ImGui::GetCursorScreenPos(); - draw_list->AddCircleFilled(ImVec2(p2.x + offset_x, p2.y + offset_y), rad, ImGui::GetColorU32(ImGuiCol_Text)); - ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2)); + const ImVec2 p2 = GetCursorScreenPos(); + draw_list->AddCircleFilled(ImVec2(p2.x + offset_x, p2.y + offset_y), rad, GetColorU32(ImGuiCol_Text)); + Dummy(ImVec2(canvas_width, RAD_MAX * 2)); */ - ImGui::EndGroup(); - ImGui::SameLine(); + + EndGroup(); + SameLine(); } - ImGui::EndTooltip(); + EndTooltip(); } - ImGui::SameLine(); + SameLine(); HelpMarker("When drawing circle primitives with \"num_segments == 0\" tessellation will be calculated automatically."); - ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. - ImGui::DragFloat("Disabled Alpha", &style.DisabledAlpha, 0.005f, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); HelpMarker("Additional alpha multiplier for disabled items (multiply over current value of Alpha)."); - ImGui::PopItemWidth(); - ImGui::EndTabItem(); + + DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. + DragFloat("Disabled Alpha", &style.DisabledAlpha, 0.005f, 0.0f, 1.0f, "%.2f"); SameLine(); HelpMarker("Additional alpha multiplier for disabled items (multiply over current value of Alpha)."); + PopItemWidth(); + + EndTabItem(); } - ImGui::EndTabBar(); + + EndTabBar(); } - ImGui::PopItemWidth(); + PopItemWidth(); } + //----------------------------------------------------------------------------- // [SECTION] User Guide / ShowUserGuide() //----------------------------------------------------------------------------- + +// We omit the ImGui:: prefix in this function, as we don't expect user to be copy and pasting this code. void ImGui::ShowUserGuide() { - ImGuiIO& io = ImGui::GetIO(); - ImGui::BulletText("Double-click on title bar to collapse window."); - ImGui::BulletText( + ImGuiIO& io = GetIO(); + BulletText("Double-click on title bar to collapse window."); + BulletText( "Click and drag on lower corner to resize window\n" "(double-click to auto fit window to its contents)."); - ImGui::BulletText("CTRL+Click on a slider or drag box to input value as text."); - ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); - ImGui::BulletText("CTRL+Tab to select a window."); + BulletText("CTRL+Click on a slider or drag box to input value as text."); + BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); + BulletText("CTRL+Tab to select a window."); if (io.FontAllowUserScaling) - ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents."); - ImGui::BulletText("While inputting text:\n"); - ImGui::Indent(); - ImGui::BulletText("CTRL+Left/Right to word jump."); - ImGui::BulletText("CTRL+A or double-click to select all."); - ImGui::BulletText("CTRL+X/C/V to use clipboard cut/copy/paste."); - ImGui::BulletText("CTRL+Z to undo, CTRL+Y/CTRL+SHIFT+Z to redo."); - ImGui::BulletText("ESCAPE to revert."); - ImGui::Unindent(); - ImGui::BulletText("With keyboard navigation enabled:"); - ImGui::Indent(); - ImGui::BulletText("Arrow keys to navigate."); - ImGui::BulletText("Space to activate a widget."); - ImGui::BulletText("Return to input text into a widget."); - ImGui::BulletText("Escape to deactivate a widget, close popup, exit child window."); - ImGui::BulletText("Alt to jump to the menu layer of a window."); - ImGui::Unindent(); + BulletText("CTRL+Mouse Wheel to zoom window contents."); + BulletText("While inputting text:\n"); + Indent(); + BulletText("CTRL+Left/Right to word jump."); + BulletText("CTRL+A or double-click to select all."); + BulletText("CTRL+X/C/V to use clipboard cut/copy/paste."); + BulletText("CTRL+Z to undo, CTRL+Y/CTRL+SHIFT+Z to redo."); + BulletText("ESCAPE to revert."); + Unindent(); + BulletText("With keyboard navigation enabled:"); + Indent(); + BulletText("Arrow keys to navigate."); + BulletText("Space to activate a widget."); + BulletText("Return to input text into a widget."); + BulletText("Escape to deactivate a widget, close popup, exit child window."); + BulletText("Alt to jump to the menu layer of a window."); + Unindent(); } + //----------------------------------------------------------------------------- // [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar() //----------------------------------------------------------------------------- // - ShowExampleAppMainMenuBar() // - ShowExampleMenuFile() //----------------------------------------------------------------------------- + // Demonstrate creating a "main" fullscreen menu bar and populating it. // Note the difference between BeginMainMenuBar() and BeginMenuBar(): // - BeginMenuBar() = menu-bar inside current window (which needs the ImGuiWindowFlags_MenuBar flag!) @@ -7775,6 +8756,7 @@ static void ShowExampleAppMainMenuBar() ImGui::EndMainMenuBar(); } } + // Note that shortcuts are currently provided for display only // (future version will add explicit flags to BeginMenu() to request processing shortcuts) static void ShowExampleMenuFile() @@ -7803,6 +8785,7 @@ static void ShowExampleMenuFile() } if (ImGui::MenuItem("Save", "Ctrl+S")) {} if (ImGui::MenuItem("Save As..")) {} + ImGui::Separator(); IMGUI_DEMO_MARKER("Examples/Menu/Options"); if (ImGui::BeginMenu("Options")) @@ -7820,6 +8803,7 @@ static void ShowExampleMenuFile() ImGui::Combo("Combo", &n, "Yes\0No\0Maybe\0\0"); ImGui::EndMenu(); } + IMGUI_DEMO_MARKER("Examples/Menu/Colors"); if (ImGui::BeginMenu("Colors")) { @@ -7835,6 +8819,7 @@ static void ShowExampleMenuFile() } ImGui::EndMenu(); } + // Here we demonstrate appending again to the "Options" menu (which we already created above) // Of course in this demo it is a little bit silly that this function calls BeginMenu("Options") twice. // In a real code-base using it would make senses to use this feature from very different code locations. @@ -7845,6 +8830,7 @@ static void ShowExampleMenuFile() ImGui::Checkbox("SomeOption", &b); ImGui::EndMenu(); } + if (ImGui::BeginMenu("Disabled", false)) // Disabled { IM_ASSERT(0); @@ -7853,9 +8839,11 @@ static void ShowExampleMenuFile() ImGui::Separator(); if (ImGui::MenuItem("Quit", "Alt+F4")) {} } + //----------------------------------------------------------------------------- // [SECTION] Example App: Debug Console / ShowExampleAppConsole() //----------------------------------------------------------------------------- + // Demonstrate creating a simple console window, with scrolling, filtering, completion and history. // For the console example, we are using a more C++ like approach of declaring a class to hold both data and functions. struct ExampleAppConsole @@ -7868,12 +8856,14 @@ struct ExampleAppConsole ImGuiTextFilter Filter; bool AutoScroll; bool ScrollToBottom; + ExampleAppConsole() { IMGUI_DEMO_MARKER("Examples/Console"); ClearLog(); memset(InputBuf, 0, sizeof(InputBuf)); HistoryPos = -1; + // "CLASSIFY" is here to provide the test case where "C"+[tab] completes to "CL" and display multiple matches. Commands.push_back("HELP"); Commands.push_back("HISTORY"); @@ -7889,17 +8879,20 @@ struct ExampleAppConsole for (int i = 0; i < History.Size; i++) ImGui::MemFree(History[i]); } + // Portable helpers static int Stricmp(const char* s1, const char* s2) { int d; while ((d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; } return d; } static int Strnicmp(const char* s1, const char* s2, int n) { int d = 0; while (n > 0 && (d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; n--; } return d; } static char* Strdup(const char* s) { IM_ASSERT(s); size_t len = strlen(s) + 1; void* buf = ImGui::MemAlloc(len); IM_ASSERT(buf); return (char*)memcpy(buf, (const void*)s, len); } static void Strtrim(char* s) { char* str_end = s + strlen(s); while (str_end > s && str_end[-1] == ' ') str_end--; *str_end = 0; } + void ClearLog() { for (int i = 0; i < Items.Size; i++) ImGui::MemFree(Items[i]); Items.clear(); } + void AddLog(const char* fmt, ...) IM_FMTARGS(2) { // FIXME-OPT @@ -7911,6 +8904,7 @@ struct ExampleAppConsole va_end(args); Items.push_back(Strdup(buf)); } + void Draw(const char* title, bool* p_open) { ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); @@ -7919,6 +8913,7 @@ struct ExampleAppConsole ImGui::End(); return; } + // As a specific feature guaranteed by the library, after calling Begin() the last Item represent the title bar. // So e.g. IsItemHovered() will return true when hovering the title bar. // Here we create a context menu only available from the title bar. @@ -7928,11 +8923,14 @@ struct ExampleAppConsole *p_open = false; ImGui::EndPopup(); } + ImGui::TextWrapped( "This example implements a console with basic coloring, completion (TAB key) and history (Up/Down keys). A more elaborate " "implementation may want to store entries along with extra data such as timestamp, emitter, etc."); ImGui::TextWrapped("Enter 'HELP' for help."); + // TODO: display items starting from the bottom + if (ImGui::SmallButton("Add Debug Text")) { AddLog("%d some text", Items.Size); AddLog("some more text"); AddLog("display very important message here!"); } ImGui::SameLine(); if (ImGui::SmallButton("Add Debug Error")) { AddLog("[error] something went wrong"); } @@ -7941,13 +8939,16 @@ struct ExampleAppConsole ImGui::SameLine(); bool copy_to_clipboard = ImGui::SmallButton("Copy"); //static float t = 0.0f; if (ImGui::GetTime() - t > 0.02f) { t = ImGui::GetTime(); AddLog("Spam %f", t); } + ImGui::Separator(); + // Options menu if (ImGui::BeginPopup("Options")) { ImGui::Checkbox("Auto-scroll", &AutoScroll); ImGui::EndPopup(); } + // Options, Filter ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_O, ImGuiInputFlags_Tooltip); if (ImGui::Button("Options")) @@ -7955,6 +8956,7 @@ struct ExampleAppConsole ImGui::SameLine(); Filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180); ImGui::Separator(); + // Reserve enough left-over height for 1 separator + 1 input text const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_HorizontalScrollbar)) @@ -7964,6 +8966,7 @@ struct ExampleAppConsole if (ImGui::Selectable("Clear")) ClearLog(); ImGui::EndPopup(); } + // Display every line as a separate entry so we can change their color or add custom widgets. // If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping @@ -7995,6 +8998,7 @@ struct ExampleAppConsole { if (!Filter.PassFilter(item)) continue; + // Normally you would store more information in your item than just a string. // (e.g. make Items[] an array of structure, store color/type etc.) ImVec4 color; @@ -8009,15 +9013,18 @@ struct ExampleAppConsole } if (copy_to_clipboard) ImGui::LogFinish(); + // Keep up at the bottom of the scroll region if we were already at the bottom at the beginning of the frame. // Using a scrollbar or mouse-wheel will take away from the bottom edge. if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) ImGui::SetScrollHereY(1.0f); ScrollToBottom = false; + ImGui::PopStyleVar(); } ImGui::EndChild(); ImGui::Separator(); + // Command-line bool reclaim_focus = false; ImGuiInputTextFlags input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_EscapeClearsAll | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory; @@ -8030,15 +9037,19 @@ struct ExampleAppConsole strcpy(s, ""); reclaim_focus = true; } + // Auto-focus on window apparition ImGui::SetItemDefaultFocus(); if (reclaim_focus) ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget + ImGui::End(); } + void ExecCommand(const char* command_line) { AddLog("# %s\n", command_line); + // Insert into history. First find match and delete it so it can be pushed to the back. // This isn't trying to be smart or optimal. HistoryPos = -1; @@ -8050,6 +9061,7 @@ struct ExampleAppConsole break; } History.push_back(Strdup(command_line)); + // Process command if (Stricmp(command_line, "CLEAR") == 0) { @@ -8071,15 +9083,18 @@ struct ExampleAppConsole { AddLog("Unknown command: '%s'\n", command_line); } + // On command input, we scroll to bottom even if AutoScroll==false ScrollToBottom = true; } + // In C++11 you'd be better off using lambdas for this sort of forwarding callbacks static int TextEditCallbackStub(ImGuiInputTextCallbackData* data) { ExampleAppConsole* console = (ExampleAppConsole*)data->UserData; return console->TextEditCallback(data); } + int TextEditCallback(ImGuiInputTextCallbackData* data) { //AddLog("cursor: %d, selection: %d-%d", data->CursorPos, data->SelectionStart, data->SelectionEnd); @@ -8088,6 +9103,7 @@ struct ExampleAppConsole case ImGuiInputTextFlags_CallbackCompletion: { // Example of TEXT COMPLETION + // Locate beginning of current word const char* word_end = data->Buf + data->CursorPos; const char* word_start = word_end; @@ -8098,11 +9114,13 @@ struct ExampleAppConsole break; word_start--; } + // Build a list of candidates ImVector candidates; for (int i = 0; i < Commands.Size; i++) if (Strnicmp(Commands[i], word_start, (int)(word_end - word_start)) == 0) candidates.push_back(Commands[i]); + if (candidates.Size == 0) { // No match @@ -8133,16 +9151,19 @@ struct ExampleAppConsole break; match_len++; } + if (match_len > 0) { data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start)); data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len); } + // List matches AddLog("Possible matches:\n"); for (int i = 0; i < candidates.Size; i++) AddLog("- %s\n", candidates[i]); } + break; } case ImGuiInputTextFlags_CallbackHistory: @@ -8162,6 +9183,7 @@ struct ExampleAppConsole if (++HistoryPos >= History.Size) HistoryPos = -1; } + // A better implementation would preserve the data on the current input line along with cursor position. if (prev_history_pos != HistoryPos) { @@ -8174,14 +9196,17 @@ struct ExampleAppConsole return 0; } }; + static void ShowExampleAppConsole(bool* p_open) { static ExampleAppConsole console; console.Draw("Example: Console", p_open); } + //----------------------------------------------------------------------------- // [SECTION] Example App: Debug Log / ShowExampleAppLog() //----------------------------------------------------------------------------- + // Usage: // static ExampleAppLog my_log; // my_log.AddLog("Hello %d world\n", 123); @@ -8192,17 +9217,20 @@ struct ExampleAppLog ImGuiTextFilter Filter; ImVector LineOffsets; // Index to lines offset. We maintain this with AddLog() calls. bool AutoScroll; // Keep scrolling if already at the bottom. + ExampleAppLog() { AutoScroll = true; Clear(); } + void Clear() { Buf.clear(); LineOffsets.clear(); LineOffsets.push_back(0); } + void AddLog(const char* fmt, ...) IM_FMTARGS(2) { int old_size = Buf.size(); @@ -8214,6 +9242,7 @@ struct ExampleAppLog if (Buf[old_size] == '\n') LineOffsets.push_back(old_size + 1); } + void Draw(const char* title, bool* p_open = NULL) { if (!ImGui::Begin(title, p_open)) @@ -8221,12 +9250,14 @@ struct ExampleAppLog ImGui::End(); return; } + // Options menu if (ImGui::BeginPopup("Options")) { ImGui::Checkbox("Auto-scroll", &AutoScroll); ImGui::EndPopup(); } + // Main window if (ImGui::Button("Options")) ImGui::OpenPopup("Options"); @@ -8236,13 +9267,16 @@ struct ExampleAppLog bool copy = ImGui::Button("Copy"); ImGui::SameLine(); Filter.Draw("Filter", -100.0f); + ImGui::Separator(); + if (ImGui::BeginChild("scrolling", ImVec2(0, 0), ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar)) { if (clear) Clear(); if (copy) ImGui::LogToClipboard(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); const char* buf = Buf.begin(); const char* buf_end = Buf.end(); @@ -8289,6 +9323,7 @@ struct ExampleAppLog clipper.End(); } ImGui::PopStyleVar(); + // Keep up at the bottom of the scroll region if we were already at the bottom at the beginning of the frame. // Using a scrollbar or mouse-wheel will take away from the bottom edge. if (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) @@ -8298,10 +9333,12 @@ struct ExampleAppLog ImGui::End(); } }; + // Demonstrate creating a simple log window with basic filtering. static void ShowExampleAppLog(bool* p_open) { static ExampleAppLog log; + // For the demo: add a debug button _BEFORE_ the normal log window contents // We take advantage of a rarely used feature: multiple calls to Begin()/End() are appending to the _same_ window. // Most of the contents of the window will be added by the log.Draw() call. @@ -8323,12 +9360,15 @@ static void ShowExampleAppLog(bool* p_open) } } ImGui::End(); + // Actually call in the regular Log helper (which will Begin() into the same window as we just did) log.Draw("Example: Log", p_open); } + //----------------------------------------------------------------------------- // [SECTION] Example App: Simple Layout / ShowExampleAppLayout() //----------------------------------------------------------------------------- + // Demonstrate create a window with multiple child windows. static void ShowExampleAppLayout(bool* p_open) { @@ -8345,6 +9385,7 @@ static void ShowExampleAppLayout(bool* p_open) } ImGui::EndMenuBar(); } + // Left static int selected = 0; { @@ -8360,6 +9401,7 @@ static void ShowExampleAppLayout(bool* p_open) ImGui::EndChild(); } ImGui::SameLine(); + // Right { ImGui::BeginGroup(); @@ -8389,6 +9431,7 @@ static void ShowExampleAppLayout(bool* p_open) } ImGui::End(); } + //----------------------------------------------------------------------------- // [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() //----------------------------------------------------------------------------- @@ -8397,10 +9440,12 @@ static void ShowExampleAppLayout(bool* p_open) // - We may want more advanced filtering (child nodes) and clipper support: both will need extra work. // - We would want to customize some keyboard interactions to easily keyboard navigate between the tree and the properties. //----------------------------------------------------------------------------- + struct ExampleAppPropertyEditor { ImGuiTextFilter Filter; ExampleTreeNode* VisibleNode = NULL; + void Draw(ExampleTreeNode* root_node) { // Left side: draw tree @@ -8413,6 +9458,7 @@ struct ExampleAppPropertyEditor if (ImGui::InputTextWithHint("##Filter", "incl,-excl", Filter.InputBuf, IM_ARRAYSIZE(Filter.InputBuf), ImGuiInputTextFlags_EscapeClearsAll)) Filter.Build(); ImGui::PopItemFlag(); + if (ImGui::BeginTable("##bg", 1, ImGuiTableFlags_RowBg)) { for (ExampleTreeNode* node : root_node->Childs) @@ -8422,8 +9468,10 @@ struct ExampleAppPropertyEditor } } ImGui::EndChild(); + // Right side: draw properties ImGui::SameLine(); + ImGui::BeginGroup(); // Lock X position if (ExampleTreeNode* node = VisibleNode) { @@ -8487,14 +9535,17 @@ struct ExampleAppPropertyEditor } ImGui::EndGroup(); } + void DrawTreeNode(ExampleTreeNode* node) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::PushID(node->UID); ImGuiTreeNodeFlags tree_flags = ImGuiTreeNodeFlags_None; - tree_flags |= ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; // Standard opening mode as we are likely to want to add selection afterwards - tree_flags |= ImGuiTreeNodeFlags_NavLeftJumpsBackHere; // Left arrow support + tree_flags |= ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;// Standard opening mode as we are likely to want to add selection afterwards + tree_flags |= ImGuiTreeNodeFlags_NavLeftJumpsToParent; // Left arrow support + tree_flags |= ImGuiTreeNodeFlags_SpanFullWidth; // Span full width for easier mouse reach + tree_flags |= ImGuiTreeNodeFlags_DrawLinesToNodes; // Always draw hierarchy outlines if (node == VisibleNode) tree_flags |= ImGuiTreeNodeFlags_Selected; if (node->Childs.Size == 0) @@ -8515,6 +9566,7 @@ struct ExampleAppPropertyEditor ImGui::PopID(); } }; + // Demonstrate creating a simple property editor. static void ShowExampleAppPropertyEditor(bool* p_open, ImGuiDemoWindowData* demo_data) { @@ -8524,16 +9576,20 @@ static void ShowExampleAppPropertyEditor(bool* p_open, ImGuiDemoWindowData* demo ImGui::End(); return; } + IMGUI_DEMO_MARKER("Examples/Property Editor"); static ExampleAppPropertyEditor property_editor; if (demo_data->DemoTree == NULL) demo_data->DemoTree = ExampleTree_CreateDemoTree(); property_editor.Draw(demo_data->DemoTree); + ImGui::End(); } + //----------------------------------------------------------------------------- // [SECTION] Example App: Long Text / ShowExampleAppLongText() //----------------------------------------------------------------------------- + // Demonstrate/test rendering huge amount of text, and the incidence of clipping. static void ShowExampleAppLongText(bool* p_open) { @@ -8544,6 +9600,7 @@ static void ShowExampleAppLongText(bool* p_open) return; } IMGUI_DEMO_MARKER("Examples/Long text display"); + static int test_type = 0; static ImGuiTextBuffer log; static int lines = 0; @@ -8591,9 +9648,11 @@ static void ShowExampleAppLongText(bool* p_open) ImGui::EndChild(); ImGui::End(); } + //----------------------------------------------------------------------------- // [SECTION] Example App: Auto Resize / ShowExampleAppAutoResize() //----------------------------------------------------------------------------- + // Demonstrate creating a window which gets auto-resized according to its content. static void ShowExampleAppAutoResize(bool* p_open) { @@ -8603,6 +9662,7 @@ static void ShowExampleAppAutoResize(bool* p_open) return; } IMGUI_DEMO_MARKER("Examples/Auto-resizing window"); + static int lines = 10; ImGui::TextUnformatted( "Window will resize every-frame to the size of its content.\n" @@ -8613,9 +9673,11 @@ static void ShowExampleAppAutoResize(bool* p_open) ImGui::Text("%*sThis is line %d", i * 4, "", i); // Pad with space to extend size horizontally ImGui::End(); } + //----------------------------------------------------------------------------- // [SECTION] Example App: Constrained Resize / ShowExampleAppConstrainedResize() //----------------------------------------------------------------------------- + // Demonstrate creating a window with custom resize constraints. // Note that size constraints currently don't work on a docked window (when in 'docking' branch) static void ShowExampleAppConstrainedResize(bool* p_open) @@ -8640,6 +9702,7 @@ static void ShowExampleAppConstrainedResize(bool* p_open) data->DesiredSize = ImVec2((int)(data->DesiredSize.x / step + 0.5f) * step, (int)(data->DesiredSize.y / step + 0.5f) * step); } }; + const char* test_desc[] = { "Between 100x100 and 500x500", @@ -8652,11 +9715,13 @@ static void ShowExampleAppConstrainedResize(bool* p_open) "Custom: Always Square", "Custom: Fixed Steps (100)", }; + // Options static bool auto_resize = false; static bool window_padding = true; static int type = 6; // Aspect Ratio static int display_lines = 10; + // Submit constraint float aspect_ratio = 16.0f / 9.0f; float fixed_step = 100.0f; @@ -8669,6 +9734,7 @@ static void ShowExampleAppConstrainedResize(bool* p_open) if (type == 6) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::AspectRatio, (void*)&aspect_ratio); // Aspect ratio if (type == 7) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square); // Always Square if (type == 8) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)&fixed_step); // Fixed Step + // Submit window if (!window_padding) ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); @@ -8708,9 +9774,11 @@ static void ShowExampleAppConstrainedResize(bool* p_open) } ImGui::End(); } + //----------------------------------------------------------------------------- // [SECTION] Example App: Simple overlay / ShowExampleAppSimpleOverlay() //----------------------------------------------------------------------------- + // Demonstrate creating a simple static window with no decoration // + a context-menu to choose which corner of the screen to use. static void ShowExampleAppSimpleOverlay(bool* p_open) @@ -8763,24 +9831,29 @@ static void ShowExampleAppSimpleOverlay(bool* p_open) } ImGui::End(); } + //----------------------------------------------------------------------------- // [SECTION] Example App: Fullscreen window / ShowExampleAppFullscreen() //----------------------------------------------------------------------------- + // Demonstrate creating a window covering the entire screen/viewport static void ShowExampleAppFullscreen(bool* p_open) { static bool use_work_area = true; static ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings; + // We demonstrate using the full viewport area or the work area (without menu-bars, task-bars etc.) // Based on your use case you may want one or the other. const ImGuiViewport* viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(use_work_area ? viewport->WorkPos : viewport->Pos); ImGui::SetNextWindowSize(use_work_area ? viewport->WorkSize : viewport->Size); + if (ImGui::Begin("Example: Fullscreen window", p_open, flags)) { ImGui::Checkbox("Use work area instead of main area", &use_work_area); ImGui::SameLine(); HelpMarker("Main Area = entire viewport,\nWork Area = entire viewport minus sections used by the main menu bars, task bars etc.\n\nEnable the main-menu bar in Examples menu to see the difference."); + ImGui::CheckboxFlags("ImGuiWindowFlags_NoBackground", &flags, ImGuiWindowFlags_NoBackground); ImGui::CheckboxFlags("ImGuiWindowFlags_NoDecoration", &flags, ImGuiWindowFlags_NoDecoration); ImGui::Indent(); @@ -8788,14 +9861,17 @@ static void ShowExampleAppFullscreen(bool* p_open) ImGui::CheckboxFlags("ImGuiWindowFlags_NoCollapse", &flags, ImGuiWindowFlags_NoCollapse); ImGui::CheckboxFlags("ImGuiWindowFlags_NoScrollbar", &flags, ImGuiWindowFlags_NoScrollbar); ImGui::Unindent(); + if (p_open && ImGui::Button("Close this window")) *p_open = false; } ImGui::End(); } + //----------------------------------------------------------------------------- // [SECTION] Example App: Manipulating Window Titles / ShowExampleAppWindowTitles() //----------------------------------------------------------------------------- + // Demonstrate the use of "##" and "###" in identifiers to manipulate ID generation. // This applies to all regular items as well. // Read FAQ section "How can I have multiple widgets with the same label?" for details. @@ -8803,18 +9879,22 @@ static void ShowExampleAppWindowTitles(bool*) { const ImGuiViewport* viewport = ImGui::GetMainViewport(); const ImVec2 base_pos = viewport->Pos; + // By default, Windows are uniquely identified by their title. // You can use the "##" and "###" markers to manipulate the display/ID. + // Using "##" to display same title but have unique identifier. ImGui::SetNextWindowPos(ImVec2(base_pos.x + 100, base_pos.y + 100), ImGuiCond_FirstUseEver); ImGui::Begin("Same title as another window##1"); IMGUI_DEMO_MARKER("Examples/Manipulating window titles"); ImGui::Text("This is window 1.\nMy title is the same as window 2, but my identifier is unique."); ImGui::End(); + ImGui::SetNextWindowPos(ImVec2(base_pos.x + 100, base_pos.y + 200), ImGuiCond_FirstUseEver); ImGui::Begin("Same title as another window##2"); ImGui::Text("This is window 2.\nMy title is the same as window 1, but my identifier is unique."); ImGui::End(); + // Using "###" to display a changing title but keep a static identifier "AnimatedTitle" char buf[128]; sprintf(buf, "Animated title %c %d###AnimatedTitle", "|/-\\"[(int)(ImGui::GetTime() / 0.25f) & 3], ImGui::GetFrameCount()); @@ -8823,9 +9903,11 @@ static void ShowExampleAppWindowTitles(bool*) ImGui::Text("This window has a changing title."); ImGui::End(); } + //----------------------------------------------------------------------------- // [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() //----------------------------------------------------------------------------- + // Add a |_| looking shape static void PathConcaveShape(ImDrawList* draw_list, float x, float y, float sz) { @@ -8833,6 +9915,7 @@ static void PathConcaveShape(ImDrawList* draw_list, float x, float y, float sz) for (const ImVec2& p : pos_norms) draw_list->PathLineTo(ImVec2(x + 0.5f + (int)(sz * p.x), y + 0.5f + (int)(sz * p.y))); } + // Demonstrate using the low-level ImDrawList to draw custom shapes. static void ShowExampleAppCustomRendering(bool* p_open) { @@ -8842,16 +9925,19 @@ static void ShowExampleAppCustomRendering(bool* p_open) return; } IMGUI_DEMO_MARKER("Examples/Custom Rendering"); + // Tip: If you do a lot of custom rendering, you probably want to use your own geometrical types and benefit of // overloaded operators, etc. Define IM_VEC2_CLASS_EXTRA in imconfig.h to create implicit conversions between your // types and ImVec2/ImVec4. Dear ImGui defines overloaded operators but they are internal to imgui.cpp and not // exposed outside (to avoid messing with your types) In this example we are not using the maths operators! + if (ImGui::BeginTabBar("##TabBar")) { if (ImGui::BeginTabItem("Primitives")) { ImGui::PushItemWidth(-ImGui::GetFontSize() * 15); ImDrawList* draw_list = ImGui::GetWindowDrawList(); + // Draw gradients // (note that those are currently exacerbating our sRGB/Linear issues) // Calling ImGui::GetColorU32() multiplies the given colors by the current Style Alpha, but you may pass the IM_COL32() directly as well.. @@ -8873,6 +9959,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddRectFilledMultiColor(p0, p1, col_a, col_b, col_b, col_a); ImGui::InvisibleButton("##gradient2", gradient_size); } + // Draw a bunch of primitives ImGui::Text("All primitives"); static float sz = 36.0f; @@ -8893,6 +9980,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); curve_segments_override |= ImGui::SliderInt("Curves segments override", &curve_segments_override_v, 3, 40); ImGui::ColorEdit4("Color", &colf.x); + const ImVec2 p = ImGui::GetCursorScreenPos(); const ImU32 col = ImColor(colf); const float spacing = 10.0f; @@ -8902,6 +9990,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) const int curve_segments = curve_segments_override ? curve_segments_override_v : 0; const ImVec2 cp3[3] = { ImVec2(0.0f, sz * 0.6f), ImVec2(sz * 0.5f, -sz * 0.4f), ImVec2(sz, sz) }; // Control points for curves const ImVec2 cp4[4] = { ImVec2(0.0f, 0.0f), ImVec2(sz * 1.3f, sz * 0.3f), ImVec2(sz - sz * 1.3f, sz - sz * 0.3f), ImVec2(sz, sz) }; + float x = p.x + 4.0f; float y = p.y + 4.0f; for (int n = 0; n < 2; n++) @@ -8921,18 +10010,23 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) draw_list->AddLine(ImVec2(x, y), ImVec2(x, y + sz), col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th); x += sz + spacing; // Diagonal line + // Path draw_list->PathArcTo(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, 3.141592f, 3.141592f * -0.5f); draw_list->PathStroke(col, ImDrawFlags_None, th); x += sz + spacing; + // Quadratic Bezier Curve (3 control points) draw_list->AddBezierQuadratic(ImVec2(x + cp3[0].x, y + cp3[0].y), ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), col, th, curve_segments); x += sz + spacing; + // Cubic Bezier Curve (4 control points) draw_list->AddBezierCubic(ImVec2(x + cp4[0].x, y + cp4[0].y), ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), col, th, curve_segments); + x = p.x + 4; y += sz + spacing; } + // Filled shapes draw_list->AddNgonFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, ngon_sides); x += sz + spacing; // N-gon draw_list->AddCircleFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, circle_segments); x += sz + spacing; // Circle @@ -8946,21 +10040,26 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + thickness), col); x += sz + spacing; // Horizontal line (faster than AddLine, but only handle integer thickness) draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + thickness, y + sz), col); x += spacing * 2.0f;// Vertical line (faster than AddLine, but only handle integer thickness) draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + 1, y + 1), col); x += sz; // Pixel (faster than AddLine) + // Path draw_list->PathArcTo(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, 3.141592f * -0.5f, 3.141592f); draw_list->PathFillConvex(col); x += sz + spacing; + // Quadratic Bezier Curve (3 control points) draw_list->PathLineTo(ImVec2(x + cp3[0].x, y + cp3[0].y)); draw_list->PathBezierQuadraticCurveTo(ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), curve_segments); draw_list->PathFillConvex(col); x += sz + spacing; + draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x + sz, y + sz), IM_COL32(0, 0, 0, 255), IM_COL32(255, 0, 0, 255), IM_COL32(255, 255, 0, 255), IM_COL32(0, 255, 0, 255)); x += sz + spacing; + ImGui::Dummy(ImVec2((sz + spacing) * 13.2f, (sz + spacing) * 3.0f)); ImGui::PopItemWidth(); ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("Canvas")) { static ImVector points; @@ -8968,9 +10067,11 @@ static void ShowExampleAppCustomRendering(bool* p_open) static bool opt_enable_grid = true; static bool opt_enable_context_menu = true; static bool adding_line = false; + ImGui::Checkbox("Enable grid", &opt_enable_grid); ImGui::Checkbox("Enable context menu", &opt_enable_context_menu); ImGui::Text("Mouse Left: drag to add lines,\nMouse Right: drag to scroll, click for context menu."); + // Typically you would use a BeginChild()/EndChild() pair to benefit from a clipping region + own scrolling. // Here we demonstrate that this can be replaced by simple offsetting + custom drawing + PushClipRect/PopClipRect() calls. // To use a child window instead we could use, e.g: @@ -8981,23 +10082,27 @@ static void ShowExampleAppCustomRendering(bool* p_open) // ImGui::PopStyleVar(); // [...] // ImGui::EndChild(); + // Using InvisibleButton() as a convenience 1) it will advance the layout cursor and 2) allows us to use IsItemHovered()/IsItemActive() ImVec2 canvas_p0 = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates! ImVec2 canvas_sz = ImGui::GetContentRegionAvail(); // Resize canvas to what's available if (canvas_sz.x < 50.0f) canvas_sz.x = 50.0f; if (canvas_sz.y < 50.0f) canvas_sz.y = 50.0f; ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y); + // Draw border and background color ImGuiIO& io = ImGui::GetIO(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(50, 50, 50, 255)); draw_list->AddRect(canvas_p0, canvas_p1, IM_COL32(255, 255, 255, 255)); + // This will catch our interactions ImGui::InvisibleButton("canvas", canvas_sz, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight); const bool is_hovered = ImGui::IsItemHovered(); // Hovered const bool is_active = ImGui::IsItemActive(); // Held const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); // Lock scrolled origin const ImVec2 mouse_pos_in_canvas(io.MousePos.x - origin.x, io.MousePos.y - origin.y); + // Add first and second point if (is_hovered && !adding_line && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { @@ -9011,6 +10116,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) adding_line = false; } + // Pan (we use a zero mouse threshold when there's no context menu) // You may decide to make that threshold dynamic based on whether the mouse is hovering something etc. const float mouse_threshold_for_pan = opt_enable_context_menu ? -1.0f : 0.0f; @@ -9019,6 +10125,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) scrolling.x += io.MouseDelta.x; scrolling.y += io.MouseDelta.y; } + // Context menu (under default mouse threshold) ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right); if (opt_enable_context_menu && drag_delta.x == 0.0f && drag_delta.y == 0.0f) @@ -9032,6 +10139,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) if (ImGui::MenuItem("Remove all", NULL, false, points.Size > 0)) { points.clear(); } ImGui::EndPopup(); } + // Draw grid + all lines in the canvas draw_list->PushClipRect(canvas_p0, canvas_p1, true); if (opt_enable_grid) @@ -9045,8 +10153,10 @@ static void ShowExampleAppCustomRendering(bool* p_open) for (int n = 0; n < points.Size; n += 2) draw_list->AddLine(ImVec2(origin.x + points[n].x, origin.y + points[n].y), ImVec2(origin.x + points[n + 1].x, origin.y + points[n + 1].y), IM_COL32(255, 255, 0, 255), 2.0f); draw_list->PopClipRect(); + ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("BG/FG draw lists")) { static bool draw_bg = true; @@ -9064,6 +10174,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::GetForegroundDrawList()->AddCircle(window_center, window_size.y * 0.6f, IM_COL32(0, 255, 0, 200), 0, 10); ImGui::EndTabItem(); } + // Demonstrate out-of-order rendering via channels splitting // We use functions in ImDrawList as each draw list contains a convenience splitter, // but you can also instantiate your own ImDrawListSplitter if you need to nest them. @@ -9083,6 +10194,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::Text("Blue shape is drawn first, into channel 1: appears in front"); ImGui::Text("Red shape is drawn after, into channel 0: appears in back"); ImVec2 p1 = ImGui::GetCursorScreenPos(); + // Create 2 channels and draw a Blue shape THEN a Red shape. // You can create any number of channels. Tables API use 1 channel per column in order to better batch draw calls. draw_list->ChannelsSplit(2); @@ -9090,6 +10202,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddRectFilled(ImVec2(p1.x, p1.y), ImVec2(p1.x + 50, p1.y + 50), IM_COL32(0, 0, 255, 255)); // Blue draw_list->ChannelsSetCurrent(0); draw_list->AddRectFilled(ImVec2(p1.x + 25, p1.y + 25), ImVec2(p1.x + 75, p1.y + 75), IM_COL32(255, 0, 0, 255)); // Red + // Flatten/reorder channels. Red shape is in channel 0 and it appears below the Blue shape in channel 1. // This works by copying draw indices only (vertices are not copied). draw_list->ChannelsMerge(); @@ -9098,13 +10211,17 @@ static void ShowExampleAppCustomRendering(bool* p_open) } ImGui::EndTabItem(); } + ImGui::EndTabBar(); } + ImGui::End(); } + //----------------------------------------------------------------------------- // [SECTION] Example App: Docking, DockSpace / ShowExampleAppDockSpace() //----------------------------------------------------------------------------- + // Demonstrate using DockSpace() to create an explicit docking node within an existing window. // Note: You can use most Docking facilities without calling any API. You DO NOT need to call DockSpace() to use Docking! // - Drag from window title bar or their tab to dock/undock. Hold SHIFT to disable docking. @@ -9136,9 +10253,11 @@ void ShowExampleAppDockSpace(bool* p_open) // - (3) we expose many flags and need a way to have them visible. // - (4) we have a local menu bar in the host window (vs. you could use BeginMainMenuBar() + DockSpaceOverViewport() // in your code, but we don't here because we allow the window to be floating) + static bool opt_fullscreen = true; static bool opt_padding = false; static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None; + // We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into, // because it would be confusing to have two docking targets within each others. ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; @@ -9157,10 +10276,12 @@ void ShowExampleAppDockSpace(bool* p_open) { dockspace_flags &= ~ImGuiDockNodeFlags_PassthruCentralNode; } + // When using ImGuiDockNodeFlags_PassthruCentralNode, DockSpace() will render our background // and handle the pass-thru hole, so we ask Begin() to not render a background. if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) window_flags |= ImGuiWindowFlags_NoBackground; + // Important: note that we proceed even if Begin() returns false (aka window is collapsed). // This is because we want to keep our DockSpace() active. If a DockSpace() is inactive, // all active windows docked into it will lose their parent and become undocked. @@ -9171,8 +10292,10 @@ void ShowExampleAppDockSpace(bool* p_open) ImGui::Begin("DockSpace Demo", p_open, window_flags); if (!opt_padding) ImGui::PopStyleVar(); + if (opt_fullscreen) ImGui::PopStyleVar(2); + // Submit the DockSpace ImGuiIO& io = ImGui::GetIO(); if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) @@ -9184,6 +10307,7 @@ void ShowExampleAppDockSpace(bool* p_open) { ShowDockingDisabledMessage(); } + if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("Options")) @@ -9193,6 +10317,7 @@ void ShowExampleAppDockSpace(bool* p_open) ImGui::MenuItem("Fullscreen", NULL, &opt_fullscreen); ImGui::MenuItem("Padding", NULL, &opt_padding); ImGui::Separator(); + if (ImGui::MenuItem("Flag: NoDockingOverCentralNode", "", (dockspace_flags & ImGuiDockNodeFlags_NoDockingOverCentralNode) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoDockingOverCentralNode; } if (ImGui::MenuItem("Flag: NoDockingSplit", "", (dockspace_flags & ImGuiDockNodeFlags_NoDockingSplit) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoDockingSplit; } if (ImGui::MenuItem("Flag: NoUndocking", "", (dockspace_flags & ImGuiDockNodeFlags_NoUndocking) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_NoUndocking; } @@ -9200,6 +10325,7 @@ void ShowExampleAppDockSpace(bool* p_open) if (ImGui::MenuItem("Flag: AutoHideTabBar", "", (dockspace_flags & ImGuiDockNodeFlags_AutoHideTabBar) != 0)) { dockspace_flags ^= ImGuiDockNodeFlags_AutoHideTabBar; } if (ImGui::MenuItem("Flag: PassthruCentralNode", "", (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0, opt_fullscreen)) { dockspace_flags ^= ImGuiDockNodeFlags_PassthruCentralNode; } ImGui::Separator(); + if (ImGui::MenuItem("Close", NULL, false, p_open != NULL)) *p_open = false; ImGui::EndMenu(); @@ -9213,13 +10339,17 @@ void ShowExampleAppDockSpace(bool* p_open) "This demo app has nothing to do with enabling docking!" "\n\n" "This demo app only demonstrate the use of ImGui::DockSpace() which allows you to manually create a docking node _within_ another window." "\n\n" "Read comments in ShowExampleAppDockSpace() for more details."); + ImGui::EndMenuBar(); } + ImGui::End(); } + //----------------------------------------------------------------------------- // [SECTION] Example App: Documents Handling / ShowExampleAppDocuments() //----------------------------------------------------------------------------- + // Simplified structure to mimic a Document model struct MyDocument { @@ -9229,6 +10359,7 @@ struct MyDocument bool OpenPrev; // Copy of Open from last update. bool Dirty; // Set when the document has been modified ImVec4 Color; // An arbitrary variable associated to the document + MyDocument(int uid, const char* name, bool open = true, const ImVec4& color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f)) { UID = uid; @@ -9241,12 +10372,14 @@ struct MyDocument void DoForceClose() { Open = false; Dirty = false; } void DoSave() { Dirty = false; } }; + struct ExampleAppDocuments { ImVector Documents; ImVector CloseQueue; MyDocument* RenamingDoc = NULL; bool RenamingStarted = false; + ExampleAppDocuments() { Documents.push_back(MyDocument(0, "Lettuce", true, ImVec4(0.4f, 0.8f, 0.4f, 1.0f))); @@ -9256,11 +10389,13 @@ struct ExampleAppDocuments Documents.push_back(MyDocument(4, "A Rather Long Title", false, ImVec4(0.4f, 0.8f, 0.8f, 1.0f))); Documents.push_back(MyDocument(5, "Some Document", false, ImVec4(0.8f, 0.8f, 1.0f, 1.0f))); } + // As we allow to change document name, we append a never-changing document ID so tabs are stable void GetTabName(MyDocument* doc, char* out_buf, size_t out_buf_size) { snprintf(out_buf, out_buf_size, "%s###doc%d", doc->Name, doc->UID); } + // Display placeholder contents for the Document void DisplayDocContents(MyDocument* doc) { @@ -9269,6 +10404,7 @@ struct ExampleAppDocuments ImGui::PushStyleColor(ImGuiCol_Text, doc->Color); ImGui::TextWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); ImGui::PopStyleColor(); + ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_R, ImGuiInputFlags_Tooltip); if (ImGui::Button("Rename..")) { @@ -9276,13 +10412,16 @@ struct ExampleAppDocuments RenamingStarted = true; } ImGui::SameLine(); + ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_M, ImGuiInputFlags_Tooltip); if (ImGui::Button("Modify")) doc->Dirty = true; + ImGui::SameLine(); ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_S, ImGuiInputFlags_Tooltip); if (ImGui::Button("Save")) doc->DoSave(); + ImGui::SameLine(); ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_W, ImGuiInputFlags_Tooltip); if (ImGui::Button("Close")) @@ -9290,11 +10429,13 @@ struct ExampleAppDocuments ImGui::ColorEdit3("color", &doc->Color.x); // Useful to test drag and drop and hold-dragged-to-open-tab behavior. ImGui::PopID(); } + // Display context menu for the Document void DisplayDocContextMenu(MyDocument* doc) { if (!ImGui::BeginPopupContextItem()) return; + char buf[256]; sprintf(buf, "Save %s", doc->Name); if (ImGui::MenuItem(buf, "Ctrl+S", false, doc->Open)) @@ -9305,6 +10446,7 @@ struct ExampleAppDocuments CloseQueue.push_back(doc); ImGui::EndPopup(); } + // [Optional] Notify the system of Tabs/Windows closure that happened outside the regular tab interface. // If a tab has been closed programmatically (aka closed from another source such as the Checkbox() in the demo, // as opposed to clicking on the regular tab closing button) and stops being submitted, it will take a frame for @@ -9323,9 +10465,11 @@ struct ExampleAppDocuments } } }; + void ShowExampleAppDocuments(bool* p_open) { static ExampleAppDocuments app; + // Options enum Target { @@ -9336,18 +10480,21 @@ void ShowExampleAppDocuments(bool* p_open) static Target opt_target = Target_Tab; static bool opt_reorderable = true; static ImGuiTabBarFlags opt_fitting_flags = ImGuiTabBarFlags_FittingPolicyDefault_; + // When (opt_target == Target_DockSpaceAndWindow) there is the possibily that one of our child Document window (e.g. "Eggplant") // that we emit gets docked into the same spot as the parent window ("Example: Documents"). // This would create a problematic feedback loop because selecting the "Eggplant" tab would make the "Example: Documents" tab // not visible, which in turn would stop submitting the "Eggplant" window. // We avoid this problem by submitting our documents window even if our parent window is not currently visible. // Another solution may be to make the "Example: Documents" window use the ImGuiWindowFlags_NoDocking. + bool window_contents_visible = ImGui::Begin("Example: Documents", p_open, ImGuiWindowFlags_MenuBar); if (!window_contents_visible && opt_target != Target_DockSpaceAndWindow) { ImGui::End(); return; } + // Menu if (ImGui::BeginMenuBar()) { @@ -9356,6 +10503,7 @@ void ShowExampleAppDocuments(bool* p_open) int open_count = 0; for (MyDocument& doc : app.Documents) open_count += doc.Open ? 1 : 0; + if (ImGui::BeginMenu("Open", open_count < app.Documents.Size)) { for (MyDocument& doc : app.Documents) @@ -9372,6 +10520,7 @@ void ShowExampleAppDocuments(bool* p_open) } ImGui::EndMenuBar(); } + // [Debug] List documents with one checkbox for each for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) { @@ -9390,7 +10539,9 @@ void ShowExampleAppDocuments(bool* p_open) bool redock_all = false; if (opt_target == Target_Tab) { ImGui::SameLine(); ImGui::Checkbox("Reorderable Tabs", &opt_reorderable); } if (opt_target == Target_DockSpaceAndWindow) { ImGui::SameLine(); redock_all = ImGui::Button("Redock all"); } + ImGui::Separator(); + // About the ImGuiWindowFlags_UnsavedDocument / ImGuiTabItemFlags_UnsavedDocument flags. // They have multiple effects: // - Display a dot next to the title. @@ -9400,6 +10551,7 @@ void ShowExampleAppDocuments(bool* p_open) // We need to assume closure by default otherwise waiting for "lack of submission" on the next frame would leave an empty // hole for one-frame, both in the tab-bar and in tab-contents when closing a tab/window. // The rarely used SetTabItemClosed() function is a way to notify of programmatic closure to avoid the one-frame hole. + // Tabs if (opt_target == Target_Tab) { @@ -9409,25 +10561,30 @@ void ShowExampleAppDocuments(bool* p_open) { if (opt_reorderable) app.NotifyOfDocumentsClosedElsewhere(); + // [DEBUG] Stress tests //if ((ImGui::GetFrameCount() % 30) == 0) docs[1].Open ^= 1; // [DEBUG] Automatically show/hide a tab. Test various interactions e.g. dragging with this on. //if (ImGui::GetIO().KeyCtrl) ImGui::SetTabItemSelected(docs[1].Name); // [DEBUG] Test SetTabItemSelected(), probably not very useful as-is anyway.. + // Submit Tabs for (MyDocument& doc : app.Documents) { if (!doc.Open) continue; + // As we allow to change document name, we append a never-changing document id so tabs are stable char doc_name_buf[64]; app.GetTabName(&doc, doc_name_buf, sizeof(doc_name_buf)); ImGuiTabItemFlags tab_flags = (doc.Dirty ? ImGuiTabItemFlags_UnsavedDocument : 0); bool visible = ImGui::BeginTabItem(doc_name_buf, &doc.Open, tab_flags); + // Cancel attempt to close when unsaved add to save queue so we can display a popup. if (!doc.Open && doc.Dirty) { doc.Open = true; app.CloseQueue.push_back(&doc); } + app.DisplayDocContextMenu(&doc); if (visible) { @@ -9435,6 +10592,7 @@ void ShowExampleAppDocuments(bool* p_open) ImGui::EndTabItem(); } } + ImGui::EndTabBar(); } } @@ -9443,27 +10601,33 @@ void ShowExampleAppDocuments(bool* p_open) if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_DockingEnable) { app.NotifyOfDocumentsClosedElsewhere(); + // Create a DockSpace node where any window can be docked ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); ImGui::DockSpace(dockspace_id); + // Create Windows for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) { MyDocument* doc = &app.Documents[doc_n]; if (!doc->Open) continue; + ImGui::SetNextWindowDockID(dockspace_id, redock_all ? ImGuiCond_Always : ImGuiCond_FirstUseEver); ImGuiWindowFlags window_flags = (doc->Dirty ? ImGuiWindowFlags_UnsavedDocument : 0); bool visible = ImGui::Begin(doc->Name, &doc->Open, window_flags); + // Cancel attempt to close when unsaved add to save queue so we can display a popup. if (!doc->Open && doc->Dirty) { doc->Open = true; app.CloseQueue.push_back(doc); } + app.DisplayDocContextMenu(doc); if (visible) app.DisplayDocContents(doc); + ImGui::End(); } } @@ -9472,12 +10636,14 @@ void ShowExampleAppDocuments(bool* p_open) ShowDockingDisabledMessage(); } } + // Early out other contents if (!window_contents_visible) { ImGui::End(); return; } + // Display renaming UI if (app.RenamingDoc != NULL) { @@ -9501,6 +10667,7 @@ void ShowExampleAppDocuments(bool* p_open) } app.RenamingStarted = false; } + // Display closing confirmation UI if (!app.CloseQueue.empty()) { @@ -9508,6 +10675,7 @@ void ShowExampleAppDocuments(bool* p_open) for (int n = 0; n < app.CloseQueue.Size; n++) if (app.CloseQueue[n]->Dirty) close_queue_unsaved_documents++; + if (close_queue_unsaved_documents == 0) { // Close documents when all are unsaved @@ -9528,6 +10696,7 @@ void ShowExampleAppDocuments(bool* p_open) if (doc->Dirty) ImGui::Text("%s", doc->Name); ImGui::EndChild(); + ImVec2 button_size(ImGui::GetFontSize() * 7.0f, 0.0f); if (ImGui::Button("Yes", button_size)) { @@ -9558,18 +10727,25 @@ void ShowExampleAppDocuments(bool* p_open) } } } + ImGui::End(); } + //----------------------------------------------------------------------------- // [SECTION] Example App: Assets Browser / ShowExampleAppAssetsBrowser() //----------------------------------------------------------------------------- + //#include "imgui_internal.h" // NavMoveRequestTryWrapping() + struct ExampleAsset { ImGuiID ID; int Type; + ExampleAsset(ImGuiID id, int type) { ID = id; Type = type; } + static const ImGuiTableSortSpecs* s_current_sort_specs; + static void SortWithSortSpecs(ImGuiTableSortSpecs* sort_specs, ExampleAsset* items, int items_count) { s_current_sort_specs = sort_specs; // Store in variable accessible by the sort function. @@ -9577,6 +10753,7 @@ struct ExampleAsset qsort(items, (size_t)items_count, sizeof(items[0]), ExampleAsset::CompareWithSortSpecs); s_current_sort_specs = NULL; } + // Compare function to be used by qsort() static int IMGUI_CDECL CompareWithSortSpecs(const void* lhs, const void* rhs) { @@ -9599,6 +10776,7 @@ struct ExampleAsset } }; const ImGuiTableSortSpecs* ExampleAsset::s_current_sort_specs = NULL; + struct ExampleAssetsBrowser { // Options @@ -9610,6 +10788,7 @@ struct ExampleAssetsBrowser int IconSpacing = 10; int IconHitSpacing = 4; // Increase hit-spacing if you want to make it possible to clear or box-select from gaps. Some spacing is required to able to amend with Shift+box-select. Value is small in Explorer. bool StretchSpacing = true; + // State ImVector Items; // Our items ExampleSelectionWithDeletion Selection; // Our selection (ImGuiSelectionBasicStorage + helper funcs to handle deletion) @@ -9617,6 +10796,7 @@ struct ExampleAssetsBrowser bool RequestDelete = false; // Deferred deletion request bool RequestSort = false; // Deferred sort request float ZoomWheelAccum = 0.0f; // Mouse wheel accumulator to handle smooth wheels better + // Calculated sizes for layout, output of UpdateLayoutSizes(). Could be locals but our code is simpler this way. ImVec2 LayoutItemSize; ImVec2 LayoutItemStep; // == LayoutItemSize + LayoutItemSpacing @@ -9625,6 +10805,7 @@ struct ExampleAssetsBrowser float LayoutOuterPadding = 0.0f; int LayoutColumnCount = 0; int LayoutLineCount = 0; + // Functions ExampleAssetsBrowser() { @@ -9644,7 +10825,8 @@ struct ExampleAssetsBrowser Items.clear(); Selection.Clear(); } - // Logic would be written in the main code BeginChild() and outputing to local variables. + + // Logic would be written in the main code BeginChild() and outputting to local variables. // We extracted it into a function so we can call it easily from multiple places. void UpdateLayoutSizes(float avail_width) { @@ -9652,17 +10834,21 @@ struct ExampleAssetsBrowser LayoutItemSpacing = (float)IconSpacing; if (StretchSpacing == false) avail_width += floorf(LayoutItemSpacing * 0.5f); + // Layout: calculate number of icon per line and number of lines LayoutItemSize = ImVec2(floorf(IconSize), floorf(IconSize)); LayoutColumnCount = IM_MAX((int)(avail_width / (LayoutItemSize.x + LayoutItemSpacing)), 1); LayoutLineCount = (Items.Size + LayoutColumnCount - 1) / LayoutColumnCount; + // Layout: when stretching: allocate remaining space to more spacing. Round before division, so item_spacing may be non-integer. if (StretchSpacing && LayoutColumnCount > 1) LayoutItemSpacing = floorf(avail_width - LayoutItemSize.x * LayoutColumnCount) / LayoutColumnCount; + LayoutItemStep = ImVec2(LayoutItemSize.x + LayoutItemSpacing, LayoutItemSize.y + LayoutItemSpacing); LayoutSelectableSpacing = IM_MAX(floorf(LayoutItemSpacing) - IconHitSpacing, 0.0f); LayoutOuterPadding = floorf(LayoutItemSpacing * 0.5f); } + void Draw(const char* title, bool* p_open) { ImGui::SetNextWindowSize(ImVec2(IconSize * 25, IconSize * 15), ImGuiCond_FirstUseEver); @@ -9671,6 +10857,7 @@ struct ExampleAssetsBrowser ImGui::End(); return; } + // Menu bar if (ImGui::BeginMenuBar()) { @@ -9694,12 +10881,15 @@ struct ExampleAssetsBrowser if (ImGui::BeginMenu("Options")) { ImGui::PushItemWidth(ImGui::GetFontSize() * 10); + ImGui::SeparatorText("Contents"); ImGui::Checkbox("Show Type Overlay", &ShowTypeOverlay); ImGui::Checkbox("Allow Sorting", &AllowSorting); + ImGui::SeparatorText("Selection Behavior"); ImGui::Checkbox("Allow dragging unselected item", &AllowDragUnselected); ImGui::Checkbox("Allow box-selection", &AllowBoxSelect); + ImGui::SeparatorText("Layout"); ImGui::SliderFloat("Icon Size", &IconSize, 16.0f, 128.0f, "%.0f"); ImGui::SameLine(); HelpMarker("Use CTRL+Wheel to zoom"); @@ -9711,6 +10901,7 @@ struct ExampleAssetsBrowser } ImGui::EndMenuBar(); } + // Show a table with ONLY one header row to showcase the idea/possibility of using this to provide a sorting UI if (AllowSorting) { @@ -9731,49 +10922,62 @@ struct ExampleAssetsBrowser } ImGui::PopStyleVar(); } + ImGuiIO& io = ImGui::GetIO(); ImGui::SetNextWindowContentSize(ImVec2(0.0f, LayoutOuterPadding + LayoutLineCount * (LayoutItemSize.y + LayoutItemSpacing))); if (ImGui::BeginChild("Assets", ImVec2(0.0f, -ImGui::GetTextLineHeightWithSpacing()), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove)) { ImDrawList* draw_list = ImGui::GetWindowDrawList(); + const float avail_width = ImGui::GetContentRegionAvail().x; UpdateLayoutSizes(avail_width); + // Calculate and store start position. ImVec2 start_pos = ImGui::GetCursorScreenPos(); start_pos = ImVec2(start_pos.x + LayoutOuterPadding, start_pos.y + LayoutOuterPadding); ImGui::SetCursorScreenPos(start_pos); + // Multi-select ImGuiMultiSelectFlags ms_flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_ClearOnClickVoid; + // - Enable box-select (in 2D mode, so that changing box-select rectangle X1/X2 boundaries will affect clipped items) if (AllowBoxSelect) ms_flags |= ImGuiMultiSelectFlags_BoxSelect2d; + // - This feature allows dragging an unselected item without selecting it (rarely used) if (AllowDragUnselected) ms_flags |= ImGuiMultiSelectFlags_SelectOnClickRelease; + // - Enable keyboard wrapping on X axis // (FIXME-MULTISELECT: We haven't designed/exposed a general nav wrapping api yet, so this flag is provided as a courtesy to avoid doing: // ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX); // When we finish implementing a more general API for this, we will obsolete this flag in favor of the new system) ms_flags |= ImGuiMultiSelectFlags_NavWrapX; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ms_flags, Selection.Size, Items.Size); + // Use custom selection adapter: store ID in selection (recommended) Selection.UserData = this; Selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self_, int idx) { ExampleAssetsBrowser* self = (ExampleAssetsBrowser*)self_->UserData; return self->Items[idx].ID; }; Selection.ApplyRequests(ms_io); + const bool want_delete = (ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (Selection.Size > 0)) || RequestDelete; const int item_curr_idx_to_focus = want_delete ? Selection.ApplyDeletionPreLoop(ms_io, Items.Size) : -1; RequestDelete = false; + // Push LayoutSelectableSpacing (which is LayoutItemSpacing minus hit-spacing, if we decide to have hit gaps between items) // Altering style ItemSpacing may seem unnecessary as we position every items using SetCursorScreenPos()... // But it is necessary for two reasons: // - Selectables uses it by default to visually fill the space between two items. // - The vertical spacing would be measured by Clipper to calculate line height if we didn't provide it explicitly (here we do). ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(LayoutSelectableSpacing, LayoutSelectableSpacing)); + // Rendering parameters const ImU32 icon_type_overlay_colors[3] = { 0, IM_COL32(200, 70, 70, 255), IM_COL32(70, 170, 70, 255) }; const ImU32 icon_bg_color = ImGui::GetColorU32(IM_COL32(35, 35, 35, 220)); const ImVec2 icon_type_overlay_size = ImVec2(4.0f, 4.0f); const bool display_label = (LayoutItemSize.x >= ImGui::CalcTextSize("999").x); + const int column_count = LayoutColumnCount; ImGuiListClipper clipper; clipper.Begin(LayoutLineCount, LayoutItemStep.y); @@ -9791,20 +10995,25 @@ struct ExampleAssetsBrowser { ExampleAsset* item_data = &Items[item_idx]; ImGui::PushID((int)item_data->ID); + // Position item ImVec2 pos = ImVec2(start_pos.x + (item_idx % column_count) * LayoutItemStep.x, start_pos.y + line_idx * LayoutItemStep.y); ImGui::SetCursorScreenPos(pos); + ImGui::SetNextItemSelectionUserData(item_idx); bool item_is_selected = Selection.Contains((ImGuiID)item_data->ID); bool item_is_visible = ImGui::IsRectVisible(LayoutItemSize); ImGui::Selectable("", item_is_selected, ImGuiSelectableFlags_None, LayoutItemSize); + // Update our selection state immediately (without waiting for EndMultiSelect() requests) // because we use this to alter the color of our text/icon. if (ImGui::IsItemToggledSelection()) item_is_selected = !item_is_selected; + // Focus (for after deletion) if (item_curr_idx_to_focus == item_idx) ImGui::SetKeyboardFocusHere(-1); + // Drag and drop if (ImGui::BeginDragDropSource()) { @@ -9822,13 +11031,16 @@ struct ExampleAssetsBrowser payload_items.push_back(id); ImGui::SetDragDropPayload("ASSETS_BROWSER_ITEMS", payload_items.Data, (size_t)payload_items.size_in_bytes()); } + // Display payload content in tooltip, by extracting it from the payload data // (we could read from selection, but it is more correct and reusable to read from payload) const ImGuiPayload* payload = ImGui::GetDragDropPayload(); const int payload_count = (int)payload->DataSize / (int)sizeof(ImGuiID); ImGui::Text("%d assets", payload_count); + ImGui::EndDragDropSource(); } + // Render icon (a real app would likely display an image/thumbnail here) // Because we use ImGuiMultiSelectFlags_BoxSelect2d, clipping vertical may occasionally be larger, so we coarse-clip our rendering as well. if (item_is_visible) @@ -9849,12 +11061,14 @@ struct ExampleAssetsBrowser draw_list->AddText(ImVec2(box_min.x, box_max.y - ImGui::GetFontSize()), label_col, label); } } + ImGui::PopID(); } } } clipper.End(); ImGui::PopStyleVar(); // ImGuiStyleVar_ItemSpacing + // Context menu if (ImGui::BeginPopupContextWindow()) { @@ -9864,10 +11078,12 @@ struct ExampleAssetsBrowser RequestDelete = true; ImGui::EndPopup(); } + ms_io = ImGui::EndMultiSelect(); Selection.ApplyRequests(ms_io); if (want_delete) Selection.ApplyDeletionPostLoop(ms_io, Items, item_curr_idx_to_focus); + // Zooming with CTRL+Wheel if (ImGui::IsWindowAppearing()) ZoomWheelAccum = 0.0f; @@ -9882,11 +11098,13 @@ struct ExampleAssetsBrowser const float hovered_item_ny = (io.MousePos.y - start_pos.y + LayoutItemSpacing * 0.5f) / LayoutItemStep.y; const int hovered_item_idx = ((int)hovered_item_ny * LayoutColumnCount) + (int)hovered_item_nx; //ImGui::SetTooltip("%f,%f -> item %d", hovered_item_nx, hovered_item_ny, hovered_item_idx); // Move those 4 lines in block above for easy debugging + // Zoom IconSize *= powf(1.1f, (float)(int)ZoomWheelAccum); IconSize = IM_CLAMP(IconSize, 16.0f, 128.0f); ZoomWheelAccum -= (int)ZoomWheelAccum; UpdateLayoutSizes(avail_width); + // Manipulate scroll to that we will land at the same Y location of currently hovered item. // - Calculate next frame position of item under mouse // - Set new scroll position to be used in next ImGui::BeginChild() call. @@ -9898,23 +11116,28 @@ struct ExampleAssetsBrowser } } ImGui::EndChild(); + ImGui::Text("Selected: %d/%d items", Selection.Size, Items.Size); ImGui::End(); } }; + void ShowExampleAppAssetsBrowser(bool* p_open) { IMGUI_DEMO_MARKER("Examples/Assets Browser"); static ExampleAssetsBrowser assets_browser; assets_browser.Draw("Example: Assets Browser", p_open); } + // End of Demo code #else + void ImGui::ShowAboutWindow(bool*) {} void ImGui::ShowDemoWindow(bool*) {} void ImGui::ShowUserGuide() {} void ImGui::ShowStyleEditor(ImGuiStyle*) {} -bool ImGui::ShowStyleSelector(const char* label) { return false; } -void ImGui::ShowFontSelector(const char* label) {} -#endif +bool ImGui::ShowStyleSelector(const char*) { return false; } + +#endif // #ifndef IMGUI_DISABLE_DEMO_WINDOWS + #endif // #ifndef IMGUI_DISABLE diff --git a/external/reshade/deps/imgui/imgui_draw.cpp b/external/reshade/deps/imgui/imgui_draw.cpp index 4f824de..63f14a4 100644 --- a/external/reshade/deps/imgui/imgui_draw.cpp +++ b/external/reshade/deps/imgui/imgui_draw.cpp @@ -1,7 +1,10 @@ -// dear imgui, v1.91.9b +// dear imgui, v1.92.2b // (drawing and font code) + /* + Index of this file: + // [SECTION] STB libraries implementation // [SECTION] Style functions // [SECTION] ImDrawList @@ -10,27 +13,35 @@ Index of this file: // [SECTION] ImDrawData // [SECTION] Helpers ShadeVertsXXX functions // [SECTION] ImFontConfig -// [SECTION] ImFontAtlas +// [SECTION] ImFontAtlas, ImFontAtlasBuilder +// [SECTION] ImFontAtlas: backend for stb_truetype // [SECTION] ImFontAtlas: glyph ranges helpers // [SECTION] ImFontGlyphRangesBuilder // [SECTION] ImFont // [SECTION] ImGui Internal Render Helpers // [SECTION] Decompression code // [SECTION] Default font data (ProggyClean.ttf) + */ + #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif + #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif + #include "imgui.h" #ifndef IMGUI_DISABLE #include "imgui_internal.h" #ifdef IMGUI_ENABLE_FREETYPE #include "misc/freetype/imgui_freetype.h" #endif + #include // vsnprintf, sscanf, printf +#include // intptr_t + // Visual Studio warnings #ifdef _MSC_VER #pragma warning (disable: 4127) // condition expression is constant @@ -39,6 +50,7 @@ Index of this file: #pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). #pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). [MSVC Static Analyzer) #endif + // Clang/GCC warnings with -Weverything #if defined(__clang__) #if __has_warning("-Wunknown-warning-option") @@ -70,19 +82,23 @@ Index of this file: #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers #endif + //------------------------------------------------------------------------- // [SECTION] STB libraries implementation (for stb_truetype and stb_rect_pack) //------------------------------------------------------------------------- + // Compile time options: //#define IMGUI_STB_NAMESPACE ImStb //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION //#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION + #ifdef IMGUI_STB_NAMESPACE namespace IMGUI_STB_NAMESPACE { #endif + #ifdef _MSC_VER #pragma warning (push) #pragma warning (disable: 4456) // declaration of 'xx' hides previous local declaration @@ -90,17 +106,20 @@ namespace IMGUI_STB_NAMESPACE #pragma warning (disable: 6385) // (stb_truetype) Reading invalid data from 'buffer': the readable size is '_Old_3`kernel_width' bytes, but '3' bytes may be read. #pragma warning (disable: 28182) // (stb_rectpack) Dereferencing NULL pointer. 'cur' contains the same NULL value as 'cur->next' did. #endif + #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used #pragma clang diagnostic ignored "-Wmissing-prototypes" #pragma clang diagnostic ignored "-Wimplicit-fallthrough" #endif + #if defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" // warning: comparison is always true due to limited range of data type [-Wtype-limits] #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" // warning: this statement may fall through #endif + #ifndef STB_RECT_PACK_IMPLEMENTATION // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds) #ifndef IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION // in case the user already have an implementation in another compilation unit #define STBRP_STATIC @@ -114,6 +133,7 @@ namespace IMGUI_STB_NAMESPACE #include "imstb_rectpack.h" #endif #endif + #ifdef IMGUI_ENABLE_STB_TRUETYPE #ifndef STB_TRUETYPE_IMPLEMENTATION // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds) #ifndef IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION // in case the user already have an implementation in another compilation unit @@ -139,26 +159,33 @@ namespace IMGUI_STB_NAMESPACE #endif #endif #endif // IMGUI_ENABLE_STB_TRUETYPE + #if defined(__GNUC__) #pragma GCC diagnostic pop #endif + #if defined(__clang__) #pragma clang diagnostic pop #endif + #if defined(_MSC_VER) #pragma warning (pop) #endif + #ifdef IMGUI_STB_NAMESPACE } // namespace ImStb using namespace IMGUI_STB_NAMESPACE; #endif + //----------------------------------------------------------------------------- // [SECTION] Style functions //----------------------------------------------------------------------------- + void ImGui::StyleColorsDark(ImGuiStyle* dst) { ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); ImVec4* colors = style->Colors; + colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f); @@ -192,6 +219,7 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.20f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_InputTextCursor] = colors[ImGuiCol_Text]; colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); colors[ImGuiCol_TabSelected] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); @@ -212,16 +240,19 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f); colors[ImGuiCol_TextLink] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_TreeLines] = colors[ImGuiCol_Border]; colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); colors[ImGuiCol_NavCursor] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); } + void ImGui::StyleColorsClassic(ImGuiStyle* dst) { ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); ImVec4* colors = style->Colors; + colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.85f); @@ -255,6 +286,7 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst) colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.10f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.78f, 0.82f, 1.00f, 0.60f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.78f, 0.82f, 1.00f, 0.90f); + colors[ImGuiCol_InputTextCursor] = colors[ImGuiCol_Text]; colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); colors[ImGuiCol_TabSelected] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); @@ -275,17 +307,20 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst) colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f); colors[ImGuiCol_TextLink] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); + colors[ImGuiCol_TreeLines] = colors[ImGuiCol_Border]; colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); colors[ImGuiCol_NavCursor] = colors[ImGuiCol_HeaderHovered]; colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); } + // Those light colors are better suited with a thicker font than the default one + FrameBorder void ImGui::StyleColorsLight(ImGuiStyle* dst) { ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); ImVec4* colors = style->Colors; + colors[ImGuiCol_Text] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); colors[ImGuiCol_WindowBg] = ImVec4(0.94f, 0.94f, 0.94f, 1.00f); @@ -319,6 +354,7 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_ResizeGrip] = ImVec4(0.35f, 0.35f, 0.35f, 0.17f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_InputTextCursor] = colors[ImGuiCol_Text]; colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.90f); colors[ImGuiCol_TabSelected] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); @@ -339,15 +375,18 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_TableRowBgAlt] = ImVec4(0.30f, 0.30f, 0.30f, 0.09f); colors[ImGuiCol_TextLink] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_TreeLines] = colors[ImGuiCol_Border]; colors[ImGuiCol_DragDropTarget] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); colors[ImGuiCol_NavCursor] = colors[ImGuiCol_HeaderHovered]; colors[ImGuiCol_NavWindowingHighlight] = ImVec4(0.70f, 0.70f, 0.70f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.20f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); } + //----------------------------------------------------------------------------- // [SECTION] ImDrawList //----------------------------------------------------------------------------- + ImDrawListSharedData::ImDrawListSharedData() { memset(this, 0, sizeof(*this)); @@ -359,10 +398,17 @@ ImDrawListSharedData::ImDrawListSharedData() } ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); } + +ImDrawListSharedData::~ImDrawListSharedData() +{ + IM_ASSERT(DrawLists.Size == 0); +} + void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) { if (CircleSegmentMaxError == max_error) return; + IM_ASSERT(max_error > 0.0f); CircleSegmentMaxError = max_error; for (int i = 0; i < IM_ARRAYSIZE(CircleSegmentCounts); i++) @@ -372,25 +418,39 @@ void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) } ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); } + ImDrawList::ImDrawList(ImDrawListSharedData* shared_data) { memset(this, 0, sizeof(*this)); - _Data = shared_data; + _SetDrawListSharedData(shared_data); } + ImDrawList::~ImDrawList() { _ClearFreeMemory(); + _SetDrawListSharedData(NULL); } + +void ImDrawList::_SetDrawListSharedData(ImDrawListSharedData* data) +{ + if (_Data != NULL) + _Data->DrawLists.find_erase_unsorted(this); + _Data = data; + if (_Data != NULL) + _Data->DrawLists.push_back(this); +} + // Initialize before use in a new frame. We always have a command ready in the buffer. -// In the majority of cases, you would want to call PushClipRect() and PushTextureID() after this. +// In the majority of cases, you would want to call PushClipRect() and PushTexture() after this. void ImDrawList::_ResetForNewFrame() { // Verify that the ImDrawCmd fields we want to memcmp() are contiguous in memory. IM_STATIC_ASSERT(offsetof(ImDrawCmd, ClipRect) == 0); - IM_STATIC_ASSERT(offsetof(ImDrawCmd, TextureId) == sizeof(ImVec4)); - IM_STATIC_ASSERT(offsetof(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureID)); + IM_STATIC_ASSERT(offsetof(ImDrawCmd, TexRef) == sizeof(ImVec4)); + IM_STATIC_ASSERT(offsetof(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureRef)); if (_Splitter._Count > 1) _Splitter.Merge(this); + CmdBuffer.resize(0); IdxBuffer.resize(0); VtxBuffer.resize(0); @@ -400,13 +460,14 @@ void ImDrawList::_ResetForNewFrame() _VtxWritePtr = NULL; _IdxWritePtr = NULL; _ClipRectStack.resize(0); - _TextureIdStack.resize(0); + _TextureStack.resize(0); _CallbacksDataBuf.resize(0); _Path.resize(0); _Splitter.Clear(); CmdBuffer.push_back(ImDrawCmd()); _FringeScale = _Data->InitialFringeScale; } + void ImDrawList::_ClearFreeMemory() { CmdBuffer.clear(); @@ -417,11 +478,12 @@ void ImDrawList::_ClearFreeMemory() _VtxWritePtr = NULL; _IdxWritePtr = NULL; _ClipRectStack.clear(); - _TextureIdStack.clear(); + _TextureStack.clear(); _CallbacksDataBuf.clear(); _Path.clear(); _Splitter.ClearFreeMemory(); } + ImDrawList* ImDrawList::CloneOutput() const { ImDrawList* dst = IM_NEW(ImDrawList(_Data)); @@ -431,16 +493,19 @@ ImDrawList* ImDrawList::CloneOutput() const dst->Flags = Flags; return dst; } + void ImDrawList::AddDrawCmd() { ImDrawCmd draw_cmd; draw_cmd.ClipRect = _CmdHeader.ClipRect; // Same as calling ImDrawCmd_HeaderCopy() - draw_cmd.TextureId = _CmdHeader.TextureId; + draw_cmd.TexRef = _CmdHeader.TexRef; draw_cmd.VtxOffset = _CmdHeader.VtxOffset; draw_cmd.IdxOffset = IdxBuffer.Size; + IM_ASSERT(draw_cmd.ClipRect.x <= draw_cmd.ClipRect.z && draw_cmd.ClipRect.y <= draw_cmd.ClipRect.w); CmdBuffer.push_back(draw_cmd); } + // Pop trailing draw command (used before merging or presenting to user) // Note that this leaves the ImDrawList in a state unfit for further commands, as most code assume that CmdBuffer.Size > 0 && CmdBuffer.back().UserCallback == NULL void ImDrawList::_PopUnusedDrawCmd() @@ -453,6 +518,7 @@ void ImDrawList::_PopUnusedDrawCmd() CmdBuffer.pop_back(); } } + void ImDrawList::AddCallback(ImDrawCallback callback, void* userdata, size_t userdata_size) { IM_ASSERT_PARANOID(CmdBuffer.Size > 0); @@ -463,6 +529,7 @@ void ImDrawList::AddCallback(ImDrawCallback callback, void* userdata, size_t use AddDrawCmd(); curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; } + curr_cmd->UserCallback = callback; if (userdata_size == 0) { @@ -482,13 +549,16 @@ void ImDrawList::AddCallback(ImDrawCallback callback, void* userdata, size_t use _CallbacksDataBuf.resize(_CallbacksDataBuf.Size + (int)userdata_size); memcpy(_CallbacksDataBuf.Data + (size_t)curr_cmd->UserCallbackDataOffset, userdata, userdata_size); } + AddDrawCmd(); // Force a new command after us (see comment below) } -// Compare ClipRect, TextureId and VtxOffset with a single memcmp() + +// Compare ClipRect, TexRef and VtxOffset with a single memcmp() #define ImDrawCmd_HeaderSize (offsetof(ImDrawCmd, VtxOffset) + sizeof(unsigned int)) -#define ImDrawCmd_HeaderCompare(CMD_LHS, CMD_RHS) (memcmp(CMD_LHS, CMD_RHS, ImDrawCmd_HeaderSize)) // Compare ClipRect, TextureId, VtxOffset -#define ImDrawCmd_HeaderCopy(CMD_DST, CMD_SRC) (memcpy(CMD_DST, CMD_SRC, ImDrawCmd_HeaderSize)) // Copy ClipRect, TextureId, VtxOffset +#define ImDrawCmd_HeaderCompare(CMD_LHS, CMD_RHS) (memcmp(CMD_LHS, CMD_RHS, ImDrawCmd_HeaderSize)) // Compare ClipRect, TexRef, VtxOffset +#define ImDrawCmd_HeaderCopy(CMD_DST, CMD_SRC) (memcpy(CMD_DST, CMD_SRC, ImDrawCmd_HeaderSize)) // Copy ClipRect, TexRef, VtxOffset #define ImDrawCmd_AreSequentialIdxOffset(CMD_0, CMD_1) (CMD_0->IdxOffset + CMD_0->ElemCount == CMD_1->IdxOffset) + // Try to merge two last draw commands void ImDrawList::_TryMergeDrawCmds() { @@ -501,6 +571,7 @@ void ImDrawList::_TryMergeDrawCmds() CmdBuffer.pop_back(); } } + // Our scheme may appears a bit unusual, basically we want the most-common calls AddLine AddRect etc. to not have to perform any check so we always have a command ready in the stack. // The cost of figuring out if a new command has to be added or if we can merge is paid in those Update** functions only. void ImDrawList::_OnChangedClipRect() @@ -514,6 +585,7 @@ void ImDrawList::_OnChangedClipRect() return; } IM_ASSERT(curr_cmd->UserCallback == NULL); + // Try to merge with previous command if it matches, else use current command ImDrawCmd* prev_cmd = curr_cmd - 1; if (curr_cmd->ElemCount == 0 && CmdBuffer.Size > 1 && ImDrawCmd_HeaderCompare(&_CmdHeader, prev_cmd) == 0 && ImDrawCmd_AreSequentialIdxOffset(prev_cmd, curr_cmd) && prev_cmd->UserCallback == NULL) @@ -523,17 +595,22 @@ void ImDrawList::_OnChangedClipRect() } curr_cmd->ClipRect = _CmdHeader.ClipRect; } -void ImDrawList::_OnChangedTextureID() + +void ImDrawList::_OnChangedTexture() { // If current command is used with different settings we need to add a new command IM_ASSERT_PARANOID(CmdBuffer.Size > 0); ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; - if (curr_cmd->ElemCount != 0 && curr_cmd->TextureId != _CmdHeader.TextureId) + if (curr_cmd->ElemCount != 0 && curr_cmd->TexRef != _CmdHeader.TexRef) { AddDrawCmd(); return; } - IM_ASSERT(curr_cmd->UserCallback == NULL); + + // Unlike other _OnChangedXXX functions this may be called by ImFontAtlasUpdateDrawListsTextures() in more locations so we need to handle this case. + if (curr_cmd->UserCallback != NULL) + return; + // Try to merge with previous command if it matches, else use current command ImDrawCmd* prev_cmd = curr_cmd - 1; if (curr_cmd->ElemCount == 0 && CmdBuffer.Size > 1 && ImDrawCmd_HeaderCompare(&_CmdHeader, prev_cmd) == 0 && ImDrawCmd_AreSequentialIdxOffset(prev_cmd, curr_cmd) && prev_cmd->UserCallback == NULL) @@ -541,8 +618,9 @@ void ImDrawList::_OnChangedTextureID() CmdBuffer.pop_back(); return; } - curr_cmd->TextureId = _CmdHeader.TextureId; + curr_cmd->TexRef = _CmdHeader.TexRef; } + void ImDrawList::_OnChangedVtxOffset() { // We don't need to compare curr_cmd->VtxOffset != _CmdHeader.VtxOffset because we know it'll be different at the time we call this. @@ -558,6 +636,7 @@ void ImDrawList::_OnChangedVtxOffset() IM_ASSERT(curr_cmd->UserCallback == NULL); curr_cmd->VtxOffset = _CmdHeader.VtxOffset; } + int ImDrawList::_CalcCircleAutoSegmentCount(float radius) const { // Automatic segment count @@ -567,6 +646,7 @@ int ImDrawList::_CalcCircleAutoSegmentCount(float radius) const else return IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleSegmentMaxError); } + // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) void ImDrawList::PushClipRect(const ImVec2& cr_min, const ImVec2& cr_max, bool intersect_with_current_clip_rect) { @@ -581,40 +661,50 @@ void ImDrawList::PushClipRect(const ImVec2& cr_min, const ImVec2& cr_max, bool i } cr.z = ImMax(cr.x, cr.z); cr.w = ImMax(cr.y, cr.w); + _ClipRectStack.push_back(cr); _CmdHeader.ClipRect = cr; _OnChangedClipRect(); } + void ImDrawList::PushClipRectFullScreen() { PushClipRect(ImVec2(_Data->ClipRectFullscreen.x, _Data->ClipRectFullscreen.y), ImVec2(_Data->ClipRectFullscreen.z, _Data->ClipRectFullscreen.w)); } + void ImDrawList::PopClipRect() { _ClipRectStack.pop_back(); _CmdHeader.ClipRect = (_ClipRectStack.Size == 0) ? _Data->ClipRectFullscreen : _ClipRectStack.Data[_ClipRectStack.Size - 1]; _OnChangedClipRect(); } -void ImDrawList::PushTextureID(ImTextureID texture_id) + +void ImDrawList::PushTexture(ImTextureRef tex_ref) { - _TextureIdStack.push_back(texture_id); - _CmdHeader.TextureId = texture_id; - _OnChangedTextureID(); + _TextureStack.push_back(tex_ref); + _CmdHeader.TexRef = tex_ref; + if (tex_ref._TexData != NULL) + IM_ASSERT(tex_ref._TexData->WantDestroyNextFrame == false); + _OnChangedTexture(); } -void ImDrawList::PopTextureID() + +void ImDrawList::PopTexture() { - _TextureIdStack.pop_back(); - _CmdHeader.TextureId = (_TextureIdStack.Size == 0) ? (ImTextureID)NULL : _TextureIdStack.Data[_TextureIdStack.Size - 1]; - _OnChangedTextureID(); + _TextureStack.pop_back(); + _CmdHeader.TexRef = (_TextureStack.Size == 0) ? ImTextureRef() : _TextureStack.Data[_TextureStack.Size - 1]; + _OnChangedTexture(); } -// This is used by ImGui::PushFont()/PopFont(). It works because we never use _TextureIdStack[] elsewhere than in PushTextureID()/PopTextureID(). -void ImDrawList::_SetTextureID(ImTextureID texture_id) + +// This is used by ImGui::PushFont()/PopFont(). It works because we never use _TextureIdStack[] elsewhere than in PushTexture()/PopTexture(). +void ImDrawList::_SetTexture(ImTextureRef tex_ref) { - if (_CmdHeader.TextureId == texture_id) + if (_CmdHeader.TexRef == tex_ref) return; - _CmdHeader.TextureId = texture_id; - _OnChangedTextureID(); + _CmdHeader.TexRef = tex_ref; + _TextureStack.back() = tex_ref; + _OnChangedTexture(); } + // Reserve space for a number of vertices and indices. // You must finish filling your reserved data before calling PrimReserve() again, as it may reallocate or // submit the intermediate results. PrimUnreserve() can be used to release unused allocations. @@ -630,24 +720,30 @@ void ImDrawList::PrimReserve(int idx_count, int vtx_count) _CmdHeader.VtxOffset = VtxBuffer.Size; _OnChangedVtxOffset(); } + ImDrawCmd* draw_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; draw_cmd->ElemCount += idx_count; + int vtx_buffer_old_size = VtxBuffer.Size; VtxBuffer.resize(vtx_buffer_old_size + vtx_count); _VtxWritePtr = VtxBuffer.Data + vtx_buffer_old_size; + int idx_buffer_old_size = IdxBuffer.Size; IdxBuffer.resize(idx_buffer_old_size + idx_count); _IdxWritePtr = IdxBuffer.Data + idx_buffer_old_size; } + // Release the number of reserved vertices/indices from the end of the last reservation made with PrimReserve(). void ImDrawList::PrimUnreserve(int idx_count, int vtx_count) { IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0); + ImDrawCmd* draw_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; draw_cmd->ElemCount -= idx_count; VtxBuffer.shrink(VtxBuffer.Size - vtx_count); IdxBuffer.shrink(IdxBuffer.Size - idx_count); } + // Fully unrolled with inline call to keep our debug builds decently fast. void ImDrawList::PrimRect(const ImVec2& a, const ImVec2& c, ImU32 col) { @@ -663,6 +759,7 @@ void ImDrawList::PrimRect(const ImVec2& a, const ImVec2& c, ImU32 col) _VtxCurrentIdx += 4; _IdxWritePtr += 6; } + void ImDrawList::PrimRectUV(const ImVec2& a, const ImVec2& c, const ImVec2& uv_a, const ImVec2& uv_c, ImU32 col) { ImVec2 b(c.x, a.y), d(a.x, c.y), uv_b(uv_c.x, uv_a.y), uv_d(uv_a.x, uv_c.y); @@ -677,6 +774,7 @@ void ImDrawList::PrimRectUV(const ImVec2& a, const ImVec2& c, const ImVec2& uv_a _VtxCurrentIdx += 4; _IdxWritePtr += 6; } + void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col) { ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; @@ -690,45 +788,55 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c _VtxCurrentIdx += 4; _IdxWritePtr += 6; } + // On AddPolyline() and AddConvexPolyFilled() we intentionally avoid using ImVec2 and superfluous function calls to optimize debug/non-inlined builds. // - Those macros expects l-values and need to be used as their own statement. // - Those macros are intentionally not surrounded by the 'do {} while (0)' idiom because even that translates to runtime with debug compilers. #define IM_NORMALIZE2F_OVER_ZERO(VX,VY) { float d2 = VX*VX + VY*VY; if (d2 > 0.0f) { float inv_len = ImRsqrt(d2); VX *= inv_len; VY *= inv_len; } } (void)0 #define IM_FIXNORMAL2F_MAX_INVLEN2 100.0f // 500.0f (see #4053, #3366) #define IM_FIXNORMAL2F(VX,VY) { float d2 = VX*VX + VY*VY; if (d2 > 0.000001f) { float inv_len2 = 1.0f / d2; if (inv_len2 > IM_FIXNORMAL2F_MAX_INVLEN2) inv_len2 = IM_FIXNORMAL2F_MAX_INVLEN2; VX *= inv_len2; VY *= inv_len2; } } (void)0 + // TODO: Thickness anti-aliased lines cap are missing their AA fringe. // We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds. void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, ImDrawFlags flags, float thickness) { if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) return; + const bool closed = (flags & ImDrawFlags_Closed) != 0; const ImVec2 opaque_uv = _Data->TexUvWhitePixel; const int count = closed ? points_count : points_count - 1; // The number of line segments we need to draw const bool thick_line = (thickness > _FringeScale); + if (Flags & ImDrawListFlags_AntiAliasedLines) { // Anti-aliased stroke const float AA_SIZE = _FringeScale; const ImU32 col_trans = col & ~IM_COL32_A_MASK; + // Thicknesses <1.0 should behave like thickness 1.0 thickness = ImMax(thickness, 1.0f); const int integer_thickness = (int)thickness; const float fractional_thickness = thickness - integer_thickness; + // Do we want to draw this line using a texture? // - For now, only draw integer-width lines using textures to avoid issues with the way scaling occurs, could be improved. // - If AA_SIZE is not 1.0f we cannot use the texture path. const bool use_texture = (Flags & ImDrawListFlags_AntiAliasedLinesUseTex) && (integer_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX) && (fractional_thickness <= 0.00001f) && (AA_SIZE == 1.0f); + // We should never hit this, because NewFrame() doesn't set ImDrawListFlags_AntiAliasedLinesUseTex unless ImFontAtlasFlags_NoBakedLines is off IM_ASSERT_PARANOID(!use_texture || !(_Data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedLines)); + const int idx_count = use_texture ? (count * 6) : (thick_line ? count * 18 : count * 12); const int vtx_count = use_texture ? (points_count * 2) : (thick_line ? points_count * 4 : points_count * 3); PrimReserve(idx_count, vtx_count); + // Temporary buffer // The first items are normals at each line point, then after that there are either 2 or 4 temp points for each line point _Data->TempBuffer.reserve_discard(points_count * ((use_texture || !thick_line) ? 3 : 5)); ImVec2* temp_normals = _Data->TempBuffer.Data; ImVec2* temp_points = temp_normals + points_count; + // Calculate normals (tangents) for each line segment for (int i1 = 0; i1 < count; i1++) { @@ -741,17 +849,20 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 } if (!closed) temp_normals[points_count - 1] = temp_normals[points_count - 2]; + // If we are drawing a one-pixel-wide line without a texture, or a textured line of any width, we only need 2 or 3 vertices per point if (use_texture || !thick_line) { // [PATH 1] Texture-based lines (thick or non-thick) // [PATH 2] Non texture-based lines (non-thick) + // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus "one pixel" for AA. // - In the texture-based path, we don't use AA_SIZE here because the +1 is tied to the generated texture // (see ImFontAtlasBuildRenderLinesTexData() function), and so alternate values won't work without changes to that code. // - In the non texture-based paths, we would allow AA_SIZE to potentially be != 1.0f with a patch (e.g. fringe_scale patch to // allow scaling geometry while preserving one-screen-pixel AA fringe). const float half_draw_size = use_texture ? ((thickness * 0.5f) + 1) : AA_SIZE; + // If line is not closed, the first and last points need to be generated differently as there are no normals to blend if (!closed) { @@ -760,6 +871,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 temp_points[(points_count-1)*2+0] = points[points_count-1] + temp_normals[points_count-1] * half_draw_size; temp_points[(points_count-1)*2+1] = points[points_count-1] - temp_normals[points_count-1] * half_draw_size; } + // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. @@ -768,18 +880,21 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 { const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1; // i2 is the second point of the line segment const unsigned int idx2 = ((i1 + 1) == points_count) ? _VtxCurrentIdx : (idx1 + (use_texture ? 2 : 3)); // Vertex index for end of segment + // Average normals float dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5f; float dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5f; IM_FIXNORMAL2F(dm_x, dm_y); dm_x *= half_draw_size; // dm_x, dm_y are offset to the outer edge of the AA area dm_y *= half_draw_size; - // Add temporary vertexes for the outer edges + + // Add temporary vertices for the outer edges ImVec2* out_vtx = &temp_points[i2 * 2]; out_vtx[0].x = points[i2].x + dm_x; out_vtx[0].y = points[i2].y + dm_y; out_vtx[1].x = points[i2].x - dm_x; out_vtx[1].y = points[i2].y - dm_y; + if (use_texture) { // Add indices for two triangles @@ -796,9 +911,11 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 _IdxWritePtr[9] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[10] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[11] = (ImDrawIdx)(idx2 + 1); // Left tri 2 _IdxWritePtr += 12; } + idx1 = idx2; } - // Add vertexes for each point on the line + + // Add vertices for each point on the line if (use_texture) { // If we're using textures we only need to emit the left/right edge vertices @@ -836,6 +953,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 { // [PATH 2] Non texture-based lines (thick): we need to draw the solid line core and thus require four vertices per point const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f; + // If line is not closed, the first and last points need to be generated differently as there are no normals to blend if (!closed) { @@ -849,6 +967,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 temp_points[points_last * 4 + 2] = points[points_last] - temp_normals[points_last] * (half_inner_thickness); temp_points[points_last * 4 + 3] = points[points_last] - temp_normals[points_last] * (half_inner_thickness + AA_SIZE); } + // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. @@ -857,6 +976,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 { const int i2 = (i1 + 1) == points_count ? 0 : (i1 + 1); // i2 is the second point of the line segment const unsigned int idx2 = (i1 + 1) == points_count ? _VtxCurrentIdx : (idx1 + 4); // Vertex index for end of segment + // Average normals float dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5f; float dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5f; @@ -865,6 +985,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 float dm_out_y = dm_y * (half_inner_thickness + AA_SIZE); float dm_in_x = dm_x * half_inner_thickness; float dm_in_y = dm_y * half_inner_thickness; + // Add temporary vertices ImVec2* out_vtx = &temp_points[i2 * 4]; out_vtx[0].x = points[i2].x + dm_out_x; @@ -875,6 +996,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 out_vtx[2].y = points[i2].y - dm_in_y; out_vtx[3].x = points[i2].x - dm_out_x; out_vtx[3].y = points[i2].y - dm_out_y; + // Add indexes _IdxWritePtr[0] = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[1] = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[2] = (ImDrawIdx)(idx1 + 2); _IdxWritePtr[3] = (ImDrawIdx)(idx1 + 2); _IdxWritePtr[4] = (ImDrawIdx)(idx2 + 2); _IdxWritePtr[5] = (ImDrawIdx)(idx2 + 1); @@ -883,8 +1005,10 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 _IdxWritePtr[12] = (ImDrawIdx)(idx2 + 2); _IdxWritePtr[13] = (ImDrawIdx)(idx1 + 2); _IdxWritePtr[14] = (ImDrawIdx)(idx1 + 3); _IdxWritePtr[15] = (ImDrawIdx)(idx1 + 3); _IdxWritePtr[16] = (ImDrawIdx)(idx2 + 3); _IdxWritePtr[17] = (ImDrawIdx)(idx2 + 2); _IdxWritePtr += 18; + idx1 = idx2; } + // Add vertices for (int i = 0; i < points_count; i++) { @@ -903,21 +1027,25 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const int idx_count = count * 6; const int vtx_count = count * 4; // FIXME-OPT: Not sharing edges PrimReserve(idx_count, vtx_count); + for (int i1 = 0; i1 < count; i1++) { const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1; const ImVec2& p1 = points[i1]; const ImVec2& p2 = points[i2]; + float dx = p2.x - p1.x; float dy = p2.y - p1.y; IM_NORMALIZE2F_OVER_ZERO(dx, dy); dx *= (thickness * 0.5f); dy *= (thickness * 0.5f); + _VtxWritePtr[0].pos.x = p1.x + dy; _VtxWritePtr[0].pos.y = p1.y - dx; _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = col; _VtxWritePtr[1].pos.x = p2.x + dy; _VtxWritePtr[1].pos.y = p2.y - dx; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = col; _VtxWritePtr[2].pos.x = p2.x - dy; _VtxWritePtr[2].pos.y = p2.y + dx; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = col; _VtxWritePtr[3].pos.x = p1.x - dy; _VtxWritePtr[3].pos.y = p1.y + dx; _VtxWritePtr[3].uv = opaque_uv; _VtxWritePtr[3].col = col; _VtxWritePtr += 4; + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + 1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + 2); _IdxWritePtr[3] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[4] = (ImDrawIdx)(_VtxCurrentIdx + 2); _IdxWritePtr[5] = (ImDrawIdx)(_VtxCurrentIdx + 3); _IdxWritePtr += 6; @@ -925,13 +1053,16 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 } } } + // - We intentionally avoid using ImVec2 and its math operators here to reduce cost to a minimum for debug/non-inlined builds. // - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col) { if (points_count < 3 || (col & IM_COL32_A_MASK) == 0) return; + const ImVec2 uv = _Data->TexUvWhitePixel; + if (Flags & ImDrawListFlags_AntiAliasedFill) { // Anti-aliased Fill @@ -940,6 +1071,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun const int idx_count = (points_count - 2)*3 + points_count * 6; const int vtx_count = (points_count * 2); PrimReserve(idx_count, vtx_count); + // Add indexes for fill unsigned int vtx_inner_idx = _VtxCurrentIdx; unsigned int vtx_outer_idx = _VtxCurrentIdx + 1; @@ -948,6 +1080,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + ((i - 1) << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + (i << 1)); _IdxWritePtr += 3; } + // Compute normals _Data->TempBuffer.reserve_discard(points_count); ImVec2* temp_normals = _Data->TempBuffer.Data; @@ -961,6 +1094,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun temp_normals[i0].x = dy; temp_normals[i0].y = -dx; } + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) { // Average normals @@ -971,10 +1105,12 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun IM_FIXNORMAL2F(dm_x, dm_y); dm_x *= AA_SIZE * 0.5f; dm_y *= AA_SIZE * 0.5f; + // Add vertices _VtxWritePtr[0].pos.x = (points[i1].x - dm_x); _VtxWritePtr[0].pos.y = (points[i1].y - dm_y); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner _VtxWritePtr[1].pos.x = (points[i1].x + dm_x); _VtxWritePtr[1].pos.y = (points[i1].y + dm_y); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer _VtxWritePtr += 2; + // Add indexes for fringes _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (i0 << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx + (i1 << 1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); @@ -1001,6 +1137,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun _VtxCurrentIdx += (ImDrawIdx)vtx_count; } } + void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step) { if (radius < 0.5f) @@ -1008,31 +1145,39 @@ void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_ _Path.push_back(center); return; } + // Calculate arc auto segment step size if (a_step <= 0) a_step = IM_DRAWLIST_ARCFAST_SAMPLE_MAX / _CalcCircleAutoSegmentCount(radius); + // Make sure we never do steps larger than one quarter of the circle a_step = ImClamp(a_step, 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); + const int sample_range = ImAbs(a_max_sample - a_min_sample); const int a_next_step = a_step; + int samples = sample_range + 1; bool extra_max_sample = false; if (a_step > 1) { samples = sample_range / a_step + 1; const int overstep = sample_range % a_step; + if (overstep > 0) { extra_max_sample = true; samples++; + // When we have overstep to avoid awkwardly looking one long line and one tiny one at the end, // distribute first step range evenly between them by reducing first step size. if (sample_range > 0) a_step -= (a_step - overstep) / 2; } } + _Path.resize(_Path.Size + samples); ImVec2* out_ptr = _Path.Data + (_Path.Size - samples); + int sample_index = a_min_sample; if (sample_index < 0 || sample_index >= IM_DRAWLIST_ARCFAST_SAMPLE_MAX) { @@ -1040,6 +1185,7 @@ void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_ if (sample_index < 0) sample_index += IM_DRAWLIST_ARCFAST_SAMPLE_MAX; } + if (a_max_sample >= a_min_sample) { for (int a = a_min_sample; a <= a_max_sample; a += a_step, sample_index += a_step, a_step = a_next_step) @@ -1047,6 +1193,7 @@ void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_ // a_step is clamped to IM_DRAWLIST_ARCFAST_SAMPLE_MAX, so we have guaranteed that it will not wrap over range twice or more if (sample_index >= IM_DRAWLIST_ARCFAST_SAMPLE_MAX) sample_index -= IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + const ImVec2 s = _Data->ArcFastVtx[sample_index]; out_ptr->x = center.x + s.x * radius; out_ptr->y = center.y + s.y * radius; @@ -1060,24 +1207,29 @@ void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_ // a_step is clamped to IM_DRAWLIST_ARCFAST_SAMPLE_MAX, so we have guaranteed that it will not wrap over range twice or more if (sample_index < 0) sample_index += IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + const ImVec2 s = _Data->ArcFastVtx[sample_index]; out_ptr->x = center.x + s.x * radius; out_ptr->y = center.y + s.y * radius; out_ptr++; } } + if (extra_max_sample) { int normalized_max_sample = a_max_sample % IM_DRAWLIST_ARCFAST_SAMPLE_MAX; if (normalized_max_sample < 0) normalized_max_sample += IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + const ImVec2 s = _Data->ArcFastVtx[normalized_max_sample]; out_ptr->x = center.x + s.x * radius; out_ptr->y = center.y + s.y * radius; out_ptr++; } + IM_ASSERT_PARANOID(_Path.Data + _Path.Size == out_ptr); } + void ImDrawList::_PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments) { if (radius < 0.5f) @@ -1085,6 +1237,7 @@ void ImDrawList::_PathArcToN(const ImVec2& center, float radius, float a_min, fl _Path.push_back(center); return; } + // Note that we are adding a point at both a_min and a_max. // If you are trying to draw a full closed circle you don't want the overlapping points! _Path.reserve(_Path.Size + (num_segments + 1)); @@ -1094,6 +1247,7 @@ void ImDrawList::_PathArcToN(const ImVec2& center, float radius, float a_min, fl _Path.push_back(ImVec2(center.x + ImCos(a) * radius, center.y + ImSin(a) * radius)); } } + // 0: East, 3: South, 6: West, 9: North, 12: East void ImDrawList::PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12) { @@ -1104,6 +1258,7 @@ void ImDrawList::PathArcToFast(const ImVec2& center, float radius, int a_min_of_ } _PathArcToFastEx(center, radius, a_min_of_12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, a_max_of_12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0); } + void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments) { if (radius < 0.5f) @@ -1111,26 +1266,32 @@ void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, floa _Path.push_back(center); return; } + if (num_segments > 0) { _PathArcToN(center, radius, a_min, a_max, num_segments); return; } + // Automatic segment count if (radius <= _Data->ArcFastRadiusCutoff) { const bool a_is_reverse = a_max < a_min; + // We are going to use precomputed values for mid samples. // Determine first and last sample in lookup table that belong to the arc. const float a_min_sample_f = IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_min / (IM_PI * 2.0f); const float a_max_sample_f = IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_max / (IM_PI * 2.0f); + const int a_min_sample = a_is_reverse ? (int)ImFloor(a_min_sample_f) : (int)ImCeil(a_min_sample_f); const int a_max_sample = a_is_reverse ? (int)ImCeil(a_max_sample_f) : (int)ImFloor(a_max_sample_f); const int a_mid_samples = a_is_reverse ? ImMax(a_min_sample - a_max_sample, 0) : ImMax(a_max_sample - a_min_sample, 0); + const float a_min_segment_angle = a_min_sample * IM_PI * 2.0f / IM_DRAWLIST_ARCFAST_SAMPLE_MAX; const float a_max_segment_angle = a_max_sample * IM_PI * 2.0f / IM_DRAWLIST_ARCFAST_SAMPLE_MAX; const bool a_emit_start = ImAbs(a_min_segment_angle - a_min) >= 1e-5f; const bool a_emit_end = ImAbs(a_max - a_max_segment_angle) >= 1e-5f; + _Path.reserve(_Path.Size + (a_mid_samples + 1 + (a_emit_start ? 1 : 0) + (a_emit_end ? 1 : 0))); if (a_emit_start) _Path.push_back(ImVec2(center.x + ImCos(a_min) * radius, center.y + ImSin(a_min) * radius)); @@ -1147,11 +1308,14 @@ void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, floa _PathArcToN(center, radius, a_min, a_max, arc_segment_count); } } + void ImDrawList::PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, float rot, float a_min, float a_max, int num_segments) { if (num_segments <= 0) num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. + _Path.reserve(_Path.Size + (num_segments + 1)); + const float cos_rot = ImCos(rot); const float sin_rot = ImSin(rot); for (int i = 0; i <= num_segments; i++) @@ -1164,6 +1328,7 @@ void ImDrawList::PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, _Path.push_back(point); } } + ImVec2 ImBezierCubicCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, float t) { float u = 1.0f - t; @@ -1173,6 +1338,7 @@ ImVec2 ImBezierCubicCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, c float w4 = t * t * t; return ImVec2(w1 * p1.x + w2 * p2.x + w3 * p3.x + w4 * p4.x, w1 * p1.y + w2 * p2.y + w3 * p3.y + w4 * p4.y); } + ImVec2 ImBezierQuadraticCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float t) { float u = 1.0f - t; @@ -1181,6 +1347,7 @@ ImVec2 ImBezierQuadraticCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p float w3 = t * t; return ImVec2(w1 * p1.x + w2 * p2.x + w3 * p3.x, w1 * p1.y + w2 * p2.y + w3 * p3.y); } + // Closely mimics ImBezierCubicClosestPointCasteljau() in imgui.cpp static void PathBezierCubicCurveToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level) { @@ -1206,6 +1373,7 @@ static void PathBezierCubicCurveToCasteljau(ImVector* path, float x1, fl PathBezierCubicCurveToCasteljau(path, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1); } } + static void PathBezierQuadraticCurveToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float tess_tol, int level) { float dx = x3 - x1, dy = y3 - y1; @@ -1223,6 +1391,7 @@ static void PathBezierQuadraticCurveToCasteljau(ImVector* path, float x1 PathBezierQuadraticCurveToCasteljau(path, x123, y123, x23, y23, x3, y3, tess_tol, level + 1); } } + void ImDrawList::PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments) { ImVec2 p1 = _Path.back(); @@ -1238,6 +1407,7 @@ void ImDrawList::PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, cons _Path.push_back(ImBezierCubicCalc(p1, p2, p3, p4, t_step * i_step)); } } + void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, int num_segments) { ImVec2 p1 = _Path.back(); @@ -1253,6 +1423,7 @@ void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, _Path.push_back(ImBezierQuadraticCalc(p1, p2, p3, t_step * i_step)); } } + static inline ImDrawFlags FixRectCornerFlags(ImDrawFlags flags) { /* @@ -1270,10 +1441,13 @@ static inline ImDrawFlags FixRectCornerFlags(ImDrawFlags flags) // Note that ImDrawFlags_Closed (== 0x01) is an invalid flag for AddRect(), AddRectFilled(), PathRect() etc. anyway. // See details in 1.82 Changelog as well as 2021/03/12 and 2023/09/08 entries in "API BREAKING CHANGES" section. IM_ASSERT((flags & 0x0F) == 0 && "Misuse of legacy hardcoded ImDrawCornerFlags values!"); + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) flags |= ImDrawFlags_RoundCornersAll; + return flags; } + void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDrawFlags flags) { if (rounding >= 0.5f) @@ -1301,6 +1475,7 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr PathArcToFast(ImVec2(a.x + rounding_bl, b.y - rounding_bl), rounding_bl, 3, 6); } } + void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness) { if ((col & IM_COL32_A_MASK) == 0) @@ -1309,6 +1484,7 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th PathLineTo(p2 + ImVec2(0.5f, 0.5f)); PathStroke(col, 0, thickness); } + // p_min = upper-left, p_max = lower-right // Note we don't render 1 pixels sized rectangles properly. void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags, float thickness) @@ -1321,6 +1497,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.49f, 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. PathStroke(col, ImDrawFlags_Closed, thickness); } + void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) @@ -1336,11 +1513,13 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c PathFillConvex(col); } } + // p_min = upper-left, p_max = lower-right void ImDrawList::AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left) { if (((col_upr_left | col_upr_right | col_bot_right | col_bot_left) & IM_COL32_A_MASK) == 0) return; + const ImVec2 uv = _Data->TexUvWhitePixel; PrimReserve(6, 4); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 1)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 2)); @@ -1350,48 +1529,58 @@ void ImDrawList::AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_ma PrimWriteVtx(p_max, uv, col_bot_right); PrimWriteVtx(ImVec2(p_min.x, p_max.y), uv, col_bot_left); } + void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness) { if ((col & IM_COL32_A_MASK) == 0) return; + PathLineTo(p1); PathLineTo(p2); PathLineTo(p3); PathLineTo(p4); PathStroke(col, ImDrawFlags_Closed, thickness); } + void ImDrawList::AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col) { if ((col & IM_COL32_A_MASK) == 0) return; + PathLineTo(p1); PathLineTo(p2); PathLineTo(p3); PathLineTo(p4); PathFillConvex(col); } + void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness) { if ((col & IM_COL32_A_MASK) == 0) return; + PathLineTo(p1); PathLineTo(p2); PathLineTo(p3); PathStroke(col, ImDrawFlags_Closed, thickness); } + void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col) { if ((col & IM_COL32_A_MASK) == 0) return; + PathLineTo(p1); PathLineTo(p2); PathLineTo(p3); PathFillConvex(col); } + void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness) { if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f) return; + if (num_segments <= 0) { // Use arc with automatic segment count @@ -1402,16 +1591,20 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu { // Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX); + // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1); } + PathStroke(col, ImDrawFlags_Closed, thickness); } + void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments) { if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f) return; + if (num_segments <= 0) { // Use arc with automatic segment count @@ -1422,87 +1615,106 @@ void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, { // Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX); + // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); } + PathFillConvex(col); } + // Guaranteed to honor 'num_segments' void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness) { if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2) return; + // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1); PathStroke(col, ImDrawFlags_Closed, thickness); } + // Guaranteed to honor 'num_segments' void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, int num_segments) { if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2) return; + // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); PathFillConvex(col); } + // Ellipse void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments, float thickness) { if ((col & IM_COL32_A_MASK) == 0) return; + if (num_segments <= 0) num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. + // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments; PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1); PathStroke(col, true, thickness); } + void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments) { if ((col & IM_COL32_A_MASK) == 0) return; + if (num_segments <= 0) num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. + // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments; PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1); PathFillConvex(col); } + // Cubic Bezier takes 4 controls points void ImDrawList::AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments) { if ((col & IM_COL32_A_MASK) == 0) return; + PathLineTo(p1); PathBezierCubicCurveTo(p2, p3, p4, num_segments); PathStroke(col, 0, thickness); } + // Quadratic Bezier takes 3 controls points void ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments) { if ((col & IM_COL32_A_MASK) == 0) return; + PathLineTo(p1); PathBezierQuadraticCurveTo(p2, p3, num_segments); PathStroke(col, 0, thickness); } + void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) { if ((col & IM_COL32_A_MASK) == 0) return; + // Accept null ranges if (text_begin == text_end || text_begin[0] == 0) return; // No need to strlen() here: font->RenderText() will do it and may early out. + // Pull default font/size from the shared ImDrawListSharedData instance if (font == NULL) font = _Data->Font; if (font_size == 0.0f) font_size = _Data->FontSize; - IM_ASSERT(font->ContainerAtlas->TexID == _CmdHeader.TextureId); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. + ImVec4 clip_rect = _CmdHeader.ClipRect; if (cpu_fine_clip_rect) { @@ -1513,55 +1725,70 @@ void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 } font->RenderText(this, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL); } + void ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end) { AddText(_Data->Font, _Data->FontSize, pos, col, text_begin, text_end); } -void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col) + +void ImDrawList::AddImage(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col) { if ((col & IM_COL32_A_MASK) == 0) return; - const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + + const bool push_texture_id = tex_ref != _CmdHeader.TexRef; if (push_texture_id) - PushTextureID(user_texture_id); + PushTexture(tex_ref); + PrimReserve(6, 4); PrimRectUV(p_min, p_max, uv_min, uv_max, col); + if (push_texture_id) - PopTextureID(); + PopTexture(); } -void ImDrawList::AddImageQuad(ImTextureID user_texture_id, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1, const ImVec2& uv2, const ImVec2& uv3, const ImVec2& uv4, ImU32 col) + +void ImDrawList::AddImageQuad(ImTextureRef tex_ref, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1, const ImVec2& uv2, const ImVec2& uv3, const ImVec2& uv4, ImU32 col) { if ((col & IM_COL32_A_MASK) == 0) return; - const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + + const bool push_texture_id = tex_ref != _CmdHeader.TexRef; if (push_texture_id) - PushTextureID(user_texture_id); + PushTexture(tex_ref); + PrimReserve(6, 4); PrimQuadUV(p1, p2, p3, p4, uv1, uv2, uv3, uv4, col); + if (push_texture_id) - PopTextureID(); + PopTexture(); } -void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags) + +void ImDrawList::AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; + flags = FixRectCornerFlags(flags); if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { - AddImage(user_texture_id, p_min, p_max, uv_min, uv_max, col); + AddImage(tex_ref, p_min, p_max, uv_min, uv_max, col); return; } - const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + + const bool push_texture_id = tex_ref != _CmdHeader.TexRef; if (push_texture_id) - PushTextureID(user_texture_id); + PushTexture(tex_ref); + int vert_start_idx = VtxBuffer.Size; PathRect(p_min, p_max, rounding, flags); PathFillConvex(col); int vert_end_idx = VtxBuffer.Size; ImGui::ShadeVertsLinearUV(this, vert_start_idx, vert_end_idx, p_min, p_max, uv_min, uv_max, true); + if (push_texture_id) - PopTextureID(); + PopTexture(); } + //----------------------------------------------------------------------------- // [SECTION] ImTriangulator, ImDrawList concave polygon fill //----------------------------------------------------------------------------- @@ -1572,12 +1799,14 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi // - ImTriangulator [Internal] // - AddConcavePolyFilled() //----------------------------------------------------------------------------- + enum ImTriangulatorNodeType { ImTriangulatorNodeType_Convex, ImTriangulatorNodeType_Ear, ImTriangulatorNodeType_Reflex }; + struct ImTriangulatorNode { ImTriangulatorNodeType Type; @@ -1585,21 +1814,27 @@ struct ImTriangulatorNode ImVec2 Pos; ImTriangulatorNode* Next; ImTriangulatorNode* Prev; + void Unlink() { Next->Prev = Prev; Prev->Next = Next; } }; + struct ImTriangulatorNodeSpan { ImTriangulatorNode** Data = NULL; int Size = 0; + void push_back(ImTriangulatorNode* node) { Data[Size++] = node; } void find_erase_unsorted(int idx) { for (int i = Size - 1; i >= 0; i--) if (Data[i]->Index == idx) { Data[i] = Data[Size - 1]; Size--; return; } } }; + struct ImTriangulator { static int EstimateTriangleCount(int points_count) { return (points_count < 3) ? 0 : points_count - 2; } static int EstimateScratchBufferSize(int points_count) { return sizeof(ImTriangulatorNode) * points_count + sizeof(ImTriangulatorNode*) * points_count * 2; } + void Init(const ImVec2* points, int points_count, void* scratch_buffer); void GetNextTriangle(unsigned int out_triangle[3]); // Return relative indexes for next triangle + // Internal functions void BuildNodes(const ImVec2* points, int points_count); void BuildReflexes(); @@ -1607,12 +1842,14 @@ struct ImTriangulator void FlipNodeList(); bool IsEar(int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const; void ReclassifyNode(ImTriangulatorNode* node); + // Internal members int _TrianglesLeft = 0; ImTriangulatorNode* _Nodes = NULL; ImTriangulatorNodeSpan _Ears; ImTriangulatorNodeSpan _Reflexes; }; + // Distribute storage for nodes, ears and reflexes. // FIXME-OPT: if everything is convex, we could report it to caller and let it switch to an convex renderer // (this would require first building reflexes to bail to convex if empty, without even building nodes) @@ -1627,6 +1864,7 @@ void ImTriangulator::Init(const ImVec2* points, int points_count, void* scratch_ BuildReflexes(); BuildEars(); } + void ImTriangulator::BuildNodes(const ImVec2* points, int points_count) { for (int i = 0; i < points_count; i++) @@ -1640,6 +1878,7 @@ void ImTriangulator::BuildNodes(const ImVec2* points, int points_count) _Nodes[0].Prev = _Nodes + points_count - 1; _Nodes[points_count - 1].Next = _Nodes; } + void ImTriangulator::BuildReflexes() { ImTriangulatorNode* n1 = _Nodes; @@ -1651,6 +1890,7 @@ void ImTriangulator::BuildReflexes() _Reflexes.push_back(n1); } } + void ImTriangulator::BuildEars() { ImTriangulatorNode* n1 = _Nodes; @@ -1664,17 +1904,20 @@ void ImTriangulator::BuildEars() _Ears.push_back(n1); } } + void ImTriangulator::GetNextTriangle(unsigned int out_triangle[3]) { if (_Ears.Size == 0) { FlipNodeList(); + ImTriangulatorNode* node = _Nodes; for (int i = _TrianglesLeft; i >= 0; i--, node = node->Next) node->Type = ImTriangulatorNodeType_Convex; _Reflexes.Size = 0; BuildReflexes(); BuildEars(); + // If we still don't have ears, it means geometry is degenerated. if (_Ears.Size == 0) { @@ -1684,17 +1927,21 @@ void ImTriangulator::GetNextTriangle(unsigned int out_triangle[3]) _Ears.Size = 1; } } + ImTriangulatorNode* ear = _Ears.Data[--_Ears.Size]; out_triangle[0] = ear->Prev->Index; out_triangle[1] = ear->Index; out_triangle[2] = ear->Next->Index; + ear->Unlink(); if (ear == _Nodes) _Nodes = ear->Next; + ReclassifyNode(ear->Prev); ReclassifyNode(ear->Next); _TrianglesLeft--; } + void ImTriangulator::FlipNodeList() { ImTriangulatorNode* prev = _Nodes; @@ -1705,15 +1952,18 @@ void ImTriangulator::FlipNodeList() while (current != _Nodes) { temp = current->Next; + current->Next = prev; prev->Prev = current; _Nodes->Next = current; current->Prev = _Nodes; + prev = current; current = temp; } _Nodes = prev; } + // A triangle is an ear is no other vertex is inside it. We can test reflexes vertices only (see reference algorithm) bool ImTriangulator::IsEar(int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const { @@ -1727,6 +1977,7 @@ bool ImTriangulator::IsEar(int i0, int i1, int i2, const ImVec2& v0, const ImVec } return true; } + void ImTriangulator::ReclassifyNode(ImTriangulatorNode* n1) { // Classify node @@ -1739,6 +1990,7 @@ void ImTriangulator::ReclassifyNode(ImTriangulatorNode* n1) type = ImTriangulatorNodeType_Ear; else type = ImTriangulatorNodeType_Convex; + // Update lists when a type changes if (type == n1->Type) return; @@ -1752,6 +2004,7 @@ void ImTriangulator::ReclassifyNode(ImTriangulatorNode* n1) _Ears.push_back(n1); n1->Type = type; } + // Use ear-clipping algorithm to triangulate a simple polygon (no self-interaction, no holes). // (Reminder: we don't perform any coarse clipping/culling in ImDrawList layer! // It is up to caller to ensure not making costly calls that will be outside of visible area. @@ -1761,6 +2014,7 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou { if (points_count < 3 || (col & IM_COL32_A_MASK) == 0) return; + const ImVec2 uv = _Data->TexUvWhitePixel; ImTriangulator triangulator; unsigned int triangle[3]; @@ -1772,9 +2026,11 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou const int idx_count = (points_count - 2) * 3 + points_count * 6; const int vtx_count = (points_count * 2); PrimReserve(idx_count, vtx_count); + // Add indexes for fill unsigned int vtx_inner_idx = _VtxCurrentIdx; unsigned int vtx_outer_idx = _VtxCurrentIdx + 1; + _Data->TempBuffer.reserve_discard((ImTriangulator::EstimateScratchBufferSize(points_count) + sizeof(ImVec2)) / sizeof(ImVec2)); triangulator.Init(points, points_count, _Data->TempBuffer.Data); while (triangulator._TrianglesLeft > 0) @@ -1783,6 +2039,7 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (triangle[0] << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (triangle[1] << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + (triangle[2] << 1)); _IdxWritePtr += 3; } + // Compute normals _Data->TempBuffer.reserve_discard(points_count); ImVec2* temp_normals = _Data->TempBuffer.Data; @@ -1796,6 +2053,7 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou temp_normals[i0].x = dy; temp_normals[i0].y = -dx; } + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) { // Average normals @@ -1806,10 +2064,12 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou IM_FIXNORMAL2F(dm_x, dm_y); dm_x *= AA_SIZE * 0.5f; dm_y *= AA_SIZE * 0.5f; + // Add vertices _VtxWritePtr[0].pos.x = (points[i1].x - dm_x); _VtxWritePtr[0].pos.y = (points[i1].y - dm_y); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner _VtxWritePtr[1].pos.x = (points[i1].x + dm_x); _VtxWritePtr[1].pos.y = (points[i1].y + dm_y); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer _VtxWritePtr += 2; + // Add indexes for fringes _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (i0 << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx + (i1 << 1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); @@ -1839,11 +2099,13 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou _VtxCurrentIdx += (ImDrawIdx)vtx_count; } } + //----------------------------------------------------------------------------- // [SECTION] ImDrawListSplitter //----------------------------------------------------------------------------- // FIXME: This may be a little confusing, trying to be a little too low-level/optimal instead of just doing vector swap.. //----------------------------------------------------------------------------- + void ImDrawListSplitter::ClearFreeMemory() { for (int i = 0; i < _Channels.Size; i++) @@ -1857,6 +2119,7 @@ void ImDrawListSplitter::ClearFreeMemory() _Count = 1; _Channels.clear(); } + void ImDrawListSplitter::Split(ImDrawList* draw_list, int channels_count) { IM_UNUSED(draw_list); @@ -1868,6 +2131,7 @@ void ImDrawListSplitter::Split(ImDrawList* draw_list, int channels_count) _Channels.resize(channels_count); } _Count = channels_count; + // Channels[] (24/32 bytes each) hold storage that we'll swap with draw_list->_CmdBuffer/_IdxBuffer // The content of Channels[0] at this point doesn't matter. We clear it to make state tidy in a debugger but we don't strictly need to. // When we switch to the next channel, we'll copy draw_list->_CmdBuffer/_IdxBuffer into Channels[0] and then Channels[1] into draw_list->CmdBuffer/_IdxBuffer @@ -1885,13 +2149,16 @@ void ImDrawListSplitter::Split(ImDrawList* draw_list, int channels_count) } } } + void ImDrawListSplitter::Merge(ImDrawList* draw_list) { // Note that we never use or rely on _Channels.Size because it is merely a buffer that we never shrink back to 0 to keep all sub-buffers ready for use. if (_Count <= 1) return; + SetCurrentChannel(draw_list, 0); draw_list->_PopUnusedDrawCmd(); + // Calculate our final buffer sizes. Also fix the incorrect IdxOffset values in each command. int new_cmd_buffer_count = 0; int new_idx_buffer_count = 0; @@ -1902,6 +2169,7 @@ void ImDrawListSplitter::Merge(ImDrawList* draw_list) ImDrawChannel& ch = _Channels[i]; if (ch._CmdBuffer.Size > 0 && ch._CmdBuffer.back().ElemCount == 0 && ch._CmdBuffer.back().UserCallback == NULL) // Equivalent of PopUnusedDrawCmd() ch._CmdBuffer.pop_back(); + if (ch._CmdBuffer.Size > 0 && last_cmd != NULL) { // Do not include ImDrawCmd_AreSequentialIdxOffset() in the compare as we rebuild IdxOffset values ourselves. @@ -1927,6 +2195,7 @@ void ImDrawListSplitter::Merge(ImDrawList* draw_list) } draw_list->CmdBuffer.resize(draw_list->CmdBuffer.Size + new_cmd_buffer_count); draw_list->IdxBuffer.resize(draw_list->IdxBuffer.Size + new_idx_buffer_count); + // Write commands and indices in order (they are fairly small structures, we don't copy vertices only indices) ImDrawCmd* cmd_write = draw_list->CmdBuffer.Data + draw_list->CmdBuffer.Size - new_cmd_buffer_count; ImDrawIdx* idx_write = draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size - new_idx_buffer_count; @@ -1937,22 +2206,27 @@ void ImDrawListSplitter::Merge(ImDrawList* draw_list) if (int sz = ch._IdxBuffer.Size) { memcpy(idx_write, ch._IdxBuffer.Data, sz * sizeof(ImDrawIdx)); idx_write += sz; } } draw_list->_IdxWritePtr = idx_write; + // Ensure there's always a non-callback draw command trailing the command-buffer if (draw_list->CmdBuffer.Size == 0 || draw_list->CmdBuffer.back().UserCallback != NULL) draw_list->AddDrawCmd(); + // If current command is used with different settings we need to add a new command ImDrawCmd* curr_cmd = &draw_list->CmdBuffer.Data[draw_list->CmdBuffer.Size - 1]; if (curr_cmd->ElemCount == 0) - ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TextureId, VtxOffset + ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TexRef, VtxOffset else if (ImDrawCmd_HeaderCompare(curr_cmd, &draw_list->_CmdHeader) != 0) draw_list->AddDrawCmd(); + _Count = 1; } + void ImDrawListSplitter::SetCurrentChannel(ImDrawList* draw_list, int idx) { IM_ASSERT(idx >= 0 && idx < _Count); if (_Current == idx) return; + // Overwrite ImVector (12/16 bytes), four times. This is merely a silly optimization instead of doing .swap() memcpy(&_Channels.Data[_Current]._CmdBuffer, &draw_list->CmdBuffer, sizeof(draw_list->CmdBuffer)); memcpy(&_Channels.Data[_Current]._IdxBuffer, &draw_list->IdxBuffer, sizeof(draw_list->IdxBuffer)); @@ -1960,18 +2234,21 @@ void ImDrawListSplitter::SetCurrentChannel(ImDrawList* draw_list, int idx) memcpy(&draw_list->CmdBuffer, &_Channels.Data[idx]._CmdBuffer, sizeof(draw_list->CmdBuffer)); memcpy(&draw_list->IdxBuffer, &_Channels.Data[idx]._IdxBuffer, sizeof(draw_list->IdxBuffer)); draw_list->_IdxWritePtr = draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size; + // If current command is used with different settings we need to add a new command ImDrawCmd* curr_cmd = (draw_list->CmdBuffer.Size == 0) ? NULL : &draw_list->CmdBuffer.Data[draw_list->CmdBuffer.Size - 1]; if (curr_cmd == NULL) draw_list->AddDrawCmd(); else if (curr_cmd->ElemCount == 0) - ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TextureId, VtxOffset + ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TexRef, VtxOffset else if (ImDrawCmd_HeaderCompare(curr_cmd, &draw_list->_CmdHeader) != 0) draw_list->AddDrawCmd(); } + //----------------------------------------------------------------------------- // [SECTION] ImDrawData //----------------------------------------------------------------------------- + void ImDrawData::Clear() { Valid = false; @@ -1979,7 +2256,9 @@ void ImDrawData::Clear() CmdLists.resize(0); // The ImDrawList are NOT owned by ImDrawData but e.g. by ImGuiContext, so we don't clear them. DisplayPos = DisplaySize = FramebufferScale = ImVec2(0.0f, 0.0f); OwnerViewport = NULL; + Textures = NULL; } + // Important: 'out_list' is generally going to be draw_data->CmdLists, but may be another temporary list // as long at it is expected that the result will be later merged into draw_data->CmdLists[]. void ImGui::AddDrawListToDrawDataEx(ImDrawData* draw_data, ImVector* out_list, ImDrawList* draw_list) @@ -1988,12 +2267,14 @@ void ImGui::AddDrawListToDrawDataEx(ImDrawData* draw_data, ImVector return; if (draw_list->CmdBuffer.Size == 1 && draw_list->CmdBuffer[0].ElemCount == 0 && draw_list->CmdBuffer[0].UserCallback == NULL) return; + // Draw list sanity check. Detect mismatch between PrimReserve() calls and incrementing _VtxCurrentIdx, _VtxWritePtr etc. // May trigger for you if you are using PrimXXX functions incorrectly. IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size); IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size); if (!(draw_list->Flags & ImDrawListFlags_AllowVtxOffset)) IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size); + // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window) // If this assert triggers because you are drawing lots of stuff manually: // - First, make sure you are coarse clipping yourself and not trying to draw many things outside visible bounds. @@ -2011,41 +2292,45 @@ void ImGui::AddDrawListToDrawDataEx(ImDrawData* draw_data, ImVector // the 64K limit to split your draw commands in multiple draw lists. if (sizeof(ImDrawIdx) == 2) IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above"); + // Resolve callback data pointers if (draw_list->_CallbacksDataBuf.Size > 0) for (ImDrawCmd& cmd : draw_list->CmdBuffer) if (cmd.UserCallback != NULL && cmd.UserCallbackDataOffset != -1 && cmd.UserCallbackDataSize > 0) cmd.UserCallbackData = draw_list->_CallbacksDataBuf.Data + cmd.UserCallbackDataOffset; + // Add to output list + records state in ImDrawData out_list->push_back(draw_list); draw_data->CmdListsCount++; draw_data->TotalVtxCount += draw_list->VtxBuffer.Size; draw_data->TotalIdxCount += draw_list->IdxBuffer.Size; } + void ImDrawData::AddDrawList(ImDrawList* draw_list) { IM_ASSERT(CmdLists.Size == CmdListsCount); draw_list->_PopUnusedDrawCmd(); ImGui::AddDrawListToDrawDataEx(this, &CmdLists, draw_list); } + // For backward compatibility: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! void ImDrawData::DeIndexAllBuffers() { ImVector new_vtx_buffer; TotalVtxCount = TotalIdxCount = 0; - for (int i = 0; i < CmdListsCount; i++) + for (ImDrawList* draw_list : CmdLists) { - ImDrawList* cmd_list = CmdLists[i]; - if (cmd_list->IdxBuffer.empty()) + if (draw_list->IdxBuffer.empty()) continue; - new_vtx_buffer.resize(cmd_list->IdxBuffer.Size); - for (int j = 0; j < cmd_list->IdxBuffer.Size; j++) - new_vtx_buffer[j] = cmd_list->VtxBuffer[cmd_list->IdxBuffer[j]]; - cmd_list->VtxBuffer.swap(new_vtx_buffer); - cmd_list->IdxBuffer.resize(0); - TotalVtxCount += cmd_list->VtxBuffer.Size; + new_vtx_buffer.resize(draw_list->IdxBuffer.Size); + for (int j = 0; j < draw_list->IdxBuffer.Size; j++) + new_vtx_buffer[j] = draw_list->VtxBuffer[draw_list->IdxBuffer[j]]; + draw_list->VtxBuffer.swap(new_vtx_buffer); + draw_list->IdxBuffer.resize(0); + TotalVtxCount += draw_list->VtxBuffer.Size; } } + // Helper to scale the ClipRect field of each ImDrawCmd. // Use if your final output buffer is at a different scale than draw_data->DisplaySize, // or if there is a difference between your window resolution and framebuffer resolution. @@ -2055,9 +2340,11 @@ void ImDrawData::ScaleClipRects(const ImVec2& fb_scale) for (ImDrawCmd& cmd : draw_list->CmdBuffer) cmd.ClipRect = ImVec4(cmd.ClipRect.x * fb_scale.x, cmd.ClipRect.y * fb_scale.y, cmd.ClipRect.z * fb_scale.x, cmd.ClipRect.w * fb_scale.y); } + //----------------------------------------------------------------------------- // [SECTION] Helpers ShadeVertsXXX functions //----------------------------------------------------------------------------- + // Generic linear color gradient, write to RGB fields, leave A untouched. void ImGui::ShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1) { @@ -2081,6 +2368,7 @@ void ImGui::ShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list, int ve vert->col = (r << IM_COL32_R_SHIFT) | (g << IM_COL32_G_SHIFT) | (b << IM_COL32_B_SHIFT) | (vert->col & IM_COL32_A_MASK); } } + // Distribute UV over (a, b) rectangle void ImGui::ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp) { @@ -2089,6 +2377,7 @@ void ImGui::ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int ve const ImVec2 scale = ImVec2( size.x != 0.0f ? (uv_size.x / size.x) : 0.0f, size.y != 0.0f ? (uv_size.y / size.y) : 0.0f); + ImDrawVert* vert_start = draw_list->VtxBuffer.Data + vert_start_idx; ImDrawVert* vert_end = draw_list->VtxBuffer.Data + vert_end_idx; if (clamp) @@ -2104,6 +2393,7 @@ void ImGui::ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int ve vertex->uv = uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale); } } + void ImGui::ShadeVertsTransformPos(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& pivot_in, float cos_a, float sin_a, const ImVec2& pivot_out) { ImDrawVert* vert_start = draw_list->VtxBuffer.Data + vert_start_idx; @@ -2111,9 +2401,12 @@ void ImGui::ShadeVertsTransformPos(ImDrawList* draw_list, int vert_start_idx, in for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex) vertex->pos = ImRotate(vertex->pos- pivot_in, cos_a, sin_a) + pivot_out; } + //----------------------------------------------------------------------------- // [SECTION] ImFontConfig //----------------------------------------------------------------------------- + +// FIXME-NEWATLAS: Oversample specification could be more dynamic. For now, favoring automatic selection. ImFontConfig::ImFontConfig() { memset(this, 0, sizeof(*this)); @@ -2125,41 +2418,161 @@ ImFontConfig::ImFontConfig() RasterizerDensity = 1.0f; EllipsisChar = 0; } + //----------------------------------------------------------------------------- -// [SECTION] ImFontAtlas +// [SECTION] ImTextureData +//----------------------------------------------------------------------------- +// - ImTextureData::Create() +// - ImTextureData::DestroyPixels() +//----------------------------------------------------------------------------- + +int ImTextureDataGetFormatBytesPerPixel(ImTextureFormat format) +{ + switch (format) + { + case ImTextureFormat_Alpha8: return 1; + case ImTextureFormat_RGBA32: return 4; + } + IM_ASSERT(0); + return 0; +} + +const char* ImTextureDataGetStatusName(ImTextureStatus status) +{ + switch (status) + { + case ImTextureStatus_OK: return "OK"; + case ImTextureStatus_Destroyed: return "Destroyed"; + case ImTextureStatus_WantCreate: return "WantCreate"; + case ImTextureStatus_WantUpdates: return "WantUpdates"; + case ImTextureStatus_WantDestroy: return "WantDestroy"; + } + return "N/A"; +} + +const char* ImTextureDataGetFormatName(ImTextureFormat format) +{ + switch (format) + { + case ImTextureFormat_Alpha8: return "Alpha8"; + case ImTextureFormat_RGBA32: return "RGBA32"; + } + return "N/A"; +} + +void ImTextureData::Create(ImTextureFormat format, int w, int h) +{ + IM_ASSERT(Status == ImTextureStatus_Destroyed); + DestroyPixels(); + Format = format; + Status = ImTextureStatus_WantCreate; + Width = w; + Height = h; + BytesPerPixel = ImTextureDataGetFormatBytesPerPixel(format); + UseColors = false; + Pixels = (unsigned char*)IM_ALLOC(Width * Height * BytesPerPixel); + IM_ASSERT(Pixels != NULL); + memset(Pixels, 0, Width * Height * BytesPerPixel); + UsedRect.x = UsedRect.y = UsedRect.w = UsedRect.h = 0; + UpdateRect.x = UpdateRect.y = (unsigned short)~0; + UpdateRect.w = UpdateRect.h = 0; +} + +void ImTextureData::DestroyPixels() +{ + if (Pixels) + IM_FREE(Pixels); + Pixels = NULL; + UseColors = false; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImFontAtlas, ImFontAtlasBuilder //----------------------------------------------------------------------------- // - Default texture data encoded in ASCII +// - ImFontAtlas() +// - ImFontAtlas::Clear() +// - ImFontAtlas::CompactCache() // - ImFontAtlas::ClearInputData() // - ImFontAtlas::ClearTexData() // - ImFontAtlas::ClearFonts() -// - ImFontAtlas::Clear() -// - ImFontAtlas::GetTexDataAsAlpha8() -// - ImFontAtlas::GetTexDataAsRGBA32() +//----------------------------------------------------------------------------- +// - ImFontAtlasUpdateNewFrame() +// - ImFontAtlasTextureBlockConvert() +// - ImFontAtlasTextureBlockPostProcess() +// - ImFontAtlasTextureBlockPostProcessMultiply() +// - ImFontAtlasTextureBlockFill() +// - ImFontAtlasTextureBlockCopy() +// - ImFontAtlasTextureBlockQueueUpload() +//----------------------------------------------------------------------------- +// - ImFontAtlas::GetTexDataAsAlpha8() [legacy] +// - ImFontAtlas::GetTexDataAsRGBA32() [legacy] +// - ImFontAtlas::Build() [legacy] +//----------------------------------------------------------------------------- // - ImFontAtlas::AddFont() // - ImFontAtlas::AddFontDefault() // - ImFontAtlas::AddFontFromFileTTF() // - ImFontAtlas::AddFontFromMemoryTTF() // - ImFontAtlas::AddFontFromMemoryCompressedTTF() // - ImFontAtlas::AddFontFromMemoryCompressedBase85TTF() -// - ImFontAtlas::AddCustomRectRegular() -// - ImFontAtlas::AddCustomRectFontGlyph() -// - ImFontAtlas::CalcCustomRectUV() -// - ImFontAtlasGetMouseCursorTexData() -// - ImFontAtlas::Build() -// - ImFontAtlasBuildMultiplyCalcLookupTable() -// - ImFontAtlasBuildMultiplyRectAlpha8() -// - ImFontAtlasBuildWithStbTruetype() -// - ImFontAtlasGetBuilderForStbTruetype() -// - ImFontAtlasUpdateSourcesPointers() -// - ImFontAtlasBuildSetupFont() -// - ImFontAtlasBuildPackCustomRects() -// - ImFontAtlasBuildRender8bppRectFromString() -// - ImFontAtlasBuildRender32bppRectFromString() -// - ImFontAtlasBuildRenderDefaultTexData() -// - ImFontAtlasBuildRenderLinesTexData() -// - ImFontAtlasBuildInit() -// - ImFontAtlasBuildFinish() +// - ImFontAtlas::RemoveFont() +// - ImFontAtlasBuildNotifySetFont() //----------------------------------------------------------------------------- +// - ImFontAtlas::AddCustomRect() +// - ImFontAtlas::RemoveCustomRect() +// - ImFontAtlas::GetCustomRect() +// - ImFontAtlas::AddCustomRectFontGlyph() [legacy] +// - ImFontAtlas::AddCustomRectFontGlyphForSize() [legacy] +// - ImFontAtlasGetMouseCursorTexData() +//----------------------------------------------------------------------------- +// - ImFontAtlasBuildMain() +// - ImFontAtlasBuildSetupFontLoader() +// - ImFontAtlasBuildPreloadAllGlyphRanges() +// - ImFontAtlasBuildUpdatePointers() +// - ImFontAtlasBuildRenderBitmapFromString() +// - ImFontAtlasBuildUpdateBasicTexData() +// - ImFontAtlasBuildUpdateLinesTexData() +// - ImFontAtlasBuildAddFont() +// - ImFontAtlasBuildSetupFontBakedEllipsis() +// - ImFontAtlasBuildSetupFontBakedBlanks() +// - ImFontAtlasBuildSetupFontBakedFallback() +// - ImFontAtlasBuildSetupFontSpecialGlyphs() +// - ImFontAtlasBuildDiscardBakes() +// - ImFontAtlasBuildDiscardFontBakedGlyph() +// - ImFontAtlasBuildDiscardFontBaked() +// - ImFontAtlasBuildDiscardFontBakes() +//----------------------------------------------------------------------------- +// - ImFontAtlasAddDrawListSharedData() +// - ImFontAtlasRemoveDrawListSharedData() +// - ImFontAtlasUpdateDrawListsTextures() +// - ImFontAtlasUpdateDrawListsSharedData() +//----------------------------------------------------------------------------- +// - ImFontAtlasBuildSetTexture() +// - ImFontAtlasBuildAddTexture() +// - ImFontAtlasBuildMakeSpace() +// - ImFontAtlasBuildRepackTexture() +// - ImFontAtlasBuildGrowTexture() +// - ImFontAtlasBuildRepackOrGrowTexture() +// - ImFontAtlasBuildGetTextureSizeEstimate() +// - ImFontAtlasBuildCompactTexture() +// - ImFontAtlasBuildInit() +// - ImFontAtlasBuildDestroy() +//----------------------------------------------------------------------------- +// - ImFontAtlasPackInit() +// - ImFontAtlasPackAllocRectEntry() +// - ImFontAtlasPackReuseRectEntry() +// - ImFontAtlasPackDiscardRect() +// - ImFontAtlasPackAddRect() +// - ImFontAtlasPackGetRect() +//----------------------------------------------------------------------------- +// - ImFontBaked_BuildGrowIndex() +// - ImFontBaked_BuildLoadGlyph() +// - ImFontBaked_BuildLoadGlyphAdvanceX() +// - ImFontAtlasDebugLogTextureRequests() +//----------------------------------------------------------------------------- +// - ImFontAtlasGetFontLoaderForStbTruetype() +//----------------------------------------------------------------------------- + // A work of art lies ahead! (. = white layer, X = black layer, others are blank) // The 2x2 white texels on the top left are the ones we'll use everywhere in Dear ImGui to render filled shapes. // (This is used when io.MouseDrawCursor = true) @@ -2195,6 +2608,7 @@ static const char FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[FONT_ATLAS_DEFAULT_TEX_DATA " - X.X X.X - " " - XX XX - " }; + static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3] = { // Pos ........ Size ......... Offset ...... @@ -2210,128 +2624,465 @@ static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3 { ImVec2(0,3), ImVec2(12,19), ImVec2(0, 0) }, // ImGuiMouseCursor_Progress // Arrow + custom code in ImGui::RenderMouseCursor() { ImVec2(109,0),ImVec2(13,15), ImVec2( 6, 7) }, // ImGuiMouseCursor_NotAllowed }; + +#define IM_FONTGLYPH_INDEX_UNUSED ((ImU16)-1) // 0xFFFF +#define IM_FONTGLYPH_INDEX_NOT_FOUND ((ImU16)-2) // 0xFFFE + ImFontAtlas::ImFontAtlas() { memset(this, 0, sizeof(*this)); + TexDesiredFormat = ImTextureFormat_RGBA32; TexGlyphPadding = 1; - PackIdMouseCursors = PackIdLines = -1; + TexMinWidth = 512; + TexMinHeight = 128; + TexMaxWidth = 8192; + TexMaxHeight = 8192; + TexRef._TexID = ImTextureID_Invalid; + RendererHasTextures = false; // Assumed false by default, as apps can call e.g Atlas::Build() after backend init and before ImGui can update. + TexNextUniqueID = 1; + FontNextUniqueID = 1; + Builder = NULL; } + ImFontAtlas::~ImFontAtlas() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - Clear(); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + RendererHasTextures = false; // Full Clear() is supported, but ClearTexData() only isn't. + ClearFonts(); + ClearTexData(); + TexList.clear_delete(); + TexData = NULL; } -void ImFontAtlas::ClearInputData() + +void ImFontAtlas::Clear() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - for (ImFontConfig& font_cfg : Sources) - if (font_cfg.FontData && font_cfg.FontDataOwnedByAtlas) - { - IM_FREE(font_cfg.FontData); - font_cfg.FontData = NULL; - } - // When clearing this we lose access to the font name and other information used to build the font. + bool backup_renderer_has_textures = RendererHasTextures; + RendererHasTextures = false; // Full Clear() is supported, but ClearTexData() only isn't. + ClearFonts(); + ClearTexData(); + RendererHasTextures = backup_renderer_has_textures; +} + +void ImFontAtlas::CompactCache() +{ + ImFontAtlasTextureCompact(this); +} + +void ImFontAtlas::SetFontLoader(const ImFontLoader* font_loader) +{ + ImFontAtlasBuildSetupFontLoader(this, font_loader); +} + +void ImFontAtlas::ClearInputData() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + for (ImFont* font : Fonts) - if (font->Sources >= Sources.Data && font->Sources < Sources.Data + Sources.Size) - { - font->Sources = NULL; - font->SourcesCount = 0; - } + ImFontAtlasFontDestroyOutput(this, font); + for (ImFontConfig& font_cfg : Sources) + ImFontAtlasFontDestroySourceData(this, &font_cfg); + for (ImFont* font : Fonts) + { + // When clearing this we lose access to the font name and other information used to build the font. + font->Sources.clear(); + font->Flags |= ImFontFlags_NoLoadGlyphs; + } Sources.clear(); - CustomRects.clear(); - PackIdMouseCursors = PackIdLines = -1; - // Important: we leave TexReady untouched } -void ImFontAtlas::ClearTexData() + +// Clear CPU-side copy of the texture data. +void ImFontAtlas::ClearTexData() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - if (TexPixelsAlpha8) - IM_FREE(TexPixelsAlpha8); - if (TexPixelsRGBA32) - IM_FREE(TexPixelsRGBA32); - TexPixelsAlpha8 = NULL; - TexPixelsRGBA32 = NULL; - TexPixelsUseColors = false; - // Important: we leave TexReady untouched + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + IM_ASSERT(RendererHasTextures == false && "Not supported for dynamic atlases, but you may call Clear()."); + for (ImTextureData* tex : TexList) + tex->DestroyPixels(); + //Locked = true; // Hoped to be able to lock this down but some reload patterns may not be happy with it. } -void ImFontAtlas::ClearFonts() + +void ImFontAtlas::ClearFonts() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + // FIXME-NEWATLAS: Illegal to remove currently bound font. + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + ImFontAtlasBuildDestroy(this); ClearInputData(); Fonts.clear_delete(); - TexReady = false; -} -void ImFontAtlas::Clear() -{ - ClearInputData(); - ClearTexData(); - ClearFonts(); -} -void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) -{ - // Build atlas on demand - if (TexPixelsAlpha8 == NULL) - Build(); - *out_pixels = TexPixelsAlpha8; - if (out_width) *out_width = TexWidth; - if (out_height) *out_height = TexHeight; - if (out_bytes_per_pixel) *out_bytes_per_pixel = 1; -} -void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) -{ - // Convert to RGBA32 format on demand - // Although it is likely to be the most commonly used format, our font rendering is 1 channel / 8 bpp - if (!TexPixelsRGBA32) - { - unsigned char* pixels = NULL; - GetTexDataAsAlpha8(&pixels, NULL, NULL); - if (pixels) + TexIsBuilt = false; + for (ImDrawListSharedData* shared_data : DrawListSharedDatas) + if (shared_data->FontAtlas == this) { - TexPixelsRGBA32 = (unsigned int*)IM_ALLOC((size_t)TexWidth * (size_t)TexHeight * 4); - const unsigned char* src = pixels; - unsigned int* dst = TexPixelsRGBA32; - for (int n = TexWidth * TexHeight; n > 0; n--) - *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++)); + shared_data->Font = NULL; + shared_data->FontScale = shared_data->FontSize = 0.0f; + } +} + +static void ImFontAtlasBuildUpdateRendererHasTexturesFromContext(ImFontAtlas* atlas) +{ + // [LEGACY] Copy back the ImGuiBackendFlags_RendererHasTextures flag from ImGui context. + // - This is the 1% exceptional case where that dependency if useful, to bypass an issue where otherwise at the + // time of an early call to Build(), it would be impossible for us to tell if the backend supports texture update. + // - Without this hack, we would have quite a pitfall as many legacy codebases have an early call to Build(). + // Whereas conversely, the portion of people using ImDrawList without ImGui is expected to be pathologically rare. + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) + if (ImGuiContext* imgui_ctx = shared_data->Context) + { + atlas->RendererHasTextures = (imgui_ctx->IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; + break; + } +} + +// Called by NewFrame() for atlases owned by a context. +// If you manually manage font atlases, you'll need to call this yourself. +// - 'frame_count' needs to be provided because we can gc/prioritize baked fonts based on their age. +// - 'frame_count' may not match those of all imgui contexts using this atlas, as contexts may be updated as different frequencies. But generally you can use ImGui::GetFrameCount() on one of your context. +void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool renderer_has_textures) +{ + IM_ASSERT(atlas->Builder == NULL || atlas->Builder->FrameCount < frame_count); // Protection against being called twice. + atlas->RendererHasTextures = renderer_has_textures; + + // Check that font atlas was built or backend support texture reload in which case we can build now + if (atlas->RendererHasTextures) + { + atlas->TexIsBuilt = true; + if (atlas->Builder == NULL) // This will only happen if fonts were not already loaded. + ImFontAtlasBuildMain(atlas); + } + // Legacy backend + if (!atlas->RendererHasTextures) + IM_ASSERT_USER_ERROR(atlas->TexIsBuilt, "Backend does not support ImGuiBackendFlags_RendererHasTextures, and font atlas is not built! Update backend OR make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()."); + if (atlas->TexIsBuilt && atlas->Builder->PreloadedAllGlyphsRanges) + IM_ASSERT_USER_ERROR(atlas->RendererHasTextures == false, "Called ImFontAtlas::Build() before ImGuiBackendFlags_RendererHasTextures got set! With new backends: you don't need to call Build()."); + + // Clear BakedCurrent cache, this is important because it ensure the uncached path gets taken once. + // We also rely on ImFontBaked* pointers never crossing frames. + ImFontAtlasBuilder* builder = atlas->Builder; + builder->FrameCount = frame_count; + for (ImFont* font : atlas->Fonts) + font->LastBaked = NULL; + + // Garbage collect BakedPool + if (builder->BakedDiscardedCount > 0) + { + int dst_n = 0, src_n = 0; + for (; src_n < builder->BakedPool.Size; src_n++) + { + ImFontBaked* p_src = &builder->BakedPool[src_n]; + if (p_src->WantDestroy) + continue; + ImFontBaked* p_dst = &builder->BakedPool[dst_n++]; + if (p_dst == p_src) + continue; + memcpy(p_dst, p_src, sizeof(ImFontBaked)); + builder->BakedMap.SetVoidPtr(p_dst->BakedId, p_dst); + } + IM_ASSERT(dst_n + builder->BakedDiscardedCount == src_n); + builder->BakedPool.Size -= builder->BakedDiscardedCount; + builder->BakedDiscardedCount = 0; + } + + // Update texture status + for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) + { + ImTextureData* tex = atlas->TexList[tex_n]; + bool remove_from_list = false; + if (tex->Status == ImTextureStatus_OK) + { + tex->Updates.resize(0); + tex->UpdateRect.x = tex->UpdateRect.y = (unsigned short)~0; + tex->UpdateRect.w = tex->UpdateRect.h = 0; + } + if (tex->Status == ImTextureStatus_WantCreate && atlas->RendererHasTextures) + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture's TexID/BackendUserData but did not update Status to OK."); + + if (tex->Status == ImTextureStatus_Destroyed) + { + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture Status to Destroyed but did not clear TexID/BackendUserData!"); + if (tex->WantDestroyNextFrame) + remove_from_list = true; // Destroy was scheduled by us + else + tex->Status = ImTextureStatus_WantCreate; // Destroy was done was backend (e.g. freed resources mid-run) + } + else if (tex->WantDestroyNextFrame && tex->Status != ImTextureStatus_WantDestroy) + { + // Request destroy. + // - Keep bool to true in order to differentiate a planned destroy vs a destroy decided by the backend. + // - We don't destroy pixels right away, as backend may have an in-flight copy from RAM. + IM_ASSERT(tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates); + tex->Status = ImTextureStatus_WantDestroy; + } + + // The backend may need defer destroying by a few frames, to handle texture used by previous in-flight rendering. + // We allow the texture staying in _WantDestroy state and increment a counter which the backend can use to take its decision. + if (tex->Status == ImTextureStatus_WantDestroy) + tex->UnusedFrames++; + + // If a texture has never reached the backend, they don't need to know about it. + if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL) + remove_from_list = true; + + // Destroy and remove + if (remove_from_list) + { + tex->DestroyPixels(); + IM_DELETE(tex); + atlas->TexList.erase(atlas->TexList.begin() + tex_n); + tex_n--; } } - *out_pixels = (unsigned char*)TexPixelsRGBA32; - if (out_width) *out_width = TexWidth; - if (out_height) *out_height = TexHeight; - if (out_bytes_per_pixel) *out_bytes_per_pixel = 4; } -ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) + +void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0); - IM_ASSERT(font_cfg->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); - IM_ASSERT(font_cfg->RasterizerDensity > 0.0f && "Is ImFontConfig struct correctly initialized?"); - // Create new font - if (!font_cfg->MergeMode) - Fonts.push_back(IM_NEW(ImFont)); - else - IM_ASSERT(Fonts.Size > 0 && "Cannot use MergeMode for the first font"); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. - Sources.push_back(*font_cfg); - ImFontConfig& new_font_cfg = Sources.back(); - if (new_font_cfg.DstFont == NULL) - new_font_cfg.DstFont = Fonts.back(); - if (!new_font_cfg.FontDataOwnedByAtlas) + IM_ASSERT(src_pixels != NULL && dst_pixels != NULL); + if (src_fmt == dst_fmt) { - new_font_cfg.FontData = IM_ALLOC(new_font_cfg.FontDataSize); - new_font_cfg.FontDataOwnedByAtlas = true; - memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); + int line_sz = w * ImTextureDataGetFormatBytesPerPixel(src_fmt); + for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) + memcpy(dst_pixels, src_pixels, line_sz); + } + else if (src_fmt == ImTextureFormat_Alpha8 && dst_fmt == ImTextureFormat_RGBA32) + { + for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) + { + const ImU8* src_p = (const ImU8*)src_pixels; + ImU32* dst_p = (ImU32*)(void*)dst_pixels; + for (int nx = w; nx > 0; nx--) + *dst_p++ = IM_COL32(255, 255, 255, (unsigned int)(*src_p++)); + } + } + else if (src_fmt == ImTextureFormat_RGBA32 && dst_fmt == ImTextureFormat_Alpha8) + { + for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) + { + const ImU32* src_p = (const ImU32*)(void*)src_pixels; + ImU8* dst_p = (ImU8*)dst_pixels; + for (int nx = w; nx > 0; nx--) + *dst_p++ = ((*src_p++) >> IM_COL32_A_SHIFT) & 0xFF; + } + } + else + { + IM_ASSERT(0); } - // Round font size - // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet. - // - Note that using io.FontGlobalScale or SetWindowFontScale(), with are legacy-ish, partially supported features, can still lead to unrounded sizes. - // - We may support it better later and remove this rounding. - new_font_cfg.SizePixels = ImTrunc(new_font_cfg.SizePixels); - // Pointers to Sources data are otherwise dangling - ImFontAtlasUpdateSourcesPointers(this); - // Invalidate texture - TexReady = false; - ClearTexData(); - return new_font_cfg.DstFont; } + +// Source buffer may be written to (used for in-place mods). +// Post-process hooks may eventually be added here. +void ImFontAtlasTextureBlockPostProcess(ImFontAtlasPostProcessData* data) +{ + // Multiply operator (legacy) + if (data->FontSrc->RasterizerMultiply != 1.0f) + ImFontAtlasTextureBlockPostProcessMultiply(data, data->FontSrc->RasterizerMultiply); +} + +void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlasPostProcessData* data, float multiply_factor) +{ + unsigned char* pixels = (unsigned char*)data->Pixels; + int pitch = data->Pitch; + if (data->Format == ImTextureFormat_Alpha8) + { + for (int ny = data->Height; ny > 0; ny--, pixels += pitch) + { + ImU8* p = (ImU8*)pixels; + for (int nx = data->Width; nx > 0; nx--, p++) + { + unsigned int v = ImMin((unsigned int)(*p * multiply_factor), (unsigned int)255); + *p = (unsigned char)v; + } + } + } + else if (data->Format == ImTextureFormat_RGBA32) //-V547 + { + for (int ny = data->Height; ny > 0; ny--, pixels += pitch) + { + ImU32* p = (ImU32*)(void*)pixels; + for (int nx = data->Width; nx > 0; nx--, p++) + { + unsigned int a = ImMin((unsigned int)(((*p >> IM_COL32_A_SHIFT) & 0xFF) * multiply_factor), (unsigned int)255); + *p = IM_COL32((*p >> IM_COL32_R_SHIFT) & 0xFF, (*p >> IM_COL32_G_SHIFT) & 0xFF, (*p >> IM_COL32_B_SHIFT) & 0xFF, a); + } + } + } + else + { + IM_ASSERT(0); + } +} + +// Fill with single color. We don't use this directly but it is convenient for anyone working on uploading custom rects. +void ImFontAtlasTextureBlockFill(ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h, ImU32 col) +{ + if (dst_tex->Format == ImTextureFormat_Alpha8) + { + ImU8 col_a = (col >> IM_COL32_A_SHIFT) & 0xFF; + for (int y = 0; y < h; y++) + memset((ImU8*)dst_tex->GetPixelsAt(dst_x, dst_y + y), col_a, w); + } + else + { + for (int y = 0; y < h; y++) + { + ImU32* p = (ImU32*)(void*)dst_tex->GetPixelsAt(dst_x, dst_y + y); + for (int x = w; x > 0; x--, p++) + *p = col; + } + } +} + +// Copy block from one texture to another +void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h) +{ + IM_ASSERT(src_tex->Pixels != NULL && dst_tex->Pixels != NULL); + IM_ASSERT(src_tex->Format == dst_tex->Format); + IM_ASSERT(src_x >= 0 && src_x + w <= src_tex->Width); + IM_ASSERT(src_y >= 0 && src_y + h <= src_tex->Height); + IM_ASSERT(dst_x >= 0 && dst_x + w <= dst_tex->Width); + IM_ASSERT(dst_y >= 0 && dst_y + h <= dst_tex->Height); + for (int y = 0; y < h; y++) + memcpy(dst_tex->GetPixelsAt(dst_x, dst_y + y), src_tex->GetPixelsAt(src_x, src_y + y), w * dst_tex->BytesPerPixel); +} + +// Queue texture block update for renderer backend +void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h) +{ + IM_ASSERT(tex->Status != ImTextureStatus_WantDestroy && tex->Status != ImTextureStatus_Destroyed); + IM_ASSERT(x >= 0 && x <= 0xFFFF && y >= 0 && y <= 0xFFFF && w >= 0 && x + w <= 0x10000 && h >= 0 && y + h <= 0x10000); + IM_UNUSED(atlas); + + ImTextureRect req = { (unsigned short)x, (unsigned short)y, (unsigned short)w, (unsigned short)h }; + int new_x1 = ImMax(tex->UpdateRect.w == 0 ? 0 : tex->UpdateRect.x + tex->UpdateRect.w, req.x + req.w); + int new_y1 = ImMax(tex->UpdateRect.h == 0 ? 0 : tex->UpdateRect.y + tex->UpdateRect.h, req.y + req.h); + tex->UpdateRect.x = ImMin(tex->UpdateRect.x, req.x); + tex->UpdateRect.y = ImMin(tex->UpdateRect.y, req.y); + tex->UpdateRect.w = (unsigned short)(new_x1 - tex->UpdateRect.x); + tex->UpdateRect.h = (unsigned short)(new_y1 - tex->UpdateRect.y); + tex->UsedRect.x = ImMin(tex->UsedRect.x, req.x); + tex->UsedRect.y = ImMin(tex->UsedRect.y, req.y); + tex->UsedRect.w = (unsigned short)(ImMax(tex->UsedRect.x + tex->UsedRect.w, req.x + req.w) - tex->UsedRect.x); + tex->UsedRect.h = (unsigned short)(ImMax(tex->UsedRect.y + tex->UsedRect.h, req.y + req.h) - tex->UsedRect.y); + atlas->TexIsBuilt = false; + + // No need to queue if status is == ImTextureStatus_WantCreate + if (tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantUpdates) + { + tex->Status = ImTextureStatus_WantUpdates; + tex->Updates.push_back(req); + } +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +static void GetTexDataAsFormat(ImFontAtlas* atlas, ImTextureFormat format, unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + ImTextureData* tex = atlas->TexData; + if (!atlas->TexIsBuilt || tex == NULL || tex->Pixels == NULL || atlas->TexDesiredFormat != format) + { + atlas->TexDesiredFormat = format; + atlas->Build(); + tex = atlas->TexData; + } + if (out_pixels) { *out_pixels = (unsigned char*)tex->Pixels; }; + if (out_width) { *out_width = tex->Width; }; + if (out_height) { *out_height = tex->Height; }; + if (out_bytes_per_pixel) { *out_bytes_per_pixel = tex->BytesPerPixel; } +} + +void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + GetTexDataAsFormat(this, ImTextureFormat_Alpha8, out_pixels, out_width, out_height, out_bytes_per_pixel); +} + +void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + GetTexDataAsFormat(this, ImTextureFormat_RGBA32, out_pixels, out_width, out_height, out_bytes_per_pixel); +} + +bool ImFontAtlas::Build() +{ + ImFontAtlasBuildMain(this); + return true; +} +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) +{ + // Sanity Checks + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + IM_ASSERT((font_cfg_in->FontData != NULL && font_cfg_in->FontDataSize > 0) || (font_cfg_in->FontLoader != NULL)); + //IM_ASSERT(font_cfg_in->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); + IM_ASSERT(font_cfg_in->RasterizerDensity > 0.0f && "Is ImFontConfig struct correctly initialized?"); + if (font_cfg_in->GlyphOffset.x != 0.0f || font_cfg_in->GlyphOffset.y != 0.0f || font_cfg_in->GlyphMinAdvanceX != 0.0f || font_cfg_in->GlyphMaxAdvanceX != FLT_MAX) + IM_ASSERT(font_cfg_in->SizePixels != 0.0f && "Specifying glyph offset/advances requires a reference size to base it on."); + + // Lazily create builder on the first call to AddFont + if (Builder == NULL) + ImFontAtlasBuildInit(this); + + // Create new font + ImFont* font; + if (!font_cfg_in->MergeMode) + { + font = IM_NEW(ImFont)(); + font->FontId = FontNextUniqueID++; + font->Flags = font_cfg_in->Flags; + font->LegacySize = font_cfg_in->SizePixels; + font->CurrentRasterizerDensity = font_cfg_in->RasterizerDensity; + Fonts.push_back(font); + } + else + { + IM_ASSERT(Fonts.Size > 0 && "Cannot use MergeMode for the first font"); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. + font = Fonts.back(); + } + + // Add to list + Sources.push_back(*font_cfg_in); + ImFontConfig* font_cfg = &Sources.back(); + if (font_cfg->DstFont == NULL) + font_cfg->DstFont = font; + font->Sources.push_back(font_cfg); + ImFontAtlasBuildUpdatePointers(this); // Pointers to Sources are otherwise dangling after we called Sources.push_back(). + + if (font_cfg->FontDataOwnedByAtlas == false) + { + font_cfg->FontDataOwnedByAtlas = true; + font_cfg->FontData = ImMemdup(font_cfg->FontData, (size_t)font_cfg->FontDataSize); + } + + // Sanity check + // We don't round cfg.SizePixels yet as relative size of merged fonts are used afterwards. + if (font_cfg->GlyphExcludeRanges != NULL) + { + int size = 0; + for (const ImWchar* p = font_cfg->GlyphExcludeRanges; p[0] != 0; p++, size++) {} + IM_ASSERT((size & 1) == 0 && "GlyphExcludeRanges[] size must be multiple of two!"); + IM_ASSERT((size <= 64) && "GlyphExcludeRanges[] size must be small!"); + font_cfg->GlyphExcludeRanges = (ImWchar*)ImMemdup(font_cfg->GlyphExcludeRanges, sizeof(font_cfg->GlyphExcludeRanges[0]) * (size + 1)); + } + if (font_cfg->FontLoader != NULL) + { + IM_ASSERT(font_cfg->FontLoader->FontBakedLoadGlyph != NULL); + IM_ASSERT(font_cfg->FontLoader->LoaderInit == NULL && font_cfg->FontLoader->LoaderShutdown == NULL); // FIXME-NEWATLAS: Unsupported yet. + } + IM_ASSERT(font_cfg->FontLoaderData == NULL); + + if (!ImFontAtlasFontSourceInit(this, font_cfg)) + { + // Rollback (this is a fragile/rarely exercised code-path. TestSuite's "misc_atlas_add_invalid_font" aim to test this) + ImFontAtlasFontDestroySourceData(this, font_cfg); + Sources.pop_back(); + font->Sources.pop_back(); + if (!font_cfg->MergeMode) + { + IM_DELETE(font); + Fonts.pop_back(); + } + return NULL; + } + ImFontAtlasFontSourceAddToFont(this, font, font_cfg); + + return font; +} + // Default font TTF is compressed with stb_compress then base85 encoded (see misc/fonts/binary_to_compressed_c.cpp for encoder) static unsigned int stb_decompress_length(const unsigned char* input); static unsigned int stb_decompress(unsigned char* output, const unsigned char* input, unsigned int length); @@ -2349,6 +3100,7 @@ static void Decode85(const unsigned char* src, unsigned char* dst) #ifndef IMGUI_DISABLE_DEFAULT_FONT static const char* GetDefaultCompressedFontDataTTF(int* out_size); #endif + // Load embedded ProggyClean.ttf at size 13, disable oversampling ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) { @@ -2362,9 +3114,10 @@ ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) if (font_cfg.SizePixels <= 0.0f) font_cfg.SizePixels = 13.0f * 1.0f; if (font_cfg.Name[0] == '\0') - ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "ProggyClean.ttf, %dpx", (int)font_cfg.SizePixels); + ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "ProggyClean.ttf"); font_cfg.EllipsisChar = (ImWchar)0x0085; - font_cfg.GlyphOffset.y = 1.0f * IM_TRUNC(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units + font_cfg.GlyphOffset.y += 1.0f * IM_TRUNC(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units + int ttf_compressed_size = 0; const char* ttf_compressed = GetDefaultCompressedFontDataTTF(&ttf_compressed_size); const ImWchar* glyph_ranges = font_cfg.GlyphRanges != NULL ? font_cfg.GlyphRanges : GetGlyphRangesDefault(); @@ -2376,14 +3129,19 @@ ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) return NULL; #endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT } + ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); size_t data_size = 0; void* data = ImFileLoadToMemory(filename, "rb", &data_size, 0); if (!data) { - IM_ASSERT_USER_ERROR(0, "Could not load font file!"); + if (font_cfg_template == NULL || (font_cfg_template->Flags & ImFontFlags_NoLoadError) == 0) + { + IMGUI_DEBUG_LOG("While loading '%s'\n", filename); + IM_ASSERT_USER_ERROR(0, "Could not load font file!"); + } return NULL; } ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); @@ -2392,14 +3150,15 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, // Store a short copy of filename into into the font name for convenience const char* p; for (p = filename + ImStrlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {} - ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", p, size_pixels); + ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s", p); } return AddFontFromMemoryTTF(data, (int)data_size, size_pixels, &font_cfg, glyph_ranges); } + // NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); IM_ASSERT(font_cfg.FontData == NULL); IM_ASSERT(font_data_size > 100 && "Incorrect value for font_data_size!"); // Heuristic to prevent accidentally passing a wrong value to font_data_size. @@ -2410,16 +3169,19 @@ ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* font_data, int font_data_size, f font_cfg.GlyphRanges = glyph_ranges; return AddFont(&font_cfg); } + ImFont* ImFontAtlas::AddFontFromMemoryCompressedTTF(const void* compressed_ttf_data, int compressed_ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) { const unsigned int buf_decompressed_size = stb_decompress_length((const unsigned char*)compressed_ttf_data); unsigned char* buf_decompressed_data = (unsigned char*)IM_ALLOC(buf_decompressed_size); stb_decompress(buf_decompressed_data, (const unsigned char*)compressed_ttf_data, (unsigned int)compressed_ttf_size); + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); IM_ASSERT(font_cfg.FontData == NULL); font_cfg.FontDataOwnedByAtlas = true; return AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, size_pixels, &font_cfg, glyph_ranges); } + ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed_ttf_data_base85, float size_pixels, const ImFontConfig* font_cfg, const ImWchar* glyph_ranges) { int compressed_ttf_size = (((int)ImStrlen(compressed_ttf_data_base85) + 4) / 5) * 4; @@ -2429,51 +3191,168 @@ ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed IM_FREE(compressed_ttf); return font; } -int ImFontAtlas::AddCustomRectRegular(int width, int height) + +// On font removal we need to remove references (otherwise we could queue removal?) +// We allow old_font == new_font which forces updating all values (e.g. sizes) +static void ImFontAtlasBuildNotifySetFont(ImFontAtlas* atlas, ImFont* old_font, ImFont* new_font) +{ + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) + { + if (shared_data->Font == old_font) + shared_data->Font = new_font; + if (ImGuiContext* ctx = shared_data->Context) + { + if (ctx->IO.FontDefault == old_font) + ctx->IO.FontDefault = new_font; + if (ctx->Font == old_font) + { + ImGuiContext* curr_ctx = ImGui::GetCurrentContext(); + bool need_bind_ctx = ctx != curr_ctx; + if (need_bind_ctx) + ImGui::SetCurrentContext(ctx); + ImGui::SetCurrentFont(new_font, ctx->FontSizeBase, ctx->FontSize); + if (need_bind_ctx) + ImGui::SetCurrentContext(curr_ctx); + } + for (ImFontStackData& font_stack_data : ctx->FontStack) + if (font_stack_data.Font == old_font) + font_stack_data.Font = new_font; + } + } +} + +void ImFontAtlas::RemoveFont(ImFont* font) +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + font->ClearOutputData(); + + ImFontAtlasFontDestroyOutput(this, font); + for (ImFontConfig* src : font->Sources) + ImFontAtlasFontDestroySourceData(this, src); + for (int src_n = 0; src_n < Sources.Size; src_n++) + if (Sources[src_n].DstFont == font) + Sources.erase(&Sources[src_n--]); + + bool removed = Fonts.find_erase(font); + IM_ASSERT(removed); + IM_UNUSED(removed); + + ImFontAtlasBuildUpdatePointers(this); + + font->ContainerAtlas = NULL; + IM_DELETE(font); + + // Notify external systems + ImFont* new_current_font = Fonts.empty() ? NULL : Fonts[0]; + ImFontAtlasBuildNotifySetFont(this, font, new_current_font); +} + +// At it is common to do an AddCustomRect() followed by a GetCustomRect(), we provide an optional 'ImFontAtlasRect* out_r = NULL' argument to retrieve the info straight away. +ImFontAtlasRectId ImFontAtlas::AddCustomRect(int width, int height, ImFontAtlasRect* out_r) { IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); - ImFontAtlasCustomRect r; - r.Width = (unsigned short)width; - r.Height = (unsigned short)height; - CustomRects.push_back(r); - return CustomRects.Size - 1; // Return index + + if (Builder == NULL) + ImFontAtlasBuildInit(this); + + ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); + if (r_id == ImFontAtlasRectId_Invalid) + return ImFontAtlasRectId_Invalid; + if (out_r != NULL) + GetCustomRect(r_id, out_r); + + if (RendererHasTextures) + { + ImTextureRect* r = ImFontAtlasPackGetRect(this, r_id); + ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); + } + return r_id; } -int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset) + +void ImFontAtlas::RemoveCustomRect(ImFontAtlasRectId id) +{ + if (ImFontAtlasPackGetRectSafe(this, id) == NULL) + return; + ImFontAtlasPackDiscardRect(this, id); +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +// This API does not make sense anymore with scalable fonts. +// - Prefer adding a font source (ImFontConfig) using a custom/procedural loader. +// - You may use ImFontFlags_LockBakedSizes to limit an existing font to known baked sizes: +// ImFont* myfont = io.Fonts->AddFontFromFileTTF(....); +// myfont->GetFontBaked(16.0f); +// myfont->Flags |= ImFontFlags_LockBakedSizes; +ImFontAtlasRectId ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) +{ + float font_size = font->LegacySize; + return AddCustomRectFontGlyphForSize(font, font_size, codepoint, width, height, advance_x, offset); +} +// FIXME: we automatically set glyph.Colored=true by default. +// If you need to alter this, you can write 'font->Glyphs.back()->Colored' after calling AddCustomRectFontGlyph(). +ImFontAtlasRectId ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) { #ifdef IMGUI_USE_WCHAR32 - IM_ASSERT(id <= IM_UNICODE_CODEPOINT_MAX); + IM_ASSERT(codepoint <= IM_UNICODE_CODEPOINT_MAX); #endif IM_ASSERT(font != NULL); IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); - ImFontAtlasCustomRect r; - r.Width = (unsigned short)width; - r.Height = (unsigned short)height; - r.GlyphID = id; - r.GlyphColored = 0; // Set to 1 manually to mark glyph as colored // FIXME: No official API for that (#8133) - r.GlyphAdvanceX = advance_x; - r.GlyphOffset = offset; - r.Font = font; - CustomRects.push_back(r); - return CustomRects.Size - 1; // Return index + + ImFontBaked* baked = font->GetFontBaked(font_size); + + ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); + if (r_id == ImFontAtlasRectId_Invalid) + return ImFontAtlasRectId_Invalid; + ImTextureRect* r = ImFontAtlasPackGetRect(this, r_id); + if (RendererHasTextures) + ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); + + if (baked->IsGlyphLoaded(codepoint)) + ImFontAtlasBakedDiscardFontGlyph(this, font, baked, baked->FindGlyph(codepoint)); + + ImFontGlyph glyph; + glyph.Codepoint = codepoint; + glyph.AdvanceX = advance_x; + glyph.X0 = offset.x; + glyph.Y0 = offset.y; + glyph.X1 = offset.x + r->w; + glyph.Y1 = offset.y + r->h; + glyph.Visible = true; + glyph.Colored = true; // FIXME: Arbitrary + glyph.PackId = r_id; + ImFontAtlasBakedAddFontGlyph(this, baked, font->Sources[0], &glyph); + return r_id; } -void ImFontAtlas::CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +bool ImFontAtlas::GetCustomRect(ImFontAtlasRectId id, ImFontAtlasRect* out_r) const { - IM_ASSERT(TexWidth > 0 && TexHeight > 0); // Font atlas needs to be built before we can calculate UV coordinates - IM_ASSERT(rect->IsPacked()); // Make sure the rectangle has been packed - *out_uv_min = ImVec2((float)rect->X * TexUvScale.x, (float)rect->Y * TexUvScale.y); - *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y); + ImTextureRect* r = ImFontAtlasPackGetRectSafe((ImFontAtlas*)this, id); + if (r == NULL) + return false; + IM_ASSERT(TexData->Width > 0 && TexData->Height > 0); // Font atlas needs to be built before we can calculate UV coordinates + if (out_r == NULL) + return true; + out_r->x = r->x; + out_r->y = r->y; + out_r->w = r->w; + out_r->h = r->h; + out_r->uv0 = ImVec2((float)(r->x), (float)(r->y)) * TexUvScale; + out_r->uv1 = ImVec2((float)(r->x + r->w), (float)(r->y + r->h)) * TexUvScale; + return true; } + bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]) { if (cursor_type <= ImGuiMouseCursor_None || cursor_type >= ImGuiMouseCursor_COUNT) return false; if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) return false; - IM_ASSERT(atlas->PackIdMouseCursors != -1); - ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdMouseCursors); - ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r->X, (float)r->Y); + + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, atlas->Builder->PackIdMouseCursors); + ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r->x, (float)r->y); ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][1]; *out_size = size; *out_offset = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][2]; @@ -2484,534 +3363,1382 @@ bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor curso out_uv_fill[1] = (pos + size) * atlas->TexUvScale; return true; } -bool ImFontAtlas::Build() + +// When atlas->RendererHasTextures = true, this is only called if no font were loaded. +void ImFontAtlasBuildMain(ImFontAtlas* atlas) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); + if (atlas->TexData && atlas->TexData->Format != atlas->TexDesiredFormat) + ImFontAtlasBuildClear(atlas); + + if (atlas->Builder == NULL) + ImFontAtlasBuildInit(atlas); + // Default font is none are specified - if (Sources.Size == 0) - AddFontDefault(); - // Select builder - // - Note that we do not reassign to atlas->FontBuilderIO, since it is likely to point to static data which - // may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are - // using a hot-reloading scheme that messes up static data, store your own instance of ImFontBuilderIO somewhere - // and point to it instead of pointing directly to return value of the GetBuilderXXX functions. - const ImFontBuilderIO* builder_io = FontBuilderIO; - if (builder_io == NULL) - { -#ifdef IMGUI_ENABLE_FREETYPE - builder_io = ImGuiFreeType::GetBuilderForFreeType(); -#elif defined(IMGUI_ENABLE_STB_TRUETYPE) - builder_io = ImFontAtlasGetBuilderForStbTruetype(); -#else - IM_ASSERT(0); // Invalid Build function -#endif - } - // Build - return builder_io->FontBuilder_Build(this); + if (atlas->Sources.Size == 0) + atlas->AddFontDefault(); + + // [LEGACY] For backends not supporting RendererHasTextures: preload all glyphs + ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); + if (atlas->RendererHasTextures == false) // ~ImGuiBackendFlags_RendererHasTextures + ImFontAtlasBuildLegacyPreloadAllGlyphRanges(atlas); + atlas->TexIsBuilt = true; } -void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_brighten_factor) -{ - for (unsigned int i = 0; i < 256; i++) - { - unsigned int value = (unsigned int)(i * in_brighten_factor); - out_table[i] = value > 255 ? 255 : (value & 0xFF); - } -} -void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride) -{ - IM_ASSERT_PARANOID(w <= stride); - unsigned char* data = pixels + x + y * stride; - for (int j = h; j > 0; j--, data += stride - w) - for (int i = w; i > 0; i--, data++) - *data = table[*data]; -} -void ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* src, int* out_oversample_h, int* out_oversample_v) + +void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, ImFontBaked* baked, int* out_oversample_h, int* out_oversample_v) { // Automatically disable horizontal oversampling over size 36 - *out_oversample_h = (src->OversampleH != 0) ? src->OversampleH : (src->SizePixels * src->RasterizerDensity > 36.0f || src->PixelSnapH) ? 1 : 2; + const float raster_size = baked->Size * baked->RasterizerDensity * src->RasterizerDensity; + *out_oversample_h = (src->OversampleH != 0) ? src->OversampleH : (raster_size > 36.0f || src->PixelSnapH) ? 1 : 2; *out_oversample_v = (src->OversampleV != 0) ? src->OversampleV : 1; } -#ifdef IMGUI_ENABLE_STB_TRUETYPE -// Temporary data for one source font (multiple source fonts can be merged into one destination ImFont) -// (C++03 doesn't allow instancing ImVector<> with function-local types so we declare the type here.) -struct ImFontBuildSrcData + +// Setup main font loader for the atlas +// Every font source (ImFontConfig) will use this unless ImFontConfig::FontLoader specify a custom loader. +void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* font_loader) { - stbtt_fontinfo FontInfo; - stbtt_pack_range PackRange; // Hold the list of codepoints to pack (essentially points to Codepoints.Data) - stbrp_rect* Rects; // Rectangle to pack. We first fill in their size and the packer will give us their position. - stbtt_packedchar* PackedChars; // Output glyphs - const ImWchar* SrcRanges; // Ranges as requested by user (user is allowed to request too much, e.g. 0x0020..0xFFFF) - int DstIndex; // Index into atlas->Fonts[] and dst_tmp_array[] - int GlyphsHighest; // Highest requested codepoint - int GlyphsCount; // Glyph count (excluding missing glyphs and glyphs already set by an earlier source font) - ImBitVector GlyphsSet; // Glyph bit map (random access, 1-bit per codepoint. This will be a maximum of 8KB) - ImVector GlyphsList; // Glyph codepoints list (flattened version of GlyphsSet) -}; -// Temporary data for one destination ImFont* (multiple source fonts can be merged into one destination ImFont) -struct ImFontBuildDstData -{ - int SrcCount; // Number of source fonts targeting this destination font. - int GlyphsHighest; - int GlyphsCount; - ImBitVector GlyphsSet; // This is used to resolve collision when multiple sources are merged into a same destination font. -}; -static void UnpackBitVectorToFlatIndexList(const ImBitVector* in, ImVector* out) -{ - IM_ASSERT(sizeof(in->Storage.Data[0]) == sizeof(int)); - const ImU32* it_begin = in->Storage.begin(); - const ImU32* it_end = in->Storage.end(); - for (const ImU32* it = it_begin; it < it_end; it++) - if (ImU32 entries_32 = *it) - for (ImU32 bit_n = 0; bit_n < 32; bit_n++) - if (entries_32 & ((ImU32)1 << bit_n)) - out->push_back((int)(((it - it_begin) << 5) + bit_n)); + if (atlas->FontLoader == font_loader) + return; + IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); + + for (ImFont* font : atlas->Fonts) + ImFontAtlasFontDestroyOutput(atlas, font); + if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderShutdown) + atlas->FontLoader->LoaderShutdown(atlas); + + atlas->FontLoader = font_loader; + atlas->FontLoaderName = font_loader ? font_loader->Name : "NULL"; + IM_ASSERT(atlas->FontLoaderData == NULL); + + if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderInit) + atlas->FontLoader->LoaderInit(atlas); + for (ImFont* font : atlas->Fonts) + ImFontAtlasFontInitOutput(atlas, font); + for (ImFont* font : atlas->Fonts) + for (ImFontConfig* src : font->Sources) + ImFontAtlasFontSourceAddToFont(atlas, font, src); } -static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) + +// Preload all glyph ranges for legacy backends. +// This may lead to multiple texture creation which might be a little slower than before. +void ImFontAtlasBuildLegacyPreloadAllGlyphRanges(ImFontAtlas* atlas) { - IM_ASSERT(atlas->Sources.Size > 0); - ImFontAtlasBuildInit(atlas); - // Clear atlas - atlas->TexID = (ImTextureID)NULL; - atlas->TexWidth = atlas->TexHeight = 0; - atlas->TexUvScale = ImVec2(0.0f, 0.0f); - atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); - atlas->ClearTexData(); - // Temporary storage for building - ImVector src_tmp_array; - ImVector dst_tmp_array; - src_tmp_array.resize(atlas->Sources.Size); - dst_tmp_array.resize(atlas->Fonts.Size); - memset(src_tmp_array.Data, 0, (size_t)src_tmp_array.size_in_bytes()); - memset(dst_tmp_array.Data, 0, (size_t)dst_tmp_array.size_in_bytes()); - // 1. Initialize font loading structure, check font data validity - for (int src_i = 0; src_i < atlas->Sources.Size; src_i++) + atlas->Builder->PreloadedAllGlyphsRanges = true; + for (ImFont* font : atlas->Fonts) { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - ImFontConfig& src = atlas->Sources[src_i]; - IM_ASSERT(src.DstFont && (!src.DstFont->IsLoaded() || src.DstFont->ContainerAtlas == atlas)); - // Find index from src.DstFont (we allow the user to set cfg.DstFont. Also it makes casual debugging nicer than when storing indices) - src_tmp.DstIndex = -1; - for (int output_i = 0; output_i < atlas->Fonts.Size && src_tmp.DstIndex == -1; output_i++) - if (src.DstFont == atlas->Fonts[output_i]) - src_tmp.DstIndex = output_i; - if (src_tmp.DstIndex == -1) + ImFontBaked* baked = font->GetFontBaked(font->LegacySize); + if (font->FallbackChar != 0) + baked->FindGlyph(font->FallbackChar); + if (font->EllipsisChar != 0) + baked->FindGlyph(font->EllipsisChar); + for (ImFontConfig* src : font->Sources) { - IM_ASSERT(src_tmp.DstIndex != -1); // src.DstFont not pointing within atlas->Fonts[] array? - return false; - } - // Initialize helper structure for font loading and verify that the TTF/OTF data is correct - const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)src.FontData, src.FontNo); - IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found."); - if (!stbtt_InitFont(&src_tmp.FontInfo, (unsigned char*)src.FontData, font_offset)) - { - IM_ASSERT(0 && "stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize."); - return false; - } - // Measure highest codepoints - ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; - src_tmp.SrcRanges = src.GlyphRanges ? src.GlyphRanges : atlas->GetGlyphRangesDefault(); - for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) - { - // Check for valid range. This may also help detect *some* dangling pointers, because a common - // user error is to setup ImFontConfig::GlyphRanges with a pointer to data that isn't persistent, - // or to forget to zero-terminate the glyph range array. - IM_ASSERT(src_range[0] <= src_range[1] && "Invalid range: is your glyph range array persistent? it is zero-terminated?"); - src_tmp.GlyphsHighest = ImMax(src_tmp.GlyphsHighest, (int)src_range[1]); - } - dst_tmp.SrcCount++; - dst_tmp.GlyphsHighest = ImMax(dst_tmp.GlyphsHighest, src_tmp.GlyphsHighest); - } - // 2. For every requested codepoint, check for their presence in the font data, and handle redundancy or overlaps between source fonts to avoid unused glyphs. - int total_glyphs_count = 0; - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; - src_tmp.GlyphsSet.Create(src_tmp.GlyphsHighest + 1); - if (dst_tmp.GlyphsSet.Storage.empty()) - dst_tmp.GlyphsSet.Create(dst_tmp.GlyphsHighest + 1); - for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) - for (unsigned int codepoint = src_range[0]; codepoint <= src_range[1]; codepoint++) - { - if (dst_tmp.GlyphsSet.TestBit(codepoint)) // Don't overwrite existing glyphs. We could make this an option for MergeMode (e.g. MergeOverwrite==true) - continue; - if (!stbtt_FindGlyphIndex(&src_tmp.FontInfo, codepoint)) // It is actually in the font? - continue; - // Add to avail set/counters - src_tmp.GlyphsCount++; - dst_tmp.GlyphsCount++; - src_tmp.GlyphsSet.SetBit(codepoint); - dst_tmp.GlyphsSet.SetBit(codepoint); - total_glyphs_count++; - } - } - // 3. Unpack our bit map into a flat list (we now have all the Unicode points that we know are requested _and_ available _and_ not overlapping another) - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - src_tmp.GlyphsList.reserve(src_tmp.GlyphsCount); - UnpackBitVectorToFlatIndexList(&src_tmp.GlyphsSet, &src_tmp.GlyphsList); - src_tmp.GlyphsSet.Clear(); - IM_ASSERT(src_tmp.GlyphsList.Size == src_tmp.GlyphsCount); - } - for (int dst_i = 0; dst_i < dst_tmp_array.Size; dst_i++) - dst_tmp_array[dst_i].GlyphsSet.Clear(); - dst_tmp_array.clear(); - // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) - // (We technically don't need to zero-clear buf_rects, but let's do it for the sake of sanity) - ImVector buf_rects; - ImVector buf_packedchars; - buf_rects.resize(total_glyphs_count); - buf_packedchars.resize(total_glyphs_count); - memset(buf_rects.Data, 0, (size_t)buf_rects.size_in_bytes()); - memset(buf_packedchars.Data, 0, (size_t)buf_packedchars.size_in_bytes()); - // 4. Gather glyphs sizes so we can pack them in our virtual canvas. - int total_surface = 0; - int buf_rects_out_n = 0; - int buf_packedchars_out_n = 0; - const int pack_padding = atlas->TexGlyphPadding; - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) - continue; - src_tmp.Rects = &buf_rects[buf_rects_out_n]; - src_tmp.PackedChars = &buf_packedchars[buf_packedchars_out_n]; - buf_rects_out_n += src_tmp.GlyphsCount; - buf_packedchars_out_n += src_tmp.GlyphsCount; - // Automatic selection of oversampling parameters - ImFontConfig& src = atlas->Sources[src_i]; - int oversample_h, oversample_v; - ImFontAtlasBuildGetOversampleFactors(&src, &oversample_h, &oversample_v); - // Convert our ranges in the format stb_truetype wants - src_tmp.PackRange.font_size = src.SizePixels * src.RasterizerDensity; - src_tmp.PackRange.first_unicode_codepoint_in_range = 0; - src_tmp.PackRange.array_of_unicode_codepoints = src_tmp.GlyphsList.Data; - src_tmp.PackRange.num_chars = src_tmp.GlyphsList.Size; - src_tmp.PackRange.chardata_for_range = src_tmp.PackedChars; - src_tmp.PackRange.h_oversample = (unsigned char)oversample_h; - src_tmp.PackRange.v_oversample = (unsigned char)oversample_v; - // Gather the sizes of all rectangles we will need to pack (this loop is based on stbtt_PackFontRangesGatherRects) - const float scale = (src.SizePixels > 0.0f) ? stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, src.SizePixels * src.RasterizerDensity) : stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -src.SizePixels * src.RasterizerDensity); - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++) - { - int x0, y0, x1, y1; - const int glyph_index_in_font = stbtt_FindGlyphIndex(&src_tmp.FontInfo, src_tmp.GlyphsList[glyph_i]); - IM_ASSERT(glyph_index_in_font != 0); - stbtt_GetGlyphBitmapBoxSubpixel(&src_tmp.FontInfo, glyph_index_in_font, scale * oversample_h, scale * oversample_v, 0, 0, &x0, &y0, &x1, &y1); - src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + pack_padding + oversample_h - 1); - src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + pack_padding + oversample_v - 1); - total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h; + const ImWchar* ranges = src->GlyphRanges ? src->GlyphRanges : atlas->GetGlyphRangesDefault(); + for (; ranges[0]; ranges += 2) + for (unsigned int c = ranges[0]; c <= ranges[1] && c <= IM_UNICODE_CODEPOINT_MAX; c++) //-V560 + baked->FindGlyph((ImWchar)c); } } - for (int i = 0; i < atlas->CustomRects.Size; i++) - total_surface += (atlas->CustomRects[i].Width + pack_padding) * (atlas->CustomRects[i].Height + pack_padding); - // We need a width for the skyline algorithm, any width! - // The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. - // User can override TexDesiredWidth and TexGlyphPadding if they wish, otherwise we use a simple heuristic to select the width based on expected surface. - const int surface_sqrt = (int)ImSqrt((float)total_surface) + 1; - atlas->TexHeight = 0; - if (atlas->TexDesiredWidth > 0) - atlas->TexWidth = atlas->TexDesiredWidth; - else - atlas->TexWidth = (surface_sqrt >= 4096 * 0.7f) ? 4096 : (surface_sqrt >= 2048 * 0.7f) ? 2048 : (surface_sqrt >= 1024 * 0.7f) ? 1024 : 512; - // 5. Start packing - // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). - const int TEX_HEIGHT_MAX = 1024 * 32; - stbtt_pack_context spc = {}; - stbtt_PackBegin(&spc, NULL, atlas->TexWidth, TEX_HEIGHT_MAX, 0, 0, NULL); - spc.padding = atlas->TexGlyphPadding; // Because we mixup stbtt_PackXXX and stbrp_PackXXX there's a bit of a hack here, not passing the value to stbtt_PackBegin() allows us to still pack a TexWidth-1 wide item. (#8107) - ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info); - // 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point. - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) - continue; - stbrp_pack_rects((stbrp_context*)spc.pack_info, src_tmp.Rects, src_tmp.GlyphsCount); - // Extend texture height and mark missing glyphs as non-packed so we won't render them. - // FIXME: We are not handling packing failure here (would happen if we got off TEX_HEIGHT_MAX or if a single if larger than TexWidth?) - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++) - if (src_tmp.Rects[glyph_i].was_packed) - atlas->TexHeight = ImMax(atlas->TexHeight, src_tmp.Rects[glyph_i].y + src_tmp.Rects[glyph_i].h); - } - // 7. Allocate texture - atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); - atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); - atlas->TexPixelsAlpha8 = (unsigned char*)IM_ALLOC(atlas->TexWidth * atlas->TexHeight); - memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); - spc.pixels = atlas->TexPixelsAlpha8; - spc.height = atlas->TexHeight; - // 8. Render/rasterize font characters into the texture - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontConfig& src = atlas->Sources[src_i]; - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) - continue; - stbtt_PackFontRangesRenderIntoRects(&spc, &src_tmp.FontInfo, &src_tmp.PackRange, 1, src_tmp.Rects); - // Apply multiply operator - if (src.RasterizerMultiply != 1.0f) - { - unsigned char multiply_table[256]; - ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, src.RasterizerMultiply); - stbrp_rect* r = &src_tmp.Rects[0]; - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++, r++) - if (r->was_packed) - ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, atlas->TexPixelsAlpha8, r->x, r->y, r->w, r->h, atlas->TexWidth * 1); - } - src_tmp.Rects = NULL; - } - // End packing - stbtt_PackEnd(&spc); - buf_rects.clear(); - // 9. Setup ImFont and glyphs for runtime - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - // When merging fonts with MergeMode=true: - // - We can have multiple input fonts writing into a same destination font. - // - dst_font->Sources is != from src which is our source configuration. - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - ImFontConfig& src = atlas->Sources[src_i]; - ImFont* dst_font = src.DstFont; - const float font_scale = stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, src.SizePixels); - int unscaled_ascent, unscaled_descent, unscaled_line_gap; - stbtt_GetFontVMetrics(&src_tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); - const float ascent = ImCeil(unscaled_ascent * font_scale); - const float descent = ImFloor(unscaled_descent * font_scale); - ImFontAtlasBuildSetupFont(atlas, dst_font, &src, ascent, descent); - const float font_off_x = src.GlyphOffset.x; - const float font_off_y = src.GlyphOffset.y + IM_ROUND(dst_font->Ascent); - const float inv_rasterization_scale = 1.0f / src.RasterizerDensity; - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++) - { - // Register glyph - const int codepoint = src_tmp.GlyphsList[glyph_i]; - const stbtt_packedchar& pc = src_tmp.PackedChars[glyph_i]; - stbtt_aligned_quad q; - float unused_x = 0.0f, unused_y = 0.0f; - stbtt_GetPackedQuad(src_tmp.PackedChars, atlas->TexWidth, atlas->TexHeight, glyph_i, &unused_x, &unused_y, &q, 0); - float x0 = q.x0 * inv_rasterization_scale + font_off_x; - float y0 = q.y0 * inv_rasterization_scale + font_off_y; - float x1 = q.x1 * inv_rasterization_scale + font_off_x; - float y1 = q.y1 * inv_rasterization_scale + font_off_y; - dst_font->AddGlyph(&src, (ImWchar)codepoint, x0, y0, x1, y1, q.s0, q.t0, q.s1, q.t1, pc.xadvance * inv_rasterization_scale); - } - } - // Cleanup - src_tmp_array.clear_destruct(); - ImFontAtlasBuildFinish(atlas); - return true; } -const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype() -{ - static ImFontBuilderIO io; - io.FontBuilder_Build = ImFontAtlasBuildWithStbTruetype; - return &io; -} -#endif // IMGUI_ENABLE_STB_TRUETYPE -void ImFontAtlasUpdateSourcesPointers(ImFontAtlas* atlas) + +// FIXME: May make ImFont::Sources a ImSpan<> and move ownership to ImFontAtlas +void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas) { + for (ImFont* font : atlas->Fonts) + font->Sources.resize(0); for (ImFontConfig& src : atlas->Sources) + src.DstFont->Sources.push_back(&src); +} + +// Render a white-colored bitmap encoded in a string +void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char) +{ + ImTextureData* tex = atlas->TexData; + IM_ASSERT(x >= 0 && x + w <= tex->Width); + IM_ASSERT(y >= 0 && y + h <= tex->Height); + + switch (tex->Format) { - ImFont* font = src.DstFont; - if (!src.MergeMode) - { - font->Sources = &src; - font->SourcesCount = 0; - } - font->SourcesCount++; + case ImTextureFormat_Alpha8: + { + ImU8* out_p = (ImU8*)tex->GetPixelsAt(x, y); + for (int off_y = 0; off_y < h; off_y++, out_p += tex->Width, in_str += w) + for (int off_x = 0; off_x < w; off_x++) + out_p[off_x] = (in_str[off_x] == in_marker_char) ? 0xFF : 0x00; + break; + } + case ImTextureFormat_RGBA32: + { + ImU32* out_p = (ImU32*)tex->GetPixelsAt(x, y); + for (int off_y = 0; off_y < h; off_y++, out_p += tex->Width, in_str += w) + for (int off_x = 0; off_x < w; off_x++) + out_p[off_x] = (in_str[off_x] == in_marker_char) ? IM_COL32_WHITE : IM_COL32_BLACK_TRANS; + break; + } } } -void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent) + +static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas) { - if (!font_config->MergeMode) + // Pack and store identifier so we can refresh UV coordinates on texture resize. + // FIXME-NEWATLAS: User/custom rects where user code wants to store UV coordinates will need to do the same thing. + ImFontAtlasBuilder* builder = atlas->Builder; + ImVec2i pack_size = (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) ? ImVec2i(2, 2) : ImVec2i(FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1, FONT_ATLAS_DEFAULT_TEX_DATA_H); + + ImFontAtlasRect r; + bool add_and_draw = (atlas->GetCustomRect(builder->PackIdMouseCursors, &r) == false); + if (add_and_draw) { - font->ClearOutputData(); - font->FontSize = font_config->SizePixels; - IM_ASSERT(font->Sources == font_config); - font->ContainerAtlas = atlas; - font->Ascent = ascent; - font->Descent = descent; - } -} -void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque) -{ - stbrp_context* pack_context = (stbrp_context*)stbrp_context_opaque; - IM_ASSERT(pack_context != NULL); - ImVector& user_rects = atlas->CustomRects; - IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong. -#ifdef __GNUC__ - if (user_rects.Size < 1) { __builtin_unreachable(); } // Workaround for GCC bug if IM_ASSERT() is defined to conditionally throw (see #5343) -#endif - const int pack_padding = atlas->TexGlyphPadding; - ImVector pack_rects; - pack_rects.resize(user_rects.Size); - memset(pack_rects.Data, 0, (size_t)pack_rects.size_in_bytes()); - for (int i = 0; i < user_rects.Size; i++) - { - pack_rects[i].w = user_rects[i].Width + pack_padding; - pack_rects[i].h = user_rects[i].Height + pack_padding; - } - stbrp_pack_rects(pack_context, &pack_rects[0], pack_rects.Size); - for (int i = 0; i < pack_rects.Size; i++) - if (pack_rects[i].was_packed) + builder->PackIdMouseCursors = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); + IM_ASSERT(builder->PackIdMouseCursors != ImFontAtlasRectId_Invalid); + + // Draw to texture + if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) { - user_rects[i].X = (unsigned short)pack_rects[i].x; - user_rects[i].Y = (unsigned short)pack_rects[i].y; - IM_ASSERT(pack_rects[i].w == user_rects[i].Width + pack_padding && pack_rects[i].h == user_rects[i].Height + pack_padding); - atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h); - } -} -void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value) -{ - IM_ASSERT(x >= 0 && x + w <= atlas->TexWidth); - IM_ASSERT(y >= 0 && y + h <= atlas->TexHeight); - unsigned char* out_pixel = atlas->TexPixelsAlpha8 + x + (y * atlas->TexWidth); - for (int off_y = 0; off_y < h; off_y++, out_pixel += atlas->TexWidth, in_str += w) - for (int off_x = 0; off_x < w; off_x++) - out_pixel[off_x] = (in_str[off_x] == in_marker_char) ? in_marker_pixel_value : 0x00; -} -void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value) -{ - IM_ASSERT(x >= 0 && x + w <= atlas->TexWidth); - IM_ASSERT(y >= 0 && y + h <= atlas->TexHeight); - unsigned int* out_pixel = atlas->TexPixelsRGBA32 + x + (y * atlas->TexWidth); - for (int off_y = 0; off_y < h; off_y++, out_pixel += atlas->TexWidth, in_str += w) - for (int off_x = 0; off_x < w; off_x++) - out_pixel[off_x] = (in_str[off_x] == in_marker_char) ? in_marker_pixel_value : IM_COL32_BLACK_TRANS; -} -static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) -{ - ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdMouseCursors); - IM_ASSERT(r->IsPacked()); - const int w = atlas->TexWidth; - if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) - { - // White pixels only - IM_ASSERT(r->Width == 2 && r->Height == 2); - const int offset = (int)r->X + (int)r->Y * w; - if (atlas->TexPixelsAlpha8 != NULL) - { - atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF; + // 2x2 white pixels + ImFontAtlasBuildRenderBitmapFromString(atlas, r.x, r.y, 2, 2, "XX" "XX", 'X'); } else { - atlas->TexPixelsRGBA32[offset] = atlas->TexPixelsRGBA32[offset + 1] = atlas->TexPixelsRGBA32[offset + w] = atlas->TexPixelsRGBA32[offset + w + 1] = IM_COL32_WHITE; + // 2x2 white pixels + mouse cursors + const int x_for_white = r.x; + const int x_for_black = r.x + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; + ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_white, r.y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.'); + ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_black, r.y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X'); } } - else - { - // White pixels and mouse cursor - IM_ASSERT(r->Width == FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1 && r->Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); - const int x_for_white = r->X; - const int x_for_black = r->X + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; - if (atlas->TexPixelsAlpha8 != NULL) - { - ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', 0xFF); - ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', 0xFF); - } - else - { - ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', IM_COL32_WHITE); - ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', IM_COL32_WHITE); - } - } - atlas->TexUvWhitePixel = ImVec2((r->X + 0.5f) * atlas->TexUvScale.x, (r->Y + 0.5f) * atlas->TexUvScale.y); + + // Refresh UV coordinates + atlas->TexUvWhitePixel = ImVec2((r.x + 0.5f) * atlas->TexUvScale.x, (r.y + 0.5f) * atlas->TexUvScale.y); } -static void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas* atlas) + +static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas) { if (atlas->Flags & ImFontAtlasFlags_NoBakedLines) return; + + // Pack and store identifier so we can refresh UV coordinates on texture resize. + ImTextureData* tex = atlas->TexData; + ImFontAtlasBuilder* builder = atlas->Builder; + + ImFontAtlasRect r; + bool add_and_draw = atlas->GetCustomRect(builder->PackIdLinesTexData, &r) == false; + if (add_and_draw) + { + ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + builder->PackIdLinesTexData = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); + IM_ASSERT(builder->PackIdLinesTexData != ImFontAtlasRectId_Invalid); + } + + // Register texture region for thick lines + // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them - ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdLines); - IM_ASSERT(r->IsPacked()); for (int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row { // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle - int y = n; - int line_width = n; - int pad_left = (r->Width - line_width) / 2; - int pad_right = r->Width - (pad_left + line_width); + const int y = n; + const int line_width = n; + const int pad_left = (r.w - line_width) / 2; + const int pad_right = r.w - (pad_left + line_width); + IM_ASSERT(pad_left + line_width + pad_right == r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels + // Write each slice - IM_ASSERT(pad_left + line_width + pad_right == r->Width && y < r->Height); // Make sure we're inside the texture bounds before we start writing pixels - if (atlas->TexPixelsAlpha8 != NULL) + if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) { - unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r->X + ((r->Y + y) * atlas->TexWidth)]; + ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y + y); for (int i = 0; i < pad_left; i++) *(write_ptr + i) = 0x00; + for (int i = 0; i < line_width; i++) *(write_ptr + pad_left + i) = 0xFF; + for (int i = 0; i < pad_right; i++) *(write_ptr + pad_left + line_width + i) = 0x00; } - else + else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) { - unsigned int* write_ptr = &atlas->TexPixelsRGBA32[r->X + ((r->Y + y) * atlas->TexWidth)]; + ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y + y); for (int i = 0; i < pad_left; i++) *(write_ptr + i) = IM_COL32(255, 255, 255, 0); + for (int i = 0; i < line_width; i++) *(write_ptr + pad_left + i) = IM_COL32_WHITE; + for (int i = 0; i < pad_right; i++) *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); } - // Calculate UVs for this line - ImVec2 uv0 = ImVec2((float)(r->X + pad_left - 1), (float)(r->Y + y)) * atlas->TexUvScale; - ImVec2 uv1 = ImVec2((float)(r->X + pad_left + line_width + 1), (float)(r->Y + y + 1)) * atlas->TexUvScale; + + // Refresh UV coordinates + ImVec2 uv0 = ImVec2((float)(r.x + pad_left - 1), (float)(r.y + y)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(r.x + pad_left + line_width + 1), (float)(r.y + y + 1)) * atlas->TexUvScale; float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } } -// Note: this is called / shared by both the stb_truetype and the FreeType builder + +//----------------------------------------------------------------------------------------------------------------------------- + +// Was tempted to lazily init FontSrc but wouldn't save much + makes it more complicated to detect invalid data at AddFont() +bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont* font) +{ + bool ret = true; + for (ImFontConfig* src : font->Sources) + if (!ImFontAtlasFontSourceInit(atlas, src)) + ret = false; + IM_ASSERT(ret); // Unclear how to react to this meaningfully. Assume that result will be same as initial AddFont() call. + return ret; +} + +// Keep source/input FontData +void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font) +{ + font->ClearOutputData(); + for (ImFontConfig* src : font->Sources) + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader && loader->FontSrcDestroy != NULL) + loader->FontSrcDestroy(atlas, src); + } +} + +//----------------------------------------------------------------------------------------------------------------------------- + +bool ImFontAtlasFontSourceInit(ImFontAtlas* atlas, ImFontConfig* src) +{ + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontSrcInit != NULL && !loader->FontSrcInit(atlas, src)) + return false; + return true; +} + +void ImFontAtlasFontSourceAddToFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src) +{ + if (src->MergeMode == false) + { + font->ClearOutputData(); + //font->FontSize = src->SizePixels; + font->ContainerAtlas = atlas; + IM_ASSERT(font->Sources[0] == src); + } + atlas->TexIsBuilt = false; // For legacy backends + ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, font, src); +} + +void ImFontAtlasFontDestroySourceData(ImFontAtlas* atlas, ImFontConfig* src) +{ + IM_UNUSED(atlas); + if (src->FontDataOwnedByAtlas) + IM_FREE(src->FontData); + src->FontData = NULL; + if (src->GlyphExcludeRanges) + IM_FREE((void*)src->GlyphExcludeRanges); + src->GlyphExcludeRanges = NULL; +} + +// Create a compact, baked "..." if it doesn't exist, by using the ".". +// This may seem overly complicated right now but the point is to exercise and improve a technique which should be increasingly used. +// FIXME-NEWATLAS: This borrows too much from FontLoader's FontLoadGlyph() handlers and suggest that we should add further helpers. +static ImFontGlyph* ImFontAtlasBuildSetupFontBakedEllipsis(ImFontAtlas* atlas, ImFontBaked* baked) +{ + ImFont* font = baked->ContainerFont; + IM_ASSERT(font->EllipsisChar != 0); + + const ImFontGlyph* dot_glyph = baked->FindGlyphNoFallback((ImWchar)'.'); + if (dot_glyph == NULL) + dot_glyph = baked->FindGlyphNoFallback((ImWchar)0xFF0E); + if (dot_glyph == NULL) + return NULL; + ImFontAtlasRectId dot_r_id = dot_glyph->PackId; // Deep copy to avoid invalidation of glyphs and rect pointers + ImTextureRect* dot_r = ImFontAtlasPackGetRect(atlas, dot_r_id); + const int dot_spacing = 1; + const float dot_step = (dot_glyph->X1 - dot_glyph->X0) + dot_spacing; + + ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, (dot_r->w * 3 + dot_spacing * 2), dot_r->h); + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id); + + ImFontGlyph glyph_in = {}; + ImFontGlyph* glyph = &glyph_in; + glyph->Codepoint = font->EllipsisChar; + glyph->AdvanceX = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + dot_step * 3.0f - dot_spacing); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents. + glyph->X0 = dot_glyph->X0; + glyph->Y0 = dot_glyph->Y0; + glyph->X1 = dot_glyph->X0 + dot_step * 3 - dot_spacing; + glyph->Y1 = dot_glyph->Y1; + glyph->Visible = true; + glyph->PackId = pack_id; + glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, NULL, glyph); + dot_glyph = NULL; // Invalidated + + // Copy to texture, post-process and queue update for backend + // FIXME-NEWATLAS-V2: Dot glyph is already post-processed as this point, so this would damage it. + dot_r = ImFontAtlasPackGetRect(atlas, dot_r_id); + ImTextureData* tex = atlas->TexData; + for (int n = 0; n < 3; n++) + ImFontAtlasTextureBlockCopy(tex, dot_r->x, dot_r->y, tex, r->x + (dot_r->w + dot_spacing) * n, r->y, dot_r->w, dot_r->h); + ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); + + return glyph; +} + +// Load fallback in order to obtain its index +// (this is called from in hot-path so we avoid extraneous parameters to minimize impact on code size) +static void ImFontAtlasBuildSetupFontBakedFallback(ImFontBaked* baked) +{ + IM_ASSERT(baked->FallbackGlyphIndex == -1); + IM_ASSERT(baked->FallbackAdvanceX == 0.0f); + ImFont* font = baked->ContainerFont; + ImFontGlyph* fallback_glyph = NULL; + if (font->FallbackChar != 0) + fallback_glyph = baked->FindGlyphNoFallback(font->FallbackChar); + if (fallback_glyph == NULL) + { + ImFontGlyph* space_glyph = baked->FindGlyphNoFallback((ImWchar)' '); + ImFontGlyph glyph; + glyph.Codepoint = 0; + glyph.AdvanceX = space_glyph ? space_glyph->AdvanceX : IM_ROUND(baked->Size * 0.40f); + fallback_glyph = ImFontAtlasBakedAddFontGlyph(font->ContainerAtlas, baked, NULL, &glyph); + } + baked->FallbackGlyphIndex = baked->Glyphs.index_from_ptr(fallback_glyph); // Storing index avoid need to update pointer on growth and simplify inner loop code + baked->FallbackAdvanceX = fallback_glyph->AdvanceX; +} + +static void ImFontAtlasBuildSetupFontBakedBlanks(ImFontAtlas* atlas, ImFontBaked* baked) +{ + // Mark space as always hidden (not strictly correct/necessary. but some e.g. icons fonts don't have a space. it tends to look neater in previews) + ImFontGlyph* space_glyph = baked->FindGlyphNoFallback((ImWchar)' '); + if (space_glyph != NULL) + space_glyph->Visible = false; + + // Setup Tab character. + // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) + if (baked->FindGlyphNoFallback('\t') == NULL && space_glyph != NULL) + { + ImFontGlyph tab_glyph; + tab_glyph.Codepoint = '\t'; + tab_glyph.AdvanceX = space_glyph->AdvanceX * IM_TABSIZE; + ImFontAtlasBakedAddFontGlyph(atlas, baked, NULL, &tab_glyph); + } +} + +// Load/identify special glyphs +// (note that this is called again for fonts with MergeMode) +void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src) +{ + IM_UNUSED(atlas); + IM_ASSERT(font->Sources.contains(src)); + + // Find Fallback character. Actual glyph loaded in GetFontBaked(). + const ImWchar fallback_chars[] = { font->FallbackChar, (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; + if (font->FallbackChar == 0) + for (ImWchar candidate_char : fallback_chars) + if (candidate_char != 0 && font->IsGlyphInFont(candidate_char)) + { + font->FallbackChar = (ImWchar)candidate_char; + break; + } + + // Setup Ellipsis character. It is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). + // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character. + // FIXME: Note that 0x2026 is rarely included in our font ranges. Because of this we are more likely to use three individual dots. + const ImWchar ellipsis_chars[] = { src->EllipsisChar, (ImWchar)0x2026, (ImWchar)0x0085 }; + if (font->EllipsisChar == 0) + for (ImWchar candidate_char : ellipsis_chars) + if (candidate_char != 0 && font->IsGlyphInFont(candidate_char)) + { + font->EllipsisChar = candidate_char; + break; + } + if (font->EllipsisChar == 0) + { + font->EllipsisChar = 0x0085; + font->EllipsisAutoBake = true; + } +} + +void ImFontAtlasBakedDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph) +{ + if (glyph->PackId != ImFontAtlasRectId_Invalid) + { + ImFontAtlasPackDiscardRect(atlas, glyph->PackId); + glyph->PackId = ImFontAtlasRectId_Invalid; + } + ImWchar c = (ImWchar)glyph->Codepoint; + IM_ASSERT(font->FallbackChar != c && font->EllipsisChar != c); // Unsupported for simplicity + IM_ASSERT(glyph >= baked->Glyphs.Data && glyph < baked->Glyphs.Data + baked->Glyphs.Size); + IM_UNUSED(font); + baked->IndexLookup[c] = IM_FONTGLYPH_INDEX_UNUSED; + baked->IndexAdvanceX[c] = baked->FallbackAdvanceX; +} + +ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density, ImGuiID baked_id) +{ + IMGUI_DEBUG_LOG_FONT("[font] Created baked %.2fpx\n", font_size); + ImFontBaked* baked = atlas->Builder->BakedPool.push_back(ImFontBaked()); + baked->Size = font_size; + baked->RasterizerDensity = font_rasterizer_density; + baked->BakedId = baked_id; + baked->ContainerFont = font; + baked->LastUsedFrame = atlas->Builder->FrameCount; + + // Initialize backend data + size_t loader_data_size = 0; + for (ImFontConfig* src : font->Sources) // Cannot easily be cached as we allow changing backend + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + loader_data_size += loader->FontBakedSrcLoaderDataSize; + } + baked->FontLoaderDatas = (loader_data_size > 0) ? IM_ALLOC(loader_data_size) : NULL; + char* loader_data_p = (char*)baked->FontLoaderDatas; + for (ImFontConfig* src : font->Sources) + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontBakedInit) + loader->FontBakedInit(atlas, src, baked, loader_data_p); + loader_data_p += loader->FontBakedSrcLoaderDataSize; + } + + ImFontAtlasBuildSetupFontBakedBlanks(atlas, baked); + return baked; +} + +// FIXME-OPT: This is not a fast query. Adding a BakedCount field in Font might allow to take a shortcut for the most common case. +ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + for (int step_n = 0; step_n < 2; step_n++) + { + ImFontBaked* closest_larger_match = NULL; + ImFontBaked* closest_smaller_match = NULL; + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + { + ImFontBaked* baked = &builder->BakedPool[baked_n]; + if (baked->ContainerFont != font || baked->WantDestroy) + continue; + if (step_n == 0 && baked->RasterizerDensity != font_rasterizer_density) // First try with same density + continue; + if (baked->Size > font_size && (closest_larger_match == NULL || baked->Size < closest_larger_match->Size)) + closest_larger_match = baked; + if (baked->Size < font_size && (closest_smaller_match == NULL || baked->Size > closest_smaller_match->Size)) + closest_smaller_match = baked; + } + if (closest_larger_match) + if (closest_smaller_match == NULL || (closest_larger_match->Size >= font_size * 2.0f && closest_smaller_match->Size > font_size * 0.5f)) + return closest_larger_match; + if (closest_smaller_match) + return closest_smaller_match; + } + return NULL; +} + +void ImFontAtlasBakedDiscard(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + IMGUI_DEBUG_LOG_FONT("[font] Discard baked %.2f for \"%s\"\n", baked->Size, font->GetDebugName()); + + for (ImFontGlyph& glyph : baked->Glyphs) + if (glyph.PackId != ImFontAtlasRectId_Invalid) + ImFontAtlasPackDiscardRect(atlas, glyph.PackId); + + char* loader_data_p = (char*)baked->FontLoaderDatas; + for (ImFontConfig* src : font->Sources) + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontBakedDestroy) + loader->FontBakedDestroy(atlas, src, baked, loader_data_p); + loader_data_p += loader->FontBakedSrcLoaderDataSize; + } + if (baked->FontLoaderDatas) + { + IM_FREE(baked->FontLoaderDatas); + baked->FontLoaderDatas = NULL; + } + builder->BakedMap.SetVoidPtr(baked->BakedId, NULL); + builder->BakedDiscardedCount++; + baked->ClearOutputData(); + baked->WantDestroy = true; + font->LastBaked = NULL; +} + +// use unused_frames==0 to discard everything. +void ImFontAtlasFontDiscardBakes(ImFontAtlas* atlas, ImFont* font, int unused_frames) +{ + if (ImFontAtlasBuilder* builder = atlas->Builder) // This can be called from font destructor + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + { + ImFontBaked* baked = &builder->BakedPool[baked_n]; + if (baked->LastUsedFrame + unused_frames > atlas->Builder->FrameCount) + continue; + if (baked->ContainerFont != font || baked->WantDestroy) + continue; + ImFontAtlasBakedDiscard(atlas, font, baked); + } +} + +// use unused_frames==0 to discard everything. +void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + { + ImFontBaked* baked = &builder->BakedPool[baked_n]; + if (baked->LastUsedFrame + unused_frames > atlas->Builder->FrameCount) + continue; + if (baked->WantDestroy || (baked->ContainerFont->Flags & ImFontFlags_LockBakedSizes)) + continue; + ImFontAtlasBakedDiscard(atlas, baked->ContainerFont, baked); + } +} + +// Those functions are designed to facilitate changing the underlying structures for ImFontAtlas to store an array of ImDrawListSharedData* +void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data) +{ + IM_ASSERT(!atlas->DrawListSharedDatas.contains(data)); + atlas->DrawListSharedDatas.push_back(data); +} + +void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data) +{ + IM_ASSERT(atlas->DrawListSharedDatas.contains(data)); + atlas->DrawListSharedDatas.find_erase(data); +} + +// Update texture identifier in all active draw lists +void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex, ImTextureRef new_tex) +{ + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) + for (ImDrawList* draw_list : shared_data->DrawLists) + { + // Replace in command-buffer + // (there is not need to replace in ImDrawListSplitter: current channel is in ImDrawList's CmdBuffer[], + // other channels will be on SetCurrentChannel() which already needs to compare CmdHeader anyhow) + if (draw_list->CmdBuffer.Size > 0 && draw_list->_CmdHeader.TexRef == old_tex) + draw_list->_SetTexture(new_tex); + + // Replace in stack + for (ImTextureRef& stacked_tex : draw_list->_TextureStack) + if (stacked_tex == old_tex) + stacked_tex = new_tex; + } +} + +// Update texture coordinates in all draw list shared context +// FIXME-NEWATLAS FIXME-OPT: Doesn't seem necessary to update for all, only one bound to current context? +void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) +{ + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) + if (shared_data->FontAtlas == atlas) + { + shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; + shared_data->TexUvLines = atlas->TexUvLines; + } +} + +// Set current texture. This is mostly called from AddTexture() + to handle a failed resize. +static void ImFontAtlasBuildSetTexture(ImFontAtlas* atlas, ImTextureData* tex) +{ + ImTextureRef old_tex_ref = atlas->TexRef; + atlas->TexData = tex; + atlas->TexUvScale = ImVec2(1.0f / tex->Width, 1.0f / tex->Height); + atlas->TexRef._TexData = tex; + //atlas->TexRef._TexID = tex->TexID; // <-- We intentionally don't do that. It would be misleading and betray promise that both fields aren't set. + ImFontAtlasUpdateDrawListsTextures(atlas, old_tex_ref, atlas->TexRef); +} + +// Create a new texture, discard previous one +ImTextureData* ImFontAtlasTextureAdd(ImFontAtlas* atlas, int w, int h) +{ + ImTextureData* old_tex = atlas->TexData; + ImTextureData* new_tex; + + // FIXME: Cannot reuse texture because old UV may have been used already (unless we remap UV). + /*if (old_tex != NULL && old_tex->Status == ImTextureStatus_WantCreate) + { + // Reuse texture not yet used by backend. + IM_ASSERT(old_tex->TexID == ImTextureID_Invalid && old_tex->BackendUserData == NULL); + old_tex->DestroyPixels(); + old_tex->Updates.clear(); + new_tex = old_tex; + old_tex = NULL; + } + else*/ + { + // Add new + new_tex = IM_NEW(ImTextureData)(); + new_tex->UniqueID = atlas->TexNextUniqueID++; + atlas->TexList.push_back(new_tex); + } + if (old_tex != NULL) + { + // Queue old as to destroy next frame + old_tex->WantDestroyNextFrame = true; + IM_ASSERT(old_tex->Status == ImTextureStatus_OK || old_tex->Status == ImTextureStatus_WantCreate || old_tex->Status == ImTextureStatus_WantUpdates); + } + + new_tex->Create(atlas->TexDesiredFormat, w, h); + atlas->TexIsBuilt = false; + + ImFontAtlasBuildSetTexture(atlas, new_tex); + + return new_tex; +} + +#if 0 +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "../stb/stb_image_write.h" +static void ImFontAtlasDebugWriteTexToDisk(ImTextureData* tex, const char* description) +{ + ImGuiContext& g = *GImGui; + char buf[128]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "[%05d] Texture #%03d - %s.png", g.FrameCount, tex->UniqueID, description); + stbi_write_png(buf, tex->Width, tex->Height, tex->BytesPerPixel, tex->Pixels, tex->GetPitch()); // tex->BytesPerPixel is technically not component, but ok for the formats we support. +} +#endif + +void ImFontAtlasTextureRepack(ImFontAtlas* atlas, int w, int h) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + builder->LockDisableResize = true; + + ImTextureData* old_tex = atlas->TexData; + ImTextureData* new_tex = ImFontAtlasTextureAdd(atlas, w, h); + new_tex->UseColors = old_tex->UseColors; + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: resize+repack %dx%d => Texture #%03d: %dx%d\n", old_tex->UniqueID, old_tex->Width, old_tex->Height, new_tex->UniqueID, new_tex->Width, new_tex->Height); + //for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + // IMGUI_DEBUG_LOG_FONT("[font] - Baked %.2fpx, %d glyphs, want_destroy=%d\n", builder->BakedPool[baked_n].FontSize, builder->BakedPool[baked_n].Glyphs.Size, builder->BakedPool[baked_n].WantDestroy); + //IMGUI_DEBUG_LOG_FONT("[font] - Old packed rects: %d, area %d px\n", builder->RectsPackedCount, builder->RectsPackedSurface); + //ImFontAtlasDebugWriteTexToDisk(old_tex, "Before Pack"); + + // Repack, lose discarded rectangle, copy pixels + // FIXME-NEWATLAS: This is unstable because packing order is based on RectsIndex + // FIXME-NEWATLAS-V2: Repacking in batch would be beneficial to packing heuristic, and fix stability. + // FIXME-NEWATLAS-TESTS: Test calling RepackTexture with size too small to fits existing rects. + ImFontAtlasPackInit(atlas); + ImVector old_rects; + ImVector old_index = builder->RectsIndex; + old_rects.swap(builder->Rects); + + for (ImFontAtlasRectEntry& index_entry : builder->RectsIndex) + { + if (index_entry.IsUsed == false) + continue; + ImTextureRect& old_r = old_rects[index_entry.TargetIndex]; + if (old_r.w == 0 && old_r.h == 0) + continue; + ImFontAtlasRectId new_r_id = ImFontAtlasPackAddRect(atlas, old_r.w, old_r.h, &index_entry); + if (new_r_id == ImFontAtlasRectId_Invalid) + { + // Undo, grow texture and try repacking again. + // FIXME-NEWATLAS-TESTS: This is a very rarely exercised path! It needs to be automatically tested properly. + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: resize failed. Will grow.\n", new_tex->UniqueID); + new_tex->WantDestroyNextFrame = true; + builder->Rects.swap(old_rects); + builder->RectsIndex = old_index; + ImFontAtlasBuildSetTexture(atlas, old_tex); + ImFontAtlasTextureGrow(atlas, w, h); // Recurse + return; + } + IM_ASSERT(ImFontAtlasRectId_GetIndex(new_r_id) == builder->RectsIndex.index_from_ptr(&index_entry)); + ImTextureRect* new_r = ImFontAtlasPackGetRect(atlas, new_r_id); + ImFontAtlasTextureBlockCopy(old_tex, old_r.x, old_r.y, new_tex, new_r->x, new_r->y, new_r->w, new_r->h); + } + IM_ASSERT(old_rects.Size == builder->Rects.Size + builder->RectsDiscardedCount); + builder->RectsDiscardedCount = 0; + builder->RectsDiscardedSurface = 0; + + // Patch glyphs UV + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + for (ImFontGlyph& glyph : builder->BakedPool[baked_n].Glyphs) + if (glyph.PackId != ImFontAtlasRectId_Invalid) + { + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, glyph.PackId); + glyph.U0 = (r->x) * atlas->TexUvScale.x; + glyph.V0 = (r->y) * atlas->TexUvScale.y; + glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; + glyph.V1 = (r->y + r->h) * atlas->TexUvScale.y; + } + + // Update other cached UV + ImFontAtlasBuildUpdateLinesTexData(atlas); + ImFontAtlasBuildUpdateBasicTexData(atlas); + + builder->LockDisableResize = false; + ImFontAtlasUpdateDrawListsSharedData(atlas); + //ImFontAtlasDebugWriteTexToDisk(new_tex, "After Pack"); +} + +void ImFontAtlasTextureGrow(ImFontAtlas* atlas, int old_tex_w, int old_tex_h) +{ + //ImFontAtlasDebugWriteTexToDisk(atlas->TexData, "Before Grow"); + ImFontAtlasBuilder* builder = atlas->Builder; + if (old_tex_w == -1) + old_tex_w = atlas->TexData->Width; + if (old_tex_h == -1) + old_tex_h = atlas->TexData->Height; + + // FIXME-NEWATLAS-V2: What to do when reaching limits exposed by backend? + // FIXME-NEWATLAS-V2: Does ImFontAtlasFlags_NoPowerOfTwoHeight makes sense now? Allow 'lock' and 'compact' operations? + IM_ASSERT(ImIsPowerOfTwo(old_tex_w) && ImIsPowerOfTwo(old_tex_h)); + IM_ASSERT(ImIsPowerOfTwo(atlas->TexMinWidth) && ImIsPowerOfTwo(atlas->TexMaxWidth) && ImIsPowerOfTwo(atlas->TexMinHeight) && ImIsPowerOfTwo(atlas->TexMaxHeight)); + + // Grow texture so it follows roughly a square. + // - Grow height before width, as width imply more packing nodes. + // - Caller should be taking account of RectsDiscardedSurface and may not need to grow. + int new_tex_w = (old_tex_h <= old_tex_w) ? old_tex_w : old_tex_w * 2; + int new_tex_h = (old_tex_h <= old_tex_w) ? old_tex_h * 2 : old_tex_h; + + // Handle minimum size first (for pathologically large packed rects) + const int pack_padding = atlas->TexGlyphPadding; + new_tex_w = ImMax(new_tex_w, ImUpperPowerOfTwo(builder->MaxRectSize.x + pack_padding)); + new_tex_h = ImMax(new_tex_h, ImUpperPowerOfTwo(builder->MaxRectSize.y + pack_padding)); + new_tex_w = ImClamp(new_tex_w, atlas->TexMinWidth, atlas->TexMaxWidth); + new_tex_h = ImClamp(new_tex_h, atlas->TexMinHeight, atlas->TexMaxHeight); + if (new_tex_w == old_tex_w && new_tex_h == old_tex_h) + return; + + ImFontAtlasTextureRepack(atlas, new_tex_w, new_tex_h); +} + +void ImFontAtlasTextureMakeSpace(ImFontAtlas* atlas) +{ + // Can some baked contents be ditched? + //IMGUI_DEBUG_LOG_FONT("[font] ImFontAtlasBuildMakeSpace()\n"); + ImFontAtlasBuilder* builder = atlas->Builder; + ImFontAtlasBuildDiscardBakes(atlas, 2); + + // Currently using a heuristic for repack without growing. + if (builder->RectsDiscardedSurface < builder->RectsPackedSurface * 0.20f) + ImFontAtlasTextureGrow(atlas); + else + ImFontAtlasTextureRepack(atlas, atlas->TexData->Width, atlas->TexData->Height); +} + +ImVec2i ImFontAtlasTextureGetSizeEstimate(ImFontAtlas* atlas) +{ + int min_w = ImUpperPowerOfTwo(atlas->TexMinWidth); + int min_h = ImUpperPowerOfTwo(atlas->TexMinHeight); + if (atlas->Builder == NULL || atlas->TexData == NULL || atlas->TexData->Status == ImTextureStatus_WantDestroy) + return ImVec2i(min_w, min_h); + + ImFontAtlasBuilder* builder = atlas->Builder; + min_w = ImMax(ImUpperPowerOfTwo(builder->MaxRectSize.x), min_w); + min_h = ImMax(ImUpperPowerOfTwo(builder->MaxRectSize.y), min_h); + const int surface_approx = builder->RectsPackedSurface - builder->RectsDiscardedSurface; // Expected surface after repack + const int surface_sqrt = (int)sqrtf((float)surface_approx); + + int new_tex_w; + int new_tex_h; + if (min_w >= min_h) + { + new_tex_w = ImMax(min_w, ImUpperPowerOfTwo(surface_sqrt)); + new_tex_h = ImMax(min_h, (int)((surface_approx + new_tex_w - 1) / new_tex_w)); + if ((atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) == 0) + new_tex_h = ImUpperPowerOfTwo(new_tex_h); + } + else + { + new_tex_h = ImMax(min_h, ImUpperPowerOfTwo(surface_sqrt)); + if ((atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) == 0) + new_tex_h = ImUpperPowerOfTwo(new_tex_h); + new_tex_w = ImMax(min_w, (int)((surface_approx + new_tex_h - 1) / new_tex_h)); + } + + IM_ASSERT(ImIsPowerOfTwo(new_tex_w) && ImIsPowerOfTwo(new_tex_h)); + return ImVec2i(new_tex_w, new_tex_h); +} + +// Clear all output. Invalidates all AddCustomRect() return values! +void ImFontAtlasBuildClear(ImFontAtlas* atlas) +{ + ImVec2i new_tex_size = ImFontAtlasTextureGetSizeEstimate(atlas); + ImFontAtlasBuildDestroy(atlas); + ImFontAtlasTextureAdd(atlas, new_tex_size.x, new_tex_size.y); + ImFontAtlasBuildInit(atlas); + for (ImFontConfig& src : atlas->Sources) + ImFontAtlasFontSourceInit(atlas, &src); + for (ImFont* font : atlas->Fonts) + for (ImFontConfig* src : font->Sources) + ImFontAtlasFontSourceAddToFont(atlas, font, src); +} + +// You should not need to call this manually! +// If you think you do, let us know and we can advise about policies auto-compact. +void ImFontAtlasTextureCompact(ImFontAtlas* atlas) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + ImFontAtlasBuildDiscardBakes(atlas, 1); + + ImTextureData* old_tex = atlas->TexData; + ImVec2i old_tex_size = ImVec2i(old_tex->Width, old_tex->Height); + ImVec2i new_tex_size = ImFontAtlasTextureGetSizeEstimate(atlas); + if (builder->RectsDiscardedCount == 0 && new_tex_size.x == old_tex_size.x && new_tex_size.y == old_tex_size.y) + return; + + ImFontAtlasTextureRepack(atlas, new_tex_size.x, new_tex_size.y); +} + +// Start packing over current empty texture void ImFontAtlasBuildInit(ImFontAtlas* atlas) { - // Register texture region for mouse cursors or standard white pixels - if (atlas->PackIdMouseCursors < 0) + // Select Backend + // - Note that we do not reassign to atlas->FontLoader, since it is likely to point to static data which + // may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are + // using a hot-reloading scheme that messes up static data, store your own instance of FontLoader somewhere + // and point to it instead of pointing directly to return value of the GetFontLoaderXXX functions. + if (atlas->FontLoader == NULL) { - if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) - atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1, FONT_ATLAS_DEFAULT_TEX_DATA_H); - else - atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(2, 2); - } - // Register texture region for thick lines - // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row - if (atlas->PackIdLines < 0) - { - if (!(atlas->Flags & ImFontAtlasFlags_NoBakedLines)) - atlas->PackIdLines = atlas->AddCustomRectRegular(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); +#ifdef IMGUI_ENABLE_FREETYPE + atlas->SetFontLoader(ImGuiFreeType::GetFontLoader()); +#elif defined(IMGUI_ENABLE_STB_TRUETYPE) + atlas->SetFontLoader(ImFontAtlasGetFontLoaderForStbTruetype()); +#else + IM_ASSERT(0); // Invalid Build function +#endif } + + // Create initial texture size + if (atlas->TexData == NULL || atlas->TexData->Pixels == NULL) + ImFontAtlasTextureAdd(atlas, ImUpperPowerOfTwo(atlas->TexMinWidth), ImUpperPowerOfTwo(atlas->TexMinHeight)); + + atlas->Builder = IM_NEW(ImFontAtlasBuilder)(); + if (atlas->FontLoader->LoaderInit) + atlas->FontLoader->LoaderInit(atlas); + + ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); + + ImFontAtlasPackInit(atlas); + + // Add required texture data + ImFontAtlasBuildUpdateLinesTexData(atlas); + ImFontAtlasBuildUpdateBasicTexData(atlas); + + // Register fonts + ImFontAtlasBuildUpdatePointers(atlas); + + // Update UV coordinates etc. stored in bound ImDrawListSharedData instance + ImFontAtlasUpdateDrawListsSharedData(atlas); + + //atlas->TexIsBuilt = true; } -// This is called/shared by both the stb_truetype and the FreeType builder. -void ImFontAtlasBuildFinish(ImFontAtlas* atlas) + +// Destroy builder and all cached glyphs. Do not destroy actual fonts. +void ImFontAtlasBuildDestroy(ImFontAtlas* atlas) { - // Render into our custom data blocks - IM_ASSERT(atlas->TexPixelsAlpha8 != NULL || atlas->TexPixelsRGBA32 != NULL); - ImFontAtlasBuildRenderDefaultTexData(atlas); - ImFontAtlasBuildRenderLinesTexData(atlas); - // Register custom rectangle glyphs - for (int i = 0; i < atlas->CustomRects.Size; i++) - { - const ImFontAtlasCustomRect* r = &atlas->CustomRects[i]; - if (r->Font == NULL || r->GlyphID == 0) - continue; - // Will ignore ImFontConfig settings: GlyphMinAdvanceX, GlyphMinAdvanceY, PixelSnapH - IM_ASSERT(r->Font->ContainerAtlas == atlas); - ImVec2 uv0, uv1; - atlas->CalcCustomRectUV(r, &uv0, &uv1); - r->Font->AddGlyph(NULL, (ImWchar)r->GlyphID, r->GlyphOffset.x, r->GlyphOffset.y, r->GlyphOffset.x + r->Width, r->GlyphOffset.y + r->Height, uv0.x, uv0.y, uv1.x, uv1.y, r->GlyphAdvanceX); - if (r->GlyphColored) - r->Font->Glyphs.back().Colored = 1; - } - // Build all fonts lookup tables for (ImFont* font : atlas->Fonts) - if (font->DirtyLookupTables) - font->BuildLookupTable(); - atlas->TexReady = true; + ImFontAtlasFontDestroyOutput(atlas, font); + if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderShutdown) + { + atlas->FontLoader->LoaderShutdown(atlas); + IM_ASSERT(atlas->FontLoaderData == NULL); + } + IM_DELETE(atlas->Builder); + atlas->Builder = NULL; } + +void ImFontAtlasPackInit(ImFontAtlas * atlas) +{ + ImTextureData* tex = atlas->TexData; + ImFontAtlasBuilder* builder = atlas->Builder; + + // In theory we could decide to reduce the number of nodes, e.g. halve them, and waste a little texture space, but it doesn't seem worth it. + const int pack_node_count = tex->Width / 2; + builder->PackNodes.resize(pack_node_count); + IM_STATIC_ASSERT(sizeof(stbrp_context) <= sizeof(stbrp_context_opaque)); + stbrp_init_target((stbrp_context*)(void*)&builder->PackContext, tex->Width, tex->Height, builder->PackNodes.Data, builder->PackNodes.Size); + builder->RectsPackedSurface = builder->RectsPackedCount = 0; + builder->MaxRectSize = ImVec2i(0, 0); + builder->MaxRectBounds = ImVec2i(0, 0); +} + +// This is essentially a free-list pattern, it may be nice to wrap it into a dedicated type. +static ImFontAtlasRectId ImFontAtlasPackAllocRectEntry(ImFontAtlas* atlas, int rect_idx) +{ + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + int index_idx; + ImFontAtlasRectEntry* index_entry; + if (builder->RectsIndexFreeListStart < 0) + { + builder->RectsIndex.resize(builder->RectsIndex.Size + 1); + index_idx = builder->RectsIndex.Size - 1; + index_entry = &builder->RectsIndex[index_idx]; + memset(index_entry, 0, sizeof(*index_entry)); + } + else + { + index_idx = builder->RectsIndexFreeListStart; + index_entry = &builder->RectsIndex[index_idx]; + IM_ASSERT(index_entry->IsUsed == false && index_entry->Generation > 0); // Generation is incremented during DiscardRect + builder->RectsIndexFreeListStart = index_entry->TargetIndex; + } + index_entry->TargetIndex = rect_idx; + index_entry->IsUsed = 1; + return ImFontAtlasRectId_Make(index_idx, index_entry->Generation); +} + +// Overwrite existing entry +static ImFontAtlasRectId ImFontAtlasPackReuseRectEntry(ImFontAtlas* atlas, ImFontAtlasRectEntry* index_entry) +{ + IM_ASSERT(index_entry->IsUsed); + index_entry->TargetIndex = atlas->Builder->Rects.Size - 1; + int index_idx = atlas->Builder->RectsIndex.index_from_ptr(index_entry); + return ImFontAtlasRectId_Make(index_idx, index_entry->Generation); +} + +// This is expected to be called in batches and followed by a repack +void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id) +{ + IM_ASSERT(id != ImFontAtlasRectId_Invalid); + + ImTextureRect* rect = ImFontAtlasPackGetRect(atlas, id); + if (rect == NULL) + return; + + ImFontAtlasBuilder* builder = atlas->Builder; + int index_idx = ImFontAtlasRectId_GetIndex(id); + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; + IM_ASSERT(index_entry->IsUsed && index_entry->TargetIndex >= 0); + index_entry->IsUsed = false; + index_entry->TargetIndex = builder->RectsIndexFreeListStart; + index_entry->Generation++; + + const int pack_padding = atlas->TexGlyphPadding; + builder->RectsIndexFreeListStart = index_idx; + builder->RectsDiscardedCount++; + builder->RectsDiscardedSurface += (rect->w + pack_padding) * (rect->h + pack_padding); + rect->w = rect->h = 0; // Clear rectangle so it won't be packed again +} + +// Important: Calling this may recreate a new texture and therefore change atlas->TexData +// FIXME-NEWFONTS: Expose other glyph padding settings for custom alteration (e.g. drop shadows). See #7962 +ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry) +{ + IM_ASSERT(w > 0 && w <= 0xFFFF); + IM_ASSERT(h > 0 && h <= 0xFFFF); + + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + const int pack_padding = atlas->TexGlyphPadding; + builder->MaxRectSize.x = ImMax(builder->MaxRectSize.x, w); + builder->MaxRectSize.y = ImMax(builder->MaxRectSize.y, h); + + // Pack + ImTextureRect r = { 0, 0, (unsigned short)w, (unsigned short)h }; + for (int attempts_remaining = 3; attempts_remaining >= 0; attempts_remaining--) + { + // Try packing + stbrp_rect pack_r = {}; + pack_r.w = w + pack_padding; + pack_r.h = h + pack_padding; + stbrp_pack_rects((stbrp_context*)(void*)&builder->PackContext, &pack_r, 1); + r.x = (unsigned short)pack_r.x; + r.y = (unsigned short)pack_r.y; + if (pack_r.was_packed) + break; + + // If we ran out of attempts, return fallback + if (attempts_remaining == 0 || builder->LockDisableResize) + { + IMGUI_DEBUG_LOG_FONT("[font] Failed packing %dx%d rectangle. Returning fallback.\n", w, h); + return ImFontAtlasRectId_Invalid; + } + + // Resize or repack atlas! (this should be a rare event) + ImFontAtlasTextureMakeSpace(atlas); + } + + builder->MaxRectBounds.x = ImMax(builder->MaxRectBounds.x, r.x + r.w + pack_padding); + builder->MaxRectBounds.y = ImMax(builder->MaxRectBounds.y, r.y + r.h + pack_padding); + builder->RectsPackedCount++; + builder->RectsPackedSurface += (w + pack_padding) * (h + pack_padding); + + builder->Rects.push_back(r); + if (overwrite_entry != NULL) + return ImFontAtlasPackReuseRectEntry(atlas, overwrite_entry); // Write into an existing entry instead of adding one (used during repack) + else + return ImFontAtlasPackAllocRectEntry(atlas, builder->Rects.Size - 1); +} + +// Generally for non-user facing functions: assert on invalid ID. +ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) +{ + IM_ASSERT(id != ImFontAtlasRectId_Invalid); + int index_idx = ImFontAtlasRectId_GetIndex(id); + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; + IM_ASSERT(index_entry->Generation == ImFontAtlasRectId_GetGeneration(id)); + IM_ASSERT(index_entry->IsUsed); + return &builder->Rects[index_entry->TargetIndex]; +} + +// For user-facing functions: return NULL on invalid ID. +// Important: return pointer is valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers. +ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId id) +{ + if (id == ImFontAtlasRectId_Invalid) + return NULL; + int index_idx = ImFontAtlasRectId_GetIndex(id); + if (atlas->Builder == NULL) + ImFontAtlasBuildInit(atlas); + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + IM_MSVC_WARNING_SUPPRESS(28182); // Static Analysis false positive "warning C28182: Dereferencing NULL pointer 'builder'" + if (index_idx >= builder->RectsIndex.Size) + return NULL; + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; + if (index_entry->Generation != ImFontAtlasRectId_GetGeneration(id) || !index_entry->IsUsed) + return NULL; + return &builder->Rects[index_entry->TargetIndex]; +} + +// Important! This assume by ImFontConfig::GlyphExcludeRanges[] is a SMALL ARRAY (e.g. <10 entries) +// Use "Input Glyphs Overlap Detection Tool" to display a list of glyphs provided by multiple sources in order to set this array up. +static bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar codepoint) +{ + if (const ImWchar* exclude_list = src->GlyphExcludeRanges) + for (; exclude_list[0] != 0; exclude_list += 2) + if (codepoint >= exclude_list[0] && codepoint <= exclude_list[1]) + return false; + return true; +} + +static void ImFontBaked_BuildGrowIndex(ImFontBaked* baked, int new_size) +{ + IM_ASSERT(baked->IndexAdvanceX.Size == baked->IndexLookup.Size); + if (new_size <= baked->IndexLookup.Size) + return; + baked->IndexAdvanceX.resize(new_size, -1.0f); + baked->IndexLookup.resize(new_size, IM_FONTGLYPH_INDEX_UNUSED); +} + +static void ImFontAtlas_FontHookRemapCodepoint(ImFontAtlas* atlas, ImFont* font, ImWchar* c) +{ + IM_UNUSED(atlas); + if (font->RemapPairs.Data.Size != 0) + *c = (ImWchar)font->RemapPairs.GetInt((ImGuiID)*c, (int)*c); +} + +static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codepoint, float* only_load_advance_x) +{ + ImFont* font = baked->ContainerFont; + ImFontAtlas* atlas = font->ContainerAtlas; + if (atlas->Locked || (font->Flags & ImFontFlags_NoLoadGlyphs)) + { + // Lazily load fallback glyph + if (baked->FallbackGlyphIndex == -1 && baked->LoadNoFallback == 0) + ImFontAtlasBuildSetupFontBakedFallback(baked); + return NULL; + } + + // User remapping hooks + ImWchar src_codepoint = codepoint; + ImFontAtlas_FontHookRemapCodepoint(atlas, font, &codepoint); + + //char utf8_buf[5]; + //IMGUI_DEBUG_LOG("[font] BuildLoadGlyph U+%04X (%s)\n", (unsigned int)codepoint, ImTextCharToUtf8(utf8_buf, (unsigned int)codepoint)); + + // Special hook + // FIXME-NEWATLAS: it would be nicer if this used a more standardized way of hooking + if (codepoint == font->EllipsisChar && font->EllipsisAutoBake) + if (ImFontGlyph* glyph = ImFontAtlasBuildSetupFontBakedEllipsis(atlas, baked)) + return glyph; + + // Call backend + char* loader_user_data_p = (char*)baked->FontLoaderDatas; + int src_n = 0; + for (ImFontConfig* src : font->Sources) + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (!src->GlyphExcludeRanges || ImFontAtlasBuildAcceptCodepointForSource(src, codepoint)) + { + if (only_load_advance_x == NULL) + { + ImFontGlyph glyph_buf; + if (loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, codepoint, &glyph_buf, NULL)) + { + // FIXME: Add hooks for e.g. #7962 + glyph_buf.Codepoint = src_codepoint; + glyph_buf.SourceIdx = src_n; + return ImFontAtlasBakedAddFontGlyph(atlas, baked, src, &glyph_buf); + } + } + else + { + // Special mode but only loading glyphs metrics. Will rasterize and pack later. + if (loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, codepoint, NULL, only_load_advance_x)) + { + ImFontAtlasBakedAddFontGlyphAdvancedX(atlas, baked, src, codepoint, *only_load_advance_x); + return NULL; + } + } + } + loader_user_data_p += loader->FontBakedSrcLoaderDataSize; + src_n++; + } + + // Lazily load fallback glyph + if (baked->LoadNoFallback) + return NULL; + if (baked->FallbackGlyphIndex == -1) + ImFontAtlasBuildSetupFontBakedFallback(baked); + + // Mark index as not found, so we don't attempt the search twice + ImFontBaked_BuildGrowIndex(baked, codepoint + 1); + baked->IndexAdvanceX[codepoint] = baked->FallbackAdvanceX; + baked->IndexLookup[codepoint] = IM_FONTGLYPH_INDEX_NOT_FOUND; + return NULL; +} + +static float ImFontBaked_BuildLoadGlyphAdvanceX(ImFontBaked* baked, ImWchar codepoint) +{ + if (baked->Size >= IMGUI_FONT_SIZE_THRESHOLD_FOR_LOADADVANCEXONLYMODE || baked->LoadNoRenderOnLayout) + { + // First load AdvanceX value used by CalcTextSize() API then load the rest when loaded by drawing API. + float only_advance_x = 0.0f; + ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(baked, (ImWchar)codepoint, &only_advance_x); + return glyph ? glyph->AdvanceX : only_advance_x; + } + else + { + ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(baked, (ImWchar)codepoint, NULL); + return glyph ? glyph->AdvanceX : baked->FallbackAdvanceX; + } +} + +// The point of this indirection is to not be inlined in debug mode in order to not bloat inner loop.b +IM_MSVC_RUNTIME_CHECKS_OFF +static float BuildLoadGlyphGetAdvanceOrFallback(ImFontBaked* baked, unsigned int codepoint) +{ + return ImFontBaked_BuildLoadGlyphAdvanceX(baked, (ImWchar)codepoint); +} +IM_MSVC_RUNTIME_CHECKS_RESTORE + +#ifndef IMGUI_DISABLE_DEBUG_TOOLS +void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas) +{ + // [DEBUG] Log texture update requests + ImGuiContext& g = *GImGui; + IM_UNUSED(g); + for (ImTextureData* tex : atlas->TexList) + { + if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + IM_ASSERT(tex->Updates.Size == 0); + if (tex->Status == ImTextureStatus_WantCreate) + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: create %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + else if (tex->Status == ImTextureStatus_WantDestroy) + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: destroy %dx%d, texid=0x%" IM_PRIX64 ", backend_data=%p\n", tex->UniqueID, tex->Width, tex->Height, tex->TexID, tex->BackendUserData); + else if (tex->Status == ImTextureStatus_WantUpdates) + { + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update %d regions, texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, tex->Updates.Size, tex->TexID, (ImU64)(intptr_t)tex->BackendUserData); + for (const ImTextureRect& r : tex->Updates) + { + IM_UNUSED(r); + IM_ASSERT(r.x >= 0 && r.y >= 0); + IM_ASSERT(r.x + r.w <= tex->Width && r.y + r.h <= tex->Height); // In theory should subtract PackPadding but it's currently part of atlas and mid-frame change would wreck assert. + //IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update (% 4d..%-4d)->(% 4d..%-4d), texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, r.x, r.y, r.x + r.w, r.y + r.h, tex->TexID, (ImU64)(intptr_t)tex->BackendUserData); + } + } + } +} +#endif + +//------------------------------------------------------------------------- +// [SECTION] ImFontAtlas: backend for stb_truetype +//------------------------------------------------------------------------- +// (imstb_truetype.h in included near the top of this file, when IMGUI_ENABLE_STB_TRUETYPE is set) +//------------------------------------------------------------------------- + +#ifdef IMGUI_ENABLE_STB_TRUETYPE + +// One for each ConfigData +struct ImGui_ImplStbTrueType_FontSrcData +{ + stbtt_fontinfo FontInfo; + float ScaleFactor; +}; + +static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src) +{ + IM_UNUSED(atlas); + + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = IM_NEW(ImGui_ImplStbTrueType_FontSrcData); + IM_ASSERT(src->FontLoaderData == NULL); + + // Initialize helper structure for font loading and verify that the TTF/OTF data is correct + const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)src->FontData, src->FontNo); + if (font_offset < 0) + { + IM_DELETE(bd_font_data); + IM_ASSERT_USER_ERROR(0, "stbtt_GetFontOffsetForIndex(): FontData is incorrect, or FontNo cannot be found."); + return false; + } + if (!stbtt_InitFont(&bd_font_data->FontInfo, (unsigned char*)src->FontData, font_offset)) + { + IM_DELETE(bd_font_data); + IM_ASSERT_USER_ERROR(0, "stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize."); + return false; + } + src->FontLoaderData = bd_font_data; + + const float ref_size = src->DstFont->Sources[0]->SizePixels; + if (src->MergeMode && src->SizePixels == 0.0f) + src->SizePixels = ref_size; + + if (src->SizePixels >= 0.0f) + bd_font_data->ScaleFactor = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, 1.0f); + else + bd_font_data->ScaleFactor = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, 1.0f); + if (src->MergeMode && src->SizePixels != 0.0f && ref_size != 0.0f) + bd_font_data->ScaleFactor *= src->SizePixels / ref_size; // FIXME-NEWATLAS: Should tidy up that a bit + + return true; +} + +static void ImGui_ImplStbTrueType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src) +{ + IM_UNUSED(atlas); + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + IM_DELETE(bd_font_data); + src->FontLoaderData = NULL; +} + +static bool ImGui_ImplStbTrueType_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint) +{ + IM_UNUSED(atlas); + + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + IM_ASSERT(bd_font_data != NULL); + + int glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); + return glyph_index != 0; +} + +static bool ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*) +{ + IM_UNUSED(atlas); + + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + if (src->MergeMode == false) + { + // FIXME-NEWFONTS: reevaluate how to use sizing metrics + // FIXME-NEWFONTS: make use of line gap value + float scale_for_layout = bd_font_data->ScaleFactor * baked->Size; + int unscaled_ascent, unscaled_descent, unscaled_line_gap; + stbtt_GetFontVMetrics(&bd_font_data->FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); + baked->Ascent = ImCeil(unscaled_ascent * scale_for_layout); + baked->Descent = ImFloor(unscaled_descent * scale_for_layout); + } + return true; +} + +static bool ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x) +{ + // Search for first font which has the glyph + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + IM_ASSERT(bd_font_data); + int glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); + if (glyph_index == 0) + return false; + + // Fonts unit to pixels + int oversample_h, oversample_v; + ImFontAtlasBuildGetOversampleFactors(src, baked, &oversample_h, &oversample_v); + const float scale_for_layout = bd_font_data->ScaleFactor * baked->Size; + const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity; + const float scale_for_raster_x = bd_font_data->ScaleFactor * baked->Size * rasterizer_density * oversample_h; + const float scale_for_raster_y = bd_font_data->ScaleFactor * baked->Size * rasterizer_density * oversample_v; + + // Obtain size and advance + int x0, y0, x1, y1; + int advance, lsb; + stbtt_GetGlyphBitmapBoxSubpixel(&bd_font_data->FontInfo, glyph_index, scale_for_raster_x, scale_for_raster_y, 0, 0, &x0, &y0, &x1, &y1); + stbtt_GetGlyphHMetrics(&bd_font_data->FontInfo, glyph_index, &advance, &lsb); + + // Load metrics only mode + if (out_advance_x != NULL) + { + IM_ASSERT(out_glyph == NULL); + *out_advance_x = advance * scale_for_layout; + return true; + } + + // Prepare glyph + out_glyph->Codepoint = codepoint; + out_glyph->AdvanceX = advance * scale_for_layout; + + // Pack and retrieve position inside texture atlas + // (generally based on stbtt_PackFontRangesRenderIntoRects) + const bool is_visible = (x0 != x1 && y0 != y1); + if (is_visible) + { + const int w = (x1 - x0 + oversample_h - 1); + const int h = (y1 - y0 + oversample_v - 1); + ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h); + if (pack_id == ImFontAtlasRectId_Invalid) + { + // Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?) + IM_ASSERT(pack_id != ImFontAtlasRectId_Invalid && "Out of texture memory."); + return false; + } + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id); + + // Render + stbtt_GetGlyphBitmapBox(&bd_font_data->FontInfo, glyph_index, scale_for_raster_x, scale_for_raster_y, &x0, &y0, &x1, &y1); + ImFontAtlasBuilder* builder = atlas->Builder; + builder->TempBuffer.resize(w * h * 1); + unsigned char* bitmap_pixels = builder->TempBuffer.Data; + memset(bitmap_pixels, 0, w * h * 1); + + // Render with oversampling + // (those functions conveniently assert if pixels are not cleared, which is another safety layer) + float sub_x, sub_y; + stbtt_MakeGlyphBitmapSubpixelPrefilter(&bd_font_data->FontInfo, bitmap_pixels, w, h, w, + scale_for_raster_x, scale_for_raster_y, 0, 0, oversample_h, oversample_v, &sub_x, &sub_y, glyph_index); + + const float ref_size = baked->ContainerFont->Sources[0]->SizePixels; + const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f; + float font_off_x = (src->GlyphOffset.x * offsets_scale); + float font_off_y = (src->GlyphOffset.y * offsets_scale); + if (src->PixelSnapH) // Snap scaled offset. This is to mitigate backward compatibility issues for GlyphOffset, but a better design would be welcome. + font_off_x = IM_ROUND(font_off_x); + if (src->PixelSnapV) + font_off_y = IM_ROUND(font_off_y); + font_off_x += sub_x; + font_off_y += sub_y + IM_ROUND(baked->Ascent); + float recip_h = 1.0f / (oversample_h * rasterizer_density); + float recip_v = 1.0f / (oversample_v * rasterizer_density); + + // Register glyph + // r->x r->y are coordinates inside texture (in pixels) + // glyph.X0, glyph.Y0 are drawing coordinates from base text position, and accounting for oversampling. + out_glyph->X0 = x0 * recip_h + font_off_x; + out_glyph->Y0 = y0 * recip_v + font_off_y; + out_glyph->X1 = (x0 + (int)r->w) * recip_h + font_off_x; + out_glyph->Y1 = (y0 + (int)r->h) * recip_v + font_off_y; + out_glyph->Visible = true; + out_glyph->PackId = pack_id; + ImFontAtlasBakedSetFontGlyphBitmap(atlas, baked, src, out_glyph, r, bitmap_pixels, ImTextureFormat_Alpha8, w); + } + + return true; +} + +const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype() +{ + static ImFontLoader loader; + loader.Name = "stb_truetype"; + loader.FontSrcInit = ImGui_ImplStbTrueType_FontSrcInit; + loader.FontSrcDestroy = ImGui_ImplStbTrueType_FontSrcDestroy; + loader.FontSrcContainsGlyph = ImGui_ImplStbTrueType_FontSrcContainsGlyph; + loader.FontBakedInit = ImGui_ImplStbTrueType_FontBakedInit; + loader.FontBakedDestroy = NULL; + loader.FontBakedLoadGlyph = ImGui_ImplStbTrueType_FontBakedLoadGlyph; + return &loader; +} + +#endif // IMGUI_ENABLE_STB_TRUETYPE + //------------------------------------------------------------------------- // [SECTION] ImFontAtlas: glyph ranges helpers //------------------------------------------------------------------------- // - GetGlyphRangesDefault() +// Obsolete functions since 1.92: // - GetGlyphRangesGreek() // - GetGlyphRangesKorean() // - GetGlyphRangesChineseFull() @@ -3021,6 +4748,7 @@ void ImFontAtlasBuildFinish(ImFontAtlas* atlas) // - GetGlyphRangesThai() // - GetGlyphRangesVietnamese() //----------------------------------------------------------------------------- + // Retrieve list of range (2 int per range, values are inclusive) const ImWchar* ImFontAtlas::GetGlyphRangesDefault() { @@ -3031,6 +4759,8 @@ const ImWchar* ImFontAtlas::GetGlyphRangesDefault() }; return &ranges[0]; } + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS const ImWchar* ImFontAtlas::GetGlyphRangesGreek() { static const ImWchar ranges[] = @@ -3041,6 +4771,7 @@ const ImWchar* ImFontAtlas::GetGlyphRangesGreek() }; return &ranges[0]; } + const ImWchar* ImFontAtlas::GetGlyphRangesKorean() { static const ImWchar ranges[] = @@ -3053,6 +4784,7 @@ const ImWchar* ImFontAtlas::GetGlyphRangesKorean() }; return &ranges[0]; } + const ImWchar* ImFontAtlas::GetGlyphRangesChineseFull() { static const ImWchar ranges[] = @@ -3068,6 +4800,7 @@ const ImWchar* ImFontAtlas::GetGlyphRangesChineseFull() }; return &ranges[0]; } + static void UnpackAccumulativeOffsetsIntoRanges(int base_codepoint, const short* accumulative_offsets, int accumulative_offsets_count, ImWchar* out_ranges) { for (int n = 0; n < accumulative_offsets_count; n++, out_ranges += 2) @@ -3077,6 +4810,7 @@ static void UnpackAccumulativeOffsetsIntoRanges(int base_codepoint, const short* } out_ranges[0] = 0; } + const ImWchar* ImFontAtlas::GetGlyphRangesChineseSimplifiedCommon() { // Store 2500 regularly used characters for Simplified Chinese. @@ -3144,6 +4878,7 @@ const ImWchar* ImFontAtlas::GetGlyphRangesChineseSimplifiedCommon() } return &full_ranges[0]; } + const ImWchar* ImFontAtlas::GetGlyphRangesJapanese() { // 2999 ideograms code points for Japanese @@ -3233,6 +4968,7 @@ const ImWchar* ImFontAtlas::GetGlyphRangesJapanese() } return &full_ranges[0]; } + const ImWchar* ImFontAtlas::GetGlyphRangesCyrillic() { static const ImWchar ranges[] = @@ -3245,6 +4981,7 @@ const ImWchar* ImFontAtlas::GetGlyphRangesCyrillic() }; return &ranges[0]; } + const ImWchar* ImFontAtlas::GetGlyphRangesThai() { static const ImWchar ranges[] = @@ -3256,6 +4993,7 @@ const ImWchar* ImFontAtlas::GetGlyphRangesThai() }; return &ranges[0]; } + const ImWchar* ImFontAtlas::GetGlyphRangesVietnamese() { static const ImWchar ranges[] = @@ -3272,9 +5010,12 @@ const ImWchar* ImFontAtlas::GetGlyphRangesVietnamese() }; return &ranges[0]; } +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + //----------------------------------------------------------------------------- // [SECTION] ImFontGlyphRangesBuilder //----------------------------------------------------------------------------- + void ImFontGlyphRangesBuilder::AddText(const char* text, const char* text_end) { while (text_end ? (text < text_end) : *text) @@ -3287,12 +5028,14 @@ void ImFontGlyphRangesBuilder::AddText(const char* text, const char* text_end) AddChar((ImWchar)c); } } + void ImFontGlyphRangesBuilder::AddRanges(const ImWchar* ranges) { for (; ranges[0]; ranges += 2) for (unsigned int c = ranges[0]; c <= ranges[1] && c <= IM_UNICODE_CODEPOINT_MAX; c++) //-V560 AddChar((ImWchar)c); } + void ImFontGlyphRangesBuilder::BuildRanges(ImVector* out_ranges) { const int max_codepoint = IM_UNICODE_CODEPOINT_MAX; @@ -3306,118 +5049,50 @@ void ImFontGlyphRangesBuilder::BuildRanges(ImVector* out_ranges) } out_ranges->push_back(0); } + //----------------------------------------------------------------------------- // [SECTION] ImFont //----------------------------------------------------------------------------- -ImFont::ImFont() + +ImFontBaked::ImFontBaked() { memset(this, 0, sizeof(*this)); - Scale = 1.0f; + FallbackGlyphIndex = -1; } -ImFont::~ImFont() + +void ImFontBaked::ClearOutputData() { - ClearOutputData(); -} -void ImFont::ClearOutputData() -{ - FontSize = 0.0f; FallbackAdvanceX = 0.0f; Glyphs.clear(); IndexAdvanceX.clear(); IndexLookup.clear(); - FallbackGlyph = NULL; - ContainerAtlas = NULL; - DirtyLookupTables = true; + FallbackGlyphIndex = -1; Ascent = Descent = 0.0f; MetricsTotalSurface = 0; - memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); } -static ImWchar FindFirstExistingGlyph(ImFont* font, const ImWchar* candidate_chars, int candidate_chars_count) + +ImFont::ImFont() { - for (int n = 0; n < candidate_chars_count; n++) - if (font->FindGlyphNoFallback(candidate_chars[n]) != NULL) - return candidate_chars[n]; - return 0; + memset(this, 0, sizeof(*this)); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + Scale = 1.0f; +#endif } -void ImFont::BuildLookupTable() + +ImFont::~ImFont() { - int max_codepoint = 0; - for (int i = 0; i != Glyphs.Size; i++) - max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint); - // Build lookup table - IM_ASSERT(Glyphs.Size > 0 && "Font has not loaded glyph!"); - IM_ASSERT(Glyphs.Size < 0xFFFF); // -1 is reserved - IndexAdvanceX.clear(); - IndexLookup.clear(); - DirtyLookupTables = false; - memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); - GrowIndex(max_codepoint + 1); - for (int i = 0; i < Glyphs.Size; i++) - { - int codepoint = (int)Glyphs[i].Codepoint; - IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX; - IndexLookup[codepoint] = (ImU16)i; - // Mark 4K page as used - const int page_n = codepoint / 8192; - Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7); - } - // Create a glyph to handle TAB - // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) - if (FindGlyph((ImWchar)' ')) - { - if (Glyphs.back().Codepoint != '\t') // So we can call this function multiple times (FIXME: Flaky) - Glyphs.resize(Glyphs.Size + 1); - ImFontGlyph& tab_glyph = Glyphs.back(); - tab_glyph = *FindGlyph((ImWchar)' '); - tab_glyph.Codepoint = '\t'; - tab_glyph.AdvanceX *= IM_TABSIZE; - IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX; - IndexLookup[(int)tab_glyph.Codepoint] = (ImU16)(Glyphs.Size - 1); - } - // Mark special glyphs as not visible (note that AddGlyph already mark as non-visible glyphs with zero-size polygons) - if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)' ')) - glyph->Visible = false; - if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)'\t')) - glyph->Visible = false; - // Setup Fallback character - const ImWchar fallback_chars[] = { (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; - FallbackGlyph = FindGlyphNoFallback(FallbackChar); - if (FallbackGlyph == NULL) - { - FallbackChar = FindFirstExistingGlyph(this, fallback_chars, IM_ARRAYSIZE(fallback_chars)); - FallbackGlyph = FindGlyphNoFallback(FallbackChar); - if (FallbackGlyph == NULL) - { - FallbackGlyph = &Glyphs.back(); - FallbackChar = (ImWchar)FallbackGlyph->Codepoint; - } - } - FallbackAdvanceX = FallbackGlyph->AdvanceX; - for (int i = 0; i < max_codepoint + 1; i++) - if (IndexAdvanceX[i] < 0.0f) - IndexAdvanceX[i] = FallbackAdvanceX; - // Setup Ellipsis character. It is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). - // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character. - // FIXME: Note that 0x2026 is rarely included in our font ranges. Because of this we are more likely to use three individual dots. - const ImWchar ellipsis_chars[] = { Sources->EllipsisChar, (ImWchar)0x2026, (ImWchar)0x0085 }; - const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E }; - if (EllipsisChar == 0) - EllipsisChar = FindFirstExistingGlyph(this, ellipsis_chars, IM_ARRAYSIZE(ellipsis_chars)); - const ImWchar dot_char = FindFirstExistingGlyph(this, dots_chars, IM_ARRAYSIZE(dots_chars)); - if (EllipsisChar != 0) - { - EllipsisCharCount = 1; - EllipsisWidth = EllipsisCharStep = FindGlyph(EllipsisChar)->X1; - } - else if (dot_char != 0) - { - const ImFontGlyph* dot_glyph = FindGlyph(dot_char); - EllipsisChar = dot_char; - EllipsisCharCount = 3; - EllipsisCharStep = (float)(int)(dot_glyph->X1 - dot_glyph->X0) + 1.0f; - EllipsisWidth = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + EllipsisCharStep * 3.0f - 1.0f); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents. - } + ClearOutputData(); } + +void ImFont::ClearOutputData() +{ + if (ImFontAtlas* atlas = ContainerAtlas) + ImFontAtlasFontDiscardBakes(atlas, this, 0); + FallbackChar = EllipsisChar = 0; + memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); + LastBaked = NULL; +} + // API is designed this way to avoid exposing the 8K page size // e.g. use with IsGlyphRangeUnused(0, 255) bool ImFont::IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last) @@ -3430,89 +5105,244 @@ bool ImFont::IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last) return false; return true; } -void ImFont::GrowIndex(int new_size) -{ - IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); - if (new_size <= IndexLookup.Size) - return; - IndexAdvanceX.resize(new_size, -1.0f); - IndexLookup.resize(new_size, (ImU16)-1); -} + // x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero. // Not to be mistaken with texture coordinates, which are held by u0/v0/u1/v1 in normalized format (0.0..1.0 on each texture axis). -// 'src' is not necessarily == 'this->Sources' because multiple source fonts+configs can be used to build one target font. -void ImFont::AddGlyph(const ImFontConfig* src, ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x) +// - 'src' is not necessarily == 'this->Sources' because multiple source fonts+configs can be used to build one target font. +ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph) { + int glyph_idx = baked->Glyphs.Size; + baked->Glyphs.push_back(*in_glyph); + ImFontGlyph* glyph = &baked->Glyphs[glyph_idx]; + IM_ASSERT(baked->Glyphs.Size < 0xFFFE); // IndexLookup[] hold 16-bit values and -1/-2 are reserved. + + // Set UV from packed rectangle + if (glyph->PackId != ImFontAtlasRectId_Invalid) + { + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, glyph->PackId); + IM_ASSERT(glyph->U0 == 0.0f && glyph->V0 == 0.0f && glyph->U1 == 0.0f && glyph->V1 == 0.0f); + glyph->U0 = (r->x) * atlas->TexUvScale.x; + glyph->V0 = (r->y) * atlas->TexUvScale.y; + glyph->U1 = (r->x + r->w) * atlas->TexUvScale.x; + glyph->V1 = (r->y + r->h) * atlas->TexUvScale.y; + baked->MetricsTotalSurface += r->w * r->h; + } + if (src != NULL) { // Clamp & recenter if needed - const float advance_x_original = advance_x; - advance_x = ImClamp(advance_x, src->GlyphMinAdvanceX, src->GlyphMaxAdvanceX); - if (advance_x != advance_x_original) + const float ref_size = baked->ContainerFont->Sources[0]->SizePixels; + const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f; + float advance_x = ImClamp(glyph->AdvanceX, src->GlyphMinAdvanceX * offsets_scale, src->GlyphMaxAdvanceX * offsets_scale); + if (advance_x != glyph->AdvanceX) { - float char_off_x = src->PixelSnapH ? ImTrunc((advance_x - advance_x_original) * 0.5f) : (advance_x - advance_x_original) * 0.5f; - x0 += char_off_x; - x1 += char_off_x; + float char_off_x = src->PixelSnapH ? ImTrunc((advance_x - glyph->AdvanceX) * 0.5f) : (advance_x - glyph->AdvanceX) * 0.5f; + glyph->X0 += char_off_x; + glyph->X1 += char_off_x; } + // Snap to pixel if (src->PixelSnapH) advance_x = IM_ROUND(advance_x); - // Bake extra spacing + + // Bake spacing + glyph->AdvanceX = advance_x + src->GlyphExtraAdvanceX; + } + if (glyph->Colored) + atlas->TexPixelsUseColors = atlas->TexData->UseColors = true; + + // Update lookup tables + const int codepoint = glyph->Codepoint; + ImFontBaked_BuildGrowIndex(baked, codepoint + 1); + baked->IndexAdvanceX[codepoint] = glyph->AdvanceX; + baked->IndexLookup[codepoint] = (ImU16)glyph_idx; + const int page_n = codepoint / 8192; + baked->ContainerFont->Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7); + + return glyph; +} + +// FIXME: Code is duplicated with code above. +void ImFontAtlasBakedAddFontGlyphAdvancedX(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImWchar codepoint, float advance_x) +{ + IM_UNUSED(atlas); + if (src != NULL) + { + // Clamp & recenter if needed + const float ref_size = baked->ContainerFont->Sources[0]->SizePixels; + const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f; + advance_x = ImClamp(advance_x, src->GlyphMinAdvanceX * offsets_scale, src->GlyphMaxAdvanceX * offsets_scale); + + // Snap to pixel + if (src->PixelSnapH) + advance_x = IM_ROUND(advance_x); + + // Bake spacing advance_x += src->GlyphExtraAdvanceX; } - int glyph_idx = Glyphs.Size; - Glyphs.resize(Glyphs.Size + 1); - ImFontGlyph& glyph = Glyphs[glyph_idx]; - glyph.Codepoint = (unsigned int)codepoint; - glyph.Visible = (x0 != x1) && (y0 != y1); - glyph.Colored = false; - glyph.X0 = x0; - glyph.Y0 = y0; - glyph.X1 = x1; - glyph.Y1 = y1; - glyph.U0 = u0; - glyph.V0 = v0; - glyph.U1 = u1; - glyph.V1 = v1; - glyph.AdvanceX = advance_x; - IM_ASSERT(Glyphs.Size < 0xFFFF); // IndexLookup[] hold 16-bit values and -1 is reserved. - // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round) - // We use (U1-U0)*TexWidth instead of X1-X0 to account for oversampling. - float pad = ContainerAtlas->TexGlyphPadding + 0.99f; - DirtyLookupTables = true; - MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * ContainerAtlas->TexWidth + pad) * (int)((glyph.V1 - glyph.V0) * ContainerAtlas->TexHeight + pad); + + ImFontBaked_BuildGrowIndex(baked, codepoint + 1); + baked->IndexAdvanceX[codepoint] = advance_x; } -void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) + +// Copy to texture, post-process and queue update for backend +void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImTextureRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch) { - IM_ASSERT(IndexLookup.Size > 0); // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function. - unsigned int index_size = (unsigned int)IndexLookup.Size; - if (dst < index_size && IndexLookup.Data[dst] == (ImU16)-1 && !overwrite_dst) // 'dst' already exists - return; - if (src >= index_size && dst >= index_size) // both 'dst' and 'src' don't exist -> no-op - return; - GrowIndex(dst + 1); - IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (ImU16)-1; - IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f; + ImTextureData* tex = atlas->TexData; + IM_ASSERT(r->x + r->w <= tex->Width && r->y + r->h <= tex->Height); + ImFontAtlasTextureBlockConvert(src_pixels, src_fmt, src_pitch, (unsigned char*)tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), r->w, r->h); + ImFontAtlasPostProcessData pp_data = { atlas, baked->ContainerFont, src, baked, glyph, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), r->w, r->h }; + ImFontAtlasTextureBlockPostProcess(&pp_data); + ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); } -// Find glyph, return fallback if missing -ImFontGlyph* ImFont::FindGlyph(ImWchar c) + +void ImFont::AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint) { - if (c >= (size_t)IndexLookup.Size) - return FallbackGlyph; - const ImU16 i = IndexLookup.Data[c]; - if (i == (ImU16)-1) - return FallbackGlyph; - return &Glyphs.Data[i]; + RemapPairs.SetInt((ImGuiID)from_codepoint, (int)to_codepoint); } -ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) + +// Find glyph, load if necessary, return fallback if missing +ImFontGlyph* ImFontBaked::FindGlyph(ImWchar c) { - if (c >= (size_t)IndexLookup.Size) + if (c < (size_t)IndexLookup.Size) IM_LIKELY + { + const int i = (int)IndexLookup.Data[c]; + if (i == IM_FONTGLYPH_INDEX_NOT_FOUND) + return &Glyphs.Data[FallbackGlyphIndex]; + if (i != IM_FONTGLYPH_INDEX_UNUSED) + return &Glyphs.Data[i]; + } + ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(this, c, NULL); + return glyph ? glyph : &Glyphs.Data[FallbackGlyphIndex]; +} + +// Attempt to load but when missing, return NULL instead of FallbackGlyph +ImFontGlyph* ImFontBaked::FindGlyphNoFallback(ImWchar c) +{ + if (c < (size_t)IndexLookup.Size) IM_LIKELY + { + const int i = (int)IndexLookup.Data[c]; + if (i == IM_FONTGLYPH_INDEX_NOT_FOUND) + return NULL; + if (i != IM_FONTGLYPH_INDEX_UNUSED) + return &Glyphs.Data[i]; + } + LoadNoFallback = true; // This is actually a rare call, not done in hot-loop, so we prioritize not adding extra cruft to ImFontBaked_BuildLoadGlyph() call sites. + ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(this, c, NULL); + LoadNoFallback = false; + return glyph; +} + +bool ImFontBaked::IsGlyphLoaded(ImWchar c) +{ + if (c < (size_t)IndexLookup.Size) IM_LIKELY + { + const int i = (int)IndexLookup.Data[c]; + if (i == IM_FONTGLYPH_INDEX_NOT_FOUND) + return false; + if (i != IM_FONTGLYPH_INDEX_UNUSED) + return true; + } + return false; +} + +// This is not fast query +bool ImFont::IsGlyphInFont(ImWchar c) +{ + ImFontAtlas* atlas = ContainerAtlas; + ImFontAtlas_FontHookRemapCodepoint(atlas, this, &c); + for (ImFontConfig* src : Sources) + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontSrcContainsGlyph != NULL && loader->FontSrcContainsGlyph(atlas, src, c)) + return true; + } + return false; +} + +// This is manually inlined in CalcTextSizeA() and CalcWordWrapPosition(), with a non-inline call to BuildLoadGlyphGetAdvanceOrFallback(). +IM_MSVC_RUNTIME_CHECKS_OFF +float ImFontBaked::GetCharAdvance(ImWchar c) +{ + if ((int)c < IndexAdvanceX.Size) + { + // Missing glyphs fitting inside index will have stored FallbackAdvanceX already. + const float x = IndexAdvanceX.Data[c]; + if (x >= 0.0f) + return x; + } + return ImFontBaked_BuildLoadGlyphAdvanceX(this, c); +} +IM_MSVC_RUNTIME_CHECKS_RESTORE + +ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size, float rasterizer_density) +{ + struct { ImGuiID FontId; float BakedSize; float RasterizerDensity; } hashed_data; + hashed_data.FontId = font_id; + hashed_data.BakedSize = baked_size; + hashed_data.RasterizerDensity = rasterizer_density; + return ImHashData(&hashed_data, sizeof(hashed_data)); +} + +// ImFontBaked pointers are valid for the entire frame but shall never be kept between frames. +ImFontBaked* ImFont::GetFontBaked(float size, float density) +{ + ImFontBaked* baked = LastBaked; + + // Round font size + // - ImGui::PushFont() will already round, but other paths calling GetFontBaked() directly also needs it (e.g. ImFontAtlasBuildPreloadAllGlyphRanges) + size = ImGui::GetRoundedFontSize(size); + + if (density < 0.0f) + density = CurrentRasterizerDensity; + if (baked && baked->Size == size && baked->RasterizerDensity == density) + return baked; + + ImFontAtlas* atlas = ContainerAtlas; + ImFontAtlasBuilder* builder = atlas->Builder; + baked = ImFontAtlasBakedGetOrAdd(atlas, this, size, density); + if (baked == NULL) return NULL; - const ImU16 i = IndexLookup.Data[c]; - if (i == (ImU16)-1) - return NULL; - return &Glyphs.Data[i]; + baked->LastUsedFrame = builder->FrameCount; + LastBaked = baked; + return baked; } + +ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density) +{ + // FIXME-NEWATLAS: Design for picking a nearest size based on some criteria? + // FIXME-NEWATLAS: Altering font density won't work right away. + IM_ASSERT(font_size > 0.0f && font_rasterizer_density > 0.0f); + ImGuiID baked_id = ImFontAtlasBakedGetId(font->FontId, font_size, font_rasterizer_density); + ImFontAtlasBuilder* builder = atlas->Builder; + ImFontBaked** p_baked_in_map = (ImFontBaked**)builder->BakedMap.GetVoidPtrRef(baked_id); + ImFontBaked* baked = *p_baked_in_map; + if (baked != NULL) + { + IM_ASSERT(baked->Size == font_size && baked->ContainerFont == font && baked->BakedId == baked_id); + return baked; + } + + // If atlas is locked, find closest match + // FIXME-OPT: This is not an optimal query. + if ((font->Flags & ImFontFlags_LockBakedSizes) || atlas->Locked) + { + baked = ImFontAtlasBakedGetClosestMatch(atlas, font, font_size, font_rasterizer_density); + if (baked != NULL) + return baked; + if (atlas->Locked) + { + IM_ASSERT(!atlas->Locked && "Cannot use dynamic font size with a locked ImFontAtlas!"); // Locked because rendering backend does not support ImGuiBackendFlags_RendererHasTextures! + return NULL; + } + } + + // Create new + baked = ImFontAtlasBakedAdd(atlas, font, font_size, font_rasterizer_density, baked_id); + *p_baked_in_map = baked; // To avoid 'builder->BakedMap.SetVoidPtr(baked_id, baked);' while we can. + return baked; +} + // Trim trailing space and find beginning of next line static inline const char* CalcWordWrapNextLineStartA(const char* text, const char* text_end) { @@ -3522,27 +5352,36 @@ static inline const char* CalcWordWrapNextLineStartA(const char* text, const cha text++; return text; } -#define ImFontGetCharAdvanceX(_FONT, _CH) ((int)(_CH) < (_FONT)->IndexAdvanceX.Size ? (_FONT)->IndexAdvanceX.Data[_CH] : (_FONT)->FallbackAdvanceX) + // Simple word-wrapping for English, not full-featured. Please submit failing cases! // This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end. // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) -const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) +const char* ImFont::CalcWordWrapPosition(float size, const char* text, const char* text_end, float wrap_width) { // For references, possible wrap point marked with ^ // "aaa bbb, ccc,ddd. eee fff. ggg!" // ^ ^ ^ ^ ^__ ^ ^ + // List of hardcoded separators: .,;!?'" + // Skip extra blanks after a line returns (that includes not counting them in width computation) // e.g. "Hello world" --> "Hello" "World" + // Cut words that cannot possibly fit within one line. // e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish" + + ImFontBaked* baked = GetFontBaked(size); + const float scale = size / baked->Size; + float line_width = 0.0f; float word_width = 0.0f; float blank_width = 0.0f; wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters + const char* word_end = text; const char* prev_word_end = NULL; bool inside_word = true; + const char* s = text; IM_ASSERT(text_end != NULL); while (s < text_end) @@ -3553,6 +5392,7 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c next_s = s + 1; else next_s = s + ImTextCharFromUtf8(&c, s, text_end); + if (c < 32) { if (c == '\n') @@ -3568,7 +5408,12 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c continue; } } - const float char_width = ImFontGetCharAdvanceX(this, c); + + // Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);' + float char_width = (c < (unsigned int)baked->IndexAdvanceX.Size) ? baked->IndexAdvanceX.Data[c] : -1.0f; + if (char_width < 0.0f) + char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c); + if (ImCharIsBlankW(c)) { if (inside_word) @@ -3593,9 +5438,11 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c line_width += word_width + blank_width; word_width = blank_width = 0.0f; } + // Allow wrapping after punctuation. - inside_word = (c != '.' && c != ',' && c != ';' && c != '!' && c != '?' && c != '\"'); + inside_word = (c != '.' && c != ',' && c != ';' && c != '!' && c != '?' && c != '\"' && c != 0x3001 && c != 0x3002); } + // We ignore blank width at the end of the line (they can be skipped) if (line_width + word_width > wrap_width) { @@ -3604,24 +5451,32 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c s = prev_word_end ? prev_word_end : word_end; break; } + s = next_s; } + // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. // +1 may not be a character start point in UTF-8 but it's ok because caller loops use (text >= word_wrap_eol). if (s == text && text < text_end) - return s + 1; + return s + ImTextCountUtf8BytesFromChar(s, text_end); return s; } + ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) { if (!text_end) text_end = text_begin + ImStrlen(text_begin); // FIXME-OPT: Need to avoid this. + const float line_height = size; - const float scale = size / FontSize; + ImFontBaked* baked = GetFontBaked(size); + const float scale = size / baked->Size; + ImVec2 text_size = ImVec2(0, 0); float line_width = 0.0f; + const bool word_wrap_enabled = (wrap_width > 0.0f); const char* word_wrap_eol = NULL; + const char* s = text_begin; while (s < text_end) { @@ -3629,7 +5484,8 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons { // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. if (!word_wrap_eol) - word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - line_width); + word_wrap_eol = CalcWordWrapPosition(size, s, text_end, wrap_width - line_width); + if (s >= word_wrap_eol) { if (text_size.x < line_width) @@ -3641,6 +5497,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons continue; } } + // Decode and advance source const char* prev_s = s; unsigned int c = (unsigned int)*s; @@ -3648,6 +5505,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons s += 1; else s += ImTextCharFromUtf8(&c, s, text_end); + if (c < 32) { if (c == '\n') @@ -3660,50 +5518,93 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons if (c == '\r') continue; } - const float char_width = ImFontGetCharAdvanceX(this, c) * scale; + + // Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);' + float char_width = (c < (unsigned int)baked->IndexAdvanceX.Size) ? baked->IndexAdvanceX.Data[c] : -1.0f; + if (char_width < 0.0f) + char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c); + char_width *= scale; + if (line_width + char_width >= max_width) { s = prev_s; break; } + line_width += char_width; } + if (text_size.x < line_width) text_size.x = line_width; + if (line_width > 0 || text_size.y == 0.0f) text_size.y += line_height; + if (remaining) *remaining = s; + return text_size; } + // Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. -void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c) +void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c, const ImVec4* cpu_fine_clip) { - const ImFontGlyph* glyph = FindGlyph(c); + ImFontBaked* baked = GetFontBaked(size); + const ImFontGlyph* glyph = baked->FindGlyph(c); if (!glyph || !glyph->Visible) return; if (glyph->Colored) col |= ~IM_COL32_A_MASK; - float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f; + float scale = (size >= 0.0f) ? (size / baked->Size) : 1.0f; float x = IM_TRUNC(pos.x); float y = IM_TRUNC(pos.y); + + float x1 = x + glyph->X0 * scale; + float x2 = x + glyph->X1 * scale; + if (cpu_fine_clip && (x1 > cpu_fine_clip->z || x2 < cpu_fine_clip->x)) + return; + float y1 = y + glyph->Y0 * scale; + float y2 = y + glyph->Y1 * scale; + float u1 = glyph->U0; + float v1 = glyph->V0; + float u2 = glyph->U1; + float v2 = glyph->V1; + + // Always CPU fine clip. Code extracted from RenderText(). + // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads. + if (cpu_fine_clip != NULL) + { + if (x1 < cpu_fine_clip->x) { u1 = u1 + (1.0f - (x2 - cpu_fine_clip->x) / (x2 - x1)) * (u2 - u1); x1 = cpu_fine_clip->x; } + if (y1 < cpu_fine_clip->y) { v1 = v1 + (1.0f - (y2 - cpu_fine_clip->y) / (y2 - y1)) * (v2 - v1); y1 = cpu_fine_clip->y; } + if (x2 > cpu_fine_clip->z) { u2 = u1 + ((cpu_fine_clip->z - x1) / (x2 - x1)) * (u2 - u1); x2 = cpu_fine_clip->z; } + if (y2 > cpu_fine_clip->w) { v2 = v1 + ((cpu_fine_clip->w - y1) / (y2 - y1)) * (v2 - v1); y2 = cpu_fine_clip->w; } + if (y1 >= y2) + return; + } draw_list->PrimReserve(6, 4); - draw_list->PrimRectUV(ImVec2(x + glyph->X0 * scale, y + glyph->Y0 * scale), ImVec2(x + glyph->X1 * scale, y + glyph->Y1 * scale), ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V1), col); + draw_list->PrimRectUV(ImVec2(x1, y1), ImVec2(x2, y2), ImVec2(u1, v1), ImVec2(u2, v2), col); } + // Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) { // Align to be pixel perfect +begin: float x = IM_TRUNC(pos.x); float y = IM_TRUNC(pos.y); if (y > clip_rect.w) return; + if (!text_end) text_end = text_begin + ImStrlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls. - const float scale = size / FontSize; - const float line_height = FontSize * scale; + + const float line_height = size; + ImFontBaked* baked = GetFontBaked(size); + + const float scale = size / baked->Size; const float origin_x = x; const bool word_wrap_enabled = (wrap_width > 0.0f); + // Fast-forward to first visible line const char* s = text_begin; if (y + line_height < clip_rect.y) @@ -3712,10 +5613,10 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im const char* line_end = (const char*)ImMemchr(s, '\n', text_end - s); if (word_wrap_enabled) { - // FIXME-OPT: This is not optimal as do first do a search for \n before calling CalcWordWrapPositionA(). - // If the specs for CalcWordWrapPositionA() were reworked to optionally return on \n we could combine both. + // FIXME-OPT: This is not optimal as do first do a search for \n before calling CalcWordWrapPosition(). + // If the specs for CalcWordWrapPosition() were reworked to optionally return on \n we could combine both. // However it is still better than nothing performing the fast-forward! - s = CalcWordWrapPositionA(scale, s, line_end ? line_end : text_end, wrap_width); + s = CalcWordWrapPosition(size, s, line_end ? line_end : text_end, wrap_width); s = CalcWordWrapNextLineStartA(s, text_end); } else @@ -3724,6 +5625,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im } y += line_height; } + // For large text, scan for the last visible line in order to avoid over-reserving in the call to PrimReserve() // Note that very large horizontal line will still be affected by the issue (e.g. a one megabyte string buffer without a newline will likely crash atm) if (text_end - s > 10000 && !word_wrap_enabled) @@ -3740,6 +5642,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im } if (s == text_end) return; + // Reserve vertices for remaining worse case (over-reserving is useful and easily amortized) const int vtx_count_max = (int)(text_end - s) * 4; const int idx_count_max = (int)(text_end - s) * 6; @@ -3748,15 +5651,19 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im ImDrawVert* vtx_write = draw_list->_VtxWritePtr; ImDrawIdx* idx_write = draw_list->_IdxWritePtr; unsigned int vtx_index = draw_list->_VtxCurrentIdx; + const int cmd_count = draw_list->CmdBuffer.Size; + const ImU32 col_untinted = col | ~IM_COL32_A_MASK; const char* word_wrap_eol = NULL; + while (s < text_end) { if (word_wrap_enabled) { // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. if (!word_wrap_eol) - word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - origin_x)); + word_wrap_eol = CalcWordWrapPosition(size, s, text_end, wrap_width - (x - origin_x)); + if (s >= word_wrap_eol) { x = origin_x; @@ -3768,12 +5675,14 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im continue; } } + // Decode and advance source unsigned int c = (unsigned int)*s; if (c < 0x80) s += 1; else s += ImTextCharFromUtf8(&c, s, text_end); + if (c < 32) { if (c == '\n') @@ -3787,9 +5696,11 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im if (c == '\r') continue; } - const ImFontGlyph* glyph = FindGlyph((ImWchar)c); - if (glyph == NULL) - continue; + + const ImFontGlyph* glyph = baked->FindGlyph((ImWchar)c); + //if (glyph == NULL) + // continue; + float char_width = glyph->AdvanceX * scale; if (glyph->Visible) { @@ -3805,6 +5716,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im float v1 = glyph->V0; float u2 = glyph->U1; float v2 = glyph->V1; + // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads. if (cpu_fine_clip) { @@ -3834,8 +5746,10 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im continue; } } + // Support for untinted glyphs ImU32 glyph_col = glyph->Colored ? col_untinted : col; + // We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug builds. Inlined here: { vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; vtx_write[0].col = glyph_col; vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v1; @@ -3852,6 +5766,21 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im } x += char_width; } + + // Edge case: calling RenderText() with unloaded glyphs triggering texture change. It doesn't happen via ImGui:: calls because CalcTextSize() is always used. + if (cmd_count != draw_list->CmdBuffer.Size) //-V547 + { + IM_ASSERT(draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ElemCount == 0); + draw_list->CmdBuffer.pop_back(); + draw_list->PrimUnreserve(idx_count_max, vtx_count_max); + draw_list->AddDrawCmd(); + //IMGUI_DEBUG_LOG("RenderText: cancel and retry to missing glyphs.\n"); // [DEBUG] + //draw_list->AddRectFilled(pos, pos + ImVec2(10, 10), IM_COL32(255, 0, 0, 255)); // [DEBUG] + goto begin; + //RenderText(draw_list, size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip); // FIXME-OPT: Would a 'goto begin' be better for code-gen? + //return; + } + // Give back unused vertices (clipped ones, blanks) ~ this is essentially a PrimUnreserve() action. draw_list->VtxBuffer.Size = (int)(vtx_write - draw_list->VtxBuffer.Data); // Same as calling shrink() draw_list->IdxBuffer.Size = (int)(idx_write - draw_list->IdxBuffer.Data); @@ -3860,6 +5789,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im draw_list->_IdxWritePtr = idx_write; draw_list->_VtxCurrentIdx = vtx_index; } + //----------------------------------------------------------------------------- // [SECTION] ImGui Internal Render Helpers //----------------------------------------------------------------------------- @@ -3875,12 +5805,14 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im // Function in need of a redesign (legacy mess) // - RenderColorRectWithAlphaCheckerboard() //----------------------------------------------------------------------------- + // Render an arrow aimed to be aligned with text (p_min is a position in the same space text would be positioned). To e.g. denote expanded/collapsed state void ImGui::RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImU32 col, ImGuiDir dir, float scale) { const float h = draw_list->_Data->FontSize * 1.00f; float r = h * 0.40f * scale; ImVec2 center = pos + ImVec2(h * 0.50f, h * 0.50f * scale); + ImVec2 a, b, c; switch (dir) { @@ -3905,16 +5837,19 @@ void ImGui::RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImU32 col, ImGuiDir d } draw_list->AddTriangleFilled(center + a, center + b, center + c, col); } + void ImGui::RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col) { // FIXME-OPT: This should be baked in font. draw_list->AddCircleFilled(pos, draw_list->_Data->FontSize * 0.20f, col, 8); } + void ImGui::RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz) { float thickness = ImMax(sz / 5.0f, 1.0f); sz -= thickness * 0.5f; pos += ImVec2(thickness * 0.25f, thickness * 0.25f); + float third = sz / 3.0f; float bx = pos.x + third; float by = pos.y + sz - third * 0.5f; @@ -3923,6 +5858,7 @@ void ImGui::RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float draw_list->PathLineTo(ImVec2(bx + third * 2.0f, by - third * 2.0f)); draw_list->PathStroke(col, 0, thickness); } + // Render an arrow. 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side. void ImGui::RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col) { @@ -3935,6 +5871,7 @@ void ImGui::RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half case ImGuiDir_None: case ImGuiDir_COUNT: break; // Fix warnings } } + // This is less wide than RenderArrow() and we use in dock nodes instead of the regular RenderArrow() to denote a change of functionality, // and because the saved space means that the left-most tab label can stay at exactly the same position as the label of a loose window. void ImGui::RenderArrowDockMenu(ImDrawList* draw_list, ImVec2 p_min, float sz, ImU32 col) @@ -3942,6 +5879,7 @@ void ImGui::RenderArrowDockMenu(ImDrawList* draw_list, ImVec2 p_min, float sz, I draw_list->AddRectFilled(p_min + ImVec2(sz * 0.20f, sz * 0.15f), p_min + ImVec2(sz * 0.80f, sz * 0.30f), col); RenderArrowPointingAt(draw_list, p_min + ImVec2(sz * 0.50f, sz * 0.85f), ImVec2(sz * 0.30f, sz * 0.40f), ImGuiDir_Down, col); } + static inline float ImAcos01(float x) { if (x <= 0.0f) return IM_PI * 0.5f; @@ -3949,6 +5887,7 @@ static inline float ImAcos01(float x) return ImAcos(x); //return (-0.69813170079773212f * x * x - 0.87266462599716477f) * x + 1.5707963267948966f; // Cheap approximation, may be enough for what we do. } + // FIXME: Cleanup and move code to ImDrawList. void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding) { @@ -3956,6 +5895,7 @@ void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, Im return; if (x_start_norm > x_end_norm) ImSwap(x_start_norm, x_end_norm); + ImVec2 p0 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_start_norm), rect.Min.y); ImVec2 p1 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_end_norm), rect.Max.y); if (rounding == 0.0f) @@ -3963,6 +5903,7 @@ void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, Im draw_list->AddRectFilled(p0, p1, col, 0.0f); return; } + rounding = ImClamp(ImMin((rect.Max.x - rect.Min.x) * 0.5f, (rect.Max.y - rect.Min.y) * 0.5f) - 1.0f, 0.0f, rounding); const float inv_rounding = 1.0f / rounding; const float arc0_b = ImAcos01(1.0f - (p0.x - rect.Min.x) * inv_rounding); @@ -4007,6 +5948,7 @@ void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, Im } draw_list->PathFillConvex(col); } + void ImGui::RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding) { const bool fill_L = (inner.Min.x > outer.Min.x); @@ -4022,6 +5964,7 @@ void ImGui::RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, if (fill_L && fill_D) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Max.y), ImVec2(inner.Min.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersBottomLeft); if (fill_R && fill_D) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Max.y), ImVec2(outer.Max.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersBottomRight); } + ImDrawFlags ImGui::CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRect& r_outer, float threshold) { bool round_l = r_in.Min.x <= r_outer.Min.x + threshold; @@ -4032,6 +5975,7 @@ ImDrawFlags ImGui::CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRe | ((round_t && round_l) ? ImDrawFlags_RoundCornersTopLeft : 0) | ((round_t && round_r) ? ImDrawFlags_RoundCornersTopRight : 0) | ((round_b && round_l) ? ImDrawFlags_RoundCornersBottomLeft : 0) | ((round_b && round_r) ? ImDrawFlags_RoundCornersBottomRight : 0); } + // Helper for ColorPicker4() // NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that. // Spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding altogether. @@ -4045,6 +5989,7 @@ void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p ImU32 col_bg1 = GetColorU32(ImAlphaBlendColors(IM_COL32(204, 204, 204, 255), col)); ImU32 col_bg2 = GetColorU32(ImAlphaBlendColors(IM_COL32(128, 128, 128, 255), col)); draw_list->AddRectFilled(p_min, p_max, col_bg1, rounding, flags); + int yi = 0; for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++) { @@ -4059,6 +6004,7 @@ void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p ImDrawFlags cell_flags = ImDrawFlags_RoundCornersNone; if (y1 <= p_min.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersTopLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersTopRight; } if (y2 >= p_max.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersBottomLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersBottomRight; } + // Combine flags cell_flags = (flags == ImDrawFlags_RoundCornersNone || cell_flags == ImDrawFlags_RoundCornersNone) ? ImDrawFlags_RoundCornersNone : (cell_flags & flags); draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col_bg2, rounding, cell_flags); @@ -4070,6 +6016,7 @@ void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p draw_list->AddRectFilled(p_min, p_max, col, rounding, flags); } } + //----------------------------------------------------------------------------- // [SECTION] Decompression code //----------------------------------------------------------------------------- @@ -4078,10 +6025,12 @@ void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p // The purpose of encoding as base85 instead of "0x00,0x01,..." style is only save on _source code_ size. // Decompression from stb.h (public domain) by Sean Barrett https://github.com/nothings/stb/blob/master/stb.h //----------------------------------------------------------------------------- + static unsigned int stb_decompress_length(const unsigned char *input) { return (input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11]; } + static unsigned char *stb__barrier_out_e, *stb__barrier_out_b; static const unsigned char *stb__barrier_in_b; static unsigned char *stb__dout; @@ -4093,6 +6042,7 @@ static void stb__match(const unsigned char *data, unsigned int length) if (data < stb__barrier_out_b) { stb__dout = stb__barrier_out_e+1; return; } while (length--) *stb__dout++ = *data++; } + static void stb__lit(const unsigned char *data, unsigned int length) { IM_ASSERT(stb__dout + length <= stb__barrier_out_e); @@ -4101,9 +6051,11 @@ static void stb__lit(const unsigned char *data, unsigned int length) memcpy(stb__dout, data, length); stb__dout += length; } + #define stb__in2(x) ((i[x] << 8) + i[(x)+1]) #define stb__in3(x) ((i[x] << 16) + stb__in2((x)+1)) #define stb__in4(x) ((i[x] << 24) + stb__in3((x)+1)) + static const unsigned char *stb_decompress_token(const unsigned char *i) { if (*i >= 0x20) { // use fewer if's for cases that expand small @@ -4120,11 +6072,13 @@ static const unsigned char *stb_decompress_token(const unsigned char *i) } return i; } + static unsigned int stb_adler32(unsigned int adler32, unsigned char *buffer, unsigned int buflen) { const unsigned long ADLER_MOD = 65521; unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16; unsigned long blocklen = buflen % 5552; + unsigned long i; while (buflen) { for (i=0; i + 7 < blocklen; i += 8) { @@ -4136,16 +6090,20 @@ static unsigned int stb_adler32(unsigned int adler32, unsigned char *buffer, uns s1 += buffer[5], s2 += s1; s1 += buffer[6], s2 += s1; s1 += buffer[7], s2 += s1; + buffer += 8; } + for (; i < blocklen; ++i) s1 += *buffer++, s2 += s1; + s1 %= ADLER_MOD, s2 %= ADLER_MOD; buflen -= blocklen; blocklen = 5552; } return (unsigned int)(s2 << 16) + (unsigned int)s1; } + static unsigned int stb_decompress(unsigned char *output, const unsigned char *i, unsigned int /*length*/) { if (stb__in4(0) != 0x57bC0000) return 0; @@ -4155,6 +6113,7 @@ static unsigned int stb_decompress(unsigned char *output, const unsigned char *i stb__barrier_out_e = output + olen; stb__barrier_out_b = output; i += 16; + stb__dout = output; for (;;) { const unsigned char *old_i = i; @@ -4176,6 +6135,7 @@ static unsigned int stb_decompress(unsigned char *output, const unsigned char *i return 0; } } + //----------------------------------------------------------------------------- // [SECTION] Default font data (ProggyClean.ttf) //----------------------------------------------------------------------------- @@ -4184,7 +6144,9 @@ static unsigned int stb_decompress(unsigned char *output, const unsigned char *i // MIT license (see License.txt in http://www.proggyfonts.net/index.php?menu=download) // Download and more information at http://www.proggyfonts.net or http://upperboundsinteractive.com/fonts.php //----------------------------------------------------------------------------- + #ifndef IMGUI_DISABLE_DEFAULT_FONT + // File: 'ProggyClean.ttf' (41208 bytes) // Exported using binary_to_compressed_c.exe -u8 "ProggyClean.ttf" proggy_clean_ttf static const unsigned int proggy_clean_ttf_compressed_size = 9583; @@ -4357,10 +6319,12 @@ static const unsigned char proggy_clean_ttf_compressed_data[9583] = 32,56,141,236,32,56,141,236,32,56,65,220,13,32,57,65,220,13,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141, 239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,35,57,102,0,0,5,250,72,249,98,247, }; + static const char* GetDefaultCompressedFontDataTTF(int* out_size) { *out_size = proggy_clean_ttf_compressed_size; return (const char*)proggy_clean_ttf_compressed_data; } #endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT + #endif // #ifndef IMGUI_DISABLE diff --git a/external/reshade/deps/imgui/imgui_internal.h b/external/reshade/deps/imgui/imgui_internal.h index c1ef366..f9a284a 100644 --- a/external/reshade/deps/imgui/imgui_internal.h +++ b/external/reshade/deps/imgui/imgui_internal.h @@ -1,8 +1,12 @@ -// dear imgui, v1.91.9b +// dear imgui, v1.92.2b // (internal structures/api) + // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. + /* + Index of this file: + // [SECTION] Header mess // [SECTION] Forward declarations // [SECTION] Context pointer @@ -33,21 +37,28 @@ Index of this file: // [SECTION] Tab bar, Tab item support // [SECTION] Table support // [SECTION] ImGui internal API +// [SECTION] ImFontLoader // [SECTION] ImFontAtlas internal API // [SECTION] Test Engine specific hooks (imgui_test_engine) + */ + #pragma once #ifndef IMGUI_DISABLE + //----------------------------------------------------------------------------- // [SECTION] Header mess //----------------------------------------------------------------------------- + #ifndef IMGUI_VERSION #include "imgui.h" #endif + #include // FILE*, sscanf #include // NULL, malloc, free, qsort, atoi, atof #include // sqrtf, fabsf, fmodf, powf, floorf, ceilf, cosf, sinf #include // INT_MIN, INT_MAX + // Enable SSE intrinsics if available #if (defined __SSE__ || defined __x86_64__ || defined _M_X64 || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1))) && !defined(IMGUI_DISABLE_SSE) #define IMGUI_ENABLE_SSE @@ -61,6 +72,7 @@ Index of this file: #if defined(IMGUI_ENABLE_SSE4_2) && !defined(IMGUI_USE_LEGACY_CRC32_ADLER) && !defined(__EMSCRIPTEN__) #define IMGUI_ENABLE_SSE4_2_CRC #endif + // Visual Studio warnings #ifdef _MSC_VER #pragma warning (push) @@ -71,6 +83,7 @@ Index of this file: #pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types #endif #endif + // Clang/GCC warnings with -Weverything #if defined(__clang__) #pragma clang diagnostic push @@ -94,11 +107,13 @@ Index of this file: #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #endif + // In 1.89.4, we moved the implementation of "courtesy maths operators" from imgui_internal.h in imgui.h // As they are frequently requested, we do not want to encourage to many people using imgui_internal.h #if defined(IMGUI_DEFINE_MATH_OPERATORS) && !defined(IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED) #error Please '#define IMGUI_DEFINE_MATH_OPERATORS' _BEFORE_ including imgui.h! #endif + // Legacy defines #ifdef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS // Renamed in 1.74 #error Use IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS @@ -106,22 +121,30 @@ Index of this file: #ifdef IMGUI_DISABLE_MATH_FUNCTIONS // Renamed in 1.74 #error Use IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS #endif + // Enable stb_truetype by default unless FreeType is enabled. // You can compile with both by defining both IMGUI_ENABLE_FREETYPE and IMGUI_ENABLE_STB_TRUETYPE together. #ifndef IMGUI_ENABLE_FREETYPE #define IMGUI_ENABLE_STB_TRUETYPE #endif + //----------------------------------------------------------------------------- // [SECTION] Forward declarations //----------------------------------------------------------------------------- + // Utilities -// (other types which are not forwarded declared are: ImBitArray<>, ImSpan<>, ImSpanAllocator<>, ImPool<>, ImChunkStream<>) +// (other types which are not forwarded declared are: ImBitArray<>, ImSpan<>, ImSpanAllocator<>, ImStableVector<>, ImPool<>, ImChunkStream<>) struct ImBitVector; // Store 1-bit per value struct ImRect; // An axis-aligned rectangle (2 points) struct ImGuiTextIndex; // Maintain a line index for a text buffer. + // ImDrawList/ImFontAtlas struct ImDrawDataBuilder; // Helper to build a ImDrawData instance struct ImDrawListSharedData; // Data shared between all ImDrawList instances +struct ImFontAtlasBuilder; // Internal storage for incrementally packing and building a ImFontAtlas +struct ImFontAtlasPostProcessData; // Data available to potential texture post-processing functions +struct ImFontAtlasRectEntry; // Packed rectangle lookup entry + // ImGui struct ImGuiBoxSelectState; // Box-selection state (currently used by multi-selection, could potentially be used by others) struct ImGuiColorMod; // Stacked color modifier, backup of modified data so we can restore it @@ -168,11 +191,13 @@ struct ImGuiWindow; // Storage for one window struct ImGuiWindowDockStyle; // Storage for window-style data which needs to be stored for docking purpose struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame, in practice we currently keep it for each window) struct ImGuiWindowSettings; // Storage for a window .ini settings (we keep one of those even if the actual window wasn't instanced during this session) + // Enumerations // Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists. enum ImGuiLocKey : int; // -> enum ImGuiLocKey // Enum: a localization entry for translation. typedef int ImGuiDataAuthority; // -> enum ImGuiDataAuthority_ // Enum: for storing the source authority (dock node vs window) of a field typedef int ImGuiLayoutType; // -> enum ImGuiLayoutType_ // Enum: Horizontal or vertical + // Flags typedef int ImGuiActivateFlags; // -> enum ImGuiActivateFlags_ // Flags: for navigation/focus function (will be for ActivateItem() later) typedef int ImGuiDebugLogFlags; // -> enum ImGuiDebugLogFlags_ // Flags: for ShowDebugLogWindow(), g.DebugLogFlags @@ -190,18 +215,27 @@ typedef int ImGuiTextFlags; // -> enum ImGuiTextFlags_ // F typedef int ImGuiTooltipFlags; // -> enum ImGuiTooltipFlags_ // Flags: for BeginTooltipEx() typedef int ImGuiTypingSelectFlags; // -> enum ImGuiTypingSelectFlags_ // Flags: for GetTypingSelectRequest() typedef int ImGuiWindowRefreshFlags; // -> enum ImGuiWindowRefreshFlags_ // Flags: for SetNextWindowRefreshPolicy() + +// Table column indexing +typedef ImS16 ImGuiTableColumnIdx; +typedef ImU16 ImGuiTableDrawChannelIdx; + //----------------------------------------------------------------------------- // [SECTION] Context pointer // See implementation of this variable in imgui.cpp for comments and details. //----------------------------------------------------------------------------- + #ifndef GImGui extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #endif + //----------------------------------------------------------------------------- // [SECTION] Macros //----------------------------------------------------------------------------- + // Internal Drag and Drop payload types. String starting with '_' are reserved for Dear ImGui. #define IMGUI_PAYLOAD_TYPE_WINDOW "_IMWINDOW" // Payload == ImGuiWindow* + // Debug Printing Into TTY // (since IMGUI_VERSION_NUM >= 18729: IMGUI_DEBUG_LOG was reworked into IMGUI_DEBUG_PRINTF (and removed framecount from it). If you were using a #define IMGUI_DEBUG_LOG please rename) #ifndef IMGUI_DEBUG_PRINTF @@ -211,6 +245,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IMGUI_DEBUG_PRINTF(_FMT,...) ((void)0) #endif #endif + // Debug Logging for ShowDebugLogWindow(). This is designed for relatively rare events so please don't spam. #define IMGUI_DEBUG_LOG_ERROR(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventError) IMGUI_DEBUG_LOG(__VA_ARGS__); else g.DebugLogSkippedErrors++; } while (0) #define IMGUI_DEBUG_LOG_ACTIVEID(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventActiveId) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) @@ -220,12 +255,14 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IMGUI_DEBUG_LOG_SELECTION(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_CLIPPER(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventClipper) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_IO(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) -#define IMGUI_DEBUG_LOG_FONT(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventFont) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +#define IMGUI_DEBUG_LOG_FONT(...) do { ImGuiContext* g2 = GImGui; if (g2 && g2->DebugLogFlags & ImGuiDebugLogFlags_EventFont) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) // Called from ImFontAtlas function which may operate without a context. #define IMGUI_DEBUG_LOG_INPUTROUTING(...) do{if (g.DebugLogFlags & ImGuiDebugLogFlags_EventInputRouting)IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_DOCKING(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventDocking) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_VIEWPORT(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventViewport) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) + // Static Asserts #define IM_STATIC_ASSERT(_COND) static_assert(_COND, "") + // "Paranoid" Debug Asserts are meant to only be enabled during specific debugging/work, otherwise would slow down the code too much. // We currently don't have many of those so the effect is currently negligible, but onward intent to add more aggressive ones in the code. //#define IMGUI_DEBUG_PARANOID @@ -234,6 +271,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #else #define IM_ASSERT_PARANOID(_EXPR) #endif + // Misc Macros #define IM_PI 3.14159265358979323846f #ifdef _WIN32 @@ -254,6 +292,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS #define IM_FLOOR IM_TRUNC #endif + // Hint for branch prediction #if (defined(__cplusplus) && (__cplusplus >= 202002L)) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 202002L)) #define IM_LIKELY [[likely]] @@ -262,18 +301,21 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IM_LIKELY #define IM_UNLIKELY #endif + // Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed the default to e.g. __vectorcall #ifdef _MSC_VER #define IMGUI_CDECL __cdecl #else #define IMGUI_CDECL #endif + // Warnings #if defined(_MSC_VER) && !defined(__clang__) #define IM_MSVC_WARNING_SUPPRESS(XXXX) __pragma(warning(suppress: XXXX)) #else #define IM_MSVC_WARNING_SUPPRESS(XXXX) #endif + // Debug Tools // Use 'Metrics/Debugger->Tools->Item Picker' to break into the call-stack of a specific item. // This will call IM_DEBUG_BREAK() which you may redefine yourself. See https://github.com/scottt/debugbreak for more reference. @@ -292,6 +334,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IM_DEBUG_BREAK() IM_ASSERT(0) // It is expected that you define IM_DEBUG_BREAK() into something that will break nicely in a debugger! #endif #endif // #ifndef IM_DEBUG_BREAK + // Format specifiers, printing 64-bit hasn't been decently standardized... // In a real application you should be using PRId64 and PRIu64 from (non-windows) and on Windows define them yourself. #if defined(_MSC_VER) && !defined(__clang__) @@ -303,6 +346,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IM_PRIu64 "llu" #define IM_PRIX64 "llX" #endif + //----------------------------------------------------------------------------- // [SECTION] Generic helpers // Note that the ImXXX helpers functions are lower-level than ImGui functions. @@ -323,25 +367,31 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer // - Helper: ImBitArray // - Helper: ImBitVector // - Helper: ImSpan<>, ImSpanAllocator<> +// - Helper: ImStableVector<> // - Helper: ImPool<> // - Helper: ImChunkStream<> // - Helper: ImGuiTextIndex // - Helper: ImGuiStorage //----------------------------------------------------------------------------- + // Helpers: Hashing IMGUI_API ImGuiID ImHashData(const void* data, size_t data_size, ImGuiID seed = 0); IMGUI_API ImGuiID ImHashStr(const char* data, size_t data_size = 0, ImGuiID seed = 0); + // Helpers: Sorting #ifndef ImQsort -static inline void ImQsort(void* base, size_t count, size_t size_of_element, int(IMGUI_CDECL *compare_func)(void const*, void const*)) { if (count > 1) qsort(base, count, size_of_element, compare_func); } +inline void ImQsort(void* base, size_t count, size_t size_of_element, int(IMGUI_CDECL *compare_func)(void const*, void const*)) { if (count > 1) qsort(base, count, size_of_element, compare_func); } #endif + // Helpers: Color Blending IMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b); + // Helpers: Bit manipulation -static inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } -static inline bool ImIsPowerOfTwo(ImU64 v) { return v != 0 && (v & (v - 1)) == 0; } -static inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } -static inline unsigned int ImCountSetBits(unsigned int v) { unsigned int count = 0; while (v > 0) { v = v & (v - 1); count++; } return count; } +inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } +inline bool ImIsPowerOfTwo(ImU64 v) { return v != 0 && (v & (v - 1)) == 0; } +inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } +inline unsigned int ImCountSetBits(unsigned int v) { unsigned int count = 0; while (v > 0) { v = v & (v - 1); count++; } return count; } + // Helpers: String #define ImStrlen strlen #define ImMemchr memchr @@ -349,6 +399,7 @@ IMGUI_API int ImStricmp(const char* str1, const char* str2); IMGUI_API int ImStrnicmp(const char* str1, const char* str2, size_t count); // Case insensitive compare to a certain count. IMGUI_API void ImStrncpy(char* dst, const char* src, size_t count); // Copy to a certain count and always zero terminate (strncpy doesn't). IMGUI_API char* ImStrdup(const char* str); // Duplicate a string. +IMGUI_API void* ImMemdup(const void* src, size_t size); // Duplicate a chunk of memory. IMGUI_API char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* str); // Copy in provided buffer, recreate buffer if needed. IMGUI_API const char* ImStrchrRange(const char* str_begin, const char* str_end, char c); // Find first occurrence of 'c' in string range. IMGUI_API const char* ImStreolRange(const char* str, const char* str_end); // End end-of-line @@ -358,11 +409,12 @@ IMGUI_API const char* ImStrSkipBlank(const char* str); IMGUI_API int ImStrlenW(const ImWchar* str); // Computer string length (ImWchar string) IMGUI_API const char* ImStrbol(const char* buf_mid_line, const char* buf_begin); // Find beginning-of-line IM_MSVC_RUNTIME_CHECKS_OFF -static inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c &= ~32 : c; } -static inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } -static inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } -static inline bool ImCharIsXdigitA(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } +inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c &= ~32 : c; } +inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } +inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } +inline bool ImCharIsXdigitA(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } IM_MSVC_RUNTIME_CHECKS_RESTORE + // Helpers: Formatting IMGUI_API int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) IM_FMTARGS(3); IMGUI_API int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) IM_FMTLIST(3); @@ -374,8 +426,9 @@ IMGUI_API const char* ImParseFormatTrimDecorations(const char* format, char* b IMGUI_API void ImParseFormatSanitizeForPrinting(const char* fmt_in, char* fmt_out, size_t fmt_out_size); IMGUI_API const char* ImParseFormatSanitizeForScanning(const char* fmt_in, char* fmt_out, size_t fmt_out_size); IMGUI_API int ImParseFormatPrecision(const char* format, int default_value); + // Helpers: UTF-8 <> wchar conversions -IMGUI_API const char* ImTextCharToUtf8(char out_buf[5], unsigned int c); // return out_buf +IMGUI_API int ImTextCharToUtf8(char out_buf[5], unsigned int c); // return output UTF-8 bytes count IMGUI_API int ImTextStrToUtf8(char* out_buf, int out_buf_size, const ImWchar* in_text, const ImWchar* in_text_end); // return output UTF-8 bytes count IMGUI_API int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end); // read one character. return input UTF-8 bytes count IMGUI_API int ImTextStrFromUtf8(ImWchar* out_buf, int out_buf_size, const char* in_text, const char* in_text_end, const char** in_remaining = NULL); // return input UTF-8 bytes count @@ -384,15 +437,16 @@ IMGUI_API int ImTextCountUtf8BytesFromChar(const char* in_text, const IMGUI_API int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end); // return number of bytes to express string in UTF-8 IMGUI_API const char* ImTextFindPreviousUtf8Codepoint(const char* in_text_start, const char* in_text_curr); // return previous UTF-8 code-point. IMGUI_API int ImTextCountLines(const char* in_text, const char* in_text_end); // return number of lines taken by text. trailing carriage return doesn't count as an extra line. + // Helpers: File System #ifdef IMGUI_DISABLE_FILE_FUNCTIONS #define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS typedef void* ImFileHandle; -static inline ImFileHandle ImFileOpen(const char*, const char*) { return NULL; } -static inline bool ImFileClose(ImFileHandle) { return false; } -static inline ImU64 ImFileGetSize(ImFileHandle) { return (ImU64)-1; } -static inline ImU64 ImFileRead(void*, ImU64, ImU64, ImFileHandle) { return 0; } -static inline ImU64 ImFileWrite(const void*, ImU64, ImU64, ImFileHandle) { return 0; } +inline ImFileHandle ImFileOpen(const char*, const char*) { return NULL; } +inline bool ImFileClose(ImFileHandle) { return false; } +inline ImU64 ImFileGetSize(ImFileHandle) { return (ImU64)-1; } +inline ImU64 ImFileRead(void*, ImU64, ImU64, ImFileHandle) { return 0; } +inline ImU64 ImFileWrite(const void*, ImU64, ImU64, ImFileHandle) { return 0; } #endif #ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS typedef FILE* ImFileHandle; @@ -405,6 +459,7 @@ IMGUI_API ImU64 ImFileWrite(const void* data, ImU64 size, ImU64 coun #define IMGUI_DISABLE_TTY_FUNCTIONS // Can't use stdout, fflush if we are not using default file functions #endif IMGUI_API void* ImFileLoadToMemory(const char* filename, const char* mode, size_t* out_file_size = NULL, int padding_bytes = 0); + // Helpers: Maths IM_MSVC_RUNTIME_CHECKS_OFF // - Wrapper for standard libs functions. (Note that imgui_demo.cpp does _not_ use them to keep the code easy to copy) @@ -418,55 +473,58 @@ IM_MSVC_RUNTIME_CHECKS_OFF #define ImAtan2(Y, X) atan2f((Y), (X)) #define ImAtof(STR) atof(STR) #define ImCeil(X) ceilf(X) -static inline float ImPow(float x, float y) { return powf(x, y); } // DragBehaviorT/SliderBehaviorT uses ImPow with either float/double and need the precision -static inline double ImPow(double x, double y) { return pow(x, y); } -static inline float ImLog(float x) { return logf(x); } // DragBehaviorT/SliderBehaviorT uses ImLog with either float/double and need the precision -static inline double ImLog(double x) { return log(x); } -static inline int ImAbs(int x) { return x < 0 ? -x : x; } -static inline float ImAbs(float x) { return fabsf(x); } -static inline double ImAbs(double x) { return fabs(x); } -static inline float ImSign(float x) { return (x < 0.0f) ? -1.0f : (x > 0.0f) ? 1.0f : 0.0f; } // Sign operator - returns -1, 0 or 1 based on sign of argument -static inline double ImSign(double x) { return (x < 0.0) ? -1.0 : (x > 0.0) ? 1.0 : 0.0; } +inline float ImPow(float x, float y) { return powf(x, y); } // DragBehaviorT/SliderBehaviorT uses ImPow with either float/double and need the precision +inline double ImPow(double x, double y) { return pow(x, y); } +inline float ImLog(float x) { return logf(x); } // DragBehaviorT/SliderBehaviorT uses ImLog with either float/double and need the precision +inline double ImLog(double x) { return log(x); } +inline int ImAbs(int x) { return x < 0 ? -x : x; } +inline float ImAbs(float x) { return fabsf(x); } +inline double ImAbs(double x) { return fabs(x); } +inline float ImSign(float x) { return (x < 0.0f) ? -1.0f : (x > 0.0f) ? 1.0f : 0.0f; } // Sign operator - returns -1, 0 or 1 based on sign of argument +inline double ImSign(double x) { return (x < 0.0) ? -1.0 : (x > 0.0) ? 1.0 : 0.0; } #ifdef IMGUI_ENABLE_SSE -static inline float ImRsqrt(float x) { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); } +inline float ImRsqrt(float x) { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); } #else -static inline float ImRsqrt(float x) { return 1.0f / sqrtf(x); } +inline float ImRsqrt(float x) { return 1.0f / sqrtf(x); } #endif -static inline double ImRsqrt(double x) { return 1.0 / sqrt(x); } +inline double ImRsqrt(double x) { return 1.0 / sqrt(x); } #endif // - ImMin/ImMax/ImClamp/ImLerp/ImSwap are used by widgets which support variety of types: signed/unsigned int/long long float/double // (Exceptionally using templates here but we could also redefine them for those types) -template static inline T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; } -template static inline T ImMax(T lhs, T rhs) { return lhs >= rhs ? lhs : rhs; } -template static inline T ImClamp(T v, T mn, T mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } -template static inline T ImLerp(T a, T b, float t) { return (T)(a + (b - a) * t); } -template static inline void ImSwap(T& a, T& b) { T tmp = a; a = b; b = tmp; } -template static inline T ImAddClampOverflow(T a, T b, T mn, T mx) { if (b < 0 && (a < mn - b)) return mn; if (b > 0 && (a > mx - b)) return mx; return a + b; } -template static inline T ImSubClampOverflow(T a, T b, T mn, T mx) { if (b > 0 && (a < mn + b)) return mn; if (b < 0 && (a > mx + b)) return mx; return a - b; } +template T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; } +template T ImMax(T lhs, T rhs) { return lhs >= rhs ? lhs : rhs; } +template T ImClamp(T v, T mn, T mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } +template T ImLerp(T a, T b, float t) { return (T)(a + (b - a) * t); } +template void ImSwap(T& a, T& b) { T tmp = a; a = b; b = tmp; } +template T ImAddClampOverflow(T a, T b, T mn, T mx) { if (b < 0 && (a < mn - b)) return mn; if (b > 0 && (a > mx - b)) return mx; return a + b; } +template T ImSubClampOverflow(T a, T b, T mn, T mx) { if (b > 0 && (a < mn + b)) return mn; if (b < 0 && (a > mx + b)) return mx; return a - b; } // - Misc maths helpers -static inline ImVec2 ImMin(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x < rhs.x ? lhs.x : rhs.x, lhs.y < rhs.y ? lhs.y : rhs.y); } -static inline ImVec2 ImMax(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x >= rhs.x ? lhs.x : rhs.x, lhs.y >= rhs.y ? lhs.y : rhs.y); } -static inline ImVec2 ImClamp(const ImVec2& v, const ImVec2&mn, const ImVec2&mx) { return ImVec2((v.x < mn.x) ? mn.x : (v.x > mx.x) ? mx.x : v.x, (v.y < mn.y) ? mn.y : (v.y > mx.y) ? mx.y : v.y); } -static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t) { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); } -static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); } -static inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); } -static inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } -static inline float ImLengthSqr(const ImVec2& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y); } -static inline float ImLengthSqr(const ImVec4& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y) + (lhs.z * lhs.z) + (lhs.w * lhs.w); } -static inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = (lhs.x * lhs.x) + (lhs.y * lhs.y); if (d > 0.0f) return ImRsqrt(d); return fail_value; } -static inline float ImTrunc(float f) { return (float)(int)(f); } -static inline ImVec2 ImTrunc(const ImVec2& v) { return ImVec2((float)(int)(v.x), (float)(int)(v.y)); } -static inline float ImFloor(float f) { return (float)((f >= 0 || (float)(int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf() -static inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); } -static inline int ImModPositive(int a, int b) { return (a + b) % b; } -static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } -static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } -static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } -static inline float ImLinearRemapClamp(float s0, float s1, float d0, float d1, float x) { return ImSaturate((x - s0) / (s1 - s0)) * (d1 - d0) + d0; } -static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } -static inline bool ImIsFloatAboveGuaranteedIntegerPrecision(float f) { return f <= -16777216 || f >= 16777216; } -static inline float ImExponentialMovingAverage(float avg, float sample, int n) { avg -= avg / n; avg += sample / n; return avg; } +inline ImVec2 ImMin(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x < rhs.x ? lhs.x : rhs.x, lhs.y < rhs.y ? lhs.y : rhs.y); } +inline ImVec2 ImMax(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x >= rhs.x ? lhs.x : rhs.x, lhs.y >= rhs.y ? lhs.y : rhs.y); } +inline ImVec2 ImClamp(const ImVec2& v, const ImVec2&mn, const ImVec2&mx){ return ImVec2((v.x < mn.x) ? mn.x : (v.x > mx.x) ? mx.x : v.x, (v.y < mn.y) ? mn.y : (v.y > mx.y) ? mx.y : v.y); } +inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t) { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); } +inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); } +inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); } +inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } +inline float ImLengthSqr(const ImVec2& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y); } +inline float ImLengthSqr(const ImVec4& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y) + (lhs.z * lhs.z) + (lhs.w * lhs.w); } +inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = (lhs.x * lhs.x) + (lhs.y * lhs.y); if (d > 0.0f) return ImRsqrt(d); return fail_value; } +inline float ImTrunc(float f) { return (float)(int)(f); } +inline ImVec2 ImTrunc(const ImVec2& v) { return ImVec2((float)(int)(v.x), (float)(int)(v.y)); } +inline float ImFloor(float f) { return (float)((f >= 0 || (float)(int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf() +inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); } +inline float ImTrunc64(float f) { return (float)(ImS64)(f); } +inline float ImRound64(float f) { return (float)(ImS64)(f + 0.5f); } +inline int ImModPositive(int a, int b) { return (a + b) % b; } +inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } +inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } +inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } +inline float ImLinearRemapClamp(float s0, float s1, float d0, float d1, float x) { return ImSaturate((x - s0) / (s1 - s0)) * (d1 - d0) + d0; } +inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } +inline bool ImIsFloatAboveGuaranteedIntegerPrecision(float f) { return f <= -16777216 || f >= 16777216; } +inline float ImExponentialMovingAverage(float avg, float sample, int n){ avg -= avg / n; avg += sample / n; return avg; } IM_MSVC_RUNTIME_CHECKS_RESTORE + // Helpers: Geometry IMGUI_API ImVec2 ImBezierCubicCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, float t); IMGUI_API ImVec2 ImBezierCubicClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, int num_segments); // For curves with explicit number of segments @@ -478,6 +536,7 @@ IMGUI_API ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, co IMGUI_API void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w); inline float ImTriangleArea(const ImVec2& a, const ImVec2& b, const ImVec2& c) { return ImFabs((a.x * (b.y - c.y)) + (b.x * (c.y - a.y)) + (c.x * (a.y - b.y))) * 0.5f; } inline bool ImTriangleIsClockwise(const ImVec2& a, const ImVec2& b, const ImVec2& c) { return ((b.x - a.x) * (c.y - b.y)) - ((c.x - b.x) * (b.y - a.y)) > 0.0f; } + // Helper: ImVec1 (1D vector) // (this odd construct is used to facilitate the transition between 1D and 2D, and the maintenance of some branches/patches) IM_MSVC_RUNTIME_CHECKS_OFF @@ -487,6 +546,15 @@ struct ImVec1 constexpr ImVec1() : x(0.0f) { } constexpr ImVec1(float _x) : x(_x) { } }; + +// Helper: ImVec2i (2D vector, integer) +struct ImVec2i +{ + int x, y; + constexpr ImVec2i() : x(0), y(0) {} + constexpr ImVec2i(int _x, int _y) : x(_x), y(_y) {} +}; + // Helper: ImVec2ih (2D vector, half-size integer, for long-term packed storage) struct ImVec2ih { @@ -495,16 +563,19 @@ struct ImVec2ih constexpr ImVec2ih(short _x, short _y) : x(_x), y(_y) {} constexpr explicit ImVec2ih(const ImVec2& rhs) : x((short)rhs.x), y((short)rhs.y) {} }; + // Helper: ImRect (2D axis aligned bounding-box) // NB: we can't rely on ImVec2 math operators being available here! struct IMGUI_API ImRect { ImVec2 Min; // Upper-left ImVec2 Max; // Lower-right + constexpr ImRect() : Min(0.0f, 0.0f), Max(0.0f, 0.0f) {} constexpr ImRect(const ImVec2& min, const ImVec2& max) : Min(min), Max(max) {} constexpr ImRect(const ImVec4& v) : Min(v.x, v.y), Max(v.z, v.w) {} constexpr ImRect(float x1, float y1, float x2, float y2) : Min(x1, y1), Max(x2, y2) {} + ImVec2 GetCenter() const { return ImVec2((Min.x + Max.x) * 0.5f, (Min.y + Max.y) * 0.5f); } ImVec2 GetSize() const { return ImVec2(Max.x - Min.x, Max.y - Min.y); } float GetWidth() const { return Max.x - Min.x; } @@ -531,6 +602,7 @@ struct IMGUI_API ImRect bool IsInverted() const { return Min.x > Max.x || Min.y > Max.y; } ImVec4 ToVec4() const { return ImVec4(Min.x, Min.y, Max.x, Max.y); } }; + // Helper: ImBitArray #define IM_BITARRAY_TESTBIT(_ARRAY, _N) ((_ARRAY[(_N) >> 5] & ((ImU32)1 << ((_N) & 31))) != 0) // Macro version of ImBitArrayTestBit(): ensure args have side-effect or are costly! #define IM_BITARRAY_CLEARBIT(_ARRAY, _N) ((_ARRAY[(_N) >> 5] &= ~((ImU32)1 << ((_N) & 31)))) // Macro version of ImBitArrayClearBit(): ensure args have side-effect or are costly! @@ -551,7 +623,9 @@ inline void ImBitArraySetBitRange(ImU32* arr, int n, int n2) // Works on ran n = (n + 32) & ~31; } } + typedef ImU32* ImBitArrayPtr; // Name for use in structs + // Helper: ImBitArray class (wrapper over ImBitArray functions) // Store 1-bit per value. template @@ -567,6 +641,7 @@ struct ImBitArray void SetBitRange(int n, int n2) { n += OFFSET; n2 += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT && n2 > n && n2 <= BITCOUNT); ImBitArraySetBitRange(Storage, n, n2); } // Works on range [n..n2) bool operator[](int n) const { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return IM_BITARRAY_TESTBIT(Storage, n); } }; + // Helper: ImBitVector // Store 1-bit per value. struct IMGUI_API ImBitVector @@ -579,6 +654,7 @@ struct IMGUI_API ImBitVector void ClearBit(int n) { IM_ASSERT(n < (Storage.Size << 5)); ImBitArrayClearBit(Storage.Data, n); } }; IM_MSVC_RUNTIME_CHECKS_RESTORE + // Helper: ImSpan<> // Pointing to a span of data we don't own. template @@ -586,23 +662,28 @@ struct ImSpan { T* Data; T* DataEnd; + // Constructors, destructor inline ImSpan() { Data = DataEnd = NULL; } inline ImSpan(T* data, int size) { Data = data; DataEnd = data + size; } inline ImSpan(T* data, T* data_end) { Data = data; DataEnd = data_end; } + inline void set(T* data, int size) { Data = data; DataEnd = data + size; } inline void set(T* data, T* data_end) { Data = data; DataEnd = data_end; } inline int size() const { return (int)(ptrdiff_t)(DataEnd - Data); } inline int size_in_bytes() const { return (int)(ptrdiff_t)(DataEnd - Data) * (int)sizeof(T); } inline T& operator[](int i) { T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } inline const T& operator[](int i) const { const T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; } + inline T* begin() { return Data; } inline const T* begin() const { return Data; } inline T* end() { return DataEnd; } inline const T* end() const { return DataEnd; } + // Utilities inline int index_from_ptr(const T* it) const { IM_ASSERT(it >= Data && it < DataEnd); const ptrdiff_t off = it - Data; return (int)off; } }; + // Helper: ImSpanAllocator<> // Facilitate storing multiple chunks into a single large block (the "arena") // - Usage: call Reserve() N times, allocate GetArenaSizeInBytes() worth, pass it to SetArenaBasePtr(), call GetSpan() N times to retrieve the aligned ranges. @@ -614,6 +695,7 @@ struct ImSpanAllocator int CurrIdx; int Offsets[CHUNKS]; int Sizes[CHUNKS]; + ImSpanAllocator() { memset(this, 0, sizeof(*this)); } inline void Reserve(int n, size_t sz, int a=4) { IM_ASSERT(n == CurrIdx && n < CHUNKS); CurrOff = IM_MEMALIGN(CurrOff, a); Offsets[n] = CurrOff; Sizes[n] = (int)sz; CurrIdx++; CurrOff += (int)sz; } inline int GetArenaSizeInBytes() { return CurrOff; } @@ -623,6 +705,40 @@ struct ImSpanAllocator template inline void GetSpan(int n, ImSpan* span) { span->set((T*)GetSpanPtrBegin(n), (T*)GetSpanPtrEnd(n)); } }; + +// Helper: ImStableVector<> +// Allocating chunks of BLOCK_SIZE items. Objects pointers are never invalidated when growing, only by clear(). +// Important: does not destruct anything! +// Implemented only the minimum set of functions we need for it. +template +struct ImStableVector +{ + int Size = 0; + int Capacity = 0; + ImVector Blocks; + + // Functions + inline ~ImStableVector() { for (T* block : Blocks) IM_FREE(block); } + + inline void clear() { Size = Capacity = 0; Blocks.clear_delete(); } + inline void resize(int new_size) { if (new_size > Capacity) reserve(new_size); Size = new_size; } + inline void reserve(int new_cap) + { + new_cap = IM_MEMALIGN(new_cap, BLOCK_SIZE); + int old_count = Capacity / BLOCK_SIZE; + int new_count = new_cap / BLOCK_SIZE; + if (new_count <= old_count) + return; + Blocks.resize(new_count); + for (int n = old_count; n < new_count; n++) + Blocks[n] = (T*)IM_ALLOC(sizeof(T) * BLOCK_SIZE); + Capacity = new_cap; + } + inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; } + inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; } + inline T* push_back(const T& v) { int i = Size; IM_ASSERT(i >= 0); if (Size == Capacity) reserve(Capacity + BLOCK_SIZE); void* ptr = &Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; memcpy(ptr, &v, sizeof(v)); Size++; return (T*)ptr; } +}; + // Helper: ImPool<> // Basic keyed storage for contiguous instances, slow/amortized insertion, O(1) indexable, O(Log N) queries by ID over a dense/hot buffer, // Honor constructor/destructor. Add/remove invalidate all pointers. Indexes have the same lifetime as the associated object. @@ -634,6 +750,7 @@ struct ImPool ImGuiStorage Map; // ID->Index ImPoolIdx FreeIdx; // Next free idx to use ImPoolIdx AliveCount; // Number of active/alive items (for display purpose) + ImPool() { FreeIdx = AliveCount = 0; } ~ImPool() { Clear(); } T* GetByKey(ImGuiID key) { int idx = Map.GetInt(key, -1); return (idx != -1) ? &Buf[idx] : NULL; } @@ -646,6 +763,7 @@ struct ImPool void Remove(ImGuiID key, const T* p) { Remove(key, GetIndex(p)); } void Remove(ImGuiID key, ImPoolIdx idx) { Buf[idx].~T(); *(int*)&Buf[idx] = FreeIdx; FreeIdx = idx; Map.SetInt(key, -1); AliveCount--; } void Reserve(int capacity) { Buf.reserve(capacity); Map.Data.reserve(capacity); } + // To iterate a ImPool: for (int n = 0; n < pool.GetMapSize(); n++) if (T* t = pool.TryGetMapData(n)) { ... } // Can be avoided if you know .Remove() has never been called on the pool, or AliveCount == GetMapSize() int GetAliveCount() const { return AliveCount; } // Number of active/alive items in the pool (for display purpose) @@ -653,6 +771,7 @@ struct ImPool int GetMapSize() const { return Map.Data.Size; } // It is the map we need iterate to find valid items, since we don't have "alive" storage anywhere T* TryGetMapData(ImPoolIdx n) { int idx = Map.Data[n].val_i; if (idx == -1) return NULL; return GetByIndex(idx); } }; + // Helper: ImChunkStream<> // Build and iterate a contiguous stream of variable-sized structures. // This is used by Settings to store persistent data while reducing allocation count. @@ -662,6 +781,7 @@ template struct ImChunkStream { ImVector Buf; + void clear() { Buf.clear(); } bool empty() const { return Buf.Size == 0; } int size() const { return Buf.Size; } @@ -674,23 +794,28 @@ struct ImChunkStream T* ptr_from_offset(int off) { IM_ASSERT(off >= 4 && off < Buf.Size); return (T*)(void*)(Buf.Data + off); } void swap(ImChunkStream& rhs) { rhs.Buf.swap(Buf); } }; + // Helper: ImGuiTextIndex // Maintain a line index for a text buffer. This is a strong candidate to be moved into the public API. struct ImGuiTextIndex { ImVector LineOffsets; int EndOffset = 0; // Because we don't own text buffer we need to maintain EndOffset (may bake in LineOffsets?) + void clear() { LineOffsets.clear(); EndOffset = 0; } int size() { return LineOffsets.Size; } const char* get_line_begin(const char* base, int n) { return base + LineOffsets[n]; } const char* get_line_end(const char* base, int n) { return base + (n + 1 < LineOffsets.Size ? (LineOffsets[n + 1] - 1) : EndOffset); } void append(const char* base, int old_size, int new_size); }; + // Helper: ImGuiStorage IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStoragePair* in_end, ImGuiID key); + //----------------------------------------------------------------------------- // [SECTION] ImDrawList support //----------------------------------------------------------------------------- + // ImDrawList: Helper function to calculate a circle's segment count given its radius and a "maximum error" value. // Estimation of number of circle segment based on error is derived using method described in https://stackoverflow.com/a/2244088/15194693 // Number of segments (N) is calculated using equation: @@ -705,47 +830,67 @@ IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStorag #define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN 4 #define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX 512 #define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(_RAD,_MAXERROR) ImClamp(IM_ROUNDUP_TO_EVEN((int)ImCeil(IM_PI / ImAcos(1 - ImMin((_MAXERROR), (_RAD)) / (_RAD)))), IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX) + // Raw equation from IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC rewritten for 'r' and 'error'. #define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(_N,_MAXERROR) ((_MAXERROR) / (1 - ImCos(IM_PI / ImMax((float)(_N), IM_PI)))) #define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_ERROR(_N,_RAD) ((1 - ImCos(IM_PI / ImMax((float)(_N), IM_PI))) / (_RAD)) + // ImDrawList: Lookup table size for adaptive arc drawing, cover full circle. #ifndef IM_DRAWLIST_ARCFAST_TABLE_SIZE #define IM_DRAWLIST_ARCFAST_TABLE_SIZE 48 // Number of samples in lookup table. #endif #define IM_DRAWLIST_ARCFAST_SAMPLE_MAX IM_DRAWLIST_ARCFAST_TABLE_SIZE // Sample index _PathArcToFastEx() for 360 angle. + // Data shared between all ImDrawList instances // Conceptually this could have been called e.g. ImDrawListSharedContext // Typically one ImGui context would create and maintain one of this. // You may want to create your own instance of you try to ImDrawList completely without ImGui. In that case, watch out for future changes to this structure. struct IMGUI_API ImDrawListSharedData { - ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas - const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas - ImFont* Font; // Current/default font (optional, for simplified AddText overload) - float FontSize; // Current/default font size (optional, for simplified AddText overload) - float FontScale; // Current/default font scale (== FontSize / Font->FontSize) + ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel) + const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines) + ImFontAtlas* FontAtlas; // Current font atlas + ImFont* Font; // Current font (used for simplified AddText overload) + float FontSize; // Current font size (used for for simplified AddText overload) + float FontScale; // Current font scale (== FontSize / Font->FontSize) float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() float CircleSegmentMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc float InitialFringeScale; // Initial scale to apply to AA fringe ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() ImVector TempBuffer; // Temporary write buffer + ImVector DrawLists; // All draw lists associated to this ImDrawListSharedData + ImGuiContext* Context; // [OPTIONAL] Link to Dear ImGui context. 99% of ImDrawList/ImFontAtlas can function without an ImGui context, but this facilitate handling one legacy edge case. + // Lookup tables ImVec2 ArcFastVtx[IM_DRAWLIST_ARCFAST_TABLE_SIZE]; // Sample points on the quarter of the circle. float ArcFastRadiusCutoff; // Cutoff radius after which arc drawing will fallback to slower PathArcTo() ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead) + ImDrawListSharedData(); + ~ImDrawListSharedData(); void SetCircleTessellationMaxError(float max_error); }; + struct ImDrawDataBuilder { ImVector* Layers[2]; // Pointers to global layers for: regular, tooltip. LayersP[0] is owned by DrawData. ImVector LayerData1; + ImDrawDataBuilder() { memset(this, 0, sizeof(*this)); } }; + +struct ImFontStackData +{ + ImFont* Font; + float FontSizeBeforeScaling; // ~~ style.FontSizeBase + float FontSizeAfterScaling; // ~~ g.FontSize +}; + //----------------------------------------------------------------------------- // [SECTION] Style support //----------------------------------------------------------------------------- + struct ImGuiStyleVarInfo { ImU32 Count : 8; // 1+ @@ -753,12 +898,14 @@ struct ImGuiStyleVarInfo ImU32 Offset : 16; // Offset in parent structure void* GetVarPtr(void* parent) const { return (void*)((unsigned char*)parent + Offset); } }; + // Stacked color modifier, backup of modified data so we can restore it struct ImGuiColorMod { ImGuiCol Col; ImVec4 BackupValue; }; + // Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable. struct ImGuiStyleMod { @@ -768,13 +915,16 @@ struct ImGuiStyleMod ImGuiStyleMod(ImGuiStyleVar idx, float v) { VarIdx = idx; BackupFloat[0] = v; } ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v) { VarIdx = idx; BackupFloat[0] = v.x; BackupFloat[1] = v.y; } }; + //----------------------------------------------------------------------------- // [SECTION] Data types support //----------------------------------------------------------------------------- + struct ImGuiDataTypeStorage { ImU8 Data[8]; // Opaque storage to fit any data up to ImGuiDataType_COUNT }; + // Type information associated to one ImGuiDataType. Retrieve with DataTypeGetInfo(). struct ImGuiDataTypeInfo { @@ -783,15 +933,18 @@ struct ImGuiDataTypeInfo const char* PrintFmt; // Default printf format for the type const char* ScanFmt; // Default scanf format for the type }; + // Extend ImGuiDataType_ enum ImGuiDataTypePrivate_ { ImGuiDataType_Pointer = ImGuiDataType_COUNT, ImGuiDataType_ID, }; + //----------------------------------------------------------------------------- // [SECTION] Widgets support: flags, enums, data structures //----------------------------------------------------------------------------- + // Extend ImGuiItemFlags // - input: PushItemFlag() manipulates g.CurrentItemFlags, g.NextItemData.ItemFlags, ItemAdd() calls may add extra flags too. // - output: stored in g.LastItemData.ItemFlags @@ -805,14 +958,19 @@ enum ImGuiItemFlagsPrivate_ ImGuiItemFlags_AllowOverlap = 1 << 14, // false // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame. ImGuiItemFlags_NoNavDisableMouseHover = 1 << 15, // false // Nav keyboard/gamepad mode doesn't disable hover highlight (behave as if NavHighlightItemUnderNav==false). ImGuiItemFlags_NoMarkEdited = 1 << 16, // false // Skip calling MarkItemEdited() + ImGuiItemFlags_NoFocus = 1 << 17, // false // [EXPERIMENTAL: Not very well specced] Clicking doesn't take focus. Automatically sets ImGuiButtonFlags_NoFocus + ImGuiButtonFlags_NoNavFocus in ButtonBehavior(). + // Controlled by widget code ImGuiItemFlags_Inputable = 1 << 20, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature. ImGuiItemFlags_HasSelectionUserData = 1 << 21, // false // Set by SetNextItemSelectionUserData() ImGuiItemFlags_IsMultiSelect = 1 << 22, // false // Set by SetNextItemSelectionUserData() + ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups, // Please don't change, use PushItemFlag() instead. + // Obsolete //ImGuiItemFlags_SelectableDontClosePopup = !ImGuiItemFlags_AutoClosePopups, // Can't have a redirect as we inverted the behavior }; + // Status flags for an already submitted item // - output: stored in g.LastItemData.StatusFlags enum ImGuiItemStatusFlags_ @@ -829,6 +987,8 @@ enum ImGuiItemStatusFlags_ ImGuiItemStatusFlags_Visible = 1 << 8, // [WIP] Set when item is overlapping the current clipping rectangle (Used internally. Please don't use yet: API/system will change as we refactor Itemadd()). ImGuiItemStatusFlags_HasClipRect = 1 << 9, // g.LastItemData.ClipRect is valid. ImGuiItemStatusFlags_HasShortcut = 1 << 10, // g.LastItemData.Shortcut valid. Set by SetNextItemShortcut() -> ItemAdd(). + //ImGuiItemStatusFlags_FocusedByTabbing = 1 << 8, // Removed IN 1.90.1 (Dec 2023). The trigger is part of g.NavActivateId. See commit 54c1bdeceb. + // Additional status + semantic for ImGuiTestEngine #ifdef IMGUI_ENABLE_TEST_ENGINE ImGuiItemStatusFlags_Openable = 1 << 20, // Item is an openable (e.g. TreeNode) @@ -838,6 +998,7 @@ enum ImGuiItemStatusFlags_ ImGuiItemStatusFlags_Inputable = 1 << 24, // Item is a text-inputable (e.g. InputText, SliderXXX, DragXXX) #endif }; + // Extend ImGuiHoveredFlags_ enum ImGuiHoveredFlagsPrivate_ { @@ -845,6 +1006,7 @@ enum ImGuiHoveredFlagsPrivate_ ImGuiHoveredFlags_AllowedMaskForIsWindowHovered = ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_NoPopupHierarchy | ImGuiHoveredFlags_DockHierarchy | ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_Stationary, ImGuiHoveredFlags_AllowedMaskForIsItemHovered = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped | ImGuiHoveredFlags_AllowWhenDisabled | ImGuiHoveredFlags_NoNavOverride | ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayMask_, }; + // Extend ImGuiInputTextFlags_ enum ImGuiInputTextFlagsPrivate_ { @@ -853,6 +1015,7 @@ enum ImGuiInputTextFlagsPrivate_ ImGuiInputTextFlags_MergedItem = 1 << 27, // For internal use by TempInputText(), will skip calling ItemAdd(). Require bounding-box to strictly match. ImGuiInputTextFlags_LocalizeDecimalPoint= 1 << 28, // For internal use by InputScalar() and TempInputScalar() }; + // Extend ImGuiButtonFlags_ enum ImGuiButtonFlagsPrivate_ { @@ -874,20 +1037,25 @@ enum ImGuiButtonFlagsPrivate_ ImGuiButtonFlags_NoHoveredOnFocus = 1 << 19, // don't report as hovered when nav focus is on this item ImGuiButtonFlags_NoSetKeyOwner = 1 << 20, // don't set key/input owner on the initial click (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) ImGuiButtonFlags_NoTestKeyOwner = 1 << 21, // don't test key/input owner when polling the key (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) + ImGuiButtonFlags_NoFocus = 1 << 22, // [EXPERIMENTAL: Not very well specced]. Don't focus parent window when clicking. ImGuiButtonFlags_PressedOnMask_ = ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick | ImGuiButtonFlags_PressedOnDragDropHold, ImGuiButtonFlags_PressedOnDefault_ = ImGuiButtonFlags_PressedOnClickRelease, + //ImGuiButtonFlags_NoKeyModifiers = ImGuiButtonFlags_NoKeyModsAllowed, // Renamed in 1.91.4 }; + // Extend ImGuiComboFlags_ enum ImGuiComboFlagsPrivate_ { ImGuiComboFlags_CustomPreview = 1 << 20, // enable BeginComboPreview() }; + // Extend ImGuiSliderFlags_ enum ImGuiSliderFlagsPrivate_ { ImGuiSliderFlags_Vertical = 1 << 20, // Should this slider be orientated vertically? ImGuiSliderFlags_ReadOnly = 1 << 21, // Consider using g.NextItemData.ItemFlags |= ImGuiItemFlags_ReadOnly instead. }; + // Extend ImGuiSelectableFlags_ enum ImGuiSelectableFlagsPrivate_ { @@ -901,13 +1069,17 @@ enum ImGuiSelectableFlagsPrivate_ ImGuiSelectableFlags_NoPadWithHalfSpacing = 1 << 26, // Disable padding each side with ItemSpacing * 0.5f ImGuiSelectableFlags_NoSetKeyOwner = 1 << 27, // Don't set key/input owner on the initial click (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) }; + // Extend ImGuiTreeNodeFlags_ enum ImGuiTreeNodeFlagsPrivate_ { + ImGuiTreeNodeFlags_NoNavFocus = 1 << 27,// Don't claim nav focus when interacting with this item (#8551) ImGuiTreeNodeFlags_ClipLabelForTrailingButton = 1 << 28,// FIXME-WIP: Hard-coded for CollapsingHeader() ImGuiTreeNodeFlags_UpsideDownArrow = 1 << 29,// FIXME-WIP: Turn Down arrow into an Up arrow, for reversed trees (#6517) ImGuiTreeNodeFlags_OpenOnMask_ = ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_OpenOnArrow, + ImGuiTreeNodeFlags_DrawLinesMask_ = ImGuiTreeNodeFlags_DrawLinesNone | ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes, }; + enum ImGuiSeparatorFlags_ { ImGuiSeparatorFlags_None = 0, @@ -915,6 +1087,7 @@ enum ImGuiSeparatorFlags_ ImGuiSeparatorFlags_Vertical = 1 << 1, ImGuiSeparatorFlags_SpanAllColumns = 1 << 2, // Make separator cover all columns of a legacy Columns() set. }; + // Flags for FocusWindow(). This is not called ImGuiFocusFlags to avoid confusion with public-facing ImGuiFocusedFlags. // FIXME: Once we finishing replacing more uses of GetTopMostPopupModal()+IsWindowWithinBeginStackOf() // and FindBlockingModal() with this, we may want to change the flag to be opt-out instead of opt-in. @@ -924,16 +1097,19 @@ enum ImGuiFocusRequestFlags_ ImGuiFocusRequestFlags_RestoreFocusedChild = 1 << 0, // Find last focused child (if any) and focus it instead. ImGuiFocusRequestFlags_UnlessBelowModal = 1 << 1, // Do not set focus if the window is below a modal. }; + enum ImGuiTextFlags_ { ImGuiTextFlags_None = 0, ImGuiTextFlags_NoWidthForLargeClippedText = 1 << 0, }; + enum ImGuiTooltipFlags_ { ImGuiTooltipFlags_None = 0, ImGuiTooltipFlags_OverridePrevious = 1 << 1, // Clear/ignore previously submitted tooltip (defaults to append) }; + // FIXME: this is in development, not exposed/functional as a generic feature yet. // Horizontal/Vertical enums are fixed to 0/1 so they may be used to index ImVec2 enum ImGuiLayoutType_ @@ -941,16 +1117,19 @@ enum ImGuiLayoutType_ ImGuiLayoutType_Horizontal = 0, ImGuiLayoutType_Vertical = 1 }; + // Flags for LogBegin() text capturing function enum ImGuiLogFlags_ { ImGuiLogFlags_None = 0, + ImGuiLogFlags_OutputTTY = 1 << 0, ImGuiLogFlags_OutputFile = 1 << 1, ImGuiLogFlags_OutputBuffer = 1 << 2, ImGuiLogFlags_OutputClipboard = 1 << 3, ImGuiLogFlags_OutputMask_ = ImGuiLogFlags_OutputTTY | ImGuiLogFlags_OutputFile | ImGuiLogFlags_OutputBuffer | ImGuiLogFlags_OutputClipboard, }; + // X/Y enums are fixed to 0/1 so they may be used to index ImVec2 enum ImGuiAxis { @@ -958,11 +1137,13 @@ enum ImGuiAxis ImGuiAxis_X = 0, ImGuiAxis_Y = 1 }; + enum ImGuiPlotType { ImGuiPlotType_Lines, ImGuiPlotType_Histogram, }; + // Storage data for BeginComboPreview()/EndComboPreview() struct IMGUI_API ImGuiComboPreviewData { @@ -972,8 +1153,10 @@ struct IMGUI_API ImGuiComboPreviewData ImVec2 BackupCursorPosPrevLine; float BackupPrevLineTextBaseOffset; ImGuiLayoutType BackupLayout; + ImGuiComboPreviewData() { memset(this, 0, sizeof(*this)); } }; + // Stacked storage data for BeginGroup()/EndGroup() struct IMGUI_API ImGuiGroupData { @@ -991,6 +1174,7 @@ struct IMGUI_API ImGuiGroupData bool BackupIsSameLine; bool EmitItem; }; + // Simple column measurement, currently used for MenuItem() only.. This is very short-sighted/throw-away code and NOT a generic helper. struct IMGUI_API ImGuiMenuColumns { @@ -1002,19 +1186,23 @@ struct IMGUI_API ImGuiMenuColumns ImU16 OffsetShortcut; ImU16 OffsetMark; ImU16 Widths[4]; // Width of: Icon, Label, Shortcut, Mark (accumulators for current frame) + ImGuiMenuColumns() { memset(this, 0, sizeof(*this)); } void Update(float spacing, bool window_reappearing); float DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark); void CalcNextTotalWidth(bool update_offsets); }; + // Internal temporary state for deactivating InputText() instances. struct IMGUI_API ImGuiInputTextDeactivatedState { ImGuiID ID; // widget id owning the text state (which just got deactivated) ImVector TextA; // text buffer + ImGuiInputTextDeactivatedState() { memset(this, 0, sizeof(*this)); } void ClearFreeMemory() { ID = 0; TextA.clear(); } }; + // Forward declare imstb_textedit.h structure + make its main configuration define accessible #undef IMSTB_TEXTEDIT_STRING #undef IMSTB_TEXTEDIT_CHARTYPE @@ -1025,6 +1213,7 @@ struct IMGUI_API ImGuiInputTextDeactivatedState #define IMSTB_TEXTEDIT_UNDOCHARCOUNT 999 namespace ImStb { struct STB_TexteditState; } typedef ImStb::STB_TexteditState ImStbTexteditState; + // Internal state of the currently focused/edited text input box // For a given item ID, access with ImGui::GetInputTextState() struct IMGUI_API ImGuiInputTextState @@ -1047,12 +1236,14 @@ struct IMGUI_API ImGuiInputTextState bool WantReloadUserBuf; // force a reload of user buf so it may be modified externally. may be automatic in future version. int ReloadSelectionStart; int ReloadSelectionEnd; + ImGuiInputTextState(); ~ImGuiInputTextState(); void ClearText() { TextLen = 0; TextA[0] = 0; CursorClamp(); } void ClearFreeMemory() { TextA.clear(); TextToRevertTo.clear(); } void OnKeyPressed(int key); // Cannot be inline because we call in code in stb_textedit.h implementation void OnCharPressed(unsigned int c); + // Cursor & Selection void CursorAnimReset(); void CursorClamp(); @@ -1062,6 +1253,7 @@ struct IMGUI_API ImGuiInputTextState int GetSelectionStart() const; int GetSelectionEnd() const; void SelectAll(); + // Reload user buf (WIP #2890) // If you modify underlying user-passed const char* while active you need to call this (InputText V2 may lift this) // strcpy(my_buf, "hello"); @@ -1071,6 +1263,7 @@ struct IMGUI_API ImGuiInputTextState void ReloadUserBufAndKeepSelection(); void ReloadUserBufAndMoveToEnd(); }; + enum ImGuiWindowRefreshFlags_ { ImGuiWindowRefreshFlags_None = 0, @@ -1079,6 +1272,7 @@ enum ImGuiWindowRefreshFlags_ ImGuiWindowRefreshFlags_RefreshOnFocus = 1 << 2, // [EXPERIMENTAL] Always refresh on focus // Refresh policy/frequency, Load Balancing etc. }; + enum ImGuiNextWindowDataFlags_ { ImGuiNextWindowDataFlags_None = 0, @@ -1097,10 +1291,12 @@ enum ImGuiNextWindowDataFlags_ ImGuiNextWindowDataFlags_HasDock = 1 << 12, ImGuiNextWindowDataFlags_HasWindowClass = 1 << 13, }; + // Storage for SetNexWindow** functions struct ImGuiNextWindowData { ImGuiNextWindowDataFlags HasFlags; + // Members below are NOT cleared. Always rely on HasFlags. ImGuiCond PosCond; ImGuiCond SizeCond; @@ -1124,9 +1320,11 @@ struct ImGuiNextWindowData ImGuiWindowClass WindowClass; ImVec2 MenuBarOffsetMinVal; // (Always on) This is not exposed publicly, so we don't clear it and it doesn't have a corresponding flag (could we? for consistency?) ImGuiWindowRefreshFlags RefreshFlagsVal; + ImGuiNextWindowData() { memset(this, 0, sizeof(*this)); } inline void ClearFlags() { HasFlags = ImGuiNextWindowDataFlags_None; } }; + enum ImGuiNextItemDataFlags_ { ImGuiNextItemDataFlags_None = 0, @@ -1136,10 +1334,12 @@ enum ImGuiNextItemDataFlags_ ImGuiNextItemDataFlags_HasRefVal = 1 << 3, ImGuiNextItemDataFlags_HasStorageID = 1 << 4, }; + struct ImGuiNextItemData { ImGuiNextItemDataFlags HasFlags; // Called HasFlags instead of Flags to avoid mistaking this ImGuiItemFlags ItemFlags; // Currently only tested/used for ImGuiItemFlags_AllowOverlap and ImGuiItemFlags_HasSelectionUserData. + // Members below are NOT cleared by ItemAdd() meaning they are still valid during e.g. NavProcessItem(). Always rely on HasFlags. ImGuiID FocusScopeId; // Set by SetNextItemSelectionUserData() ImGuiSelectionUserData SelectionUserData; // Set by SetNextItemSelectionUserData() (note that NULL/0 is a valid value, we use -1 == ImGuiSelectionUserData_Invalid to mark invalid values) @@ -1150,9 +1350,11 @@ struct ImGuiNextItemData ImU8 OpenCond; // Set by SetNextItemOpen() ImGuiDataTypeStorage RefVal; // Not exposed yet, for ImGuiInputTextFlags_ParseEmptyAsRefVal ImGuiID StorageId; // Set by SetNextItemStorageID() + ImGuiNextItemData() { memset(this, 0, sizeof(*this)); SelectionUserData = -1; } inline void ClearFlags() { HasFlags = ImGuiNextItemDataFlags_None; ItemFlags = ImGuiItemFlags_None; } // Also cleared manually by ItemAdd()! }; + // Status storage for the last submitted item struct ImGuiLastItemData { @@ -1165,19 +1367,25 @@ struct ImGuiLastItemData ImRect DisplayRect; // Display rectangle. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasDisplayRect) is set. ImRect ClipRect; // Clip rectangle at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasClipRect) is set.. ImGuiKeyChord Shortcut; // Shortcut at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasShortcut) is set.. + ImGuiLastItemData() { memset(this, 0, sizeof(*this)); } }; + // Store data emitted by TreeNode() for usage by TreePop() -// - To implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere: store the minimum amount of data +// - To implement ImGuiTreeNodeFlags_NavLeftJumpsToParent: store the minimum amount of data // which we can't infer in TreePop(), to perform the equivalent of NavApplyItemToResult(). // Only stored when the node is a potential candidate for landing on a Left arrow jump. struct ImGuiTreeNodeStackData { ImGuiID ID; ImGuiTreeNodeFlags TreeFlags; - ImGuiItemFlags ItemFlags; // Used for nav landing - ImRect NavRect; // Used for nav landing + ImGuiItemFlags ItemFlags; // Used for nav landing + ImRect NavRect; // Used for nav landing + float DrawLinesX1; + float DrawLinesToNodesY2; + ImGuiTableColumnIdx DrawLinesTableColumn; }; + // sizeof() = 20 struct IMGUI_API ImGuiErrorRecoveryState { @@ -1192,8 +1400,10 @@ struct IMGUI_API ImGuiErrorRecoveryState short SizeOfItemFlagsStack; short SizeOfBeginPopupStack; short SizeOfDisabledStack; + ImGuiErrorRecoveryState() { memset(this, 0, sizeof(*this)); } }; + // Data saved for each window pushed into the stack struct ImGuiWindowStackData { @@ -1203,19 +1413,23 @@ struct ImGuiWindowStackData bool DisabledOverrideReenable; // Non-child window override disabled flag float DisabledOverrideReenableAlphaBackup; }; + struct ImGuiShrinkWidthItem { int Index; float Width; float InitialWidth; }; + struct ImGuiPtrOrIndex { void* Ptr; // Either field can be set, not both. e.g. Dock node tab bars are loose while BeginTabBar() ones are in a pool. int Index; // Usually index in a main pool. + ImGuiPtrOrIndex(void* ptr) { Ptr = ptr; Index = -1; } ImGuiPtrOrIndex(int index) { Ptr = NULL; Index = index; } }; + // Data used by IsItemDeactivated()/IsItemDeactivatedAfterEdit() functions struct ImGuiDeactivatedItemData { @@ -1224,15 +1438,18 @@ struct ImGuiDeactivatedItemData bool HasBeenEditedBefore; bool IsAlive; }; + //----------------------------------------------------------------------------- // [SECTION] Popup support //----------------------------------------------------------------------------- + enum ImGuiPopupPositionPolicy { ImGuiPopupPositionPolicy_Default, ImGuiPopupPositionPolicy_ComboBox, ImGuiPopupPositionPolicy_Tooltip, }; + // Storage for popup stacks (g.OpenPopupStack and g.BeginPopupStack) struct ImGuiPopupData { @@ -1244,13 +1461,17 @@ struct ImGuiPopupData ImGuiID OpenParentId; // Set on OpenPopup(), we need this to differentiate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) ImVec2 OpenPopupPos; // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse) ImVec2 OpenMousePos; // Set on OpenPopup(), copy of mouse position at the time of opening popup + ImGuiPopupData() { memset(this, 0, sizeof(*this)); ParentNavLayer = OpenFrameCount = -1; } }; + //----------------------------------------------------------------------------- // [SECTION] Inputs support //----------------------------------------------------------------------------- + // Bit array for named keys typedef ImBitArray ImBitArrayForNamedKeys; + // [Internal] Key ranges #define ImGuiKey_LegacyNativeKey_BEGIN 0 #define ImGuiKey_LegacyNativeKey_END 512 @@ -1262,6 +1483,7 @@ typedef ImBitArray ImBitAr #define ImGuiKey_Mouse_END (ImGuiKey_MouseWheelY + 1) #define ImGuiKey_Aliases_BEGIN (ImGuiKey_Mouse_BEGIN) #define ImGuiKey_Aliases_END (ImGuiKey_Mouse_END) + // [Internal] Named shortcuts for Navigation #define ImGuiKey_NavKeyboardTweakSlow ImGuiMod_Ctrl #define ImGuiKey_NavKeyboardTweakFast ImGuiMod_Shift @@ -1271,6 +1493,7 @@ typedef ImBitArray ImBitAr #define ImGuiKey_NavGamepadCancel (g.IO.ConfigNavSwapGamepadButtons ? ImGuiKey_GamepadFaceDown : ImGuiKey_GamepadFaceRight) #define ImGuiKey_NavGamepadMenu ImGuiKey_GamepadFaceLeft #define ImGuiKey_NavGamepadInput ImGuiKey_GamepadFaceUp + enum ImGuiInputEventType { ImGuiInputEventType_None = 0, @@ -1283,7 +1506,8 @@ enum ImGuiInputEventType ImGuiInputEventType_Focus, ImGuiInputEventType_COUNT }; -enum ImGuiInputSource + +enum ImGuiInputSource : int { ImGuiInputSource_None = 0, ImGuiInputSource_Mouse, // Note: may be Mouse or TouchScreen or Pen. See io.MouseSource to distinguish them. @@ -1291,6 +1515,7 @@ enum ImGuiInputSource ImGuiInputSource_Gamepad, ImGuiInputSource_COUNT }; + // FIXME: Structures in the union below need to be declared as anonymous unions appears to be an extension? // Using ImVec2() would fail on Clang 'union member 'MousePos' has a non-trivial default constructor' struct ImGuiInputEventMousePos { float PosX, PosY; ImGuiMouseSource MouseSource; }; @@ -1300,6 +1525,7 @@ struct ImGuiInputEventMouseViewport { ImGuiID HoveredViewportID; }; struct ImGuiInputEventKey { ImGuiKey Key; bool Down; float AnalogValue; }; struct ImGuiInputEventText { unsigned int Char; }; struct ImGuiInputEventAppFocused { bool Focused; }; + struct ImGuiInputEvent { ImGuiInputEventType Type; @@ -1316,13 +1542,17 @@ struct ImGuiInputEvent ImGuiInputEventAppFocused AppFocused; // if Type == ImGuiInputEventType_Focus }; bool AddedByTestEngine; + ImGuiInputEvent() { memset(this, 0, sizeof(*this)); } }; + // Input function taking an 'ImGuiID owner_id' argument defaults to (ImGuiKeyOwner_Any == 0) aka don't test ownership, which matches legacy behavior. #define ImGuiKeyOwner_Any ((ImGuiID)0) // Accept key that have an owner, UNLESS a call to SetKeyOwner() explicitly used ImGuiInputFlags_LockThisFrame or ImGuiInputFlags_LockUntilRelease. #define ImGuiKeyOwner_NoOwner ((ImGuiID)-1) // Require key to have no owner. //#define ImGuiKeyOwner_None ImGuiKeyOwner_NoOwner // We previously called this 'ImGuiKeyOwner_None' but it was inconsistent with our pattern that _None values == 0 and quite dangerous. Also using _NoOwner makes the IsKeyPressed() calls more explicit. + typedef ImS16 ImGuiKeyRoutingIndex; + // Routing table entry (sizeof() == 16 bytes) struct ImGuiKeyRoutingData { @@ -1332,8 +1562,10 @@ struct ImGuiKeyRoutingData ImU8 RoutingNextScore; // Lower is better (0: perfect score) ImGuiID RoutingCurr; ImGuiID RoutingNext; + ImGuiKeyRoutingData() { NextEntryIndex = -1; Mods = 0; RoutingCurrScore = RoutingNextScore = 255; RoutingCurr = RoutingNext = ImGuiKeyOwner_NoOwner; } }; + // Routing table: maintain a desired owner for each possible key-chord (key + mods), and setup owner in NewFrame() when mods are matching. // Stored in main context (1 instance) struct ImGuiKeyRoutingTable @@ -1341,9 +1573,11 @@ struct ImGuiKeyRoutingTable ImGuiKeyRoutingIndex Index[ImGuiKey_NamedKey_COUNT]; // Index of first entry in Entries[] ImVector Entries; ImVector EntriesNext; // Double-buffer to avoid reallocation (could use a shared buffer) + ImGuiKeyRoutingTable() { Clear(); } void Clear() { for (int n = 0; n < IM_ARRAYSIZE(Index); n++) Index[n] = -1; Entries.clear(); EntriesNext.clear(); } }; + // This extends ImGuiKeyData but only for named keys (legacy keys don't support the new features) // Stored in main context (1 per named key). In the future it might be merged into ImGuiKeyData. struct ImGuiKeyOwnerData @@ -1352,8 +1586,10 @@ struct ImGuiKeyOwnerData ImGuiID OwnerNext; bool LockThisFrame; // Reading this key requires explicit owner id (until end of frame). Set by ImGuiInputFlags_LockThisFrame. bool LockUntilRelease; // Reading this key requires explicit owner id (until key is released). Set by ImGuiInputFlags_LockUntilRelease. When this is true LockThisFrame is always true as well. + ImGuiKeyOwnerData() { OwnerCurr = OwnerNext = ImGuiKeyOwner_NoOwner; LockThisFrame = LockUntilRelease = false; } }; + // Extend ImGuiInputFlags_ // Flags for extended versions of IsKeyPressed(), IsMouseClicked(), Shortcut(), SetKeyOwner(), SetItemKeyOwner() // Don't mistake with ImGuiInputTextFlags! (which is for ImGui::InputText() function) @@ -1370,14 +1606,17 @@ enum ImGuiInputFlagsPrivate_ ImGuiInputFlags_RepeatUntilKeyModsChange = 1 << 5, // Stop repeating when released OR if keyboard mods are changed (default for Shortcut) ImGuiInputFlags_RepeatUntilKeyModsChangeFromNone = 1 << 6, // Stop repeating when released OR if keyboard mods are leaving the None state. Allows going from Mod+Key to Key by releasing Mod. ImGuiInputFlags_RepeatUntilOtherKeyPress = 1 << 7, // Stop repeating when released OR if any other keyboard key is pressed during the repeat + // Flags for SetKeyOwner(), SetItemKeyOwner() // - Locking key away from non-input aware code. Locking is useful to make input-owner-aware code steal keys from non-input-owner-aware code. If all code is input-owner-aware locking would never be necessary. ImGuiInputFlags_LockThisFrame = 1 << 20, // Further accesses to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared at end of frame. ImGuiInputFlags_LockUntilRelease = 1 << 21, // Further accesses to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared when the key is released or at end of each frame if key is released. + // - Condition for SetItemKeyOwner() ImGuiInputFlags_CondHovered = 1 << 22, // Only set if item is hovered (default to both) ImGuiInputFlags_CondActive = 1 << 23, // Only set if item is active (default to both) ImGuiInputFlags_CondDefault_ = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive, + // [Internal] Mask of which function support which flags ImGuiInputFlags_RepeatRateMask_ = ImGuiInputFlags_RepeatRateDefault | ImGuiInputFlags_RepeatRateNavMove | ImGuiInputFlags_RepeatRateNavTweak, ImGuiInputFlags_RepeatUntilMask_ = ImGuiInputFlags_RepeatUntilRelease | ImGuiInputFlags_RepeatUntilKeyModsChange | ImGuiInputFlags_RepeatUntilKeyModsChangeFromNone | ImGuiInputFlags_RepeatUntilOtherKeyPress, @@ -1392,9 +1631,11 @@ enum ImGuiInputFlagsPrivate_ ImGuiInputFlags_SupportedBySetKeyOwner = ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease, ImGuiInputFlags_SupportedBySetItemKeyOwner = ImGuiInputFlags_SupportedBySetKeyOwner | ImGuiInputFlags_CondMask_, }; + //----------------------------------------------------------------------------- // [SECTION] Clipper support //----------------------------------------------------------------------------- + // Note that Max is exclusive, so perhaps should be using a Begin/End convention. struct ImGuiListClipperRange { @@ -1403,9 +1644,11 @@ struct ImGuiListClipperRange bool PosToIndexConvert; // Begin/End are absolute position (will be converted to indices later) ImS8 PosToIndexOffsetMin; // Add to Min after converting to indices ImS8 PosToIndexOffsetMax; // Add to Min after converting to indices + static ImGuiListClipperRange FromIndices(int min, int max) { ImGuiListClipperRange r = { min, max, false, 0, 0 }; return r; } static ImGuiListClipperRange FromPositions(float y1, float y2, int off_min, int off_max) { ImGuiListClipperRange r = { (int)y1, (int)y2, true, (ImS8)off_min, (ImS8)off_max }; return r; } }; + // Temporary clipper data, buffers shared/reused between instances struct ImGuiListClipperData { @@ -1414,12 +1657,15 @@ struct ImGuiListClipperData int StepNo; int ItemsFrozen; ImVector Ranges; + ImGuiListClipperData() { memset(this, 0, sizeof(*this)); } void Reset(ImGuiListClipper* clipper) { ListClipper = clipper; StepNo = ItemsFrozen = 0; Ranges.resize(0); } }; + //----------------------------------------------------------------------------- // [SECTION] Navigation support //----------------------------------------------------------------------------- + enum ImGuiActivateFlags_ { ImGuiActivateFlags_None = 0, @@ -1429,6 +1675,7 @@ enum ImGuiActivateFlags_ ImGuiActivateFlags_FromTabbing = 1 << 3, // Activation requested by a tabbing request ImGuiActivateFlags_FromShortcut = 1 << 4, // Activation requested by an item shortcut via SetNextItemShortcut() function. }; + // Early work-in-progress API for ScrollToItem() enum ImGuiScrollFlags_ { @@ -1443,6 +1690,7 @@ enum ImGuiScrollFlags_ ImGuiScrollFlags_MaskX_ = ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleCenterX | ImGuiScrollFlags_AlwaysCenterX, ImGuiScrollFlags_MaskY_ = ImGuiScrollFlags_KeepVisibleEdgeY | ImGuiScrollFlags_KeepVisibleCenterY | ImGuiScrollFlags_AlwaysCenterY, }; + enum ImGuiNavRenderCursorFlags_ { ImGuiNavRenderCursorFlags_None = 0, @@ -1454,8 +1702,10 @@ enum ImGuiNavRenderCursorFlags_ ImGuiNavHighlightFlags_Compact = ImGuiNavRenderCursorFlags_Compact, // Renamed in 1.91.4 ImGuiNavHighlightFlags_AlwaysDraw = ImGuiNavRenderCursorFlags_AlwaysDraw, // Renamed in 1.91.4 ImGuiNavHighlightFlags_NoRounding = ImGuiNavRenderCursorFlags_NoRounding, // Renamed in 1.91.4 + //ImGuiNavHighlightFlags_TypeThin = ImGuiNavRenderCursorFlags_Compact, // Renamed in 1.90.2 #endif }; + enum ImGuiNavMoveFlags_ { ImGuiNavMoveFlags_None = 0, @@ -1477,12 +1727,14 @@ enum ImGuiNavMoveFlags_ ImGuiNavMoveFlags_NoSetNavCursorVisible = 1 << 14, // Do not alter the nav cursor visible state ImGuiNavMoveFlags_NoClearActiveId = 1 << 15, // (Experimental) Do not clear active id when applying move result }; + enum ImGuiNavLayer { ImGuiNavLayer_Main = 0, // Main scrolling layer ImGuiNavLayer_Menu = 1, // Menu layer (access with Alt) ImGuiNavLayer_COUNT }; + // Storage for navigation query/results struct ImGuiNavItemData { @@ -1495,18 +1747,22 @@ struct ImGuiNavItemData float DistCenter; // Move // Best candidate center distance to current NavId float DistAxial; // Move // Best candidate axial distance to current NavId ImGuiSelectionUserData SelectionUserData;//I+Mov // Best candidate SetNextItemSelectionUserData() value. Valid if (ItemFlags & ImGuiItemFlags_HasSelectionUserData) + ImGuiNavItemData() { Clear(); } void Clear() { Window = NULL; ID = FocusScopeId = 0; ItemFlags = 0; SelectionUserData = -1; DistBox = DistCenter = DistAxial = FLT_MAX; } }; + // Storage for PushFocusScope(), g.FocusScopeStack[], g.NavFocusRoute[] struct ImGuiFocusScopeData { ImGuiID ID; ImGuiID WindowID; }; + //----------------------------------------------------------------------------- // [SECTION] Typing-select support //----------------------------------------------------------------------------- + // Flags for GetTypingSelectRequest() enum ImGuiTypingSelectFlags_ { @@ -1514,6 +1770,7 @@ enum ImGuiTypingSelectFlags_ ImGuiTypingSelectFlags_AllowBackspace = 1 << 0, // Backspace to delete character inputs. If using: ensure GetTypingSelectRequest() is not called more than once per frame (filter by e.g. focus state) ImGuiTypingSelectFlags_AllowSingleCharMode = 1 << 1, // Allow "single char" search mode which is activated when pressing the same character multiple times. }; + // Returned by GetTypingSelectRequest(), designed to eventually be public. struct IMGUI_API ImGuiTypingSelectRequest { @@ -1524,6 +1781,7 @@ struct IMGUI_API ImGuiTypingSelectRequest bool SingleCharMode; // Notify when buffer contains same character repeated, to implement special mode. In this situation it preferred to not display any on-screen search indication. ImS8 SingleCharSize; // Length in bytes of first letter codepoint (1 for ascii, 2-4 for UTF-8). If (SearchBufferLen==RepeatCharSize) only 1 letter has been input. }; + // Storage for GetTypingSelectRequest() struct IMGUI_API ImGuiTypingSelectState { @@ -1533,12 +1791,15 @@ struct IMGUI_API ImGuiTypingSelectState int LastRequestFrame = 0; float LastRequestTime = 0.0f; bool SingleCharModeLock = false; // After a certain single char repeat count we lock into SingleCharMode. Two benefits: 1) buffer never fill, 2) we can provide an immediate SingleChar mode without timer elapsing. + ImGuiTypingSelectState() { memset(this, 0, sizeof(*this)); } void Clear() { SearchBuffer[0] = 0; SingleCharModeLock = false; } // We preserve remaining data for easier debugging }; + //----------------------------------------------------------------------------- // [SECTION] Columns support //----------------------------------------------------------------------------- + // Flags for internal's BeginColumns(). This is an obsolete API. Prefer using BeginTable() nowadays! enum ImGuiOldColumnFlags_ { @@ -1548,6 +1809,7 @@ enum ImGuiOldColumnFlags_ ImGuiOldColumnFlags_NoPreserveWidths = 1 << 2, // Disable column width preservation when adjusting columns ImGuiOldColumnFlags_NoForceWithinWindow = 1 << 3, // Disable forcing columns to fit within window ImGuiOldColumnFlags_GrowParentContentsSize = 1 << 4, // Restore pre-1.51 behavior of extending the parent window contents size but _without affecting the columns width at all_. Will eventually remove. + // Obsolete names (will be removed) #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS //ImGuiColumnsFlags_None = ImGuiOldColumnFlags_None, @@ -1558,14 +1820,17 @@ enum ImGuiOldColumnFlags_ //ImGuiColumnsFlags_GrowParentContentsSize = ImGuiOldColumnFlags_GrowParentContentsSize, #endif }; + struct ImGuiOldColumnData { float OffsetNorm; // Column start offset, normalized 0.0 (far left) -> 1.0 (far right) float OffsetNormBeforeResize; ImGuiOldColumnFlags Flags; // Not exposed ImRect ClipRect; + ImGuiOldColumnData() { memset(this, 0, sizeof(*this)); } }; + struct ImGuiOldColumns { ImGuiID ID; @@ -1583,11 +1848,14 @@ struct ImGuiOldColumns ImRect HostBackupParentWorkRect;//Backup of WorkRect at the time of BeginColumns() ImVector Columns; ImDrawListSplitter Splitter; + ImGuiOldColumns() { memset(this, 0, sizeof(*this)); } }; + //----------------------------------------------------------------------------- // [SECTION] Box-select support //----------------------------------------------------------------------------- + struct ImGuiBoxSelectState { // Active box-selection data (persistent, 1 active at a time) @@ -1602,18 +1870,23 @@ struct ImGuiBoxSelectState ImVec2 EndPosRel; // End position in window-contents relative space ImVec2 ScrollAccum; // Scrolling accumulator (to behave at high-frame spaces) ImGuiWindow* Window; + // Temporary/Transient data bool UnclipMode; // (Temp/Transient, here in hot area). Set/cleared by the BeginMultiSelect()/EndMultiSelect() owning active box-select. ImRect UnclipRect; // Rectangle where ItemAdd() clipping may be temporarily disabled. Need support by multi-select supporting widgets. ImRect BoxSelectRectPrev; // Selection rectangle in absolute coordinates (derived every frame from BoxSelectStartPosRel and MousePos) ImRect BoxSelectRectCurr; + ImGuiBoxSelectState() { memset(this, 0, sizeof(*this)); } }; + //----------------------------------------------------------------------------- // [SECTION] Multi-select support //----------------------------------------------------------------------------- + // We always assume that -1 is an invalid value (which works for indices and pointers) #define ImGuiSelectionUserData_Invalid ((ImGuiSelectionUserData)-1) + // Temporary storage for multi-select struct IMGUI_API ImGuiMultiSelectTempData { @@ -1633,10 +1906,12 @@ struct IMGUI_API ImGuiMultiSelectTempData bool NavIdPassedBy; bool RangeSrcPassedBy; // Set by the item that matches RangeSrcItem. bool RangeDstPassedBy; // Set by the item that matches NavJustMovedToId when IsSetRange is set. + ImGuiMultiSelectTempData() { Clear(); } void Clear() { size_t io_sz = sizeof(IO); ClearIO(); memset((void*)(&IO + 1), 0, sizeof(*this) - io_sz); } // Zero-clear except IO as we preserve IO.Requests[] buffer allocation. void ClearIO() { IO.Requests.resize(0); IO.RangeSrcItem = IO.NavIdItem = ImGuiSelectionUserData_Invalid; IO.NavIdSelected = IO.RangeSrcReset = false; } }; + // Persistent storage for multi-select (as long as selection is alive) struct IMGUI_API ImGuiMultiSelectState { @@ -1648,14 +1923,19 @@ struct IMGUI_API ImGuiMultiSelectState ImS8 NavIdSelected; // -1 (don't have) or true/false ImGuiSelectionUserData RangeSrcItem; // ImGuiSelectionUserData NavIdItem; // SetNextItemSelectionUserData() value for NavId (if part of submitted items) + ImGuiMultiSelectState() { Window = NULL; ID = 0; LastFrameActive = LastSelectionSize = 0; RangeSelected = NavIdSelected = -1; RangeSrcItem = NavIdItem = ImGuiSelectionUserData_Invalid; } }; + //----------------------------------------------------------------------------- // [SECTION] Docking support //----------------------------------------------------------------------------- + #define DOCKING_HOST_DRAW_CHANNEL_BG 0 // Dock host: background fill #define DOCKING_HOST_DRAW_CHANNEL_FG 1 // Dock host: decorations and contents + #ifdef IMGUI_HAS_DOCK + // Extend ImGuiDockNodeFlags_ enum ImGuiDockNodeFlagsPrivate_ { @@ -1669,6 +1949,7 @@ enum ImGuiDockNodeFlagsPrivate_ ImGuiDockNodeFlags_NoResizeX = 1 << 16, // // ImGuiDockNodeFlags_NoResizeY = 1 << 17, // // ImGuiDockNodeFlags_DockedWindowsInFocusRoute= 1 << 18, // // Any docked window will be automatically be focus-route chained (window->ParentWindowForFocusRoute set to this) so Shortcut() in this window can run when any docked window is focused. + // Disable docking/undocking actions in this dockspace or individual node (existing docked nodes will be preserved) // Those are not exposed in public because the desirable sharing/inheriting/copy-flag-on-split behaviors are quite difficult to design and understand. // The two public flags ImGuiDockNodeFlags_NoDockingOverCentralNode/ImGuiDockNodeFlags_NoDockingSplit don't have those issues. @@ -1677,13 +1958,16 @@ enum ImGuiDockNodeFlagsPrivate_ ImGuiDockNodeFlags_NoDockingOverOther = 1 << 21, // // Disable this node from being docked over another window or non-empty node. ImGuiDockNodeFlags_NoDockingOverEmpty = 1 << 22, // // Disable this node from being docked over an empty node (e.g. DockSpace with no other windows) ImGuiDockNodeFlags_NoDocking = ImGuiDockNodeFlags_NoDockingOverMe | ImGuiDockNodeFlags_NoDockingOverOther | ImGuiDockNodeFlags_NoDockingOverEmpty | ImGuiDockNodeFlags_NoDockingSplit | ImGuiDockNodeFlags_NoDockingSplitOther, + // Masks ImGuiDockNodeFlags_SharedFlagsInheritMask_ = ~0, ImGuiDockNodeFlags_NoResizeFlagsMask_ = (int)ImGuiDockNodeFlags_NoResize | ImGuiDockNodeFlags_NoResizeX | ImGuiDockNodeFlags_NoResizeY, + // When splitting, those local flags are moved to the inheriting child, never duplicated ImGuiDockNodeFlags_LocalFlagsTransferMask_ = (int)ImGuiDockNodeFlags_NoDockingSplit | ImGuiDockNodeFlags_NoResizeFlagsMask_ | (int)ImGuiDockNodeFlags_AutoHideTabBar | ImGuiDockNodeFlags_CentralNode | ImGuiDockNodeFlags_NoTabBar | ImGuiDockNodeFlags_HiddenTabBar | ImGuiDockNodeFlags_NoWindowMenuButton | ImGuiDockNodeFlags_NoCloseButton, ImGuiDockNodeFlags_SavedFlagsMask_ = ImGuiDockNodeFlags_NoResizeFlagsMask_ | ImGuiDockNodeFlags_DockSpace | ImGuiDockNodeFlags_CentralNode | ImGuiDockNodeFlags_NoTabBar | ImGuiDockNodeFlags_HiddenTabBar | ImGuiDockNodeFlags_NoWindowMenuButton | ImGuiDockNodeFlags_NoCloseButton, }; + // Store the source authority (dock node vs window) of a field enum ImGuiDataAuthority_ { @@ -1691,6 +1975,7 @@ enum ImGuiDataAuthority_ ImGuiDataAuthority_DockNode, ImGuiDataAuthority_Window, }; + enum ImGuiDockNodeState { ImGuiDockNodeState_Unknown, @@ -1698,6 +1983,7 @@ enum ImGuiDockNodeState ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing, ImGuiDockNodeState_HostWindowVisible, }; + // sizeof() 156~192 struct IMGUI_API ImGuiDockNode { @@ -1717,6 +2003,7 @@ struct IMGUI_API ImGuiDockNode ImGuiAxis SplitAxis; // [Split node only] Split axis (X or Y) ImGuiWindowClass WindowClass; // [Root node only] ImU32 LastBgColor; + ImGuiWindow* HostWindow; ImGuiWindow* VisibleWindow; // Generally point to window which is ID is == SelectedTabID, but when CTRL+Tabbing this can be a different window. ImGuiDockNode* CentralNode; // [Root node only] Pointer to central node. @@ -1743,6 +2030,7 @@ struct IMGUI_API ImGuiDockNode bool WantMouseMove :1; // After a node extraction we need to transition toward moving the newly created host window bool WantHiddenTabBarUpdate :1; bool WantHiddenTabBarToggle :1; + ImGuiDockNode(ImGuiID id); ~ImGuiDockNode(); bool IsRootNode() const { return ParentNode == NULL; } @@ -1755,9 +2043,11 @@ struct IMGUI_API ImGuiDockNode bool IsLeafNode() const { return ChildNodes[0] == NULL; } bool IsEmpty() const { return ChildNodes[0] == NULL && Windows.Size == 0; } ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } + void SetLocalFlags(ImGuiDockNodeFlags flags) { LocalFlags = flags; UpdateMergedFlags(); } void UpdateMergedFlags() { MergedFlags = SharedFlags | LocalFlags | LocalFlagsInWindows; } }; + // List of colors that are stored at the time of Begin() into Docked Windows. // We currently store the packed colors in a simple array window->DockStyle.Colors[]. // A better solution may involve appending into a log of colors in ImGuiContext + store offsets into those arrays in ImGuiWindow, @@ -1774,11 +2064,13 @@ enum ImGuiWindowDockStyleCol ImGuiWindowDockStyleCol_TabDimmedSelectedOverline, ImGuiWindowDockStyleCol_COUNT }; + // We don't store style.Alpha: dock_node->LastBgColor embeds it and otherwise it would only affect the docking tab, which intuitively I would say we don't want to. struct ImGuiWindowDockStyle { ImU32 Colors[ImGuiWindowDockStyleCol_COUNT]; }; + struct ImGuiDockContext { ImGuiStorage Nodes; // Map ID -> ImGuiDockNode*: Active nodes @@ -1787,10 +2079,13 @@ struct ImGuiDockContext bool WantFullRebuild; ImGuiDockContext() { memset(this, 0, sizeof(*this)); } }; + #endif // #ifdef IMGUI_HAS_DOCK + //----------------------------------------------------------------------------- // [SECTION] Viewport support //----------------------------------------------------------------------------- + // ImGuiViewport Private/Internals fields (cardinal sin: we are using inheritance!) // Every instance of ImGuiViewport is in fact a ImGuiViewportP. struct ImGuiViewportP : public ImGuiViewport @@ -1813,6 +2108,7 @@ struct ImGuiViewportP : public ImGuiViewport ImVec2 LastPlatformPos; ImVec2 LastPlatformSize; ImVec2 LastRendererSize; + // Per-viewport work area // - Insets are >= 0.0f values, distance from viewport corners to work area. // - BeginMainMenuBar() and DockspaceOverViewport() tend to use work area to avoid stepping over existing contents. @@ -1821,21 +2117,26 @@ struct ImGuiViewportP : public ImGuiViewport ImVec2 WorkInsetMax; // " ImVec2 BuildWorkInsetMin; // Work Area inset accumulator for current frame, to become next frame's WorkInset ImVec2 BuildWorkInsetMax; // " + ImGuiViewportP() { Window = NULL; Idx = -1; LastFrameActive = BgFgDrawListsLastFrame[0] = BgFgDrawListsLastFrame[1] = LastFocusedStampCount = -1; LastNameHash = 0; Alpha = LastAlpha = 1.0f; LastFocusedHadNavWindow = false; PlatformMonitor = -1; BgFgDrawLists[0] = BgFgDrawLists[1] = NULL; LastPlatformPos = LastPlatformSize = LastRendererSize = ImVec2(FLT_MAX, FLT_MAX); } ~ImGuiViewportP() { if (BgFgDrawLists[0]) IM_DELETE(BgFgDrawLists[0]); if (BgFgDrawLists[1]) IM_DELETE(BgFgDrawLists[1]); } void ClearRequestFlags() { PlatformRequestClose = PlatformRequestMove = PlatformRequestResize = false; } + // Calculate work rect pos/size given a set of offset (we have 1 pair of offset for rect locked from last frame data, and 1 pair for currently building rect) ImVec2 CalcWorkRectPos(const ImVec2& inset_min) const { return ImVec2(Pos.x + inset_min.x, Pos.y + inset_min.y); } ImVec2 CalcWorkRectSize(const ImVec2& inset_min, const ImVec2& inset_max) const { return ImVec2(ImMax(0.0f, Size.x - inset_min.x - inset_max.x), ImMax(0.0f, Size.y - inset_min.y - inset_max.y)); } void UpdateWorkRect() { WorkPos = CalcWorkRectPos(WorkInsetMin); WorkSize = CalcWorkRectSize(WorkInsetMin, WorkInsetMax); } // Update public fields + // Helpers to retrieve ImRect (we don't need to store BuildWorkRect as every access tend to change it, hence the code asymmetry) ImRect GetMainRect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } ImRect GetWorkRect() const { return ImRect(WorkPos.x, WorkPos.y, WorkPos.x + WorkSize.x, WorkPos.y + WorkSize.y); } ImRect GetBuildWorkRect() const { ImVec2 pos = CalcWorkRectPos(BuildWorkInsetMin); ImVec2 size = CalcWorkRectSize(BuildWorkInsetMin, BuildWorkInsetMax); return ImRect(pos.x, pos.y, pos.x + size.x, pos.y + size.y); } }; + //----------------------------------------------------------------------------- // [SECTION] Settings support //----------------------------------------------------------------------------- + // Windows data saved in imgui.ini file // Because we never destroy or rename ImGuiWindowSettings, we can store the names in a separate buffer easily. // (this is designed to be stored in a ImChunkStream buffer, with the variable-length Name following our structure) @@ -1853,9 +2154,11 @@ struct ImGuiWindowSettings bool IsChild; bool WantApply; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) bool WantDelete; // Set to invalidate/delete the settings entry + ImGuiWindowSettings() { memset(this, 0, sizeof(*this)); DockOrder = -1; } char* GetName() { return (char*)(this + 1); } }; + struct ImGuiSettingsHandler { const char* TypeName; // Short description stored in .ini file. Disallowed characters: '[' ']' @@ -1867,11 +2170,14 @@ struct ImGuiSettingsHandler void (*ApplyAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler); // Read: Called after reading (in registration order) void (*WriteAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* out_buf); // Write: Output every entries into 'out_buf' void* UserData; + ImGuiSettingsHandler() { memset(this, 0, sizeof(*this)); } }; + //----------------------------------------------------------------------------- // [SECTION] Localization support //----------------------------------------------------------------------------- + // This is experimental and not officially supported, it'll probably fall short of features, if/when it does we may backtrack. enum ImGuiLocKey : int { @@ -1890,14 +2196,17 @@ enum ImGuiLocKey : int ImGuiLocKey_DockingDragToUndockOrMoveNode, ImGuiLocKey_COUNT }; + struct ImGuiLocEntry { ImGuiLocKey Key; const char* Text; }; + //----------------------------------------------------------------------------- // [SECTION] Error handling, State recovery support //----------------------------------------------------------------------------- + // Macros used by Recoverable Error handling // - Only dispatch error if _EXPR: evaluate as assert (similar to an assert macro). // - The message will always be a string literal, in order to increase likelihood of being display by an assert handler. @@ -1906,11 +2215,14 @@ struct ImGuiLocEntry #ifndef IM_ASSERT_USER_ERROR #define IM_ASSERT_USER_ERROR(_EXPR,_MSG) do { if (!(_EXPR) && ImGui::ErrorLog(_MSG)) { IM_ASSERT((_EXPR) && _MSG); } } while (0) // Recoverable User Error #endif + // The error callback is currently not public, as it is expected that only advanced users will rely on it. typedef void (*ImGuiErrorCallback)(ImGuiContext* ctx, void* user_data, const char* msg); // Function signature for g.ErrorCallback + //----------------------------------------------------------------------------- // [SECTION] Metrics, Debug Tools //----------------------------------------------------------------------------- + // See IMGUI_DEBUG_LOG() and IMGUI_DEBUG_LOG_XXX() macros. enum ImGuiDebugLogFlags_ { @@ -1928,24 +2240,29 @@ enum ImGuiDebugLogFlags_ ImGuiDebugLogFlags_EventInputRouting = 1 << 9, ImGuiDebugLogFlags_EventDocking = 1 << 10, ImGuiDebugLogFlags_EventViewport = 1 << 11, + ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventFont | ImGuiDebugLogFlags_EventInputRouting | ImGuiDebugLogFlags_EventDocking | ImGuiDebugLogFlags_EventViewport, ImGuiDebugLogFlags_OutputToTTY = 1 << 20, // Also send output to TTY ImGuiDebugLogFlags_OutputToTestEngine = 1 << 21, // Also send output to Test Engine }; + struct ImGuiDebugAllocEntry { int FrameCount; ImS16 AllocCount; ImS16 FreeCount; }; + struct ImGuiDebugAllocInfo { int TotalAllocCount; // Number of call to MemAlloc(). int TotalFreeCount; ImS16 LastEntriesIdx; // Current index in buffer ImGuiDebugAllocEntry LastEntriesBuf[6]; // Track last 6 frames that had allocations + ImGuiDebugAllocInfo() { memset(this, 0, sizeof(*this)); } }; + struct ImGuiMetricsConfig { bool ShowDebugLog = false; @@ -1956,12 +2273,15 @@ struct ImGuiMetricsConfig bool ShowDrawCmdMesh = true; bool ShowDrawCmdBoundingBoxes = true; bool ShowTextEncodingViewer = false; + bool ShowTextureUsedRect = false; bool ShowDockingNodes = false; int ShowWindowsRectsType = -1; int ShowTablesRectsType = -1; int HighlightMonitorIdx = -1; ImGuiID HighlightViewportID = 0; + bool ShowFontPreview = true; }; + struct ImGuiStackLevelInfo { ImGuiID ID; @@ -1969,8 +2289,10 @@ struct ImGuiStackLevelInfo bool QuerySuccess; // Obtained result from DebugHookIdInfo() ImGuiDataType DataType : 8; char Desc[57]; // Arbitrarily sized buffer to hold a result (FIXME: could replace Results[] with a chunk stream?) FIXME: Now that we added CTRL+C this should be fixed. + ImGuiStackLevelInfo() { memset(this, 0, sizeof(*this)); } }; + // State for ID Stack tool queries struct ImGuiIDStackTool { @@ -1981,13 +2303,17 @@ struct ImGuiIDStackTool bool CopyToClipboardOnCtrlC; float CopyToClipboardLastTime; ImGuiTextBuffer ResultPathBuf; + ImGuiIDStackTool() { memset(this, 0, sizeof(*this)); CopyToClipboardLastTime = -FLT_MAX; } }; + //----------------------------------------------------------------------------- // [SECTION] Generic context hooks //----------------------------------------------------------------------------- + typedef void (*ImGuiContextHookCallback)(ImGuiContext* ctx, ImGuiContextHook* hook); enum ImGuiContextHookType { ImGuiContextHookType_NewFramePre, ImGuiContextHookType_NewFramePost, ImGuiContextHookType_EndFramePre, ImGuiContextHookType_EndFramePost, ImGuiContextHookType_RenderPre, ImGuiContextHookType_RenderPost, ImGuiContextHookType_Shutdown, ImGuiContextHookType_PendingRemoval_ }; + struct ImGuiContextHook { ImGuiID HookId; // A unique ID assigned by AddContextHook() @@ -1995,24 +2321,29 @@ struct ImGuiContextHook ImGuiID Owner; ImGuiContextHookCallback Callback; void* UserData; + ImGuiContextHook() { memset(this, 0, sizeof(*this)); } }; + //----------------------------------------------------------------------------- // [SECTION] ImGuiContext (main Dear ImGui context) //----------------------------------------------------------------------------- + struct ImGuiContext { bool Initialized; - bool FontAtlasOwnedByContext; // IO.Fonts-> is owned by the ImGuiContext and will be destructed along with it. ImGuiIO IO; ImGuiPlatformIO PlatformIO; ImGuiStyle Style; ImGuiConfigFlags ConfigFlagsCurrFrame; // = g.IO.ConfigFlags at the time of NewFrame() ImGuiConfigFlags ConfigFlagsLastFrame; - ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() - float FontSize; // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. - float FontBaseSize; // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. - float FontScale; // == FontSize / Font->FontSize + ImVector FontAtlases; // List of font atlases used by the context (generally only contains g.IO.Fonts aka the main font atlas) + ImFont* Font; // Currently bound font. (== FontStack.back().Font) + ImFontBaked* FontBaked; // Currently bound font at currently bound size. (== Font->GetFontBaked(FontSize)) + float FontSize; // Currently bound font size == line height (== FontSizeBase + externals scales applied in the UpdateCurrentFontSize() function). + float FontSizeBase; // Font size before scaling == style.FontSizeBase == value passed to PushFont() when specified. + float FontBakedScale; // == FontBaked->Size / FontSize. Scale factor over baked size. Rarely used nowadays, very often == 1.0f. + float FontRasterizerDensity; // Current font density. Used by all calls to GetFontBaked(). float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale ImDrawListSharedData DrawListSharedData; double Time; @@ -2027,11 +2358,13 @@ struct ImGuiContext bool TestEngineHookItems; // Will call test engine hooks: ImGuiTestEngineHook_ItemAdd(), ImGuiTestEngineHook_ItemInfo(), ImGuiTestEngineHook_Log() void* TestEngine; // Test engine user data char ContextName[16]; // Storage for a context name (to facilitate debugging multi-context setups) + // Inputs ImVector InputEventsQueue; // Input events which will be trickled/written into IO structure. ImVector InputEventsTrail; // Past input events processed in NewFrame(). This is to allow domain-specific application to access e.g mouse/pen trail. ImGuiMouseSource InputEventsNextMouseSource; ImU32 InputEventsNextEventId; + // Windows state ImVector Windows; // Windows, sorted in display order, back to front ImVector WindowsFocusOrder; // Root windows, sorted in focus order, back to front. @@ -2053,8 +2386,9 @@ struct ImGuiContext float WheelingWindowReleaseTimer; ImVec2 WheelingWindowWheelRemainder; ImVec2 WheelingAxisAvg; + // Item/widgets state and tracking information - ImGuiID DebugDrawIdConflicts; // Set when we detect multiple items with the same identifier + ImGuiID DebugDrawIdConflictsId; // Set when we detect multiple items with the same identifier ImGuiID DebugHookIdInfo; // Will call core hooks: DebugHookIdInfo() from GetID functions, used by ID Stack Tool [next HoveredId/ActiveId to not pull in an extra cache-line] ImGuiID HoveredId; // Hovered widget, filled during the frame ImGuiID HoveredIdPreviousFrame; @@ -2074,6 +2408,7 @@ struct ImGuiContext bool ActiveIdHasBeenEditedBefore; // Was the value associated to the widget Edited over the course of the Active state. bool ActiveIdHasBeenEditedThisFrame; bool ActiveIdFromShortcut; + ImGuiID ActiveIdDisabledId; // When clicking a disabled item we set ActiveId=window->MoveId to avoid interference with widget code. Actual item ID is stored here. int ActiveIdMouseButton : 8; ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) ImGuiWindow* ActiveIdWindow; @@ -2083,6 +2418,7 @@ struct ImGuiContext ImGuiDataTypeStorage ActiveIdValueOnActivation; // Backup of initial value at the time of activation. ONLY SET BY SPECIFIC WIDGETS: DragXXX and SliderXXX. ImGuiID LastActiveId; // Store the last non-zero ActiveId, useful for animation. float LastActiveIdTimer; // Store the last non-zero ActiveId timer since the beginning of activation, useful for animation. + // Key/Input Ownership + Shortcut Routing system // - The idea is that instead of "eating" a given key, we can link to an owner. // - Input query can then read input by specifying ImGuiKeyOwner_Any (== 0), ImGuiKeyOwner_NoOwner (== -1) or a custom ID. @@ -2097,6 +2433,7 @@ struct ImGuiContext bool ActiveIdUsingAllKeyboardKeys; // Active widget will want to read all keyboard keys inputs. (this is a shortcut for not taking ownership of 100+ keys, frequently used by drag operations) ImGuiKeyChord DebugBreakInShortcutRouting; // Set to break in SetShortcutRouting()/Shortcut() calls. //ImU32 ActiveIdUsingNavInputMask; // [OBSOLETE] Since (IMGUI_VERSION_NUM >= 18804) : 'g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);' becomes --> 'SetKeyOwner(ImGuiKey_Escape, g.ActiveId) and/or SetKeyOwner(ImGuiKey_NavGamepadCancel, g.ActiveId);' + // Next window/item data ImGuiID CurrentFocusScopeId; // Value for currently appending items == g.FocusScopeStack.back(). Not to be mistaken with g.NavFocusScopeId. ImGuiItemFlags CurrentItemFlags; // Value for currently appending items == g.ItemFlagsStack.back() @@ -2105,17 +2442,19 @@ struct ImGuiContext ImGuiLastItemData LastItemData; // Storage for last submitted item (setup by ItemAdd) ImGuiNextWindowData NextWindowData; // Storage for SetNextWindow** functions bool DebugShowGroupRects; + // Shared stacks ImGuiCol DebugFlashStyleColorIdx; // (Keep close to ColorStack to share cache line) ImVector ColorStack; // Stack for PushStyleColor()/PopStyleColor() - inherited by Begin() ImVector StyleVarStack; // Stack for PushStyleVar()/PopStyleVar() - inherited by Begin() - ImVector FontStack; // Stack for PushFont()/PopFont() - inherited by Begin() + ImVector FontStack; // Stack for PushFont()/PopFont() - inherited by Begin() ImVector FocusScopeStack; // Stack for PushFocusScope()/PopFocusScope() - inherited by BeginChild(), pushed into by Begin() ImVector ItemFlagsStack; // Stack for PushItemFlag()/PopItemFlag() - inherited by Begin() ImVector GroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin() ImVector OpenPopupStack; // Which popups are open (persistent) ImVector BeginPopupStack; // Which level of BeginPopup() we are in (reset every frame) ImVectorTreeNodeStack; // Stack for TreeNode() + // Viewports ImVector Viewports; // Active viewports (always 1+, and generally 1 unless multi-viewports are enabled). Each viewports hold their copy of ImDrawData. ImGuiViewportP* CurrentViewport; // We track changes of viewport (happening in Begin) so we can call Platform_OnChangedViewport() @@ -2127,6 +2466,7 @@ struct ImGuiContext int ViewportCreatedCount; // Unique sequential creation counter (mostly for testing/debugging) int PlatformWindowsCreatedCount; // Unique sequential creation counter (mostly for testing/debugging) int ViewportFocusedStampCount; // Every time the front-most window changes, we stamp its viewport with an incrementing counter + // Keyboard/Gamepad Navigation bool NavCursorVisible; // Nav focus cursor/rectangle is visible? We hide it after a mouse click. We show it after a nav move. bool NavHighlightItemUnderNav; // Disable mouse hovering highlight. Highlight navigation focused item instead of mouse hovered item. @@ -2138,18 +2478,20 @@ struct ImGuiContext ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusedWindow' ImGuiID NavFocusScopeId; // Focused focus scope (e.g. selection code often wants to "clear other items" when landing on an item of the same scope) ImGuiNavLayer NavLayer; // Focused layer (main scrolling layer, or menu/title bar layer) - ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItem() + ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItemByID() ImGuiID NavActivateDownId; // ~~ IsKeyDown(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyDown(ImGuiKey_NavGamepadActivate) ? NavId : 0 ImGuiID NavActivatePressedId; // ~~ IsKeyPressed(ImGuiKey_Space) || IsKeyPressed(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate) ? NavId : 0 (no repeat) ImGuiActivateFlags NavActivateFlags; ImVector NavFocusRoute; // Reversed copy focus scope stack for NavId (should contains NavFocusScopeId). This essentially follow the window->ParentWindowForFocusRoute chain. ImGuiID NavHighlightActivatedId; float NavHighlightActivatedTimer; - ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame. + ImGuiID NavNextActivateId; // Set by ActivateItemByID(), queued until next frame. ImGuiActivateFlags NavNextActivateFlags; ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource_Keyboard or ImGuiInputSource_Mouse ImGuiSelectionUserData NavLastValidSelectionUserData; // Last valid data passed to SetNextItemSelectionUser(), or -1. For current window. Not reset when focusing an item that doesn't have selection data. ImS8 NavCursorHideFrames; + //ImGuiID NavActivateInputId; // Removed in 1.89.4 (July 2023). This is now part of g.NavActivateId and sets g.NavActivateFlags |= ImGuiActivateFlags_PreferInput. See commit c9a53aa74, issue #5606. + // Navigation: Init & Move Requests bool NavAnyRequest; // ~~ NavMoveRequest || NavInitRequest this is to perform early out in ItemAdd() bool NavInitRequest; // Init request for appearing window to select first item @@ -2173,6 +2515,7 @@ struct ImGuiContext ImGuiNavItemData NavMoveResultLocalVisible; // Best move request candidate within NavWindow that are mostly visible (when using ImGuiNavMoveFlags_AlsoScoreVisibleSet flag) ImGuiNavItemData NavMoveResultOther; // Best move request candidate within NavWindow's flattened hierarchy (when using ImGuiWindowFlags_NavFlattened flag) ImGuiNavItemData NavTabbingResultFirst; // First tabbing request candidate within NavWindow and flattened hierarchy + // Navigation: record of last move request ImGuiID NavJustMovedFromFocusScopeId; // Just navigated from this focus scope id (result of a successfully MoveRequest). ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest). @@ -2180,7 +2523,9 @@ struct ImGuiContext ImGuiKeyChord NavJustMovedToKeyMods; bool NavJustMovedToIsTabbing; // Copy of ImGuiNavMoveFlags_IsTabbing. Maybe we should store whole flags. bool NavJustMovedToHasSelectionData; // Copy of move result's ItemFlags & ImGuiItemFlags_HasSelectionUserData). Maybe we should just store ImGuiNavItemData. + // Navigation: Windowing (CTRL+TAB for list, or Menu button + keys or directional pads to move/resize) + bool ConfigNavWindowingWithGamepad; // = true. Enable CTRL+TAB by holding ImGuiKey_GamepadFaceLeft (== ImGuiKey_NavGamepadMenu). When false, the button may still be used to toggle Menu layer. ImGuiKeyChord ConfigNavWindowingKeyNext; // = ImGuiMod_Ctrl | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiKey_Tab on OS X). For reconfiguration (see #4828) ImGuiKeyChord ConfigNavWindowingKeyPrev; // = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab on OS X) ImGuiWindow* NavWindowingTarget; // Target window when doing CTRL+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most! @@ -2188,12 +2533,15 @@ struct ImGuiContext ImGuiWindow* NavWindowingListWindow; // Internal window actually listing the CTRL+Tab contents float NavWindowingTimer; float NavWindowingHighlightAlpha; - bool NavWindowingToggleLayer; - ImGuiKey NavWindowingToggleKey; + ImGuiInputSource NavWindowingInputSource; + bool NavWindowingToggleLayer; // Set while Alt or GamepadMenu is held, may be cleared by other operations, and processed when releasing the key. + ImGuiKey NavWindowingToggleKey; // Keyboard/gamepad key used when toggling to menu layer. ImVec2 NavWindowingAccumDeltaPos; ImVec2 NavWindowingAccumDeltaSize; + // Render float DimBgRatio; // 0.0..1.0 animation when fading in a dimming background (for modal window and CTRL+TAB list) + // Drag and Drop bool DragDropActive; bool DragDropWithinSource; // Set when within a BeginDragDropXXX/EndDragDropXXX block for a drag source. @@ -2213,9 +2561,11 @@ struct ImGuiContext ImGuiID DragDropHoldJustPressedId; // Set when holding a payload just made ButtonBehavior() return a press. ImVector DragDropPayloadBufHeap; // We don't expose the ImVector<> directly, ImGuiPayload only holds pointer+size unsigned char DragDropPayloadBufLocal[16]; // Local buffer for small payloads + // Clipper int ClipperTempDataStacked; ImVector ClipperTempData; + // Tables ImGuiTable* CurrentTable; ImGuiID DebugBreakInTable; // Set to break in BeginTable() call. @@ -2224,17 +2574,20 @@ struct ImGuiContext ImPool Tables; // Persistent table data ImVector TablesLastTimeActive; // Last used timestamp of each tables (SOA, for efficient GC) ImVector DrawChannelsTempMergeBuffer; + // Tab bars ImGuiTabBar* CurrentTabBar; ImPool TabBars; ImVector CurrentTabBarStack; ImVector ShrinkWidthBuffer; + // Multi-Select state ImGuiBoxSelectState BoxSelectState; ImGuiMultiSelectTempData* CurrentMultiSelect; int MultiSelectTempDataStacked; // Temporary multi-select data size (because we leave previous instances undestructed, we generally don't use MultiSelectTempData.Size) ImVector MultiSelectTempData; ImPool MultiSelectStorage; + // Hover Delay system ImGuiID HoverItemDelayId; ImGuiID HoverItemDelayIdPreviousFrame; @@ -2242,14 +2595,17 @@ struct ImGuiContext float HoverItemDelayClearTimer; // Currently used by IsItemHovered(): grace time before g.TooltipHoverTimer gets cleared. ImGuiID HoverItemUnlockedStationaryId; // Mouse has once been stationary on this item. Only reset after departing the item. ImGuiID HoverWindowUnlockedStationaryId; // Mouse has once been stationary on this window. Only reset after departing the window. + // Mouse state ImGuiMouseCursor MouseCursor; float MouseStationaryTimer; // Time the mouse has been stationary (with some loose heuristic) ImVec2 MouseLastValidPos; + // Widget state ImGuiInputTextState InputTextState; ImGuiInputTextDeactivatedState InputTextDeactivatedState; - ImFont InputTextPasswordFont; + ImFontBaked InputTextPasswordFontBackupBaked; + ImFontFlags InputTextPasswordFontBackupFlags; ImGuiID TempInputId; // Temporary text input when CTRL+clicking on a slider, etc. ImGuiDataTypeStorage DataTypeZeroValue; // 0 for all data types int BeginMenuDepth; @@ -2279,14 +2635,17 @@ struct ImGuiContext ImVector ClipboardHandlerData; // If no custom clipboard handler is defined ImVector MenusIdSubmittedThisFrame; // A list of menu IDs that were rendered at least once ImGuiTypingSelectState TypingSelectState; // State for GetTypingSelectRequest() + // Platform support - ImGuiPlatformImeData PlatformImeData; // Data updated by current frame + ImGuiPlatformImeData PlatformImeData; // Data updated by current frame. Will be applied at end of the frame. For some backends, this is required to have WantVisible=true in order to receive text message. ImGuiPlatformImeData PlatformImeDataPrev; // Previous frame data. When changed we call the platform_io.Platform_SetImeDataFn() handler. - ImGuiID PlatformImeViewport; + // Extensions // FIXME: We could provide an API to register one slot in an array held in ImGuiContext? + ImVector UserTextures; // List of textures created/managed by user or third-party extension. Automatically appended into platform_io.Textures[]. ImGuiDockContext DockContext; void (*DockNodeWindowMenuHandler)(ImGuiContext* ctx, ImGuiDockNode* node, ImGuiTabBar* tab_bar); + // Settings bool SettingsLoaded; float SettingsDirtyTimer; // Save .ini Settings to memory when time reaches zero @@ -2296,8 +2655,10 @@ struct ImGuiContext ImChunkStream SettingsTables; // ImGuiTable .ini settings entries ImVector Hooks; // Hooks for extensions (e.g. test engine) ImGuiID HookIdNext; // Next available HookId + // Localization const char* LocalizationTable[ImGuiLocKey_COUNT]; + // Capture/Logging bool LogEnabled; // Currently capturing ImGuiLogFlags LogFlags; // Capture flags/type @@ -2311,6 +2672,7 @@ struct ImGuiContext int LogDepthRef; int LogDepthToExpand; int LogDepthToExpandDefault; // Default/stored value for LogDepthMaxExpand if not specified in the LogXXX function call. + // Error Handling ImGuiErrorCallback ErrorCallback; // = NULL. May be exposed in public API eventually. void* ErrorCallbackUserData; // = NULL @@ -2319,6 +2681,7 @@ struct ImGuiContext int ErrorCountCurrentFrame; // [Internal] Number of errors submitted this frame. ImGuiErrorRecoveryState StackSizesInNewFrame; // [Internal] ImGuiErrorRecoveryState*StackSizesInBeginForCurrentWindow; // [Internal] + // Debug Tools // (some of the highly frequently used data are interleaved in other structures above: DebugBreakXXX fields, DebugHookIdInfo, DebugLocateId etc.) int DebugDrawIdConflictsCount; // Locked count (preserved when holding CTRL) @@ -2341,6 +2704,11 @@ struct ImGuiContext ImGuiIDStackTool DebugIDStackTool; ImGuiDebugAllocInfo DebugAllocInfo; ImGuiDockNode* DebugHoveredDockNode; // Hovered dock node. +#if defined(IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS) && !defined(IMGUI_DISABLE_DEBUG_TOOLS) + ImGuiStorage DebugDrawIdConflictsAliveCount; + ImGuiStorage DebugDrawIdConflictsHighlightSet; +#endif + // Misc float FramerateSecPerFrame[60]; // Calculate estimate of framerate for user over the last 60 frames.. int FramerateSecPerFrameIdx; @@ -2348,14 +2716,17 @@ struct ImGuiContext float FramerateSecPerFrameAccum; int WantCaptureMouseNextFrame; // Explicit capture override via SetNextFrameWantCaptureMouse()/SetNextFrameWantCaptureKeyboard(). Default to -1. int WantCaptureKeyboardNextFrame; // " - int WantTextInputNextFrame; + int WantTextInputNextFrame; // Copied in EndFrame() from g.PlatformImeData.WantTextInput. Needs to be set for some backends (SDL3) to emit character inputs. ImVector TempBuffer; // Temporary text buffer char TempKeychordName[64]; + ImGuiContext(ImFontAtlas* shared_font_atlas); }; + //----------------------------------------------------------------------------- // [SECTION] ImGuiWindowTempData, ImGuiWindow //----------------------------------------------------------------------------- + // Transient per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the DC variable name in ImGuiWindow. // (That's theory, in practice the delimitation between ImGuiWindow and ImGuiWindowTempData is quite tenuous and could be reconsidered..) // (This doesn't need a constructor because we zero-clear it as part of ImGuiWindow and all frame-temporary data are setup on Begin) @@ -2377,6 +2748,7 @@ struct IMGUI_API ImGuiWindowTempData ImVec1 ColumnsOffset; // Offset to the current column (if ColumnsCurrent > 0). FIXME: This and the above should be a stack to allow use cases like Tree->Column->Tree. Need revamp columns API. ImVec1 GroupOffset; ImVec2 CursorStartPosLossyness;// Record the loss of precision of CursorStartPos due to really large scrolling amount. This is used by clipper to compensate and fix the most common use case of large scroll area. + // Keyboard/Gamepad navigation ImGuiNavLayer NavLayerCurrent; // Current layer, 0..31 (we currently only use 0..1) short NavLayersActiveMask; // Which layers have been written to (result from previous frame) @@ -2384,12 +2756,14 @@ struct IMGUI_API ImGuiWindowTempData bool NavIsScrollPushableX; // Set when current work location may be scrolled horizontally when moving left / right. This is generally always true UNLESS within a column. bool NavHideHighlightOneFrame; bool NavWindowHasScrollY; // Set per window when scrolling can be used (== ScrollMax.y > 0.0f) + // Miscellaneous bool MenuBarAppending; // FIXME: Remove this ImVec2 MenuBarOffset; // MenuBarOffset.x is sort of equivalent of a per-layer CursorPos.x, saved/restored as we switch to the menu bar. The only situation when MenuBarOffset.y is > 0 if when (SafeAreaPadding.y > FramePadding.y), often used on TVs. ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items measurement int TreeDepth; // Current tree depth. - ImU32 TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. Could be turned into a ImU64 if necessary. + ImU32 TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. Could be turned into a ImU64 if necessary. + ImU32 TreeRecordsClippedNodesY2Mask; // Store whether we should keep recording Y2. Cleared when passing clip max. Equivalent TreeHasStackDataDepthMask value should always be set. ImVector ChildWindows; ImGuiStorage* StateStorage; // Current persistent per-window storage (store e.g. tree node open/close state) ImGuiOldColumns* CurrentColumns; // Current columns set @@ -2397,11 +2771,13 @@ struct IMGUI_API ImGuiWindowTempData ImGuiLayoutType LayoutType; ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() ImU32 ModalDimBgColor; + // Status flags ImGuiItemStatusFlags WindowItemStatusFlags; ImGuiItemStatusFlags ChildItemStatusFlags; ImGuiItemStatusFlags DockTabItemStatusFlags; ImRect DockTabItemRect; + // Local parameters stacks // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings. float ItemWidth; // Current item width (>0.0: width in pixels, <0.0: align xx pixels to the right of window). @@ -2409,6 +2785,7 @@ struct IMGUI_API ImGuiWindowTempData ImVector ItemWidthStack; // Store item widths to restore (attention: .back() is not == ItemWidth) ImVector TextWrapPosStack; // Store text wrap pos to restore (attention: .back() is not == TextWrapPos) }; + // Storage for one window struct IMGUI_API ImGuiWindow { @@ -2482,8 +2859,10 @@ struct IMGUI_API ImGuiWindow ImGuiCond SetWindowDockAllowFlags : 8; // store acceptable condition flags for SetNextWindowDock() use. ImVec2 SetWindowPosVal; // store window position when using a non-zero Pivot (position set needs to be processed when we know the window size) ImVec2 SetWindowPosPivot; // store window pivot for positioning. ImVec2(0, 0) when positioning from top-left corner; ImVec2(0.5f, 0.5f) for centering; ImVec2(1, 1) for bottom right. + ImVector IDStack; // ID stack. ID are hashes seeded with the value at the top of the stack. (In theory this should be in the TempData structure) ImGuiWindowTempData DC; // Temporary per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the "DC" variable name. + // The best way to understand what those rectangles are is to use the 'Metrics->Tools->Show Windows Rectangles' viewer. // The main 'OuterRect', omitted as a field, is window->Rect(). ImRect OuterRectClipped; // == Window->Rect() just after setup in Begin(). == window->Rect() for root window. @@ -2495,6 +2874,7 @@ struct IMGUI_API ImGuiWindow ImRect ContentRegionRect; // FIXME: This is currently confusing/misleading. It is essentially WorkRect but not handling of scrolling. We currently rely on it as right/bottom aligned sizing operation need some size to rely on. ImVec2ih HitTestHoleSize; // Define an optional rectangular hole where mouse will pass-through the window. ImVec2ih HitTestHoleOffset; + int LastFrameActive; // Last frame number the window was Active. int LastFrameJustFocused; // Last frame number the window was made Focused. float LastTimeActive; // Last timestamp the window was Active (using float as we don't need high precision there) @@ -2503,9 +2883,9 @@ struct IMGUI_API ImGuiWindow ImVector ColumnsStorage; float FontWindowScale; // User scale multiplier per-window, via SetWindowFontScale() float FontWindowScaleParents; - float FontDpiScale; float FontRefSize; // This is a copy of window->CalcFontSize() at the time of Begin(), trying to phase out CalcFontSize() especially as it may be called on non-current window. int SettingsOffset; // Offset into SettingsWindows[] (offsets are always valid as we only grow the array from the back) + ImDrawList* DrawList; // == &DrawListInst (for backward compatibility reason with code using imgui_internal.h we keep this a pointer) ImDrawList DrawListInst; ImGuiWindow* ParentWindow; // If we are a child _or_ popup _or_ docked window, this is pointing to our parent. Otherwise NULL. @@ -2516,14 +2896,17 @@ struct IMGUI_API ImGuiWindow ImGuiWindow* RootWindowForTitleBarHighlight; // Point to ourself or first ancestor which will display TitleBgActive color when this window is active. ImGuiWindow* RootWindowForNav; // Point to ourself or first ancestor which doesn't have the NavFlattened flag. ImGuiWindow* ParentWindowForFocusRoute; // Set to manual link a window to its logical parent so that Shortcut() chain are honoerd (e.g. Tool linked to Document) + ImGuiWindow* NavLastChildNavWindow; // When going to the menu bar, we remember the child window we came from. (This could probably be made implicit if we kept g.Windows sorted by last focused including child window.) ImGuiID NavLastIds[ImGuiNavLayer_COUNT]; // Last known NavId for this window, per layer (0/1) ImRect NavRectRel[ImGuiNavLayer_COUNT]; // Reference rectangle, in window relative space ImVec2 NavPreferredScoringPosRel[ImGuiNavLayer_COUNT]; // Preferred X/Y position updated when moving on a given axis, reset to FLT_MAX. ImGuiID NavRootFocusScopeId; // Focus Scope ID at the time of Begin() + int MemoryDrawListIdxCapacity; // Backup of last idx/vtx count, so when waking up the window we can preallocate and avoid iterative alloc/copy int MemoryDrawListVtxCapacity; bool MemoryCompacted; // Set when window extraneous data have been garbage collected + // Docking bool DockIsActive :1; // When docking artifacts are actually visible. When this is set, DockNode is guaranteed to be != NULL. ~~ (DockNode != NULL) && (DockNode->Windows.Size > 1). bool DockNodeIsVisible :1; @@ -2534,23 +2917,30 @@ struct IMGUI_API ImGuiWindow ImGuiDockNode* DockNode; // Which node are we docked into. Important: Prefer testing DockIsActive in many cases as this will still be set when the dock node is hidden. ImGuiDockNode* DockNodeAsHost; // Which node are we owning (for parent windows) ImGuiID DockId; // Backup of last valid DockNode->ID, so single window remember their dock node id even when they are not bound any more + public: ImGuiWindow(ImGuiContext* context, const char* name); ~ImGuiWindow(); + ImGuiID GetID(const char* str, const char* str_end = NULL); ImGuiID GetID(const void* ptr); ImGuiID GetID(int n); ImGuiID GetIDFromPos(const ImVec2& p_abs); ImGuiID GetIDFromRectangle(const ImRect& r_abs); + // We don't use g.FontSize because the window may be != g.CurrentWindow. ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } - float CalcFontSize() const { ImGuiContext& g = *Ctx; return g.FontBaseSize * FontWindowScale * FontDpiScale * FontWindowScaleParents; } ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight)); } ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight; return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight); } + + // [Obsolete] ImGuiWindow::CalcFontSize() was removed in 1.92.x because error-prone/misleading. You can use window->FontRefSize for a copy of g.FontSize at the time of the last Begin() call for this window. + //float CalcFontSize() const { ImGuiContext& g = *Ctx; return g.FontSizeBase * FontWindowScale * FontDpiScale * FontWindowScaleParents; }; + //----------------------------------------------------------------------------- // [SECTION] Tab bar, Tab item support //----------------------------------------------------------------------------- + // Extend ImGuiTabBarFlags_ enum ImGuiTabBarFlagsPrivate_ { @@ -2558,6 +2948,7 @@ enum ImGuiTabBarFlagsPrivate_ ImGuiTabBarFlags_IsFocused = 1 << 21, ImGuiTabBarFlags_SaveSettings = 1 << 22, // FIXME: Settings are handled by the docking system, this only request the tab bar to mark settings dirty when reordering tabs }; + // Extend ImGuiTabItemFlags_ enum ImGuiTabItemFlagsPrivate_ { @@ -2567,6 +2958,7 @@ enum ImGuiTabItemFlagsPrivate_ ImGuiTabItemFlags_Invisible = 1 << 22, // To reserve space e.g. with ImGuiTabItemFlags_Leading ImGuiTabItemFlags_Unsorted = 1 << 23, // [Docking] Trailing tabs with the _Unsorted flag will be sorted based on the DockOrder of their Window. }; + // Storage for one active tab item (sizeof() 48 bytes) struct ImGuiTabItem { @@ -2577,14 +2969,16 @@ struct ImGuiTabItem int LastFrameSelected; // This allows us to infer an ordered list of the last activated tabs with little maintenance float Offset; // Position relative to beginning of tab float Width; // Width currently displayed - float ContentWidth; // Width of label, stored during BeginTabItem() call + float ContentWidth; // Width of label + padding, stored during BeginTabItem() call (misnamed as "Content" would normally imply width of label only) float RequestedWidth; // Width optionally requested by caller, -1.0f is unused ImS32 NameOffset; // When Window==NULL, offset to name within parent ImGuiTabBar::TabsNames ImS16 BeginOrder; // BeginTabItem() order, used to re-order tabs after toggling ImGuiTabBarFlags_Reorderable ImS16 IndexDuringLayout; // Index only used during TabBarLayout(). Tabs gets reordered so 'Tabs[n].IndexDuringLayout == n' but may mismatch during additions. bool WantClose; // Marked as closed by SetTabItemClosed() + ImGuiTabItem() { memset(this, 0, sizeof(*this)); LastFrameVisible = LastFrameSelected = -1; RequestedWidth = -1.0f; NameOffset = -1; BeginOrder = IndexDuringLayout = -1; } }; + // Storage for a tab bar (sizeof() 160 bytes) struct IMGUI_API ImGuiTabBar { @@ -2598,6 +2992,7 @@ struct IMGUI_API ImGuiTabBar int CurrFrameVisible; int PrevFrameVisible; ImRect BarRect; + float BarRectPrevWidth; // Backup of previous width. When width change we enforce keep horizontal scroll on focused tab. float CurrTabsContentsHeight; float PrevTabsContentsHeight; // Record the height of contents submitted below the tab bar float WidthAllTabs; // Actual width of all tabs (locked during layout) @@ -2616,22 +3011,24 @@ struct IMGUI_API ImGuiTabBar bool WantLayout; bool VisibleTabWasSubmitted; bool TabsAddedNew; // Set to true when a new tab item or button has been added to the tab bar during last frame + bool ScrollButtonEnabled; ImS16 TabsActiveCount; // Number of tabs submitted this frame. ImS16 LastTabItemIdx; // Index of last BeginTabItem() tab for use by EndTabItem() float ItemSpacingY; ImVec2 FramePadding; // style.FramePadding locked at the time of BeginTabBar() ImVec2 BackupCursorPos; ImGuiTextBuffer TabsNames; // For non-docking tab bar we re-append names in a contiguous buffer. + ImGuiTabBar(); }; + //----------------------------------------------------------------------------- // [SECTION] Table support //----------------------------------------------------------------------------- + #define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color. -#define IMGUI_TABLE_MAX_COLUMNS 512 // May be further lifted -// Our current column maximum is 64 but we may raise that in the future. -typedef ImS16 ImGuiTableColumnIdx; -typedef ImU16 ImGuiTableDrawChannelIdx; +#define IMGUI_TABLE_MAX_COLUMNS 512 // Arbitrary "safety" maximum, may be lifted in the future if needed. Must fit in ImGuiTableColumnIdx/ImGuiTableDrawChannelIdx. + // [Internal] sizeof() ~ 112 // We use the terminology "Enabled" to refer to a column that is not Hidden by user/api. // We use the terminology "Clipped" to refer to a column that is out of sight because of scrolling/clipping. @@ -2680,6 +3077,7 @@ struct ImGuiTableColumn ImU8 SortDirectionsAvailCount : 2; // Number of available sort directions (0 to 3) ImU8 SortDirectionsAvailMask : 4; // Mask of available sort directions (1-bit each) ImU8 SortDirectionsAvailList; // Ordered list of available sort directions (2-bits each, total 8-bits) + ImGuiTableColumn() { memset(this, 0, sizeof(*this)); @@ -2692,6 +3090,7 @@ struct ImGuiTableColumn DrawChannelCurrent = DrawChannelFrozen = DrawChannelUnfrozen = (ImU8)-1; } }; + // Transient cell data stored per row. // sizeof() ~ 6 bytes struct ImGuiTableCellData @@ -2699,6 +3098,7 @@ struct ImGuiTableCellData ImU32 BgColor; // Actual color ImGuiTableColumnIdx Column; // Column number }; + // Parameters for TableAngledHeadersRowEx() // This may end up being refactored for more general purpose. // sizeof() ~ 12 bytes @@ -2709,6 +3109,7 @@ struct ImGuiTableHeaderData ImU32 BgColor0; ImU32 BgColor1; }; + // Per-instance data that needs preserving across frames (seemingly most others do not need to be preserved aside from debug needs. Does that means they could be moved to ImGuiTableTempData?) // sizeof() ~ 24 bytes struct ImGuiTableInstanceData @@ -2719,8 +3120,10 @@ struct ImGuiTableInstanceData float LastFrozenHeight; // Height of frozen section from last frame int HoveredRowLast; // Index of row which was hovered last frame. int HoveredRowNext; // Index of row hovered this frame, set after encountering it. + ImGuiTableInstanceData() { TableInstanceID = 0; LastOuterHeight = LastTopHeadersRowHeight = LastFrozenHeight = 0.0f; HoveredRowLast = HoveredRowNext = -1; } }; + // sizeof() ~ 592 bytes + heap allocs described in TableBeginInitMemory() struct IMGUI_API ImGuiTable { @@ -2823,7 +3226,7 @@ struct IMGUI_API ImGuiTable bool IsSortSpecsDirty; bool IsUsingHeaders; // Set when the first row had the ImGuiTableRowFlags_Headers flag. bool IsContextPopupOpen; // Set when default context menu is open (also see: ContextPopupColumn, InstanceInteracted). - bool DisableDefaultContextMenu; // Disable default context menu contents. You may submit your own using TableBeginContextMenuPopup()/EndPopup() + bool DisableDefaultContextMenu; // Disable default context menu. You may submit your own using TableBeginContextMenuPopup()/EndPopup() bool IsSettingsRequestLoad; bool IsSettingsDirty; // Set when table settings have changed and needs to be reported into ImGuiTableSetttings data. bool IsDefaultDisplayOrder; // Set when display order is unchanged from default (DisplayOrder contains 0...Count-1) @@ -2837,9 +3240,11 @@ struct IMGUI_API ImGuiTable bool HasScrollbarYPrev; // Whether ANY instance of this table had a vertical scrollbar during the previous. bool MemoryCompacted; bool HostSkipItems; // Backup of InnerWindow->SkipItem at the end of BeginTable(), because we will overwrite InnerWindow->SkipItem on a per-column basis + ImGuiTable() { memset(this, 0, sizeof(*this)); LastFrameActive = -1; } ~ImGuiTable() { IM_FREE(RawData); } }; + // Transient data that are only needed between BeginTable() and EndTable(), those buffers are shared (1 per level of stacked table). // - Accessing those requires chasing an extra pointer so for very frequently used data we leave them in the main table structure. // - We also leave out of this structure data that tend to be particularly useful for debugging/metrics. @@ -2851,8 +3256,10 @@ struct IMGUI_API ImGuiTableTempData float LastTimeActive; // Last timestamp this structure was used float AngledHeadersExtraWidth; // Used in EndTable() ImVector AngledHeadersRequests; // Used in TableAngledHeadersRow() + ImVec2 UserOuterSize; // outer_size.x passed to BeginTable() ImDrawListSplitter DrawSplitter; + ImRect HostBackupWorkRect; // Backup of InnerWindow->WorkRect at the end of BeginTable() ImRect HostBackupParentWorkRect; // Backup of InnerWindow->ParentWorkRect at the end of BeginTable() ImVec2 HostBackupPrevLineSize; // Backup of InnerWindow->DC.PrevLineSize at the end of BeginTable() @@ -2861,8 +3268,10 @@ struct IMGUI_API ImGuiTableTempData ImVec1 HostBackupColumnsOffset; // Backup of OuterWindow->DC.ColumnsOffset at the end of BeginTable() float HostBackupItemWidth; // Backup of OuterWindow->DC.ItemWidth at the end of BeginTable() int HostBackupItemWidthStackSize;//Backup of OuterWindow->DC.ItemWidthStack.Size at the end of BeginTable() + ImGuiTableTempData() { memset(this, 0, sizeof(*this)); LastTimeActive = -1.0f; } }; + // sizeof() ~ 16 struct ImGuiTableColumnSettings { @@ -2874,6 +3283,7 @@ struct ImGuiTableColumnSettings ImU8 SortDirection : 2; ImS8 IsEnabled : 2; // "Visible" in ini file ImU8 IsStretch : 1; + ImGuiTableColumnSettings() { WidthOrWeight = 0.0f; @@ -2885,6 +3295,7 @@ struct ImGuiTableColumnSettings IsStretch = 0; } }; + // This is designed to be stored in a single ImChunkStream (1 header followed by N ImGuiTableColumnSettings, etc.) struct ImGuiTableSettings { @@ -2894,13 +3305,16 @@ struct ImGuiTableSettings ImGuiTableColumnIdx ColumnsCount; ImGuiTableColumnIdx ColumnsCountMax; // Maximum number of columns this settings instance can store, we can recycle a settings instance with lower number of columns but not higher bool WantApply; // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context) + ImGuiTableSettings() { memset(this, 0, sizeof(*this)); } ImGuiTableColumnSettings* GetColumnSettings() { return (ImGuiTableColumnSettings*)(this + 1); } }; + //----------------------------------------------------------------------------- // [SECTION] ImGui internal API // No guarantee of forward compatibility here! //----------------------------------------------------------------------------- + namespace ImGui { // Windows @@ -2931,6 +3345,7 @@ namespace ImGui inline ImRect WindowRectRelToAbs(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x + off.x, r.Min.y + off.y, r.Max.x + off.x, r.Max.y + off.y); } inline ImVec2 WindowPosAbsToRel(ImGuiWindow* window, const ImVec2& p) { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x - off.x, p.y - off.y); } inline ImVec2 WindowPosRelToAbs(ImGuiWindow* window, const ImVec2& p) { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x + off.x, p.y + off.y); } + // Windows: Display Order and Focus Order IMGUI_API void FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags = 0); IMGUI_API void FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window, ImGuiViewport* filter_viewport, ImGuiFocusRequestFlags flags); @@ -2940,29 +3355,45 @@ namespace ImGui IMGUI_API void BringWindowToDisplayBehind(ImGuiWindow* window, ImGuiWindow* above_window); IMGUI_API int FindWindowDisplayIndex(ImGuiWindow* window); IMGUI_API ImGuiWindow* FindBottomMostVisibleWindowWithinBeginStack(ImGuiWindow* window); + // Windows: Idle, Refresh Policies [EXPERIMENTAL] IMGUI_API void SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags); + // Fonts, drawing - IMGUI_API void SetCurrentFont(ImFont* font); - inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } + IMGUI_API void RegisterUserTexture(ImTextureData* tex); // Register external texture. EXPERIMENTAL: DO NOT USE YET. + IMGUI_API void UnregisterUserTexture(ImTextureData* tex); + IMGUI_API void RegisterFontAtlas(ImFontAtlas* atlas); + IMGUI_API void UnregisterFontAtlas(ImFontAtlas* atlas); + IMGUI_API void SetCurrentFont(ImFont* font, float font_size_before_scaling, float font_size_after_scaling); + IMGUI_API void UpdateCurrentFontSize(float restore_font_size_after_scaling); + IMGUI_API void SetFontRasterizerDensity(float rasterizer_density); + inline float GetFontRasterizerDensity() { return GImGui->FontRasterizerDensity; } + inline float GetRoundedFontSize(float size) { return IM_ROUND(size); } + IMGUI_API ImFont* GetDefaultFont(); IMGUI_API void PushPasswordFont(); + IMGUI_API void PopPasswordFont(); inline ImDrawList* GetForegroundDrawList(ImGuiWindow* window) { return GetForegroundDrawList(window->Viewport); } IMGUI_API void AddDrawListToDrawDataEx(ImDrawData* draw_data, ImVector* out_list, ImDrawList* draw_list); + // Init IMGUI_API void Initialize(); IMGUI_API void Shutdown(); // Since 1.60 this is a _private_ function. You can call DestroyContext() to destroy the context created by CreateContext(). + // NewFrame IMGUI_API void UpdateInputEvents(bool trickle_fast_inputs); - IMGUI_API void UpdateHoveredWindowAndCaptureFlags(); + IMGUI_API void UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos); IMGUI_API void FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_viewport, ImGuiWindow** out_hovered_window, ImGuiWindow** out_hovered_window_under_moving_window); IMGUI_API void StartMouseMovingWindow(ImGuiWindow* window); IMGUI_API void StartMouseMovingWindowOrNode(ImGuiWindow* window, ImGuiDockNode* node, bool undock); + IMGUI_API void StopMouseMovingWindow(); IMGUI_API void UpdateMouseMovingWindowNewFrame(); IMGUI_API void UpdateMouseMovingWindowEndFrame(); + // Generic context hooks IMGUI_API ImGuiID AddContextHook(ImGuiContext* context, const ImGuiContextHook* hook); IMGUI_API void RemoveContextHook(ImGuiContext* context, ImGuiID hook_to_remove); IMGUI_API void CallContextHooks(ImGuiContext* context, ImGuiContextHookType type); + // Viewports IMGUI_API void TranslateWindowsInViewport(ImGuiViewportP* viewport, const ImVec2& old_pos, const ImVec2& new_pos, const ImVec2& old_size, const ImVec2& new_size); IMGUI_API void ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale); @@ -2971,6 +3402,7 @@ namespace ImGui IMGUI_API void SetCurrentViewport(ImGuiWindow* window, ImGuiViewportP* viewport); IMGUI_API const ImGuiPlatformMonitor* GetViewportPlatformMonitor(ImGuiViewport* viewport); IMGUI_API ImGuiViewportP* FindHoveredViewportFromPlatformWindowStack(const ImVec2& mouse_platform_pos); + // Settings IMGUI_API void MarkIniSettingsDirty(); IMGUI_API void MarkIniSettingsDirty(ImGuiWindow* window); @@ -2978,19 +3410,23 @@ namespace ImGui IMGUI_API void AddSettingsHandler(const ImGuiSettingsHandler* handler); IMGUI_API void RemoveSettingsHandler(const char* type_name); IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name); + // Settings - Windows IMGUI_API ImGuiWindowSettings* CreateNewWindowSettings(const char* name); IMGUI_API ImGuiWindowSettings* FindWindowSettingsByID(ImGuiID id); IMGUI_API ImGuiWindowSettings* FindWindowSettingsByWindow(ImGuiWindow* window); IMGUI_API void ClearWindowSettings(const char* name); + // Localization IMGUI_API void LocalizeRegisterEntries(const ImGuiLocEntry* entries, int count); inline const char* LocalizeGetMsg(ImGuiLocKey key) { ImGuiContext& g = *GImGui; const char* msg = g.LocalizationTable[key]; return msg ? msg : "*Missing Text*"; } + // Scrolling IMGUI_API void SetScrollX(ImGuiWindow* window, float scroll_x); IMGUI_API void SetScrollY(ImGuiWindow* window, float scroll_y); IMGUI_API void SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x_ratio); IMGUI_API void SetScrollFromPosY(ImGuiWindow* window, float local_y, float center_y_ratio); + // Early work-in-progress API (ScrollToItem() will become public) IMGUI_API void ScrollToItem(ImGuiScrollFlags flags = 0); IMGUI_API void ScrollToRect(ImGuiWindow* window, const ImRect& rect, ImGuiScrollFlags flags = 0); @@ -2998,6 +3434,7 @@ namespace ImGui //#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS inline void ScrollToBringRectIntoView(ImGuiWindow* window, const ImRect& rect) { ScrollToRect(window, rect, ImGuiScrollFlags_KeepVisibleEdgeY); } //#endif + // Basic Accessors inline ImGuiItemStatusFlags GetItemStatusFlags() { ImGuiContext& g = *GImGui; return g.LastItemData.StatusFlags; } inline ImGuiItemFlags GetItemFlags() { ImGuiContext& g = *GImGui; return g.LastItemData.ItemFlags; } @@ -3013,6 +3450,7 @@ namespace ImGui IMGUI_API void PushOverrideID(ImGuiID id); // Push given value as-is at the top of the ID stack (whereas PushID combines old and new hashes) IMGUI_API ImGuiID GetIDWithSeed(const char* str_id_begin, const char* str_id_end, ImGuiID seed); IMGUI_API ImGuiID GetIDWithSeed(int n, ImGuiID seed); + // Basic Helpers for widget code IMGUI_API void ItemSize(const ImVec2& size, float text_baseline_y = -1.0f); inline void ItemSize(const ImRect& bb, float text_baseline_y = -1.0f) { ItemSize(bb.GetSize(), text_baseline_y); } // FIXME: This is a misleading API since we expect CursorPos to be bb.Min. @@ -3024,18 +3462,22 @@ namespace ImGui IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_w, float default_h); IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); IMGUI_API void PushMultiItemsWidths(int components, float width_full); - IMGUI_API void ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess); + IMGUI_API void ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess, float width_min); + // Parameter stacks (shared) IMGUI_API const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx); IMGUI_API void BeginDisabledOverrideReenable(); IMGUI_API void EndDisabledOverrideReenable(); + // Logging/Capture IMGUI_API void LogBegin(ImGuiLogFlags flags, int auto_open_depth); // -> BeginCapture() when we design v2 api, for now stay under the radar by using the old name. IMGUI_API void LogToBuffer(int auto_open_depth = -1); // Start logging/capturing to internal buffer IMGUI_API void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL); IMGUI_API void LogSetNextTextDecoration(const char* prefix, const char* suffix); + // Childs IMGUI_API bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags); + // Popups, Modals IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags); IMGUI_API bool BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags extra_window_flags); @@ -3050,17 +3492,21 @@ namespace ImGui IMGUI_API ImGuiWindow* FindBlockingModal(ImGuiWindow* window); IMGUI_API ImVec2 FindBestWindowPosForPopup(ImGuiWindow* window); IMGUI_API ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy); + // Tooltips IMGUI_API bool BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags extra_window_flags); IMGUI_API bool BeginTooltipHidden(); + // Menus IMGUI_API bool BeginViewportSideBar(const char* name, ImGuiViewport* viewport, ImGuiDir dir, float size, ImGuiWindowFlags window_flags); IMGUI_API bool BeginMenuEx(const char* label, const char* icon, bool enabled = true); IMGUI_API bool MenuItemEx(const char* label, const char* icon, const char* shortcut = NULL, bool selected = false, bool enabled = true); + // Combos IMGUI_API bool BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags); IMGUI_API bool BeginComboPreview(); IMGUI_API void EndComboPreview(); + // Keyboard/Gamepad Navigation IMGUI_API void NavInitWindow(ImGuiWindow* window, bool force_reinit); IMGUI_API void NavInitRequestApplyResult(); @@ -3068,7 +3514,7 @@ namespace ImGui IMGUI_API void NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags); IMGUI_API void NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags); IMGUI_API void NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result); - IMGUI_API void NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiTreeNodeStackData* tree_node_data); + IMGUI_API void NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, const ImGuiTreeNodeStackData* tree_node_data); IMGUI_API void NavMoveRequestCancel(); IMGUI_API void NavMoveRequestApplyResult(); IMGUI_API void NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags); @@ -3079,11 +3525,13 @@ namespace ImGui IMGUI_API void SetNavWindow(ImGuiWindow* window); IMGUI_API void SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel); IMGUI_API void SetNavFocusScope(ImGuiID focus_scope_id); + // Focus/Activation // This should be part of a larger set of API: FocusItem(offset = -1), FocusItemByID(id), ActivateItem(offset = -1), ActivateItemByID(id) etc. which are // much harder to design and implement than expected. I have a couple of private branches on this matter but it's not simple. For now implementing the easy ones. IMGUI_API void FocusItem(); // Focus last item (no selection/activation). - IMGUI_API void ActivateItemByID(ImGuiID id); // Activate an item by ID (button, checkbox, tree node etc.). Activation is queued and processed on the next frame when the item is encountered again. + IMGUI_API void ActivateItemByID(ImGuiID id); // Activate an item by ID (button, checkbox, tree node etc.). Activation is queued and processed on the next frame when the item is encountered again. Was called 'ActivateItem()' before 1.89.7. + // Inputs // FIXME: Eventually we should aim to move e.g. IsActiveIdUsingKey() into IsKeyXXX functions. inline bool IsNamedKey(ImGuiKey key) { return key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END; } @@ -3103,6 +3551,7 @@ namespace ImGui if (key == ImGuiMod_Super) return ImGuiKey_ReservedForModSuper; return key; } + IMGUI_API ImGuiKeyData* GetKeyData(ImGuiContext* ctx, ImGuiKey key); inline ImGuiKeyData* GetKeyData(ImGuiKey key) { ImGuiContext& g = *GImGui; return GetKeyData(&g, key); } IMGUI_API const char* GetKeyChordName(ImGuiKeyChord key_chord); @@ -3115,6 +3564,7 @@ namespace ImGui IMGUI_API void TeleportMousePos(const ImVec2& pos); IMGUI_API void SetActiveIdUsingAllKeyboardKeys(); inline bool IsActiveIdUsingNavDir(ImGuiDir dir) { ImGuiContext& g = *GImGui; return (g.ActiveIdUsingNavDirMask & (1 << dir)) != 0; } + // [EXPERIMENTAL] Low-Level: Key/Input Ownership // - The idea is that instead of "eating" a given input, we can link to an owner id. // - Ownership is most often claimed as a result of reacting to a press/down event (but occasionally may be claimed ahead). @@ -3132,6 +3582,7 @@ namespace ImGui IMGUI_API void SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags); // Set key owner to last item if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'. IMGUI_API bool TestKeyOwner(ImGuiKey key, ImGuiID owner_id); // Test that key is either not owned, either owned by 'owner_id' inline ImGuiKeyOwnerData* GetKeyOwnerData(ImGuiContext* ctx, ImGuiKey key) { if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); IM_ASSERT(IsNamedKey(key)); return &ctx->KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; } + // [EXPERIMENTAL] High-Level: Input Access functions w/ support for Key/Input Ownership // - Important: legacy IsKeyPressed(ImGuiKey, bool repeat=true) _DEFAULTS_ to repeat, new IsKeyPressed() requires _EXPLICIT_ ImGuiInputFlags_Repeat flag. // - Expected to be later promoted to public API, the prototypes are designed to replace existing ones (since owner_id can default to Any == 0) @@ -3146,6 +3597,7 @@ namespace ImGui IMGUI_API bool IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGuiID owner_id = 0); IMGUI_API bool IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id); IMGUI_API bool IsMouseDoubleClicked(ImGuiMouseButton button, ImGuiID owner_id); + // Shortcut Testing & Routing // - Set Shortcut() and SetNextItemShortcut() in imgui.h // - When a policy (except for ImGuiInputFlags_RouteAlways *) is set, Shortcut() will register itself with SetShortcutRouting(), @@ -3164,6 +3616,7 @@ namespace ImGui IMGUI_API bool SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id); // owner_id needs to be explicit and cannot be 0 IMGUI_API bool TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id); IMGUI_API ImGuiKeyRoutingData* GetShortcutRoutingData(ImGuiKeyChord key_chord); + // Docking // (some functions are only declared in imgui.cpp, see Docking section) IMGUI_API void DockContextInitialize(ImGuiContext* ctx); @@ -3194,6 +3647,7 @@ namespace ImGui IMGUI_API void BeginDockableDragDropSource(ImGuiWindow* window); IMGUI_API void BeginDockableDragDropTarget(ImGuiWindow* window); IMGUI_API void SetWindowDock(ImGuiWindow* window, ImGuiID dock_id, ImGuiCond cond); + // Docking - Builder function needs to be generally called before the node is used/submitted. // - The DockBuilderXXX functions are designed to _eventually_ become a public API, but it is too early to expose it and guarantee stability. // - Do not hold on ImGuiDockNode* pointers! They may be invalidated by any split/merge/remove operation and every frame. @@ -3217,6 +3671,7 @@ namespace ImGui IMGUI_API void DockBuilderCopyNode(ImGuiID src_node_id, ImGuiID dst_node_id, ImVector* out_node_remap_pairs); IMGUI_API void DockBuilderCopyWindowSettings(const char* src_name, const char* dst_name); IMGUI_API void DockBuilderFinish(ImGuiID node_id); + // [EXPERIMENTAL] Focus Scope // This is generally used to identify a unique input location (for e.g. a selection set) // There is one per window (automatically set in Begin), but: @@ -3228,12 +3683,14 @@ namespace ImGui IMGUI_API void PushFocusScope(ImGuiID id); IMGUI_API void PopFocusScope(); inline ImGuiID GetCurrentFocusScope() { ImGuiContext& g = *GImGui; return g.CurrentFocusScopeId; } // Focus scope we are outputting into, set by PushFocusScope() + // Drag and Drop IMGUI_API bool IsDragDropActive(); IMGUI_API bool BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id); IMGUI_API void ClearDragDrop(); IMGUI_API bool IsDragDropPayloadBeingAccepted(); IMGUI_API void RenderDragDropTargetRect(const ImRect& bb, const ImRect& item_clip_rect); + // Typing-Select API // (provide Windows Explorer style "select items by typing partial name" + "cycle through items by typing same letter" feature) // (this is currently not documented nor used by main library, but should work. See "widgets_typingselect" in imgui_test_suite for usage code. Please let us know if you use this!) @@ -3241,9 +3698,11 @@ namespace ImGui IMGUI_API int TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx); IMGUI_API int TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx); IMGUI_API int TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data); + // Box-Select API IMGUI_API bool BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags); IMGUI_API void EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flags); + // Multi-Select API IMGUI_API void MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags); IMGUI_API void MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed); @@ -3251,6 +3710,7 @@ namespace ImGui IMGUI_API void MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item); inline ImGuiBoxSelectState* GetBoxSelectState(ImGuiID id) { ImGuiContext& g = *GImGui; return (id != 0 && g.BoxSelectState.ID == id && g.BoxSelectState.IsActive) ? &g.BoxSelectState : NULL; } inline ImGuiMultiSelectState* GetMultiSelectState(ImGuiID id) { ImGuiContext& g = *GImGui; return g.MultiSelectStorage.GetByKey(id); } + // Internal Columns API (this is not exposed because we will encourage transitioning to the Tables API) IMGUI_API void SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect); IMGUI_API void BeginColumns(const char* str_id, int count, ImGuiOldColumnFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns(). @@ -3262,6 +3722,7 @@ namespace ImGui IMGUI_API ImGuiOldColumns* FindOrCreateColumns(ImGuiWindow* window, ImGuiID id); IMGUI_API float GetColumnOffsetFromNorm(const ImGuiOldColumns* columns, float offset_norm); IMGUI_API float GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset); + // Tables: Candidates for public API IMGUI_API void TableOpenContextMenu(int column_n = -1); IMGUI_API void TableSetColumnWidth(int column_n, float width); @@ -3271,7 +3732,10 @@ namespace ImGui IMGUI_API float TableGetHeaderAngledMaxLabelWidth(); IMGUI_API void TablePushBackgroundChannel(); IMGUI_API void TablePopBackgroundChannel(); + IMGUI_API void TablePushColumnChannel(int column_n); + IMGUI_API void TablePopColumnChannel(); IMGUI_API void TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label_width, const ImGuiTableHeaderData* data, int data_count); + // Tables: Internals inline ImGuiTable* GetCurrentTable() { ImGuiContext& g = *GImGui; return g.CurrentTable; } IMGUI_API ImGuiTable* TableFindByID(ImGuiID id); @@ -3307,6 +3771,7 @@ namespace ImGui IMGUI_API void TableGcCompactTransientBuffers(ImGuiTable* table); IMGUI_API void TableGcCompactTransientBuffers(ImGuiTableTempData* table); IMGUI_API void TableGcCompactSettings(); + // Tables: Settings IMGUI_API void TableLoadSettings(ImGuiTable* table); IMGUI_API void TableSaveSettings(ImGuiTable* table); @@ -3315,6 +3780,7 @@ namespace ImGui IMGUI_API void TableSettingsAddSettingsHandler(); IMGUI_API ImGuiTableSettings* TableSettingsCreate(ImGuiID id, int columns_count); IMGUI_API ImGuiTableSettings* TableSettingsFindByID(ImGuiID id); + // Tab Bars inline ImGuiTabBar* GetCurrentTabBar() { ImGuiContext& g = *GImGui; return g.CurrentTabBar; } IMGUI_API bool BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& bb, ImGuiTabBarFlags flags); @@ -3338,6 +3804,7 @@ namespace ImGui IMGUI_API ImVec2 TabItemCalcSize(ImGuiWindow* window); IMGUI_API void TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col); IMGUI_API void TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped); + // Render helpers // AVOID USING OUTSIDE OF IMGUI.CPP! NOT FOR PUBLIC CONSUMPTION. THOSE FUNCTIONS ARE A MESS. THEIR SIGNATURE AND BEHAVIOR WILL CHANGE, THEY NEED TO BE REFACTORED INTO SOMETHING DECENT. // NB: All position are in absolute pixels coordinates (we are never using window coordinates internally) @@ -3345,7 +3812,7 @@ namespace ImGui IMGUI_API void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width); IMGUI_API void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL); IMGUI_API void RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL); - IMGUI_API void RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float clip_max_x, float ellipsis_max_x, const char* text, const char* text_end, const ImVec2* text_size_if_known); + IMGUI_API void RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float ellipsis_max_x, const char* text, const char* text_end, const ImVec2* text_size_if_known); IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders = true, float rounding = 0.0f); IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, ImDrawFlags flags = 0); @@ -3355,6 +3822,7 @@ namespace ImGui #endif IMGUI_API const char* FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text. IMGUI_API void RenderMouseCursor(ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill, ImU32 col_border, ImU32 col_shadow); + // Render helpers (those functions don't access any ImGui state!) IMGUI_API void RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImU32 col, ImGuiDir dir, float scale = 1.0f); IMGUI_API void RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col); @@ -3364,15 +3832,21 @@ namespace ImGui IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding); IMGUI_API void RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding); IMGUI_API ImDrawFlags CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRect& r_outer, float threshold); - // Widgets + + // Widgets: Text IMGUI_API void TextEx(const char* text, const char* text_end = NULL, ImGuiTextFlags flags = 0); + IMGUI_API void TextAligned(float align_x, float size_x, const char* fmt, ...); // FIXME-WIP: Works but API is likely to be reworked. This is designed for 1 item on the line. (#7024) + IMGUI_API void TextAlignedV(float align_x, float size_x, const char* fmt, va_list args); + + // Widgets IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags = 0); - IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0); + IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0); IMGUI_API void SeparatorEx(ImGuiSeparatorFlags flags, float thickness = 1.0f); IMGUI_API void SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_width); IMGUI_API bool CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value); IMGUI_API bool CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value); + // Widgets: Window Decorations IMGUI_API bool CloseButton(ImGuiID id, const ImVec2& pos); IMGUI_API bool CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node); @@ -3382,17 +3856,22 @@ namespace ImGui IMGUI_API ImGuiID GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis); IMGUI_API ImGuiID GetWindowResizeCornerID(ImGuiWindow* window, int n); // 0..3: corners IMGUI_API ImGuiID GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir); + // Widgets low-level behaviors IMGUI_API bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags = 0); IMGUI_API bool DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags); IMGUI_API bool SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb); IMGUI_API bool SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f, float hover_visibility_delay = 0.0f, ImU32 bg_col = 0); + // Widgets: Tree Nodes IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); + IMGUI_API void TreeNodeDrawLineToChildNode(const ImVec2& target_pos); + IMGUI_API void TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data); IMGUI_API void TreePushOverrideID(ImGuiID id); IMGUI_API bool TreeNodeGetOpen(ImGuiID storage_id); IMGUI_API void TreeNodeSetOpen(ImGuiID storage_id, bool open); IMGUI_API bool TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags); // Return open state. Consume previous SetNextItemOpen() data, if any. May return true when logging. + // Template functions are instantiated in imgui_widgets.cpp for a finite number of types. // To use them externally (for custom widget) you may need an "extern template" statement in your code in order to link to existing instances and silence Clang warnings (see #2036). // e.g. " extern template IMGUI_API float RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, float v); " @@ -3402,6 +3881,7 @@ namespace ImGui template IMGUI_API bool SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, T* v, T v_min, T v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb); template IMGUI_API T RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, T v); template IMGUI_API bool CheckboxFlagsT(const char* label, T* flags, T flags_value); + // Data type helpers IMGUI_API const ImGuiDataTypeInfo* DataTypeGetInfo(ImGuiDataType data_type); IMGUI_API int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format); @@ -3410,6 +3890,7 @@ namespace ImGui IMGUI_API int DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2); IMGUI_API bool DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max); IMGUI_API bool DataTypeIsZero(ImGuiDataType data_type, const void* p_data); + // InputText IMGUI_API bool InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); IMGUI_API void InputTextDeactivateHook(ImGuiID id); @@ -3419,20 +3900,25 @@ namespace ImGui inline ImGuiInputTextState* GetInputTextState(ImGuiID id) { ImGuiContext& g = *GImGui; return (id != 0 && g.InputTextState.ID == id) ? &g.InputTextState : NULL; } // Get input text state if active IMGUI_API void SetNextItemRefVal(ImGuiDataType data_type, void* p_data); inline bool IsItemActiveAsInputText() { ImGuiContext& g = *GImGui; return g.ActiveId != 0 && g.ActiveId == g.LastItemData.ID && g.InputTextState.ID == g.LastItemData.ID; } // This may be useful to apply workaround that a based on distinguish whenever an item is active as a text input field. + // Color IMGUI_API void ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags); IMGUI_API void ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags); IMGUI_API void ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags); + // Plot IMGUI_API int PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, const ImVec2& size_arg); + // Shade functions (write over already created vertices) IMGUI_API void ShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1); IMGUI_API void ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp); IMGUI_API void ShadeVertsTransformPos(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& pivot_in, float cos_a, float sin_a, const ImVec2& pivot_out); + // Garbage collection IMGUI_API void GcCompactTransientMiscBuffers(); IMGUI_API void GcCompactTransientWindowBuffers(ImGuiWindow* window); IMGUI_API void GcAwakeTransientWindowBuffers(ImGuiWindow* window); + // Error handling, State Recovery IMGUI_API bool ErrorLog(const char* msg); IMGUI_API void ErrorRecoveryStoreState(ImGuiErrorRecoveryState* state_out); @@ -3442,6 +3928,7 @@ namespace ImGui IMGUI_API void ErrorCheckEndFrameFinalizeErrorTooltip(); IMGUI_API bool BeginErrorTooltip(); IMGUI_API void EndErrorTooltip(); + // Debug Tools IMGUI_API void DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr, size_t size); // size >= 0 : alloc, size = -1 : free IMGUI_API void DebugDrawCursorPos(ImU32 col = IM_COL32(255, 0, 0, 255)); @@ -3461,7 +3948,9 @@ namespace ImGui IMGUI_API void DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label); IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); IMGUI_API void DebugNodeFont(ImFont* font); + IMGUI_API void DebugNodeFontGlyphesForSrcMask(ImFont* font, ImFontBaked* baked, int src_mask); IMGUI_API void DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph); + IMGUI_API void DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRect* highlight_rect = NULL); // ID used to facilitate persisting the "current" texture. IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); IMGUI_API void DebugNodeTable(ImGuiTable* table); @@ -3477,11 +3966,13 @@ namespace ImGui IMGUI_API void DebugNodePlatformMonitor(ImGuiPlatformMonitor* monitor, const char* label, int idx); IMGUI_API void DebugRenderKeyboardPreview(ImDrawList* draw_list); IMGUI_API void DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* viewport, const ImRect& bb); + // Obsolete functions #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS //inline void SetItemUsingMouseWheel() { SetItemKeyOwner(ImGuiKey_MouseWheelY); } // Changed in 1.89 //inline bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0) { return TreeNodeUpdateNextOpen(id, flags); } // Renamed in 1.89 //inline bool IsKeyPressedMap(ImGuiKey key, bool repeat = true) { IM_ASSERT(IsNamedKey(key)); return IsKeyPressed(key, repeat); } // Removed in 1.87: Mapping from named key is always identity! + // Refactored focus/nav/tabbing system in 1.82 and 1.84. If you have old/custom copy-and-pasted widgets which used FocusableItemRegister(): // (Old) IMGUI_VERSION_NUM < 18209: using 'ItemAdd(....)' and 'bool tab_focused = FocusableItemRegister(...)' // (Old) IMGUI_VERSION_NUM >= 18209: using 'ItemAdd(..., ImGuiItemAddFlags_Focusable)' and 'bool tab_focused = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Focused) != 0' @@ -3489,39 +3980,209 @@ namespace ImGui //inline bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id) // -> pass ImGuiItemAddFlags_Inputable flag to ItemAdd() //inline void FocusableItemUnregister(ImGuiWindow* window) // -> unnecessary: TempInputText() uses ImGuiInputTextFlags_MergedItem #endif + } // namespace ImGui + + +//----------------------------------------------------------------------------- +// [SECTION] ImFontLoader +//----------------------------------------------------------------------------- + +// Hooks and storage for a given font backend. +// This structure is likely to evolve as we add support for incremental atlas updates. +// Conceptually this could be public, but API is still going to be evolve. +struct ImFontLoader +{ + const char* Name; + bool (*LoaderInit)(ImFontAtlas* atlas); + void (*LoaderShutdown)(ImFontAtlas* atlas); + bool (*FontSrcInit)(ImFontAtlas* atlas, ImFontConfig* src); + void (*FontSrcDestroy)(ImFontAtlas* atlas, ImFontConfig* src); + bool (*FontSrcContainsGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint); + bool (*FontBakedInit)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src); + void (*FontBakedDestroy)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src); + bool (*FontBakedLoadGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x); + + // Size of backend data, Per Baked * Per Source. Buffers are managed by core to avoid excessive allocations. + // FIXME: At this point the two other types of buffers may be managed by core to be consistent? + size_t FontBakedSrcLoaderDataSize; + + ImFontLoader() { memset(this, 0, sizeof(*this)); } +}; + +#ifdef IMGUI_ENABLE_STB_TRUETYPE +IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); +#endif +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +typedef ImFontLoader ImFontBuilderIO; // [renamed/changed in 1.92] The types are not actually compatible but we provide this as a compile-time error report helper. +#endif + //----------------------------------------------------------------------------- // [SECTION] ImFontAtlas internal API //----------------------------------------------------------------------------- -// This structure is likely to evolve as we add support for incremental atlas updates. -// Conceptually this could be in ImGuiPlatformIO, but we are far from ready to make this public. -struct ImFontBuilderIO + +#define IMGUI_FONT_SIZE_MAX (512.0f) +#define IMGUI_FONT_SIZE_THRESHOLD_FOR_LOADADVANCEXONLYMODE (128.0f) + +// Helpers: ImTextureRef ==/!= operators provided as convenience +// (note that _TexID and _TexData are never set simultaneously) +inline bool operator==(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID == rhs._TexID && lhs._TexData == rhs._TexData; } +inline bool operator!=(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID != rhs._TexID || lhs._TexData != rhs._TexData; } + +// Refer to ImFontAtlasPackGetRect() to better understand how this works. +#define ImFontAtlasRectId_IndexMask_ (0x000FFFFF) // 20-bits: index to access builder->RectsIndex[]. +#define ImFontAtlasRectId_GenerationMask_ (0x3FF00000) // 10-bits: entry generation, so each ID is unique and get can safely detected old identifiers. +#define ImFontAtlasRectId_GenerationShift_ (20) +inline int ImFontAtlasRectId_GetIndex(ImFontAtlasRectId id) { return id & ImFontAtlasRectId_IndexMask_; } +inline int ImFontAtlasRectId_GetGeneration(ImFontAtlasRectId id) { return (id & ImFontAtlasRectId_GenerationMask_) >> ImFontAtlasRectId_GenerationShift_; } +inline ImFontAtlasRectId ImFontAtlasRectId_Make(int index_idx, int gen_idx) { IM_ASSERT(index_idx < ImFontAtlasRectId_IndexMask_ && gen_idx < (ImFontAtlasRectId_GenerationMask_ >> ImFontAtlasRectId_GenerationShift_)); return (ImFontAtlasRectId)(index_idx | (gen_idx << ImFontAtlasRectId_GenerationShift_)); } + +// Packed rectangle lookup entry (we need an indirection to allow removing/reordering rectangles) +// User are returned ImFontAtlasRectId values which are meant to be persistent. +// We handle this with an indirection. While Rects[] may be in theory shuffled, compacted etc., RectsIndex[] cannot it is keyed by ImFontAtlasRectId. +// RectsIndex[] is used both as an index into Rects[] and an index into itself. This is basically a free-list. See ImFontAtlasBuildAllocRectIndexEntry() code. +// Having this also makes it easier to e.g. sort rectangles during repack. +struct ImFontAtlasRectEntry { - bool (*FontBuilder_Build)(ImFontAtlas* atlas); + int TargetIndex : 20; // When Used: ImFontAtlasRectId -> into Rects[]. When unused: index to next unused RectsIndex[] slot to consume free-list. + int Generation : 10; // Increased each time the entry is reused for a new rectangle. + unsigned int IsUsed : 1; }; -// Helper for font builder -#ifdef IMGUI_ENABLE_STB_TRUETYPE -IMGUI_API const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype(); + +// Data available to potential texture post-processing functions +struct ImFontAtlasPostProcessData +{ + ImFontAtlas* FontAtlas; + ImFont* Font; + ImFontConfig* FontSrc; + ImFontBaked* FontBaked; + ImFontGlyph* Glyph; + + // Pixel data + void* Pixels; + ImTextureFormat Format; + int Pitch; + int Width; + int Height; +}; + +// We avoid dragging imstb_rectpack.h into public header (partly because binding generators are having issues with it) +#ifdef IMGUI_STB_NAMESPACE +namespace IMGUI_STB_NAMESPACE { struct stbrp_node; } +typedef IMGUI_STB_NAMESPACE::stbrp_node stbrp_node_im; +#else +struct stbrp_node; +typedef stbrp_node stbrp_node_im; #endif -IMGUI_API void ImFontAtlasUpdateSourcesPointers(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, float ascent, float descent); -IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque); -IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value); -IMGUI_API void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value); -IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); -IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); -IMGUI_API void ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* src, int* out_oversample_h, int* out_oversample_v); +struct stbrp_context_opaque { char data[80]; }; + +// Internal storage for incrementally packing and building a ImFontAtlas +struct ImFontAtlasBuilder +{ + stbrp_context_opaque PackContext; // Actually 'stbrp_context' but we don't want to define this in the header file. + ImVector PackNodes; + ImVector Rects; + ImVector RectsIndex; // ImFontAtlasRectId -> index into Rects[] + ImVector TempBuffer; // Misc scratch buffer + int RectsIndexFreeListStart;// First unused entry + int RectsPackedCount; // Number of packed rectangles. + int RectsPackedSurface; // Number of packed pixels. Used when compacting to heuristically find the ideal texture size. + int RectsDiscardedCount; + int RectsDiscardedSurface; + int FrameCount; // Current frame count + ImVec2i MaxRectSize; // Largest rectangle to pack (de-facto used as a "minimum texture size") + ImVec2i MaxRectBounds; // Bottom-right most used pixels + bool LockDisableResize; // Disable resizing texture + bool PreloadedAllGlyphsRanges; // Set when missing ImGuiBackendFlags_RendererHasTextures features forces atlas to preload everything. + + // Cache of all ImFontBaked + ImStableVector BakedPool; + ImGuiStorage BakedMap; // BakedId --> ImFontBaked* + int BakedDiscardedCount; + + // Custom rectangle identifiers + ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure. + ImFontAtlasRectId PackIdLinesTexData; + + ImFontAtlasBuilder() { memset(this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = -1; } +}; + +IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildDestroy(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildMain(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* font_loader); +IMGUI_API void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char); +IMGUI_API void ImFontAtlasBuildClear(ImFontAtlas* atlas); // Clear output and custom rects + +IMGUI_API ImTextureData* ImFontAtlasTextureAdd(ImFontAtlas* atlas, int w, int h); +IMGUI_API void ImFontAtlasTextureMakeSpace(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasTextureRepack(ImFontAtlas* atlas, int w, int h); +IMGUI_API void ImFontAtlasTextureGrow(ImFontAtlas* atlas, int old_w = -1, int old_h = -1); +IMGUI_API void ImFontAtlasTextureCompact(ImFontAtlas* atlas); +IMGUI_API ImVec2i ImFontAtlasTextureGetSizeEstimate(ImFontAtlas* atlas); + +IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); +IMGUI_API void ImFontAtlasBuildLegacyPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy +IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, ImFontBaked* baked, int* out_oversample_h, int* out_oversample_v); +IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames); + +IMGUI_API bool ImFontAtlasFontSourceInit(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API void ImFontAtlasFontSourceAddToFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); +IMGUI_API void ImFontAtlasFontDestroySourceData(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont* font); // Using FontDestroyOutput/FontInitOutput sequence useful notably if font loader params have changed +IMGUI_API void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font); +IMGUI_API void ImFontAtlasFontDiscardBakes(ImFontAtlas* atlas, ImFont* font, int unused_frames); + +IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size, float rasterizer_density); +IMGUI_API ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density); +IMGUI_API ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density); +IMGUI_API ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density, ImGuiID baked_id); +IMGUI_API void ImFontAtlasBakedDiscard(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked); +IMGUI_API ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph); +IMGUI_API void ImFontAtlasBakedAddFontGlyphAdvancedX(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImWchar codepoint, float advance_x); +IMGUI_API void ImFontAtlasBakedDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph); +IMGUI_API void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImTextureRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch); + +IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas); +IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry = NULL); +IMGUI_API ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id); +IMGUI_API ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId id); +IMGUI_API void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id); + +IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool renderer_has_textures); +IMGUI_API void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data); +IMGUI_API void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data); +IMGUI_API void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex, ImTextureRef new_tex); +IMGUI_API void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas); + +IMGUI_API void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h); +IMGUI_API void ImFontAtlasTextureBlockPostProcess(ImFontAtlasPostProcessData* data); +IMGUI_API void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlasPostProcessData* data, float multiply_factor); +IMGUI_API void ImFontAtlasTextureBlockFill(ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h, ImU32 col); +IMGUI_API void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h); +IMGUI_API void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h); + +IMGUI_API int ImTextureDataGetFormatBytesPerPixel(ImTextureFormat format); +IMGUI_API const char* ImTextureDataGetStatusName(ImTextureStatus status); +IMGUI_API const char* ImTextureDataGetFormatName(ImTextureFormat format); + +#ifndef IMGUI_DISABLE_DEBUG_TOOLS +IMGUI_API void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas); +#endif + IMGUI_API bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]); + //----------------------------------------------------------------------------- // [SECTION] Test Engine specific hooks (imgui_test_engine) //----------------------------------------------------------------------------- + #ifdef IMGUI_ENABLE_TEST_ENGINE extern void ImGuiTestEngineHook_ItemAdd(ImGuiContext* ctx, ImGuiID id, const ImRect& bb, const ImGuiLastItemData* item_data); // item_data may be NULL extern void ImGuiTestEngineHook_ItemInfo(ImGuiContext* ctx, ImGuiID id, const char* label, ImGuiItemStatusFlags flags); extern void ImGuiTestEngineHook_Log(ImGuiContext* ctx, const char* fmt, ...); extern const char* ImGuiTestEngine_FindItemDebugLabel(ImGuiContext* ctx, ImGuiID id); + // In IMGUI_VERSION_NUM >= 18934: changed IMGUI_TEST_ENGINE_ITEM_ADD(bb,id) to IMGUI_TEST_ENGINE_ITEM_ADD(id,bb,item_data); #define IMGUI_TEST_ENGINE_ITEM_ADD(_ID,_BB,_ITEM_DATA) if (g.TestEngineHookItems) ImGuiTestEngineHook_ItemAdd(&g, _ID, _BB, _ITEM_DATA) // Register item bounding box #define IMGUI_TEST_ENGINE_ITEM_INFO(_ID,_LABEL,_FLAGS) if (g.TestEngineHookItems) ImGuiTestEngineHook_ItemInfo(&g, _ID, _LABEL, _FLAGS) // Register item label and status flags (optional) @@ -3530,13 +4191,17 @@ extern const char* ImGuiTestEngine_FindItemDebugLabel(ImGuiContext* ctx, ImGuiI #define IMGUI_TEST_ENGINE_ITEM_ADD(_BB,_ID) ((void)0) #define IMGUI_TEST_ENGINE_ITEM_INFO(_ID,_LABEL,_FLAGS) ((void)g) #endif + //----------------------------------------------------------------------------- + #if defined(__clang__) #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif + #ifdef _MSC_VER #pragma warning (pop) #endif + #endif // #ifndef IMGUI_DISABLE diff --git a/external/reshade/deps/imgui/imgui_stdlib.cpp b/external/reshade/deps/imgui/imgui_stdlib.cpp index 23b1bc7..c04d487 100644 --- a/external/reshade/deps/imgui/imgui_stdlib.cpp +++ b/external/reshade/deps/imgui/imgui_stdlib.cpp @@ -1,23 +1,29 @@ // dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.) // This is also an example of how you may wrap your own similar types. + // Changelog: // - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string + // See more C++ related extension (fmt, RAII, syntaxis sugar) on Wiki: // https://github.com/ocornut/imgui/wiki/Useful-Extensions#cness + #include "imgui.h" #ifndef IMGUI_DISABLE #include "imgui_stdlib.h" + // Clang warnings with -Weverything #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness #endif + struct InputTextCallback_UserData { std::string* Str; ImGuiInputTextCallback ChainCallback; void* ChainCallbackUserData; }; + static int InputTextCallback(ImGuiInputTextCallbackData* data) { InputTextCallback_UserData* user_data = (InputTextCallback_UserData*)data->UserData; @@ -38,37 +44,45 @@ static int InputTextCallback(ImGuiInputTextCallbackData* data) } return 0; } + bool ImGui::InputText(const char* label, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); flags |= ImGuiInputTextFlags_CallbackResize; + InputTextCallback_UserData cb_user_data; cb_user_data.Str = str; cb_user_data.ChainCallback = callback; cb_user_data.ChainCallbackUserData = user_data; return InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data); } + bool ImGui::InputTextMultiline(const char* label, std::string* str, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); flags |= ImGuiInputTextFlags_CallbackResize; + InputTextCallback_UserData cb_user_data; cb_user_data.Str = str; cb_user_data.ChainCallback = callback; cb_user_data.ChainCallbackUserData = user_data; return InputTextMultiline(label, (char*)str->c_str(), str->capacity() + 1, size, flags, InputTextCallback, &cb_user_data); } + bool ImGui::InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); flags |= ImGuiInputTextFlags_CallbackResize; + InputTextCallback_UserData cb_user_data; cb_user_data.Str = str; cb_user_data.ChainCallback = callback; cb_user_data.ChainCallbackUserData = user_data; return InputTextWithHint(label, hint, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data); } + #if defined(__clang__) #pragma clang diagnostic pop #endif + #endif // #ifndef IMGUI_DISABLE diff --git a/external/reshade/deps/imgui/imgui_stdlib.h b/external/reshade/deps/imgui/imgui_stdlib.h index 5bc1678..697fc34 100644 --- a/external/reshade/deps/imgui/imgui_stdlib.h +++ b/external/reshade/deps/imgui/imgui_stdlib.h @@ -1,12 +1,18 @@ // dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.) // This is also an example of how you may wrap your own similar types. + // Changelog: // - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string + // See more C++ related extension (fmt, RAII, syntaxis sugar) on Wiki: // https://github.com/ocornut/imgui/wiki/Useful-Extensions#cness + #pragma once + #ifndef IMGUI_DISABLE + #include + namespace ImGui { // ImGui::InputText() with std::string @@ -15,4 +21,5 @@ namespace ImGui IMGUI_API bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr); IMGUI_API bool InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr); } -#endif // #ifndef IMGUI_DISABLE \ No newline at end of file + +#endif // #ifndef IMGUI_DISABLE diff --git a/external/reshade/deps/imgui/imgui_tables.cpp b/external/reshade/deps/imgui/imgui_tables.cpp index aba665a..370d8f6 100644 --- a/external/reshade/deps/imgui/imgui_tables.cpp +++ b/external/reshade/deps/imgui/imgui_tables.cpp @@ -1,7 +1,10 @@ -// dear imgui, v1.91b +// dear imgui, v1.92.2b // (tables and columns code) + /* + Index of this file: + // [SECTION] Commentary // [SECTION] Header mess // [SECTION] Tables: Main code @@ -17,14 +20,18 @@ Index of this file: // [SECTION] Tables: Garbage Collection // [SECTION] Tables: Debugging // [SECTION] Columns, BeginColumns, EndColumns, etc. + */ + // Navigating this file: // - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. // - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. // - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. + //----------------------------------------------------------------------------- // [SECTION] Commentary //----------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // Typical tables call flow: (root level is generally public API): //----------------------------------------------------------------------------- @@ -62,6 +69,7 @@ Index of this file: // | TableMergeDrawChannels() - merge draw channels if clipping isn't required // | EndChild() - (if ScrollX/ScrollY is set) //----------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // TABLE SIZING //----------------------------------------------------------------------------- @@ -103,6 +111,7 @@ Index of this file: // - Even if not really useful, we allow 'inner_width < outer_size.x' for consistency and to facilitate understanding // of what the value does. //----------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // COLUMNS SIZING POLICIES // (Reference: ImGuiTableFlags_SizingXXX flags and ImGuiTableColumnFlags_WidthXXX flags) @@ -139,6 +148,8 @@ Index of this file: // - therefore you generally cannot have ALL items of the columns use e.g. SetNextItemWidth(-FLT_MIN). // - but if the column has one or more items of known/fixed size, this will become the reference width used by SetNextItemWidth(-FLT_MIN). //----------------------------------------------------------------------------- + + //----------------------------------------------------------------------------- // TABLES CLIPPING/CULLING //----------------------------------------------------------------------------- @@ -171,20 +182,26 @@ Index of this file: // About clipping/culling of whole Tables: // - Scrolling tables with a known outer size can be clipped earlier as BeginTable() will return false. //----------------------------------------------------------------------------- + //----------------------------------------------------------------------------- // [SECTION] Header mess //----------------------------------------------------------------------------- + #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif + #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif + #include "imgui.h" #ifndef IMGUI_DISABLE #include "imgui_internal.h" + // System includes #include // intptr_t + // Visual Studio warnings #ifdef _MSC_VER #pragma warning (disable: 4127) // condition expression is constant @@ -195,6 +212,7 @@ Index of this file: #pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). #pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). #endif + // Clang/GCC warnings with -Weverything #if defined(__clang__) #if __has_warning("-Wunknown-warning-option") @@ -223,6 +241,7 @@ Index of this file: #pragma GCC diagnostic ignored "-Wstrict-overflow" #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #endif + //----------------------------------------------------------------------------- // [SECTION] Tables: Main code //----------------------------------------------------------------------------- @@ -239,6 +258,7 @@ Index of this file: // - TableSetupColumn() // - TableSetupScrollFreeze() //----------------------------------------------------------------------------- + // Configuration static const int TABLE_DRAW_CHANNEL_BG0 = 0; static const int TABLE_DRAW_CHANNEL_BG2_FROZEN = 1; @@ -246,53 +266,66 @@ static const int TABLE_DRAW_CHANNEL_NOCLIP = 2; // When usin static const float TABLE_BORDER_SIZE = 1.0f; // FIXME-TABLE: Currently hard-coded because of clipping assumptions with outer borders rendering. static const float TABLE_RESIZE_SEPARATOR_HALF_THICKNESS = 4.0f; // Extend outside inner borders. static const float TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER = 0.06f; // Delay/timer before making the hover feedback (color+cursor) visible because tables/columns tends to be more cramped. + // Helper inline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags, ImGuiWindow* outer_window) { // Adjust flags: set default sizing policy if ((flags & ImGuiTableFlags_SizingMask_) == 0) flags |= ((flags & ImGuiTableFlags_ScrollX) || (outer_window->Flags & ImGuiWindowFlags_AlwaysAutoResize)) ? ImGuiTableFlags_SizingFixedFit : ImGuiTableFlags_SizingStretchSame; + // Adjust flags: enable NoKeepColumnsVisible when using ImGuiTableFlags_SizingFixedSame if ((flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedSame) flags |= ImGuiTableFlags_NoKeepColumnsVisible; + // Adjust flags: enforce borders when resizable if (flags & ImGuiTableFlags_Resizable) flags |= ImGuiTableFlags_BordersInnerV; + // Adjust flags: disable NoHostExtendX/NoHostExtendY if we have any scrolling going on if (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) flags &= ~(ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_NoHostExtendY); + // Adjust flags: NoBordersInBodyUntilResize takes priority over NoBordersInBody if (flags & ImGuiTableFlags_NoBordersInBodyUntilResize) flags &= ~ImGuiTableFlags_NoBordersInBody; + // Adjust flags: disable saved settings if there's nothing to save if ((flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Sortable)) == 0) flags |= ImGuiTableFlags_NoSavedSettings; + // Inherit _NoSavedSettings from top-level window (child windows always have _NoSavedSettings set) if (outer_window->RootWindow->Flags & ImGuiWindowFlags_NoSavedSettings) flags |= ImGuiTableFlags_NoSavedSettings; + return flags; } + ImGuiTable* ImGui::TableFindByID(ImGuiID id) { ImGuiContext& g = *GImGui; return g.Tables.GetByKey(id); } + // Read about "TABLE SIZING" at the top of this file. bool ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width) { ImGuiID id = GetID(str_id); return BeginTableEx(str_id, id, columns_count, flags, outer_size, inner_width); } + bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width) { ImGuiContext& g = *GImGui; ImGuiWindow* outer_window = GetCurrentWindow(); if (outer_window->SkipItems) // Consistent with other tables + beneficial side effect that assert on miscalling EndTable() will be more visible. return false; + // Sanity checks IM_ASSERT(columns_count > 0 && columns_count < IMGUI_TABLE_MAX_COLUMNS); if (flags & ImGuiTableFlags_ScrollX) IM_ASSERT(inner_width >= 0.0f); + // If an outer size is specified ahead we will be able to early out when not visible. Exact clipping criteria may evolve. // FIXME: coarse clipping because access to table data causes two issues: // - instance numbers varying/unstable. may not be a direct problem for users, but could make outside access broken or confusing, e.g. TestEngine. @@ -312,11 +345,14 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG g.NextWindowData.ClearFlags(); return false; } + // [DEBUG] Debug break requested by user if (g.DebugBreakInTable == id) IM_DEBUG_BREAK(); + // Acquire storage for the table ImGuiTable* table = g.Tables.GetOrAddByKey(id); + // Acquire temporary buffers const int table_idx = g.Tables.GetIndex(table); if (++g.TablesTempDataStacked > g.TablesTempData.Size) @@ -325,9 +361,11 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG temp_data->TableIndex = table_idx; table->DrawSplitter = &table->TempData->DrawSplitter; table->DrawSplitter->Clear(); + // Fix flags table->IsDefaultSizingPolicy = (flags & ImGuiTableFlags_SizingMask_) == 0; flags = TableFixFlags(flags, outer_window); + // Initialize const int previous_frame_active = table->LastFrameActive; const int instance_no = (previous_frame_active != g.FrameCount) ? 0 : table->InstanceCurrent + 1; @@ -341,6 +379,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->InnerWidth = inner_width; table->NavLayer = (ImS8)outer_window->DC.NavLayerCurrent; temp_data->UserOuterSize = outer_size; + // Instance data (for instance 0, TableID == TableInstanceID) ImGuiID instance_id; table->InstanceCurrent = (ImS16)instance_no; @@ -357,6 +396,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG } ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); table_instance->TableInstanceID = instance_id; + // When not using a child window, WorkRect.Max will grow as we append contents. if (use_child_window) { @@ -365,18 +405,22 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG ImVec2 override_content_size(FLT_MAX, FLT_MAX); if ((flags & ImGuiTableFlags_ScrollX) && !(flags & ImGuiTableFlags_ScrollY)) override_content_size.y = FLT_MIN; + // Ensure specified width (when not specified, Stretched columns will act as if the width == OuterWidth and // never lead to any scrolling). We don't handle inner_width < 0.0f, we could potentially use it to right-align // based on the right side of the child window work rect, which would require knowing ahead if we are going to // have decoration taking horizontal spaces (typically a vertical scrollbar). if ((flags & ImGuiTableFlags_ScrollX) && inner_width > 0.0f) override_content_size.x = inner_width; + if (override_content_size.x != FLT_MAX || override_content_size.y != FLT_MAX) SetNextWindowContentSize(ImVec2(override_content_size.x != FLT_MAX ? override_content_size.x : 0.0f, override_content_size.y != FLT_MAX ? override_content_size.y : 0.0f)); + // Reset scroll if we are reactivating it if ((previous_flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) == 0) if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasScroll) == 0) SetNextWindowScroll(ImVec2(0.0f, 0.0f)); + // Create scrolling region (without border and zero window padding) ImGuiChildFlags child_child_flags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : ImGuiChildFlags_None; ImGuiWindowFlags child_window_flags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasWindowFlags) ? g.NextWindowData.WindowFlags : ImGuiWindowFlags_None; @@ -388,9 +432,11 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->OuterRect = table->InnerWindow->Rect(); table->InnerRect = table->InnerWindow->InnerRect; IM_ASSERT(table->InnerWindow->WindowPadding.x == 0.0f && table->InnerWindow->WindowPadding.y == 0.0f && table->InnerWindow->WindowBorderSize == 0.0f); + // Allow submitting when host is measuring if (table->InnerWindow->SkipItems && outer_window_is_measuring_size) table->InnerWindow->SkipItems = false; + // When using multiple instances, ensure they have the same amount of horizontal decorations (aka vertical scrollbar) so stretched columns can be aligned) if (instance_no == 0) { @@ -405,11 +451,14 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // But at this point we do NOT have a correct value for .Max.y (unless a height has been explicitly passed in). It will only be updated in EndTable(). table->WorkRect = table->OuterRect = table->InnerRect = outer_rect; table->HasScrollbarYPrev = table->HasScrollbarYCurr = false; + table->InnerWindow->DC.TreeDepth++; // This is designed to always linking ImGuiTreeNodeFlags_DrawLines linking accross a table } + // Push a standardized ID for both child-using and not-child-using tables PushOverrideID(id); if (instance_no > 0) PushOverrideID(instance_id); // FIXME: Somehow this is not resolved by stack-tool, even tho GetIDWithSeed() submitted the symbol. + // Backup a copy of host window members we will modify ImGuiWindow* inner_window = table->InnerWindow; table->HostIndentX = inner_window->DC.Indent.x; @@ -424,6 +473,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG temp_data->HostBackupItemWidth = outer_window->DC.ItemWidth; temp_data->HostBackupItemWidthStackSize = outer_window->DC.ItemWidthStack.Size; inner_window->DC.PrevLineSize = inner_window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); + // Make borders not overlap our contents by offsetting HostClipRect (#6765, #7428, #3752) // (we normally shouldn't alter HostClipRect as we rely on TableMergeDrawChannels() expanding non-clipped column toward the // limits of that rectangle, in order for ImDrawListSplitter::Merge() to merge the draw commands. However since the overlap @@ -446,6 +496,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->HostClipRect.Max.y = ImMax(table->HostClipRect.Max.y - TABLE_BORDER_SIZE, table->HostClipRect.Min.y); } } + // Padding and Spacing // - None ........Content..... Pad .....Content........ // - PadOuter | Pad ..Content..... Pad .....Content.. Pad | @@ -459,9 +510,11 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->CellSpacingX1 = inner_spacing_explicit + inner_spacing_for_border; table->CellSpacingX2 = inner_spacing_explicit; table->CellPaddingX = inner_padding_explicit; + const float outer_padding_for_border = (flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f; const float outer_padding_explicit = pad_outer_x ? g.Style.CellPadding.x : 0.0f; table->OuterPaddingX = (outer_padding_for_border + outer_padding_explicit) - table->CellPaddingX; + table->CurrentColumn = -1; table->CurrentRow = -1; table->RowBgColorCounter = 0; @@ -470,6 +523,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->InnerClipRect.ClipWith(table->WorkRect); // We need this to honor inner_width table->InnerClipRect.ClipWithFull(table->HostClipRect); table->InnerClipRect.Max.y = (flags & ImGuiTableFlags_NoHostExtendY) ? ImMin(table->InnerClipRect.Max.y, inner_window->WorkRect.Max.y) : table->HostClipRect.Max.y; + table->RowPosY1 = table->RowPosY2 = table->WorkRect.Min.y; // This is needed somehow table->RowTextBaseline = 0.0f; // This will be cleared again by TableBeginRow() table->RowCellPaddingY = 0.0f; @@ -481,23 +535,28 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->IsActiveIdInTable = false; table->AngledHeadersHeight = 0.0f; temp_data->AngledHeadersExtraWidth = 0.0f; + // Using opaque colors facilitate overlapping lines of the grid, otherwise we'd need to improve TableDrawBorders() table->BorderColorStrong = GetColorU32(ImGuiCol_TableBorderStrong); table->BorderColorLight = GetColorU32(ImGuiCol_TableBorderLight); + // Make table current g.CurrentTable = table; - outer_window->DC.NavIsScrollPushableX = false; // Shortcut for NavUpdateCurrentWindowIsScrollPushableX(); + inner_window->DC.NavIsScrollPushableX = false; // Shortcut for NavUpdateCurrentWindowIsScrollPushableX(); outer_window->DC.CurrentTableIdx = table_idx; if (inner_window != outer_window) // So EndChild() within the inner window can restore the table properly. inner_window->DC.CurrentTableIdx = table_idx; + if ((previous_flags & ImGuiTableFlags_Reorderable) && (flags & ImGuiTableFlags_Reorderable) == 0) table->IsResetDisplayOrderRequest = true; + // Mark as used to avoid GC if (table_idx >= g.TablesLastTimeActive.Size) g.TablesLastTimeActive.resize(table_idx + 1, -1.0f); g.TablesLastTimeActive[table_idx] = (float)g.Time; temp_data->LastTimeActive = (float)g.Time; table->MemoryCompacted = false; + // Setup memory buffer (clear data if columns count changed) ImGuiTableColumn* old_columns_to_preserve = NULL; void* old_columns_raw_data = NULL; @@ -548,9 +607,11 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG } if (old_columns_raw_data) IM_FREE(old_columns_raw_data); + // Load settings if (table->IsSettingsRequestLoad) TableLoadSettings(table); + // Handle DPI/font resize // This is designed to facilitate DPI changes with the assumption that e.g. style.CellPadding has been scaled as well. // It will also react to changing fonts with mixed results. It doesn't need to be perfect but merely provide a decent transition. @@ -565,18 +626,23 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->Columns[n].WidthRequest = table->Columns[n].WidthRequest * scale_factor; } table->RefScale = new_ref_scale_unit; + // Disable output until user calls TableNextRow() or TableNextColumn() leading to the TableUpdateLayout() call.. // This is not strictly necessary but will reduce cases were "out of table" output will be misleading to the user. // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option. inner_window->SkipItems = true; + // Clear names // At this point the ->NameOffset field of each column will be invalid until TableUpdateLayout() or the first call to TableSetupColumn() if (table->ColumnsNames.Buf.Size > 0) table->ColumnsNames.Buf.resize(0); + // Apply queued resizing/reordering/hiding requests TableBeginApplyRequests(table); + return true; } + // For reference, the average total _allocation count_ for a table is: // + 0 (for ImGuiTable instance, we are pooling allocations in g.Tables[]) // + 1 (for table->RawData allocated below) @@ -606,6 +672,7 @@ void ImGui::TableBeginInitMemory(ImGuiTable* table, int columns_count) table->EnabledMaskByIndex = (ImU32*)span_allocator.GetSpanPtrBegin(4); table->VisibleMaskByIndex = (ImU32*)span_allocator.GetSpanPtrBegin(5); } + // Apply queued resizing/reordering/hiding requests void ImGui::TableBeginApplyRequests(ImGuiTable* table) { @@ -619,6 +686,7 @@ void ImGui::TableBeginApplyRequests(ImGuiTable* table) table->LastResizedColumn = table->ResizedColumn; table->ResizedColumnNextWidth = FLT_MAX; table->ResizedColumn = -1; + // Process auto-fit for single column, which is a special case for stretch columns and fixed columns with FixedSame policy. // FIXME-TABLE: Would be nice to redistribute available stretch space accordingly to other weights, instead of giving it all to siblings. if (table->AutoFitSingleColumn != -1) @@ -627,6 +695,7 @@ void ImGui::TableBeginApplyRequests(ImGuiTable* table) table->AutoFitSingleColumn = -1; } } + // Handle reordering request // Note: we don't clear ReorderColumn after handling the request. if (table->InstanceCurrent == 0) @@ -652,6 +721,7 @@ void ImGui::TableBeginApplyRequests(ImGuiTable* table) for (int order_n = src_order + reorder_dir; order_n != dst_order + reorder_dir; order_n += reorder_dir) table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder -= (ImGuiTableColumnIdx)reorder_dir; IM_ASSERT(dst_column->DisplayOrder == dst_order - reorder_dir); + // Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[]. Rebuild later from the former. for (int column_n = 0; column_n < table->ColumnsCount; column_n++) table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n; @@ -659,6 +729,7 @@ void ImGui::TableBeginApplyRequests(ImGuiTable* table) table->IsSettingsDirty = true; } } + // Handle display order reset request if (table->IsResetDisplayOrderRequest) { @@ -668,10 +739,12 @@ void ImGui::TableBeginApplyRequests(ImGuiTable* table) table->IsSettingsDirty = true; } } + // Adjust flags: default width mode + stretch columns are not allowed when auto extending static void TableSetupColumnFlags(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags flags_in) { ImGuiTableColumnFlags flags = flags_in; + // Sizing Policy if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0) { @@ -685,21 +758,27 @@ static void TableSetupColumnFlags(ImGuiTable* table, ImGuiTableColumn* column, I { IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_WidthMask_)); // Check that only 1 of each set is used. } + // Resize if ((table->Flags & ImGuiTableFlags_Resizable) == 0) flags |= ImGuiTableColumnFlags_NoResize; + // Sorting if ((flags & ImGuiTableColumnFlags_NoSortAscending) && (flags & ImGuiTableColumnFlags_NoSortDescending)) flags |= ImGuiTableColumnFlags_NoSort; + // Indentation if ((flags & ImGuiTableColumnFlags_IndentMask_) == 0) flags |= (table->Columns.index_from_ptr(column) == 0) ? ImGuiTableColumnFlags_IndentEnable : ImGuiTableColumnFlags_IndentDisable; + // Alignment //if ((flags & ImGuiTableColumnFlags_AlignMask_) == 0) // flags |= ImGuiTableColumnFlags_AlignCenter; //IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_AlignMask_)); // Check that only 1 of each set is used. + // Preserve status flags column->Flags = flags | (column->Flags & ImGuiTableColumnFlags_StatusMask_); + // Build an ordered list of available sort directions column->SortDirectionsAvailCount = column->SortDirectionsAvailMask = column->SortDirectionsAvailList = 0; if (table->Flags & ImGuiTableFlags_Sortable) @@ -716,6 +795,7 @@ static void TableSetupColumnFlags(ImGuiTable* table, ImGuiTableColumn* column, I ImGui::TableFixColumnSortDirection(table, column); } } + // Layout columns for the frame. This is in essence the followup to BeginTable() and this is our largest function. // Runs on the first call to TableNextRow(), to give a chance for TableSetupColumn() and other TableSetupXXXXX() functions to be called first. // FIXME-TABLE: Our width (and therefore our WorkRect) will be minimal in the first frame for _WidthAuto columns. @@ -724,6 +804,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) { ImGuiContext& g = *GImGui; IM_ASSERT(table->IsLayoutLocked == false); + const ImGuiTableFlags table_sizing_policy = (table->Flags & ImGuiTableFlags_SizingMask_); table->IsDefaultDisplayOrder = true; table->ColumnsEnabledCount = 0; @@ -731,6 +812,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) ImBitArrayClearAllBits(table->EnabledMaskByDisplayOrder, table->ColumnsCount); table->LeftMostEnabledColumn = -1; table->MinColumnWidth = ImMax(1.0f, g.Style.FramePadding.x * 1.0f); // g.Style.ColumnsMinSpacing; // FIXME-TABLE + // [Part 1] Apply/lock Enabled and Order states. Calculate auto/ideal width for columns. Count fixed/stretch columns. // Process columns in their visible orders as we are building the Prev/Next indices. int count_fixed = 0; // Number of columns that have fixed sizing policies @@ -746,6 +828,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (column_n != order_n) table->IsDefaultDisplayOrder = false; ImGuiTableColumn* column = &table->Columns[column_n]; + // Clear column setup if not submitted by user. Currently we make it mandatory to call TableSetupColumn() every frame. // It would easily work without but we're not ready to guarantee it since e.g. names need resubmission anyway. // We take a slight shortcut but in theory we could be calling TableSetupColumn() here with dummy values, it should yield the same effect. @@ -756,6 +839,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->UserID = 0; column->InitStretchWeightOrWidth = -1.0f; } + // Update Enabled state, mark settings and sort specs dirty if (!(table->Flags & ImGuiTableFlags_Hideable) || (column->Flags & ImGuiTableColumnFlags_NoHide)) column->IsUserEnabledNextFrame = true; @@ -765,19 +849,23 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->IsSettingsDirty = true; } column->IsEnabled = column->IsUserEnabled && (column->Flags & ImGuiTableColumnFlags_Disabled) == 0; + if (column->SortOrder != -1 && !column->IsEnabled) table->IsSortSpecsDirty = true; if (column->SortOrder > 0 && !(table->Flags & ImGuiTableFlags_SortMulti)) table->IsSortSpecsDirty = true; + // Auto-fit unsized columns const bool start_auto_fit = (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? (column->WidthRequest < 0.0f) : (column->StretchWeight < 0.0f); if (start_auto_fit) column->AutoFitQueue = column->CannotSkipItemsQueue = (1 << 3) - 1; // Fit for three frames + if (!column->IsEnabled) { column->IndexWithinEnabledSet = -1; continue; } + // Mark as enabled and link to previous/next enabled column column->PrevEnabledColumn = (ImGuiTableColumnIdx)prev_visible_column_idx; column->NextEnabledColumn = -1; @@ -790,16 +878,19 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) ImBitArraySetBit(table->EnabledMaskByDisplayOrder, column->DisplayOrder); prev_visible_column_idx = column_n; IM_ASSERT(column->IndexWithinEnabledSet <= column->DisplayOrder); + // Calculate ideal/auto column width (that's the width required for all contents to be visible without clipping) // Combine width from regular rows + width from headers unless requested not to. if (!column->IsPreserveWidthAuto && table->InstanceCurrent == 0) column->WidthAuto = TableGetColumnWidthAuto(table, column); + // Non-resizable columns keep their requested width (apply user value regardless of IsPreserveWidthAuto) const bool column_is_resizable = (column->Flags & ImGuiTableColumnFlags_NoResize) == 0; if (column_is_resizable) has_resizable = true; if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && column->InitStretchWeightOrWidth > 0.0f && !column_is_resizable) column->WidthAuto = column->InitStretchWeightOrWidth; + if (column->AutoFitQueue != 0x00) has_auto_fit_request = true; if (column->Flags & ImGuiTableColumnFlags_WidthStretch) @@ -817,6 +908,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->IsSortSpecsDirty = true; table->RightMostEnabledColumn = (ImGuiTableColumnIdx)prev_visible_column_idx; IM_ASSERT(table->LeftMostEnabledColumn >= 0 && table->RightMostEnabledColumn >= 0); + // [Part 2] Disable child window clipping while fitting columns. This is not strictly necessary but makes it possible to avoid // the column fitting having to wait until the first visible frame of the child container (may or not be a good thing). Also see #6510. // FIXME-TABLE: for always auto-resizing columns may not want to do that all the time. @@ -824,6 +916,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->InnerWindow->SkipItems = false; if (has_auto_fit_request) table->IsSettingsDirty = true; + // [Part 3] Fix column flags and record a few extra information. float sum_width_requests = 0.0f; // Sum of all width for fixed and auto-resize columns, excluding width contributed by Stretch columns but including spacing/padding. float stretch_sum_weights = 0.0f; // Sum of all weights for stretch columns. @@ -833,6 +926,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n)) continue; ImGuiTableColumn* column = &table->Columns[column_n]; + const bool column_is_resizable = (column->Flags & ImGuiTableColumnFlags_NoResize) == 0; if (column->Flags & ImGuiTableColumnFlags_WidthFixed) { @@ -840,12 +934,14 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) float width_auto = column->WidthAuto; if (table_sizing_policy == ImGuiTableFlags_SizingFixedSame && (column->AutoFitQueue != 0x00 || !column_is_resizable)) width_auto = fixed_max_width_auto; + // Apply automatic width // Latch initial size for fixed columns and update it constantly for auto-resizing column (unless clipped!) if (column->AutoFitQueue != 0x00) column->WidthRequest = width_auto; else if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !column_is_resizable && column->IsRequestOutput) column->WidthRequest = width_auto; + // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets // (e.g. TextWrapped) too much. Otherwise what tends to happen is that TextWrapped would output a very // large height (= first frame scrollbar display very off + clipper would skip lots of items). @@ -868,6 +964,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) else column->StretchWeight = 1.0f; } + stretch_sum_weights += column->StretchWeight; if (table->LeftMostStretchedColumn == -1 || table->Columns[table->LeftMostStretchedColumn].DisplayOrder > column->DisplayOrder) table->LeftMostStretchedColumn = (ImGuiTableColumnIdx)column_n; @@ -879,6 +976,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } table->ColumnsEnabledFixedCount = (ImGuiTableColumnIdx)count_fixed; table->ColumnsStretchSumWeights = stretch_sum_weights; + // [Part 4] Apply final widths based on requested widths const ImRect work_rect = table->WorkRect; const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1); @@ -892,6 +990,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n)) continue; ImGuiTableColumn* column = &table->Columns[column_n]; + // Allocate width for stretched/weighted columns (StretchWeight gets converted into WidthRequest) if (column->Flags & ImGuiTableColumnFlags_WidthStretch) { @@ -899,14 +998,17 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->WidthRequest = IM_TRUNC(ImMax(width_avail_for_stretched_columns * weight_ratio, table->MinColumnWidth) + 0.01f); width_remaining_for_stretched_columns -= column->WidthRequest; } + // [Resize Rule 1] The right-most Visible column is not resizable if there is at least one Stretch column // See additional comments in TableSetColumnWidth(). if (column->NextEnabledColumn == -1 && table->LeftMostStretchedColumn != -1) column->Flags |= ImGuiTableColumnFlags_NoDirectResize_; + // Assign final width, record width in case we will need to shrink column->WidthGiven = ImTrunc(ImMax(column->WidthRequest, table->MinColumnWidth)); table->ColumnsGivenWidth += column->WidthGiven; } + // [Part 5] Redistribute stretch remainder width due to rounding (remainder width is < 1.0f * number of Stretch column). // Using right-to-left distribution (more likely to match resizing cursor). if (width_remaining_for_stretched_columns >= 1.0f && !(table->Flags & ImGuiTableFlags_PreciseWidths)) @@ -921,6 +1023,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->WidthGiven += 1.0f; width_remaining_for_stretched_columns -= 1.0f; } + // Determine if table is hovered which will be used to flag columns as hovered. // - In principle we'd like to use the equivalent of IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), // but because our item is partially submitted at this point we use ItemHoverable() and a workaround (temporarily @@ -935,11 +1038,13 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) g.ActiveId = 0; const bool is_hovering_table = ItemHoverable(mouse_hit_rect, 0, ImGuiItemFlags_None); g.ActiveId = backup_active_id; + // Determine skewed MousePos.x to support angled headers. float mouse_skewed_x = g.IO.MousePos.x; if (table->AngledHeadersHeight > 0.0f) if (g.IO.MousePos.y >= table->OuterRect.Min.y && g.IO.MousePos.y <= table->OuterRect.Min.y + table->AngledHeadersHeight) mouse_skewed_x += ImTrunc((table->OuterRect.Min.y + table->AngledHeadersHeight - g.IO.MousePos.y) * table->AngledHeadersSlope); + // [Part 6] Setup final position, offset, skip/clip states and clipping rectangles, detect hovered column // Process columns in their visible orders as we are comparing the visible order and adjusting host_clip_rect while looping. int visible_n = 0; @@ -953,15 +1058,19 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) { const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; + // Initial nav layer: using FreezeRowsCount, NOT FreezeRowsRequest, so Header line changes layer when frozen column->NavLayerCurrent = (ImS8)(table->FreezeRowsCount > 0 ? ImGuiNavLayer_Menu : (ImGuiNavLayer)table->NavLayer); + if (offset_x_frozen && table->FreezeColumnsCount == visible_n) { offset_x += work_rect.Min.x - table->OuterRect.Min.x; offset_x_frozen = false; } + // Clear status flags column->Flags &= ~ImGuiTableColumnFlags_StatusMask_; + if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n)) { // Hidden column: clear a few fields and we are done with it for the remainder of the function. @@ -976,13 +1085,16 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->ItemWidth = 1.0f; continue; } + // Lock start position column->MinX = offset_x; + // Lock width based on start position and minimum/maximum width for this position column->WidthMax = TableCalcMaxColumnWidth(table, column_n); column->WidthGiven = ImMin(column->WidthGiven, column->WidthMax); column->WidthGiven = ImMax(column->WidthGiven, ImMin(column->WidthRequest, table->MinColumnWidth)); column->MaxX = offset_x + column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f; + // Lock other positions // - ClipRect.Min.x: Because merging draw commands doesn't compare min boundaries, we make ClipRect.Min.x match left bounds to be consistent regardless of merging. // - ClipRect.Max.x: using WorkMaxX instead of MaxX (aka including padding) makes things more consistent when resizing down, tho slightly detrimental to visibility in very-small column. @@ -997,6 +1109,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->ClipRect.Max.x = column->MaxX; //column->WorkMaxX; column->ClipRect.Max.y = FLT_MAX; column->ClipRect.ClipWithFull(host_clip_rect); + // Mark column as Clipped (not in sight) // Note that scrolling tables (where inner_window != outer_window) handle Y clipped earlier in BeginTable() so IsVisibleY really only applies to non-scrolling tables. // FIXME-TABLE: Because InnerClipRect.Max.y is conservatively ==outer_window->ClipRect.Max.y, we never can mark columns _Above_ the scroll line as not IsVisibleY. @@ -1009,8 +1122,10 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) const bool is_visible = column->IsVisibleX; //&& column->IsVisibleY; if (is_visible) ImBitArraySetBit(table->VisibleMaskByIndex, column_n); + // Mark column as requesting output from user. Note that fixed + non-resizable sets are auto-fitting at all times and therefore always request output. column->IsRequestOutput = is_visible || column->AutoFitQueue != 0 || column->CannotSkipItemsQueue != 0; + // Mark column as SkipItems (ignoring all items/layout) // (table->HostSkipItems is a copy of inner_window->SkipItems before we cleared it above in Part 2) column->IsSkipItems = !column->IsEnabled || table->HostSkipItems; @@ -1018,18 +1133,21 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) IM_ASSERT(!is_visible); if (column->IsRequestOutput && !column->IsSkipItems) has_at_least_one_column_requesting_output = true; + // Update status flags column->Flags |= ImGuiTableColumnFlags_IsEnabled; if (is_visible) column->Flags |= ImGuiTableColumnFlags_IsVisible; if (column->SortOrder != -1) column->Flags |= ImGuiTableColumnFlags_IsSorted; + // Detect hovered column if (is_hovering_table && mouse_skewed_x >= column->ClipRect.Min.x && mouse_skewed_x < column->ClipRect.Max.x) { column->Flags |= ImGuiTableColumnFlags_IsHovered; table->HoveredColumnBody = (ImGuiTableColumnIdx)column_n; } + // Alignment // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in // many cases (to be able to honor this we might be able to store a log of cells width, per row, for @@ -1038,6 +1156,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // column->WorkMinX = ImMax(column->WorkMinX, column->MaxX - column->ContentWidthRowsUnfrozen); //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter) // column->WorkMinX = ImLerp(column->WorkMinX, ImMax(column->StartX, column->MaxX - column->ContentWidthRowsUnfrozen), 0.5f); + // Reset content width variables if (table->InstanceCurrent == 0) { @@ -1055,17 +1174,21 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->ContentMaxXHeadersUsed += offset_from_previous_instance; column->ContentMaxXHeadersIdeal += offset_from_previous_instance; } + // Don't decrement auto-fit counters until container window got a chance to submit its items if (table->HostSkipItems == false && table->InstanceCurrent == 0) { column->AutoFitQueue >>= 1; column->CannotSkipItemsQueue >>= 1; } + if (visible_n < table->FreezeColumnsCount) host_clip_rect.Min.x = ImClamp(column->MaxX + TABLE_BORDER_SIZE, host_clip_rect.Min.x, host_clip_rect.Max.x); + offset_x += column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f; visible_n++; } + // In case the table is visible (e.g. decorations) but all columns clipped, we keep a column visible. // Else if give no chance to a clipper-savy user to submit rows and therefore total contents height used by scrollbar. if (has_at_least_one_column_requesting_output == false) @@ -1073,6 +1196,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->Columns[table->LeftMostEnabledColumn].IsRequestOutput = true; table->Columns[table->LeftMostEnabledColumn].IsSkipItems = false; } + // [Part 7] Detect/store when we are hovering the unused space after the right-most column (so e.g. context menus can react on it) // Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, either // because of using _WidthAuto/_WidthStretch). This will hide the resizing option from the context menu. @@ -1082,7 +1206,9 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->HoveredColumnBody = (ImGuiTableColumnIdx)table->ColumnsCount; if (has_resizable == false && (table->Flags & ImGuiTableFlags_Resizable)) table->Flags &= ~ImGuiTableFlags_Resizable; + table->IsActiveIdAliveBeforeTable = (g.ActiveIdIsAlive != 0); + // [Part 8] Lock actual OuterRect/WorkRect right-most position. // This is done late to handle the case of fixed-columns tables not claiming more widths that they need. // Because of this we are careful with uses of WorkRect and InnerClipRect before this point. @@ -1096,6 +1222,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->InnerWindow->ParentWorkRect = table->WorkRect; table->BorderX1 = table->InnerClipRect.Min.x; table->BorderX2 = table->InnerClipRect.Max.x; + // Setup window's WorkRect.Max.y for GetContentRegionAvail(). Other values will be updated in each TableBeginCell() call. float window_content_max_y; if (table->Flags & ImGuiTableFlags_NoHostExtendY) @@ -1103,14 +1230,17 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) else window_content_max_y = ImMax(table->InnerWindow->ContentRegionRect.Max.y, (table->Flags & ImGuiTableFlags_ScrollY) ? 0.0f : table->OuterRect.Max.y); table->InnerWindow->WorkRect.Max.y = ImClamp(window_content_max_y - g.Style.CellPadding.y, table->InnerWindow->WorkRect.Min.y, table->InnerWindow->WorkRect.Max.y); + // [Part 9] Allocate draw channels and setup background cliprect TableSetupDrawChannels(table); + // [Part 10] Hit testing on borders if (table->Flags & ImGuiTableFlags_Resizable) TableUpdateBorders(table); table_instance->LastTopHeadersRowHeight = 0.0f; table->IsLayoutLocked = true; table->IsUsingHeaders = false; + // Highlight header table->HighlightColumnHeader = -1; if (table->IsContextPopupOpen && table->ContextPopupColumn != -1 && table->InstanceInteracted == table->InstanceCurrent) @@ -1118,9 +1248,10 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) else if ((table->Flags & ImGuiTableFlags_HighlightHoveredColumn) && table->HoveredColumnBody != -1 && table->HoveredColumnBody != table->ColumnsCount && table->HoveredColumnBorder == -1) if (g.ActiveId == 0 || (table->IsActiveIdInTable || g.DragDropActive)) table->HighlightColumnHeader = table->HoveredColumnBody; + // [Part 11] Default context menu // - To append to this menu: you can call TableBeginContextMenuPopup()/.../EndPopup(). - // - To modify or replace this: set table->IsContextPopupNoDefaultContents = true, then call TableBeginContextMenuPopup()/.../EndPopup(). + // - To modify or replace this: set table->DisableDefaultContextMenu = true, then call TableBeginContextMenuPopup()/.../EndPopup(). // - You may call TableDrawDefaultContextMenu() with selected flags to display specific sections of the default menu, // e.g. TableDrawDefaultContextMenu(table, table->Flags & ~ImGuiTableFlags_Hideable) will display everything EXCEPT columns visibility options. if (table->DisableDefaultContextMenu == false && TableBeginContextMenuPopup(table)) @@ -1128,16 +1259,19 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) TableDrawDefaultContextMenu(table, table->Flags); EndPopup(); } + // [Part 12] Sanitize and build sort specs before we have a chance to use them for display. // This path will only be exercised when sort specs are modified before header rows (e.g. init or visibility change) if (table->IsSortSpecsDirty && (table->Flags & ImGuiTableFlags_Sortable)) TableSortSpecsBuild(table); + // [Part 13] Setup inner window decoration size (for scrolling / nav tracking to properly take account of frozen rows/columns) if (table->FreezeColumnsRequest > 0) table->InnerWindow->DecoInnerSizeX1 = table->Columns[table->DisplayOrderToIndex[table->FreezeColumnsRequest - 1]].MaxX - table->OuterRect.Min.x; if (table->FreezeRowsRequest > 0) table->InnerWindow->DecoInnerSizeY1 = table_instance->LastFrozenHeight; table_instance->LastFrozenHeight = 0.0f; + // Initial state ImGuiWindow* inner_window = table->InnerWindow; if (table->Flags & ImGuiTableFlags_NoClip) @@ -1145,12 +1279,14 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) else inner_window->DrawList->PushClipRect(inner_window->InnerClipRect.Min, inner_window->InnerClipRect.Max, false); // FIXME: use table->InnerClipRect? } + // Process hit-testing on resizing borders. Actual size change will be applied in EndTable() // - Set table->HoveredColumnBorder with a short delay/timer to reduce visual feedback noise. void ImGui::TableUpdateBorders(ImGuiTable* table) { ImGuiContext& g = *GImGui; IM_ASSERT(table->Flags & ImGuiTableFlags_Resizable); + // At this point OuterRect height may be zero or under actual final height, so we rely on temporal coherency and // use the final height from last frame. Because this is only affecting _interaction_ with columns, it is not // really problematic (whereas the actual visual will be displayed in EndTable() and using the current frame height). @@ -1160,24 +1296,30 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) const float hit_y1 = (table->FreezeRowsCount >= 1 ? table->OuterRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight; const float hit_y2_body = ImMax(table->OuterRect.Max.y, hit_y1 + table_instance->LastOuterHeight - table->AngledHeadersHeight); const float hit_y2_head = hit_y1 + table_instance->LastTopHeadersRowHeight; + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n)) continue; + const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; if (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) continue; + // ImGuiTableFlags_NoBordersInBodyUntilResize will be honored in TableDrawBorders() const float border_y2_hit = (table->Flags & ImGuiTableFlags_NoBordersInBody) ? hit_y2_head : hit_y2_body; if ((table->Flags & ImGuiTableFlags_NoBordersInBody) && table->IsUsingHeaders == false) continue; + if (!column->IsVisibleX && table->LastResizedColumn != column_n) continue; + ImGuiID column_id = TableGetColumnResizeID(table, column_n, table->InstanceCurrent); ImRect hit_rect(column->MaxX - hit_half_width, hit_y1, column->MaxX + hit_half_width, border_y2_hit); ItemAdd(hit_rect, column_id, NULL, ImGuiItemFlags_NoNav); //GetForegroundDrawList()->AddRect(hit_rect.Min, hit_rect.Max, IM_COL32(255, 0, 0, 100)); + bool hovered = false, held = false; bool pressed = ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick | ImGuiButtonFlags_NoNavFocus); if (pressed && IsMouseDoubleClicked(0)) @@ -1200,6 +1342,7 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) } } } + void ImGui::EndTable() { ImGuiContext& g = *GImGui; @@ -1209,25 +1352,31 @@ void ImGui::EndTable() IM_ASSERT_USER_ERROR(table != NULL, "EndTable() call should only be done while in BeginTable() scope!"); return; } + // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some // cases, and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border) //IM_ASSERT(table->IsLayoutLocked && "Table unused: never called TableNextRow(), is that the intent?"); + // If the user never got to call TableNextRow() or TableNextColumn(), we call layout ourselves to ensure all our // code paths are consistent (instead of just hoping that TableBegin/TableEnd will work), get borders drawn, etc. if (!table->IsLayoutLocked) TableUpdateLayout(table); + const ImGuiTableFlags flags = table->Flags; ImGuiWindow* inner_window = table->InnerWindow; ImGuiWindow* outer_window = table->OuterWindow; ImGuiTableTempData* temp_data = table->TempData; IM_ASSERT(inner_window == g.CurrentWindow); IM_ASSERT(outer_window == inner_window || outer_window == inner_window->ParentWindow); + if (table->IsInsideRow) TableEndRow(table); + // Context menu in columns body if (flags & ImGuiTableFlags_ContextMenuInBody) if (table->HoveredColumnBody != -1 && !IsAnyItemHovered() && IsMouseReleased(ImGuiMouseButton_Right)) TableOpenContextMenu((int)table->HoveredColumnBody); + // Finalize table height ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); inner_window->DC.PrevLineSize = temp_data->HostBackupPrevLineSize; @@ -1241,6 +1390,7 @@ void ImGui::EndTable() table->OuterRect.Max.y = table->InnerRect.Max.y = ImMax(table->OuterRect.Max.y, inner_content_max_y); // Patch OuterRect/InnerRect height table->WorkRect.Max.y = ImMax(table->WorkRect.Max.y, table->OuterRect.Max.y); table_instance->LastOuterHeight = table->OuterRect.GetHeight(); + // Setup inner scrolling range // FIXME: This ideally should be done earlier, in BeginTable() SetNextWindowContentSize call, just like writing to inner_window->DC.CursorMaxPos.y, // but since the later is likely to be impossible to do we'd rather update both axes together. @@ -1254,13 +1404,16 @@ void ImGui::EndTable() max_pos_x = ImMax(max_pos_x, table->ResizeLockMinContentsX2); table->InnerWindow->DC.CursorMaxPos.x = max_pos_x + table->TempData->AngledHeadersExtraWidth; } + // Pop clipping rect if (!(flags & ImGuiTableFlags_NoClip)) inner_window->DrawList->PopClipRect(); inner_window->ClipRect = inner_window->DrawList->_ClipRectStack.back(); + // Draw borders if ((flags & ImGuiTableFlags_Borders) != 0) TableDrawBorders(table); + #if 0 // Strip out dummy channel draw calls // We have no way to prevent user submitting direct ImDrawList calls into a hidden column (but ImGui:: calls will be clipped out) @@ -1273,12 +1426,14 @@ void ImGui::EndTable() dummy_channel->_IdxBuffer.resize(0); } #endif + // Flatten channels and merge draw calls ImDrawListSplitter* splitter = table->DrawSplitter; splitter->SetCurrentChannel(inner_window->DrawList, 0); if ((table->Flags & ImGuiTableFlags_NoClip) == 0) TableMergeDrawChannels(table); splitter->Merge(inner_window->DrawList); + // Update ColumnsAutoFitWidth to get us ahead for host using our size to auto-resize without waiting for next BeginTable() float auto_fit_width_for_fixed = 0.0f; float auto_fit_width_for_stretched = 0.0f; @@ -1297,6 +1452,7 @@ void ImGui::EndTable() } const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1); table->ColumnsAutoFitWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount + auto_fit_width_for_fixed + ImMax(auto_fit_width_for_stretched, auto_fit_width_for_stretched_min); + // Update scroll if ((table->Flags & ImGuiTableFlags_ScrollX) == 0 && inner_window != outer_window) { @@ -1312,6 +1468,7 @@ void ImGui::EndTable() else if (column->MaxX > table->InnerClipRect.Max.x) SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x + neighbor_width_to_keep_visible, 1.0f); } + // Apply resizing/dragging at the end of the frame if (table->ResizedColumn != -1 && table->InstanceCurrent == table->InstanceInteracted) { @@ -1320,13 +1477,16 @@ void ImGui::EndTable() const float new_width = ImTrunc(new_x2 - column->MinX - table->CellSpacingX1 - table->CellPaddingX * 2.0f); table->ResizedColumnNextWidth = new_width; } + table->IsActiveIdInTable = (g.ActiveIdIsAlive != 0 && table->IsActiveIdAliveBeforeTable == false); + // Pop from id stack IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == table_instance->TableInstanceID, "Mismatching PushID/PopID!"); IM_ASSERT_USER_ERROR(outer_window->DC.ItemWidthStack.Size >= temp_data->HostBackupItemWidthStackSize, "Too many PopItemWidth!"); if (table->InstanceCurrent > 0) PopID(); PopID(); + // Restore window data that we modified const ImVec2 backup_outer_max_pos = outer_window->DC.CursorMaxPos; inner_window->WorkRect = temp_data->HostBackupWorkRect; @@ -1336,6 +1496,7 @@ void ImGui::EndTable() outer_window->DC.ItemWidth = temp_data->HostBackupItemWidth; outer_window->DC.ItemWidthStack.Size = temp_data->HostBackupItemWidthStackSize; outer_window->DC.ColumnsOffset = temp_data->HostBackupColumnsOffset; + // Layout in outer window // (FIXME: To allow auto-fit and allow desirable effect of SameLine() we dissociate 'used' vs 'ideal' size by overriding // CursorPosPrevLine and CursorMaxPos manually. That should be a more general layout feature, see same problem e.g. #3414) @@ -1350,9 +1511,11 @@ void ImGui::EndTable() } else { + table->InnerWindow->DC.TreeDepth--; ItemSize(table->OuterRect.GetSize()); ItemAdd(table->OuterRect, 0); } + // Override declared contents width/height to enable auto-resize while not needlessly adding a scrollbar if (table->Flags & ImGuiTableFlags_NoHostExtendX) { @@ -1386,10 +1549,12 @@ void ImGui::EndTable() // OuterRect.Max.y may already have been pushed downward from the initial value (unless ImGuiTableFlags_NoHostExtendY is set) outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, table->OuterRect.Max.y); } + // Save settings if (table->IsSettingsDirty) TableSaveSettings(table); table->IsInitializing = false; + // Clear or restore current table, if any IM_ASSERT(g.CurrentWindow == outer_window && g.CurrentTable == table); IM_ASSERT(g.TablesTempDataStacked > 0); @@ -1403,6 +1568,7 @@ void ImGui::EndTable() outer_window->DC.CurrentTableIdx = g.CurrentTable ? g.Tables.GetIndex(g.CurrentTable) : -1; NavUpdateCurrentWindowIsScrollPushableX(); } + // Called in TableSetupColumn() when initializing and in TableLoadSettings() for defaults before applying stored settings. // 'init_mask' specify which fields to initialize. static void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags init_mask) @@ -1427,6 +1593,7 @@ static void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, column->SortDirection = (flags & ImGuiTableColumnFlags_DefaultSort) ? ((flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending)) : (ImS8)ImGuiSortDirection_None; } } + // See "COLUMNS SIZING POLICIES" comments at the top of this file // If (init_width_or_weight <= 0.0f) it is ignored void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id) @@ -1445,12 +1612,15 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo IM_ASSERT_USER_ERROR(table->DeclColumnsCount < table->ColumnsCount, "Called TableSetupColumn() too many times!"); return; } + ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount]; table->DeclColumnsCount++; + // Assert when passing a width or weight if policy is entirely left to default, to avoid storing width into weight and vice-versa. // Give a grace to users of ImGuiTableFlags_ScrollX. if (table->IsDefaultSizingPolicy && (flags & ImGuiTableColumnFlags_WidthMask_) == 0 && (flags & ImGuiTableFlags_ScrollX) == 0) IM_ASSERT(init_width_or_weight <= 0.0f && "Can only specify width/weight if sizing policy is set explicitly in either Table or Column."); + // When passing a width automatically enforce WidthFixed policy // (whereas TableSetupColumnFlags would default to WidthAuto if table is not resizable) if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0 && init_width_or_weight > 0.0f) @@ -1461,9 +1631,11 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo flags |= ImGuiTableColumnFlags_NoHeaderLabel; table->AngledHeadersCount++; } + TableSetupColumnFlags(table, column, flags); column->UserID = user_id; flags = column->Flags; + // Initialize defaults column->InitStretchWeightOrWidth = init_width_or_weight; if (table->IsInitializing) @@ -1473,6 +1645,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo init_flags |= ImGuiTableFlags_Resizable; TableInitColumnDefaults(table, column, init_flags); } + // Store name (append with zero-terminator in contiguous buffer) // FIXME: If we recorded the number of \n in names we could compute header row height column->NameOffset = -1; @@ -1482,6 +1655,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo table->ColumnsNames.append(label, label + ImStrlen(label) + 1); } } + // [Public] void ImGui::TableSetupScrollFreeze(int columns, int rows) { @@ -1495,11 +1669,13 @@ void ImGui::TableSetupScrollFreeze(int columns, int rows) IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!"); IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS); IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit + table->FreezeColumnsRequest = (table->Flags & ImGuiTableFlags_ScrollX) ? (ImGuiTableColumnIdx)ImMin(columns, table->ColumnsCount) : 0; table->FreezeColumnsCount = (table->InnerWindow->Scroll.x != 0.0f) ? table->FreezeColumnsRequest : 0; table->FreezeRowsRequest = (table->Flags & ImGuiTableFlags_ScrollY) ? (ImGuiTableColumnIdx)rows : 0; table->FreezeRowsCount = (table->InnerWindow->Scroll.y != 0.0f) ? table->FreezeRowsRequest : 0; table->IsUnfrozenRows = (table->FreezeRowsCount == 0); // Make sure this is set before TableUpdateLayout() so ImGuiListClipper can benefit from it.b + // Ensure frozen columns are ordered in their section. We still allow multiple frozen columns to be reordered. // FIXME-TABLE: This work for preserving 2143 into 21|43. How about 4321 turning into 21|43? (preserve relative order in each section) for (int column_n = 0; column_n < table->FreezeColumnsRequest; column_n++) @@ -1512,6 +1688,7 @@ void ImGui::TableSetupScrollFreeze(int columns, int rows) } } } + //----------------------------------------------------------------------------- // [SECTION] Tables: Simple accessors //----------------------------------------------------------------------------- @@ -1526,12 +1703,14 @@ void ImGui::TableSetupScrollFreeze(int columns, int rows) // - TableGetHoveredRow() [Internal] // - TableSetBgColor() //----------------------------------------------------------------------------- + int ImGui::TableGetColumnCount() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; return table ? table->ColumnsCount : 0; } + const char* ImGui::TableGetColumnName(int column_n) { ImGuiContext& g = *GImGui; @@ -1542,6 +1721,7 @@ const char* ImGui::TableGetColumnName(int column_n) column_n = table->CurrentColumn; return TableGetColumnName(table, column_n); } + const char* ImGui::TableGetColumnName(const ImGuiTable* table, int column_n) { if (table->IsLayoutLocked == false && column_n >= table->DeclColumnsCount) @@ -1551,6 +1731,7 @@ const char* ImGui::TableGetColumnName(const ImGuiTable* table, int column_n) return ""; return &table->ColumnsNames.Buf[column->NameOffset]; } + // Change user accessible enabled/disabled state of a column (often perceived as "showing/hiding" from users point of view) // Note that end-user can use the context menu to change this themselves (right-click in headers, or right-click in columns body with ImGuiTableFlags_ContextMenuInBody) // - Require table to have the ImGuiTableFlags_Hideable flag because we are manipulating user accessible state. @@ -1573,6 +1754,7 @@ void ImGui::TableSetColumnEnabled(int column_n, bool enabled) ImGuiTableColumn* column = &table->Columns[column_n]; column->IsUserEnabledNextFrame = enabled; } + // We allow querying for an extra column in order to poll the IsHovered state of the right-most section ImGuiTableColumnFlags ImGui::TableGetColumnFlags(int column_n) { @@ -1586,6 +1768,7 @@ ImGuiTableColumnFlags ImGui::TableGetColumnFlags(int column_n) return (table->HoveredColumnBody == column_n) ? ImGuiTableColumnFlags_IsHovered : ImGuiTableColumnFlags_None; return table->Columns[column_n].Flags; } + // Return the cell rectangle based on currently known height. // - Important: we generally don't know our row height until the end of the row, so Max.y will be incorrect in many situations. // The only case where this is correct is if we provided a min_row_height to TableNextRow() and don't go below it, or in TableEndRow() when we locked that height. @@ -1605,6 +1788,7 @@ ImRect ImGui::TableGetCellBgRect(const ImGuiTable* table, int column_n) x2 = ImMin(x2, table->WorkRect.Max.x); return ImRect(x1, table->RowPosY1, x2, table->RowPosY2); } + // Return the resizing ID for the right-side of the given column. ImGuiID ImGui::TableGetColumnResizeID(ImGuiTable* table, int column_n, int instance_no) { @@ -1612,6 +1796,7 @@ ImGuiID ImGui::TableGetColumnResizeID(ImGuiTable* table, int column_n, int insta ImGuiID instance_id = TableGetInstanceID(table, instance_no); return instance_id + 1 + column_n; // FIXME: #6140: still not ideal } + // Return -1 when table is not hovered. return columns_count if hovering the unused space at the right of the right-most visible column. int ImGui::TableGetHoveredColumn() { @@ -1621,6 +1806,7 @@ int ImGui::TableGetHoveredColumn() return -1; return (int)table->HoveredColumnBody; } + // Return -1 when table is not hovered. Return maxrow+1 if in table but below last submitted row. // *IMPORTANT* Unlike TableGetHoveredColumn(), this has a one frame latency in updating the value. // This difference with is the reason why this is not public yet. @@ -1633,13 +1819,21 @@ int ImGui::TableGetHoveredRow() ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); return (int)table_instance->HoveredRowLast; } + void ImGui::TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; IM_ASSERT(target != ImGuiTableBgTarget_None); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } + if (color == IM_COL32_DISABLE) color = 0; + // We cannot draw neither the cell or row background immediately as we don't know the row height at this point in time. switch (target) { @@ -1672,6 +1866,7 @@ void ImGui::TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n IM_ASSERT(0); } } + //------------------------------------------------------------------------- // [SECTION] Tables: Row changes //------------------------------------------------------------------------- @@ -1680,6 +1875,7 @@ void ImGui::TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n // - TableBeginRow() [Internal] // - TableEndRow() [Internal] //------------------------------------------------------------------------- + // [Public] Note: for row coloring we use ->RowBgColorCounter which is the same value without counting header rows int ImGui::TableGetRowIndex() { @@ -1689,50 +1885,61 @@ int ImGui::TableGetRowIndex() return 0; return table->CurrentRow; } + // [Public] Starts into the first cell of a new row void ImGui::TableNextRow(ImGuiTableRowFlags row_flags, float row_min_height) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; + if (!table->IsLayoutLocked) TableUpdateLayout(table); if (table->IsInsideRow) TableEndRow(table); + table->LastRowFlags = table->RowFlags; table->RowFlags = row_flags; table->RowCellPaddingY = g.Style.CellPadding.y; table->RowMinHeight = row_min_height; TableBeginRow(table); + // We honor min_row_height requested by user, but cannot guarantee per-row maximum height, // because that would essentially require a unique clipping rectangle per-cell. table->RowPosY2 += table->RowCellPaddingY * 2.0f; table->RowPosY2 = ImMax(table->RowPosY2, table->RowPosY1 + row_min_height); + // Disable output until user calls TableNextColumn() table->InnerWindow->SkipItems = true; } + // [Internal] Only called by TableNextRow() void ImGui::TableBeginRow(ImGuiTable* table) { ImGuiWindow* window = table->InnerWindow; IM_ASSERT(!table->IsInsideRow); + // New row table->CurrentRow++; table->CurrentColumn = -1; table->RowBgColor[0] = table->RowBgColor[1] = IM_COL32_DISABLE; table->RowCellDataCurrent = -1; table->IsInsideRow = true; + // Begin frozen rows float next_y1 = table->RowPosY2; if (table->CurrentRow == 0 && table->FreezeRowsCount > 0) next_y1 = window->DC.CursorPos.y = table->OuterRect.Min.y; + table->RowPosY1 = table->RowPosY2 = next_y1; table->RowTextBaseline = 0.0f; table->RowIndentOffsetX = window->DC.Indent.x - table->HostIndentX; // Lock indent + window->DC.PrevLineTextBaseOffset = 0.0f; window->DC.CursorPosPrevLine = ImVec2(window->DC.CursorPos.x, window->DC.CursorPos.y + table->RowCellPaddingY); // This allows users to call SameLine() to share LineSize between columns. window->DC.PrevLineSize = window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); // This allows users to call SameLine() to share LineSize between columns, and to call it from first column too. window->DC.IsSameLine = window->DC.IsSetPos = false; window->DC.CursorMaxPos.y = next_y1; + // Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging. if (table->RowFlags & ImGuiTableRowFlags_Headers) { @@ -1741,6 +1948,7 @@ void ImGui::TableBeginRow(ImGuiTable* table) table->IsUsingHeaders = true; } } + // [Internal] Called by TableNextRow() void ImGui::TableEndRow(ImGuiTable* table) { @@ -1748,14 +1956,21 @@ void ImGui::TableEndRow(ImGuiTable* table) ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(window == table->InnerWindow); IM_ASSERT(table->IsInsideRow); + if (table->CurrentColumn != -1) + { TableEndCell(table); + table->CurrentColumn = -1; + } + // Logging if (g.LogEnabled) LogRenderedText(NULL, "|"); + // Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. However it is // likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding. window->DC.CursorPos.y = table->RowPosY2; + // Row background fill const float bg_y1 = table->RowPosY1; const float bg_y2 = table->RowPosY2; @@ -1764,12 +1979,14 @@ void ImGui::TableEndRow(ImGuiTable* table) ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); if ((table->RowFlags & ImGuiTableRowFlags_Headers) && (table->CurrentRow == 0 || (table->LastRowFlags & ImGuiTableRowFlags_Headers))) table_instance->LastTopHeadersRowHeight += bg_y2 - bg_y1; + const bool is_visible = (bg_y2 >= table->InnerClipRect.Min.y && bg_y1 <= table->InnerClipRect.Max.y); if (is_visible) { // Update data for TableGetHoveredRow() if (table->HoveredColumnBody != -1 && g.IO.MousePos.y >= bg_y1 && g.IO.MousePos.y < bg_y2 && table_instance->HoveredRowNext < 0) table_instance->HoveredRowNext = table->CurrentRow; + // Decide of background color for the row ImU32 bg_col0 = 0; ImU32 bg_col1 = 0; @@ -1779,11 +1996,13 @@ void ImGui::TableEndRow(ImGuiTable* table) bg_col0 = GetColorU32((table->RowBgColorCounter & 1) ? ImGuiCol_TableRowBgAlt : ImGuiCol_TableRowBg); if (table->RowBgColor[1] != IM_COL32_DISABLE) bg_col1 = table->RowBgColor[1]; + // Decide of top border color ImU32 top_border_col = 0; const float border_size = TABLE_BORDER_SIZE; if (table->CurrentRow > 0 && (table->Flags & ImGuiTableFlags_BordersInnerH)) top_border_col = (table->LastRowFlags & ImGuiTableRowFlags_Headers) ? table->BorderColorStrong : table->BorderColorLight; + const bool draw_cell_bg_color = table->RowCellDataCurrent >= 0; const bool draw_strong_bottom_border = unfreeze_rows_actual; if ((bg_col0 | bg_col1 | top_border_col) != 0 || draw_strong_bottom_border || draw_cell_bg_color) @@ -1794,6 +2013,7 @@ void ImGui::TableEndRow(ImGuiTable* table) window->DrawList->_CmdHeader.ClipRect = table->Bg0ClipRectForDrawCmd.ToVec4(); table->DrawSplitter->SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_BG0); } + // Draw row background // We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle if (bg_col0 || bg_col1) @@ -1805,6 +2025,7 @@ void ImGui::TableEndRow(ImGuiTable* table) if (bg_col1 != 0 && row_rect.Min.y < row_rect.Max.y) window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col1); } + // Draw cell background color if (draw_cell_bg_color) { @@ -1822,13 +2043,16 @@ void ImGui::TableEndRow(ImGuiTable* table) window->DrawList->AddRectFilled(cell_bg_rect.Min, cell_bg_rect.Max, cell_data->BgColor); } } + // Draw top border if (top_border_col && bg_y1 >= table->BgClipRect.Min.y && bg_y1 < table->BgClipRect.Max.y) window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y1), ImVec2(table->BorderX2, bg_y1), top_border_col, border_size); + // Draw bottom border at the row unfreezing mark (always strong) if (draw_strong_bottom_border && bg_y2 >= table->BgClipRect.Min.y && bg_y2 < table->BgClipRect.Max.y) window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong, border_size); } + // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and // get the new cursor position. @@ -1838,15 +2062,18 @@ void ImGui::TableEndRow(ImGuiTable* table) table->Columns[column_n].NavLayerCurrent = table->NavLayer; const float y0 = ImMax(table->RowPosY2 + 1, table->InnerClipRect.Min.y); table_instance->LastFrozenHeight = y0 - table->OuterRect.Min.y; + if (unfreeze_rows_actual) { IM_ASSERT(table->IsUnfrozenRows == false); table->IsUnfrozenRows = true; + // BgClipRect starts as table->InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect table->BgClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y = ImMin(y0, table->InnerClipRect.Max.y); table->BgClipRect.Max.y = table->Bg2ClipRectForDrawCmd.Max.y = table->InnerClipRect.Max.y; table->Bg2DrawChannelCurrent = table->Bg2DrawChannelUnfrozen; IM_ASSERT(table->Bg2ClipRectForDrawCmd.Min.y <= table->Bg2ClipRectForDrawCmd.Max.y); + float row_height = table->RowPosY2 - table->RowPosY1; table->RowPosY2 = window->DC.CursorPos.y = table->WorkRect.Min.y + table->RowPosY2 - table->OuterRect.Min.y; table->RowPosY1 = table->RowPosY2 - row_height; @@ -1856,15 +2083,18 @@ void ImGui::TableEndRow(ImGuiTable* table) column->DrawChannelCurrent = column->DrawChannelUnfrozen; column->ClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y; } + // Update cliprect ahead of TableBeginCell() so clipper can access to new ClipRect->Min.y SetWindowClipRectBeforeSetChannel(window, table->Columns[0].ClipRect); table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Columns[0].DrawChannelCurrent); } } + if (!(table->RowFlags & ImGuiTableRowFlags_Headers)) table->RowBgColorCounter++; table->IsInsideRow = false; } + //------------------------------------------------------------------------- // [SECTION] Tables: Columns changes //------------------------------------------------------------------------- @@ -1874,6 +2104,7 @@ void ImGui::TableEndRow(ImGuiTable* table) // - TableBeginCell() [Internal] // - TableEndCell() [Internal] //------------------------------------------------------------------------- + int ImGui::TableGetColumnIndex() { ImGuiContext& g = *GImGui; @@ -1882,6 +2113,7 @@ int ImGui::TableGetColumnIndex() return 0; return table->CurrentColumn; } + // [Public] Append into a specific column bool ImGui::TableSetColumnIndex(int column_n) { @@ -1889,6 +2121,7 @@ bool ImGui::TableSetColumnIndex(int column_n) ImGuiTable* table = g.CurrentTable; if (!table) return false; + if (table->CurrentColumn != column_n) { if (table->CurrentColumn != -1) @@ -1900,10 +2133,12 @@ bool ImGui::TableSetColumnIndex(int column_n) } TableBeginCell(table, column_n); } + // Return whether the column is visible. User may choose to skip submitting items based on this return value, // however they shouldn't skip submitting for columns that may have the tallest contribution to row height. return table->Columns[column_n].IsRequestOutput; } + // [Public] Append into the next column, wrap and create a new row when already on last column bool ImGui::TableNextColumn() { @@ -1911,6 +2146,7 @@ bool ImGui::TableNextColumn() ImGuiTable* table = g.CurrentTable; if (!table) return false; + if (table->IsInsideRow && table->CurrentColumn + 1 < table->ColumnsCount) { if (table->CurrentColumn != -1) @@ -1922,10 +2158,13 @@ bool ImGui::TableNextColumn() TableNextRow(); TableBeginCell(table, 0); } + // Return whether the column is visible. User may choose to skip submitting items based on this return value, // however they shouldn't skip submitting for columns that may have the tallest contribution to row height. return table->Columns[table->CurrentColumn].IsRequestOutput; } + + // [Internal] Called by TableSetColumnIndex()/TableNextColumn() // This is called very frequently, so we need to be mindful of unnecessary overhead. // FIXME-TABLE FIXME-OPT: Could probably shortcut some things for non-active or clipped columns. @@ -1935,10 +2174,12 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_n) ImGuiTableColumn* column = &table->Columns[column_n]; ImGuiWindow* window = table->InnerWindow; table->CurrentColumn = column_n; + // Start position is roughly ~~ CellRect.Min + CellPadding + Indent float start_x = column->WorkMinX; if (column->Flags & ImGuiTableColumnFlags_IndentEnable) start_x += table->RowIndentOffsetX; // ~~ += window.DC.Indent.x - table->HostIndentX, except we locked it for the row. + window->DC.CursorPos.x = start_x; window->DC.CursorPos.y = table->RowPosY1 + table->RowCellPaddingY; window->DC.CursorMaxPos.x = window->DC.CursorPos.x; @@ -1946,17 +2187,21 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_n) window->DC.CursorPosPrevLine.x = window->DC.CursorPos.x; // PrevLine.y is preserved. This allows users to call SameLine() to share LineSize between columns. window->DC.CurrLineTextBaseOffset = table->RowTextBaseline; window->DC.NavLayerCurrent = (ImGuiNavLayer)column->NavLayerCurrent; + // Note how WorkRect.Max.y is only set once during layout window->WorkRect.Min.y = window->DC.CursorPos.y; window->WorkRect.Min.x = column->WorkMinX; window->WorkRect.Max.x = column->WorkMaxX; window->DC.ItemWidth = column->ItemWidth; + window->SkipItems = column->IsSkipItems; if (column->IsSkipItems) { g.LastItemData.ID = 0; g.LastItemData.StatusFlags = 0; } + + // Also see TablePushColumnChannel() if (table->Flags & ImGuiTableFlags_NoClip) { // FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed. @@ -1969,6 +2214,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_n) SetWindowClipRectBeforeSetChannel(window, column->ClipRect); table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); } + // Logging if (g.LogEnabled && !column->IsSkipItems) { @@ -1976,13 +2222,16 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_n) g.LogLinePosY = FLT_MAX; } } + // [Internal] Called by TableNextRow()/TableSetColumnIndex()/TableNextColumn() void ImGui::TableEndCell(ImGuiTable* table) { ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; ImGuiWindow* window = table->InnerWindow; + if (window->DC.IsSetPos) ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); + // Report maximum position so we can infer content size per column. float* p_max_pos_x; if (table->RowFlags & ImGuiTableRowFlags_Headers) @@ -1993,10 +2242,12 @@ void ImGui::TableEndCell(ImGuiTable* table) if (column->IsEnabled) table->RowPosY2 = ImMax(table->RowPosY2, window->DC.CursorMaxPos.y + table->RowCellPaddingY); column->ItemWidth = window->DC.ItemWidth; + // Propagate text baseline for the entire row // FIXME-TABLE: Here we propagate text baseline from the last line of the cell.. instead of the first one. table->RowTextBaseline = ImMax(table->RowTextBaseline, window->DC.PrevLineTextBaseOffset); } + //------------------------------------------------------------------------- // [SECTION] Tables: Columns width management //------------------------------------------------------------------------- @@ -2009,6 +2260,7 @@ void ImGui::TableEndCell(ImGuiTable* table) //------------------------------------------------------------------------- // Note that actual columns widths are computed in TableUpdateLayout(). //------------------------------------------------------------------------- + // Maximum column content width given current layout. Use column->MinX so this value differs on a per-column basis. float ImGui::TableCalcMaxColumnWidth(const ImGuiTable* table, int column_n) { @@ -2040,6 +2292,7 @@ float ImGui::TableCalcMaxColumnWidth(const ImGuiTable* table, int column_n) } return max_width; } + // Note this is meant to be stored in column->WidthAuto, please generally use the WidthAuto field float ImGui::TableGetColumnWidthAuto(ImGuiTable* table, ImGuiTableColumn* column) { @@ -2048,12 +2301,15 @@ float ImGui::TableGetColumnWidthAuto(ImGuiTable* table, ImGuiTableColumn* column float width_auto = content_width_body; if (!(column->Flags & ImGuiTableColumnFlags_NoHeaderWidth)) width_auto = ImMax(width_auto, content_width_headers); + // Non-resizable fixed columns preserve their requested width if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && column->InitStretchWeightOrWidth > 0.0f) if (!(table->Flags & ImGuiTableFlags_Resizable) || (column->Flags & ImGuiTableColumnFlags_NoResize)) width_auto = column->InitStretchWeightOrWidth; + return ImMax(width_auto, table->MinColumnWidth); } + // 'width' = inner column width, without padding void ImGui::TableSetColumnWidth(int column_n, float width) { @@ -2063,6 +2319,7 @@ void ImGui::TableSetColumnWidth(int column_n, float width) IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); ImGuiTableColumn* column_0 = &table->Columns[column_n]; float column_0_width = width; + // Apply constraints early // Compare both requested and actual given width to avoid overwriting requested width when column is stuck (minimum size, bounded) IM_ASSERT(table->MinColumnWidth > 0.0f); @@ -2071,14 +2328,17 @@ void ImGui::TableSetColumnWidth(int column_n, float width) column_0_width = ImClamp(column_0_width, min_width, max_width); if (column_0->WidthGiven == column_0_width || column_0->WidthRequest == column_0_width) return; + //IMGUI_DEBUG_PRINT("TableSetColumnWidth(%d, %.1f->%.1f)\n", column_0_idx, column_0->WidthGiven, column_0_width); ImGuiTableColumn* column_1 = (column_0->NextEnabledColumn != -1) ? &table->Columns[column_0->NextEnabledColumn] : NULL; + // In this surprisingly not simple because of how we support mixing Fixed and multiple Stretch columns. // - All fixed: easy. // - All stretch: easy. // - One or more fixed + one stretch: easy. // - One or more fixed + more than one stretch: tricky. // Qt when manual resize is enabled only supports a single _trailing_ stretch column, we support more cases here. + // When forwarding resize from Wn| to Fn+1| we need to be considerate of the _NoResize flag on Fn+1. // FIXME-TABLE: Find a way to rewrite all of this so interactions feel more consistent for the user. // Scenarios: @@ -2097,10 +2357,13 @@ void ImGui::TableSetColumnWidth(int column_n, float width) // - W1 F2 F3 resize from W1| --> ok: equivalent to resizing |F2. F3 will not move. // - W1 F2 F3 resize from F2| --> ok // All resizes from a Wx columns are locking other columns. + // Possible improvements: // - W1 W2 W3 resize W1| --> to not be stuck, both W2 and W3 would stretch down. Seems possible to fix. Would be most beneficial to simplify resize of all-weighted columns. // - W3 F1 F2 resize W3| --> to not be stuck past F1|, both F1 and F2 would need to stretch down, which would be lossy or ambiguous. Seems hard to fix. + // [Resize Rule 1] Can't resize from right of right-most visible column if there is any Stretch column. Implemented in TableUpdateLayout(). + // If we have all Fixed columns OR resizing a Fixed column that doesn't come after a Stretch one, we can do an offsetting resize. // This is the preferred resize path if (column_0->Flags & ImGuiTableColumnFlags_WidthFixed) @@ -2110,11 +2373,13 @@ void ImGui::TableSetColumnWidth(int column_n, float width) table->IsSettingsDirty = true; return; } + // We can also use previous column if there's no next one (this is used when doing an auto-fit on the right-most stretch column) if (column_1 == NULL) column_1 = (column_0->PrevEnabledColumn != -1) ? &table->Columns[column_0->PrevEnabledColumn] : NULL; if (column_1 == NULL) return; + // Resizing from right-side of a Stretch column before a Fixed column forward sizing to left-side of fixed column. // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b) float column_1_width = ImMax(column_1->WidthRequest - (column_0_width - column_0->WidthRequest), min_width); @@ -2126,6 +2391,7 @@ void ImGui::TableSetColumnWidth(int column_n, float width) TableUpdateColumnsWeightFromWidth(table); table->IsSettingsDirty = true; } + // Disable clipping then auto-fit, will take 2 frames // (we don't take a shortcut for unclipped columns to reduce inconsistencies when e.g. resizing multiple columns) void ImGui::TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n) @@ -2137,6 +2403,7 @@ void ImGui::TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n) column->CannotSkipItemsQueue = (1 << 0); table->AutoFitSingleColumn = (ImGuiTableColumnIdx)column_n; } + void ImGui::TableSetColumnWidthAutoAll(ImGuiTable* table) { for (int column_n = 0; column_n < table->ColumnsCount; column_n++) @@ -2148,9 +2415,11 @@ void ImGui::TableSetColumnWidthAutoAll(ImGuiTable* table) column->AutoFitQueue = (1 << 1); } } + void ImGui::TableUpdateColumnsWeightFromWidth(ImGuiTable* table) { IM_ASSERT(table->LeftMostStretchedColumn != -1 && table->RightMostStretchedColumn != -1); + // Measure existing quantities float visible_weight = 0.0f; float visible_width = 0.0f; @@ -2164,6 +2433,7 @@ void ImGui::TableUpdateColumnsWeightFromWidth(ImGuiTable* table) visible_width += column->WidthRequest; } IM_ASSERT(visible_weight > 0.0f && visible_width > 0.0f); + // Apply new weights for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { @@ -2174,6 +2444,7 @@ void ImGui::TableUpdateColumnsWeightFromWidth(ImGuiTable* table) IM_ASSERT(column->StretchWeight > 0.0f); } } + //------------------------------------------------------------------------- // [SECTION] Tables: Drawing //------------------------------------------------------------------------- @@ -2184,6 +2455,7 @@ void ImGui::TableUpdateColumnsWeightFromWidth(ImGuiTable* table) // - TableGetColumnBorderCol() [Internal] // - TableDrawBorders() [Internal] //------------------------------------------------------------------------- + // Bg2 is used by Selectable (and possibly other widgets) to render to the background. // Unlike our Bg0/1 channel which we uses for RowBg/CellBg/Borders and where we guarantee all shapes to be CPU-clipped, the Bg2 channel being widgets-facing will rely on regular ClipRect. void ImGui::TablePushBackgroundChannel() @@ -2191,21 +2463,53 @@ void ImGui::TablePushBackgroundChannel() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiTable* table = g.CurrentTable; + // Optimization: avoid SetCurrentChannel() + PushClipRect() table->HostBackupInnerClipRect = window->ClipRect; SetWindowClipRectBeforeSetChannel(window, table->Bg2ClipRectForDrawCmd); table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Bg2DrawChannelCurrent); } + void ImGui::TablePopBackgroundChannel() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiTable* table = g.CurrentTable; - ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; + // Optimization: avoid PopClipRect() + SetCurrentChannel() SetWindowClipRectBeforeSetChannel(window, table->HostBackupInnerClipRect); + table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Columns[table->CurrentColumn].DrawChannelCurrent); +} + +// Also see TableBeginCell() +void ImGui::TablePushColumnChannel(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + + // Optimization: avoid SetCurrentChannel() + PushClipRect() + if (table->Flags & ImGuiTableFlags_NoClip) + return; + ImGuiWindow* window = g.CurrentWindow; + const ImGuiTableColumn* column = &table->Columns[column_n]; + SetWindowClipRectBeforeSetChannel(window, column->ClipRect); table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); } + +void ImGui::TablePopColumnChannel() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + + // Optimization: avoid PopClipRect() + SetCurrentChannel() + if ((table->Flags & ImGuiTableFlags_NoClip) || (table->CurrentColumn == -1)) // Calling TreePop() after TableNextRow() is supported. + return; + ImGuiWindow* window = g.CurrentWindow; + const ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; + SetWindowClipRectBeforeSetChannel(window, column->ClipRect); + table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); +} + // Allocate draw channels. Called by TableUpdateLayout() // - We allocate them following storage order instead of display order so reordering columns won't needlessly // increase overall dormant memory cost. @@ -2233,6 +2537,7 @@ void ImGui::TableSetupDrawChannels(ImGuiTable* table) table->DummyDrawChannel = (ImGuiTableDrawChannelIdx)((channels_for_dummy > 0) ? channels_total - 1 : -1); table->Bg2DrawChannelCurrent = TABLE_DRAW_CHANNEL_BG2_FROZEN; table->Bg2DrawChannelUnfrozen = (ImGuiTableDrawChannelIdx)((table->FreezeRowsCount > 0) ? 2 + channels_for_row : TABLE_DRAW_CHANNEL_BG2_FROZEN); + int draw_channel_current = 2; for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { @@ -2250,6 +2555,7 @@ void ImGui::TableSetupDrawChannels(ImGuiTable* table) } column->DrawChannelCurrent = column->DrawChannelFrozen; } + // Initial draw cmd starts with a BgClipRect that matches the one of its host, to facilitate merge draw commands by default. // All our cell highlight are manually clipped with BgClipRect. When unfreezing it will be made smaller to fit scrolling rect. // (This technically isn't part of setting up draw channels, but is reasonably related to be done here) @@ -2258,6 +2564,7 @@ void ImGui::TableSetupDrawChannels(ImGuiTable* table) table->Bg2ClipRectForDrawCmd = table->HostClipRect; IM_ASSERT(table->BgClipRect.Min.y <= table->BgClipRect.Max.y); } + // This function reorder draw channels based on matching clip rectangle, to facilitate merging them. Called by EndTable(). // For simplicity we call it TableMergeDrawChannels() but in fact it only reorder channels + overwrite ClipRect, // actual merging is done by table->DrawSplitter.Merge() which is called right after TableMergeDrawChannels(). @@ -2294,6 +2601,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) const bool has_freeze_v = (table->FreezeRowsCount > 0); const bool has_freeze_h = (table->FreezeColumnsCount > 0); IM_ASSERT(splitter->_Current == 0); + // Track which groups we are going to attempt to merge, and which channels goes into each group. struct MergeGroup { @@ -2303,6 +2611,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) }; int merge_group_mask = 0x00; MergeGroup merge_groups[4]; + // Use a reusable temp buffer for the merge masks as they are dynamically sized. const int max_draw_channels = (4 + table->ColumnsCount * 2); const int size_for_masks_bitarrays_one = (int)ImBitArrayGetStorageSizeInBytes(max_draw_channels); @@ -2311,22 +2620,26 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) for (int n = 0; n < IM_ARRAYSIZE(merge_groups); n++) merge_groups[n].ChannelsMask = (ImBitArrayPtr)(void*)(g.TempBuffer.Data + (size_for_masks_bitarrays_one * n)); ImBitArrayPtr remaining_mask = (ImBitArrayPtr)(void*)(g.TempBuffer.Data + (size_for_masks_bitarrays_one * 4)); + // 1. Scan channels and take note of those which can be merged for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { if (!IM_BITARRAY_TESTBIT(table->VisibleMaskByIndex, column_n)) continue; ImGuiTableColumn* column = &table->Columns[column_n]; + const int merge_group_sub_count = has_freeze_v ? 2 : 1; for (int merge_group_sub_n = 0; merge_group_sub_n < merge_group_sub_count; merge_group_sub_n++) { const int channel_no = (merge_group_sub_n == 0) ? column->DrawChannelFrozen : column->DrawChannelUnfrozen; + // Don't attempt to merge if there are multiple draw calls within the column ImDrawChannel* src_channel = &splitter->_Channels[channel_no]; if (src_channel->_CmdBuffer.Size > 0 && src_channel->_CmdBuffer.back().ElemCount == 0 && src_channel->_CmdBuffer.back().UserCallback == NULL) // Equivalent of PopUnusedDrawCmd() src_channel->_CmdBuffer.pop_back(); if (src_channel->_CmdBuffer.Size != 1) continue; + // Find out the width of this merge group and check if it will fit in our column // (note that we assume that rendering didn't stray on the left direction. we should need a CursorMinPos to detect it) if (!(column->Flags & ImGuiTableColumnFlags_NoClip)) @@ -2341,6 +2654,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) if (content_max_x > column->ClipRect.Max.x) continue; } + const int merge_group_n = (has_freeze_h && column_n < table->FreezeColumnsCount ? 0 : 1) + (has_freeze_v && merge_group_sub_n == 0 ? 0 : 2); IM_ASSERT(channel_no < max_draw_channels); MergeGroup* merge_group = &merge_groups[merge_group_n]; @@ -2351,10 +2665,12 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) merge_group->ClipRect.Add(src_channel->_CmdBuffer[0].ClipRect); merge_group_mask |= (1 << merge_group_n); } + // Invalidate current draw channel // (we don't clear DrawChannelFrozen/DrawChannelUnfrozen solely to facilitate debugging/later inspection of data) column->DrawChannelCurrent = (ImGuiTableDrawChannelIdx)-1; } + // [DEBUG] Display merge groups #if 0 if (g.IO.KeyShift) @@ -2372,6 +2688,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) GetForegroundDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 255, 0, 255)); } #endif + // 2. Rewrite channel list in our preferred order if (merge_group_mask != 0) { @@ -2391,6 +2708,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) { MergeGroup* merge_group = &merge_groups[merge_group_n]; ImRect merge_clip_rect = merge_group->ClipRect; + // Extend outer-most clip limits to match those of host, so draw calls can be merged even if // outer-most columns have some outer padding offsetting them from their parent ClipRect. // The principal cases this is dealing with are: @@ -2419,16 +2737,19 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) continue; IM_BITARRAY_CLEARBIT(merge_group->ChannelsMask, n); merge_channels_count--; + ImDrawChannel* channel = &splitter->_Channels[n]; IM_ASSERT(channel->_CmdBuffer.Size == 1 && merge_clip_rect.Contains(ImRect(channel->_CmdBuffer[0].ClipRect))); channel->_CmdBuffer[0].ClipRect = merge_clip_rect.ToVec4(); memcpy(dst_tmp++, channel, sizeof(ImDrawChannel)); } } + // Make sure Bg2DrawChannelUnfrozen appears in the middle of our groups (whereas Bg0/Bg1 and Bg2 frozen are fixed to 0 and 1) if (merge_group_n == 1 && has_freeze_v) memcpy(dst_tmp++, &splitter->_Channels[table->Bg2DrawChannelUnfrozen], sizeof(ImDrawChannel)); } + // Append unmergeable channels that we didn't reorder at the end of the list for (int n = 0; n < splitter->_Count && remaining_count != 0; n++) { @@ -2442,6 +2763,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) memcpy(splitter->_Channels.Data + LEADING_DRAW_CHANNELS, g.DrawChannelsTempMergeBuffer.Data, (splitter->_Count - LEADING_DRAW_CHANNELS) * sizeof(ImDrawChannel)); } } + static ImU32 TableGetColumnBorderCol(ImGuiTable* table, int order_n, int column_n) { const bool is_hovered = (table->HoveredColumnBorder == column_n); @@ -2453,15 +2775,18 @@ static ImU32 TableGetColumnBorderCol(ImGuiTable* table, int order_n, int column_ return table->BorderColorStrong; return table->BorderColorLight; } + // FIXME-TABLE: This is a mess, need to redesign how we render borders (as some are also done in TableEndRow) void ImGui::TableDrawBorders(ImGuiTable* table) { ImGuiWindow* inner_window = table->InnerWindow; if (!table->OuterWindow->ClipRect.Overlaps(table->OuterRect)) return; + ImDrawList* inner_drawlist = inner_window->DrawList; table->DrawSplitter->SetCurrentChannel(inner_drawlist, TABLE_DRAW_CHANNEL_BG0); inner_drawlist->PushClipRect(table->Bg0ClipRectForDrawCmd.Min, table->Bg0ClipRectForDrawCmd.Max, false); + // Draw inner border and resizing feedback ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); const float border_size = TABLE_BORDER_SIZE; @@ -2474,6 +2799,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) { if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n)) continue; + const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; const bool is_hovered = (table->HoveredColumnBorder == column_n); @@ -2482,12 +2808,14 @@ void ImGui::TableDrawBorders(ImGuiTable* table) const bool is_frozen_separator = (table->FreezeColumnsCount == order_n + 1); if (column->MaxX > table->InnerClipRect.Max.x && !is_resized) continue; + // Decide whether right-most column is visible if (column->NextEnabledColumn == -1 && !is_resizable) if ((table->Flags & ImGuiTableFlags_SizingMask_) != ImGuiTableFlags_SizingFixedSame || (table->Flags & ImGuiTableFlags_NoHostExtendX)) continue; if (column->MaxX <= column->ClipRect.Min.x) // FIXME-TABLE FIXME-STYLE: Assume BorderSize==1, this is problematic if we want to increase the border size.. continue; + // Draw in outer window so right-most column won't be clipped // Always draw full height border when being resized/hovered, or on the delimitation of frozen column scrolling. float draw_y2 = (is_hovered || is_resized || is_frozen_separator || (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) == 0) ? draw_y2_body : draw_y2_head; @@ -2495,6 +2823,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), TableGetColumnBorderCol(table, order_n, column_n), border_size); } } + // Draw outer border // FIXME: could use AddRect or explicit VLine/HLine helper? if (table->Flags & ImGuiTableFlags_BordersOuter) @@ -2528,8 +2857,10 @@ void ImGui::TableDrawBorders(ImGuiTable* table) if (border_y >= table->BgClipRect.Min.y && border_y < table->BgClipRect.Max.y) inner_drawlist->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), table->BorderColorLight, border_size); } + inner_drawlist->PopClipRect(); } + //------------------------------------------------------------------------- // [SECTION] Tables: Sorting //------------------------------------------------------------------------- @@ -2540,6 +2871,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) // - TableSortSpecsSanitize() [Internal] // - TableSortSpecsBuild() [Internal] //------------------------------------------------------------------------- + // Return NULL if no sort specs (most often when ImGuiTableFlags_Sortable is not set) // When 'sort_specs->SpecsDirty == true' you should sort your data. It will be true when sorting specs have // changed since last call, or the first time. Make sure to set 'SpecsDirty = false' after sorting, @@ -2549,20 +2881,23 @@ ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL); - if (!(table->Flags & ImGuiTableFlags_Sortable)) + if (table == NULL || !(table->Flags & ImGuiTableFlags_Sortable)) return NULL; + // Require layout (in case TableHeadersRow() hasn't been called) as it may alter IsSortSpecsDirty in some paths. if (!table->IsLayoutLocked) TableUpdateLayout(table); + TableSortSpecsBuild(table); return &table->SortSpecs; } + static inline ImGuiSortDirection TableGetColumnAvailSortDirection(ImGuiTableColumn* column, int n) { IM_ASSERT(n < column->SortDirectionsAvailCount); return (ImGuiSortDirection)((column->SortDirectionsAvailList >> (n << 1)) & 0x03); } + // Fix sort direction if currently set on a value which is unavailable (e.g. activating NoSortAscending/NoSortDescending) void ImGui::TableFixColumnSortDirection(ImGuiTable* table, ImGuiTableColumn* column) { @@ -2571,6 +2906,7 @@ void ImGui::TableFixColumnSortDirection(ImGuiTable* table, ImGuiTableColumn* col column->SortDirection = (ImU8)TableGetColumnAvailSortDirection(column, 0); table->IsSortSpecsDirty = true; } + // Calculate next sort direction that would be set after clicking the column // - If the PreferSortDescending flag is set, we will default to a Descending direction on the first click. // - Note that the PreferSortAscending flag is never checked, it is essentially the default and therefore a no-op. @@ -2586,26 +2922,31 @@ ImGuiSortDirection ImGui::TableGetColumnNextSortDirection(ImGuiTableColumn* colu IM_ASSERT(0); return ImGuiSortDirection_None; } + // Note that the NoSortAscending/NoSortDescending flags are processed in TableSortSpecsSanitize(), and they may change/revert // the value of SortDirection. We could technically also do it here but it would be unnecessary and duplicate code. void ImGui::TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; + if (!(table->Flags & ImGuiTableFlags_SortMulti)) append_to_sort_specs = false; if (!(table->Flags & ImGuiTableFlags_SortTristate)) IM_ASSERT(sort_direction != ImGuiSortDirection_None); + ImGuiTableColumnIdx sort_order_max = 0; if (append_to_sort_specs) for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) sort_order_max = ImMax(sort_order_max, table->Columns[other_column_n].SortOrder); + ImGuiTableColumn* column = &table->Columns[column_n]; column->SortDirection = (ImU8)sort_direction; if (column->SortDirection == ImGuiSortDirection_None) column->SortOrder = -1; else if (column->SortOrder == -1 || !append_to_sort_specs) column->SortOrder = append_to_sort_specs ? sort_order_max + 1 : 0; + for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) { ImGuiTableColumn* other_column = &table->Columns[other_column_n]; @@ -2616,9 +2957,11 @@ void ImGui::TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_di table->IsSettingsDirty = true; table->IsSortSpecsDirty = true; } + void ImGui::TableSortSpecsSanitize(ImGuiTable* table) { IM_ASSERT(table->Flags & ImGuiTableFlags_Sortable); + // Clear SortOrder from hidden column and verify that there's no gap or duplicate. int sort_order_count = 0; ImU64 sort_order_mask = 0x00; @@ -2633,6 +2976,7 @@ void ImGui::TableSortSpecsSanitize(ImGuiTable* table) sort_order_mask |= ((ImU64)1 << column->SortOrder); IM_ASSERT(sort_order_count < (int)sizeof(sort_order_mask) * 8); } + const bool need_fix_linearize = ((ImU64)1 << sort_order_count) != (sort_order_mask + 1); const bool need_fix_single_sort_order = (sort_order_count > 1) && !(table->Flags & ImGuiTableFlags_SortMulti); if (need_fix_linearize || need_fix_single_sort_order) @@ -2650,6 +2994,7 @@ void ImGui::TableSortSpecsSanitize(ImGuiTable* table) IM_ASSERT(column_with_smallest_sort_order != -1); fixed_mask |= ((ImU64)1 << column_with_smallest_sort_order); table->Columns[column_with_smallest_sort_order].SortOrder = (ImGuiTableColumnIdx)sort_n; + // Fix: Make sure only one column has a SortOrder if ImGuiTableFlags_MultiSortable is not set. if (need_fix_single_sort_order) { @@ -2661,6 +3006,7 @@ void ImGui::TableSortSpecsSanitize(ImGuiTable* table) } } } + // Fallback default sort order (if no column with the ImGuiTableColumnFlags_DefaultSort flag) if (sort_order_count == 0 && !(table->Flags & ImGuiTableFlags_SortTristate)) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) @@ -2674,8 +3020,10 @@ void ImGui::TableSortSpecsSanitize(ImGuiTable* table) break; } } + table->SortSpecsCount = (ImGuiTableColumnIdx)sort_order_count; } + void ImGui::TableSortSpecsBuild(ImGuiTable* table) { bool dirty = table->IsSortSpecsDirty; @@ -2686,6 +3034,7 @@ void ImGui::TableSortSpecsBuild(ImGuiTable* table) table->SortSpecs.SpecsDirty = true; // Mark as dirty for user table->IsSortSpecsDirty = false; // Mark as not dirty for us } + // Write output // May be able to move all SortSpecs data from table (48 bytes) to ImGuiTableTempData if we decide to write it back on every BeginTable() ImGuiTableColumnSortSpecs* sort_specs = (table->SortSpecsCount == 0) ? NULL : (table->SortSpecsCount == 1) ? &table->SortSpecsSingle : table->SortSpecsMulti.Data; @@ -2702,9 +3051,11 @@ void ImGui::TableSortSpecsBuild(ImGuiTable* table) sort_spec->SortOrder = (ImGuiTableColumnIdx)column->SortOrder; sort_spec->SortDirection = (ImGuiSortDirection)column->SortDirection; } + table->SortSpecs.Specs = sort_specs; table->SortSpecs.SpecsCount = table->SortSpecsCount; } + //------------------------------------------------------------------------- // [SECTION] Tables: Headers //------------------------------------------------------------------------- @@ -2715,6 +3066,7 @@ void ImGui::TableSortSpecsBuild(ImGuiTable* table) // - TableAngledHeadersRow() // - TableAngledHeadersRowEx() [Internal] //------------------------------------------------------------------------- + float ImGui::TableGetHeaderRowHeight() { // Caring for a minor edge case: @@ -2730,6 +3082,7 @@ float ImGui::TableGetHeaderRowHeight() row_height = ImMax(row_height, CalcTextSize(TableGetColumnName(table, column_n)).y); return row_height + g.Style.CellPadding.y * 2.0f; } + float ImGui::TableGetHeaderAngledMaxLabelWidth() { ImGuiContext& g = *GImGui; @@ -2741,6 +3094,7 @@ float ImGui::TableGetHeaderAngledMaxLabelWidth() width = ImMax(width, CalcTextSize(TableGetColumnName(table, column_n), NULL, true).x); return width + g.Style.CellPadding.y * 2.0f; // Swap padding } + // [Public] This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn(). // The intent is that advanced users willing to create customized headers would not need to use this helper // and can create their own! For example: TableHeader() may be preceded by Checkbox() or other custom widgets. @@ -2757,33 +3111,39 @@ void ImGui::TableHeadersRow() IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); return; } + // Call layout if not already done. This is automatically done by TableNextRow: we do it here _only_ to make // it easier to debug-step in TableUpdateLayout(). Your own version of this function doesn't need this. if (!table->IsLayoutLocked) TableUpdateLayout(table); + // Open row const float row_height = TableGetHeaderRowHeight(); TableNextRow(ImGuiTableRowFlags_Headers, row_height); const float row_y1 = GetCursorScreenPos().y; if (table->HostSkipItems) // Merely an optimization, you may skip in your own code. return; + const int columns_count = TableGetColumnCount(); for (int column_n = 0; column_n < columns_count; column_n++) { if (!TableSetColumnIndex(column_n)) continue; + // Push an id to allow empty/unnamed headers. This is also idiomatic as it ensure there is a consistent ID path to access columns (for e.g. automation) const char* name = (TableGetColumnFlags(column_n) & ImGuiTableColumnFlags_NoHeaderLabel) ? "" : TableGetColumnName(column_n); PushID(column_n); TableHeader(name); PopID(); } + // Allow opening popup from the right-most section after the last column. ImVec2 mouse_pos = ImGui::GetMousePos(); if (IsMouseReleased(1) && TableGetHoveredColumn() == columns_count) if (mouse_pos.y >= row_y1 && mouse_pos.y < row_y1 + row_height) TableOpenContextMenu(columns_count); // Will open a non-column-specific popup. } + // Emit a column header (text + optional sort order) // We cpu-clip text here so that all columns headers can be merged into a same draw call. // Note that because of how we cpu-clip and display sorting indicators, you _cannot_ use SameLine() after a TableHeader() @@ -2793,25 +3153,30 @@ void ImGui::TableHeader(const char* label) ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return; + ImGuiTable* table = g.CurrentTable; if (table == NULL) { IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); return; } + IM_ASSERT(table->CurrentColumn != -1); const int column_n = table->CurrentColumn; ImGuiTableColumn* column = &table->Columns[column_n]; + // Label if (label == NULL) label = ""; const char* label_end = FindRenderedTextEnd(label); ImVec2 label_size = CalcTextSize(label, label_end, true); ImVec2 label_pos = window->DC.CursorPos; + // If we already got a row height, there's use that. // FIXME-TABLE: Padding problem if the correct outer-padding CellBgRect strays off our ClipRect? ImRect cell_r = TableGetCellBgRect(table, column_n); float label_height = ImMax(label_size.y, table->RowMinHeight - table->RowCellPaddingY * 2.0f); + // Calculate ideal size for sort order arrow float w_arrow = 0.0f; float w_sort_text = 0.0f; @@ -2829,18 +3194,22 @@ void ImGui::TableHeader(const char* label) w_sort_text = g.Style.ItemInnerSpacing.x + CalcTextSize(sort_order_suf).x; } } + // We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considered for merging. float max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow; column->ContentMaxXHeadersUsed = ImMax(column->ContentMaxXHeadersUsed, sort_arrow ? cell_r.Max.x : ImMin(max_pos_x, cell_r.Max.x)); column->ContentMaxXHeadersIdeal = ImMax(column->ContentMaxXHeadersIdeal, max_pos_x); + // Keep header highlighted when context menu is open. ImGuiID id = window->GetID(label); ImRect bb(cell_r.Min.x, cell_r.Min.y, cell_r.Max.x, ImMax(cell_r.Max.y, cell_r.Min.y + label_height + g.Style.CellPadding.y * 2.0f)); ItemSize(ImVec2(0.0f, label_height)); // Don't declare unclipped width, it'll be fed ContentMaxPosHeadersIdeal if (!ItemAdd(bb, id)) return; + //GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG] //GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG] + // Using AllowOverlap mode because we cover the whole cell, and we want user to be able to submit subsequent items. const bool highlight = (table->HighlightColumnHeader == column_n); bool hovered, held; @@ -2861,6 +3230,7 @@ void ImGui::TableHeader(const char* label) if (held) table->HeldHeaderColumn = (ImGuiTableColumnIdx)column_n; window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f; + // Drag and drop to re-order columns. // FIXME-TABLE: Scroll request while reordering a column and it lands out of the scrolling zone. if (held && (table->Flags & ImGuiTableFlags_Reorderable) && IsMouseDragging(0) && !g.DragDropActive) @@ -2868,6 +3238,7 @@ void ImGui::TableHeader(const char* label) // While moving a column it will jump on the other side of the mouse, so we also test for MouseDelta.x table->ReorderColumn = (ImGuiTableColumnIdx)column_n; table->InstanceInteracted = table->InstanceCurrent; + // We don't reorder: through the frozen<>unfrozen line, or through a column that is marked with ImGuiTableColumnFlags_NoReorder. if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < cell_r.Min.x) if (ImGuiTableColumn* prev_column = (column->PrevEnabledColumn != -1) ? &table->Columns[column->PrevEnabledColumn] : NULL) @@ -2880,6 +3251,7 @@ void ImGui::TableHeader(const char* label) if ((column->IndexWithinEnabledSet < table->FreezeColumnsRequest) == (next_column->IndexWithinEnabledSet < table->FreezeColumnsRequest)) table->ReorderColumnDir = +1; } + // Sort order arrow const float ellipsis_max = ImMax(cell_r.Max.x - w_arrow - w_sort_text, label_pos.x); if ((table->Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort)) @@ -2897,6 +3269,7 @@ void ImGui::TableHeader(const char* label) } RenderArrow(window->DrawList, ImVec2(x, y), GetColorU32(ImGuiCol_Text), column->SortDirection == ImGuiSortDirection_Ascending ? ImGuiDir_Up : ImGuiDir_Down, ARROW_SCALE); } + // Handle clicking on column header to adjust Sort Order if (pressed && table->ReorderColumn != column_n) { @@ -2904,17 +3277,21 @@ void ImGui::TableHeader(const char* label) TableSetColumnSortDirection(column_n, sort_direction, g.IO.KeyShift); } } + // Render clipped label. Clipping here ensure that in the majority of situations, all our header cells will // be merged into a single draw call. //window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE); - RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, label_pos.y + label_height + g.Style.FramePadding.y), ellipsis_max, ellipsis_max, label, label_end, &label_size); + RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, bb.Max.y), ellipsis_max, label, label_end, &label_size); + const bool text_clipped = label_size.x > (ellipsis_max - label_pos.x); if (text_clipped && hovered && g.ActiveId == 0) SetItemTooltip("%.*s", (int)(label_end - label), label); + // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden if (IsMouseReleased(1) && IsItemHovered()) TableOpenContextMenu(column_n); } + // Unlike TableHeadersRow() it is not expected that you can reimplement or customize this with custom widgets. // FIXME: No hit-testing/button on the angled header. void ImGui::TableAngledHeadersRow() @@ -2924,6 +3301,7 @@ void ImGui::TableAngledHeadersRow() ImGuiTableTempData* temp_data = table->TempData; temp_data->AngledHeadersRequests.resize(0); temp_data->AngledHeadersRequests.reserve(table->ColumnsEnabledCount); + // Which column needs highlight? const ImGuiID row_id = GetID("##AngledHeaders"); ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); @@ -2931,6 +3309,7 @@ void ImGui::TableAngledHeadersRow() if (highlight_column_n == -1 && table->HoveredColumnBody != -1) if (table_instance->HoveredRowLast == 0 && table->HoveredColumnBorder == -1 && (g.ActiveId == 0 || g.ActiveId == row_id || (table->IsActiveIdInTable || g.DragDropActive))) highlight_column_n = table->HoveredColumnBody; + // Build up request ImU32 col_header_bg = GetColorU32(ImGuiCol_TableHeaderBg); ImU32 col_text = GetColorU32(ImGuiCol_Text); @@ -2944,9 +3323,11 @@ void ImGui::TableAngledHeadersRow() ImGuiTableHeaderData request = { (ImGuiTableColumnIdx)column_n, col_text, col_header_bg, (column_n == highlight_column_n) ? GetColorU32(ImGuiCol_Header) : 0 }; temp_data->AngledHeadersRequests.push_back(request); } + // Render row TableAngledHeadersRowEx(row_id, g.Style.TableAngledHeadersAngle, 0.0f, temp_data->AngledHeadersRequests.Data, temp_data->AngledHeadersRequests.Size); } + // Important: data must be fed left to right void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label_width, const ImGuiTableHeaderData* data, int data_count) { @@ -2960,8 +3341,10 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label return; } IM_ASSERT(table->CurrentRow == -1 && "Must be first row"); + if (max_label_width == 0.0f) max_label_width = TableGetHeaderAngledMaxLabelWidth(); + // Angle argument expressed in (-IM_PI/2 .. +IM_PI/2) as it is easier to think about for user. const bool flip_label = (angle < 0.0f); angle -= IM_PI * 0.5f; @@ -2970,6 +3353,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label const float label_cos_a = flip_label ? ImCos(angle + IM_PI) : cos_a; const float label_sin_a = flip_label ? ImSin(angle + IM_PI) : sin_a; const ImVec2 unit_right = ImVec2(cos_a, sin_a); + // Calculate our base metrics and set angled headers data _before_ the first call to TableNextRow() // FIXME-STYLE: Would it be better for user to submit 'max_label_width' or 'row_height' ? One can be derived from the other. const float header_height = g.FontSize + g.Style.CellPadding.x * 2.0f; @@ -2977,6 +3361,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label table->AngledHeadersHeight = row_height; table->AngledHeadersSlope = (sin_a != 0.0f) ? (cos_a / sin_a) : 0.0f; const ImVec2 header_angled_vector = unit_right * (row_height / -sin_a); // vector from bottom-left to top-left, and from bottom-right to top-right + // Declare row, override and draw our own background TableNextRow(ImGuiTableRowFlags_Headers, row_height); TableNextColumn(); @@ -2989,12 +3374,15 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label PushClipRect(table->BgClipRect.Min, table->BgClipRect.Max, false); // Span all columns draw_list->AddRectFilled(ImVec2(table->BgClipRect.Min.x, row_r.Min.y), ImVec2(table->BgClipRect.Max.x, row_r.Max.y), GetColorU32(ImGuiCol_TableHeaderBg, 0.25f)); // FIXME-STYLE: Change row background with an arbitrary color. PushClipRect(ImVec2(clip_rect_min_x, table->BgClipRect.Min.y), table->BgClipRect.Max, true); // Span all columns + ButtonBehavior(row_r, row_id, NULL, NULL); KeepAliveID(row_id); - const float ascent_scaled = g.Font->Ascent * g.FontScale; // FIXME: Standardize those scaling factors better + + const float ascent_scaled = g.FontBaked->Ascent * g.FontBakedScale; // FIXME: Standardize those scaling factors better const float line_off_for_ascent_x = (ImMax((g.FontSize - ascent_scaled) * 0.5f, 0.0f) / -sin_a) * (flip_label ? -1.0f : 1.0f); const ImVec2 padding = g.Style.CellPadding; // We will always use swapped component const ImVec2 align = g.Style.TableAngledHeadersTextAlign; + // Draw background and labels in first pass, then all borders. float max_x = -FLT_MAX; for (int pass = 0; pass < 2; pass++) @@ -3003,6 +3391,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label const ImGuiTableHeaderData* request = &data[order_n]; const int column_n = request->Index; ImGuiTableColumn* column = &table->Columns[column_n]; + ImVec2 bg_shape[4]; bg_shape[0] = ImVec2(column->MaxX, row_r.Max.y); bg_shape[1] = ImVec2(column->MinX, row_r.Max.y); @@ -3014,6 +3403,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label draw_list->AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], request->BgColor0); draw_list->AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], request->BgColor1); // Optional highlight max_x = ImMax(max_x, bg_shape[3].x); + // Draw label // - First draw at an offset where RenderTextXXX() function won't meddle with applying current ClipRect, then transform to final offset. // - Handle multiple lines manually, as we want each lines to follow on the horizontal border, rather than see a whole block rotated. @@ -3021,17 +3411,21 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label const char* label_name_end = FindRenderedTextEnd(label_name); const float line_off_step_x = (g.FontSize / -sin_a); const int label_lines = ImTextCountLines(label_name, label_name_end); + // Left<>Right alignment float line_off_curr_x = flip_label ? (label_lines - 1) * line_off_step_x : 0.0f; float line_off_for_align_x = ImMax((((column->MaxX - column->MinX) - padding.x * 2.0f) - (label_lines * line_off_step_x)), 0.0f) * align.x; line_off_curr_x += line_off_for_align_x - line_off_for_ascent_x; + // Register header width column->ContentMaxXHeadersUsed = column->ContentMaxXHeadersIdeal = column->WorkMinX + ImCeil(label_lines * line_off_step_x - line_off_for_align_x); + while (label_name < label_name_end) { const char* label_name_eol = strchr(label_name, '\n'); if (label_name_eol == NULL) label_name_eol = label_name_end; + // FIXME: Individual line clipping for right-most column is broken for negative angles. ImVec2 label_size = CalcTextSize(label_name, label_name_eol); float clip_width = max_label_width - padding.y; // Using padding.y*2.0f would be symmetrical but hide more text. @@ -3039,12 +3433,14 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label ImRect clip_r(window->ClipRect.Min, window->ClipRect.Min + ImVec2(clip_width, clip_height)); int vtx_idx_begin = draw_list->_VtxCurrentIdx; PushStyleColor(ImGuiCol_Text, request->TextColor); - RenderTextEllipsis(draw_list, clip_r.Min, clip_r.Max, clip_r.Max.x, clip_r.Max.x, label_name, label_name_eol, &label_size); + RenderTextEllipsis(draw_list, clip_r.Min, clip_r.Max, clip_r.Max.x, label_name, label_name_eol, &label_size); PopStyleColor(); int vtx_idx_end = draw_list->_VtxCurrentIdx; + // Up<>Down alignment const float available_space = ImMax(clip_width - label_size.x + ImAbs(padding.x * cos_a) * 2.0f - ImAbs(padding.y * sin_a) * 2.0f, 0.0f); const float vertical_offset = available_space * align.y * (flip_label ? -1.0f : 1.0f); + // Rotate and offset label ImVec2 pivot_in = ImVec2(window->ClipRect.Min.x - vertical_offset, window->ClipRect.Min.y + label_size.y); ImVec2 pivot_out = ImVec2(column->WorkMinX, row_r.Max.y); @@ -3055,6 +3451,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label pivot_out.x += flip_label ? line_off_curr_x + line_off_step_x : line_off_curr_x; ShadeVertsTransformPos(draw_list, vtx_idx_begin, vtx_idx_end, pivot_in, label_cos_a, label_sin_a, pivot_out); // Rotate and offset //if (g.IO.KeyShift) { ImDrawList* fg_dl = GetForegroundDrawList(); vtx_idx_begin = fg_dl->_VtxCurrentIdx; fg_dl->AddRect(clip_r.Min, clip_r.Max, IM_COL32(0, 255, 0, 255), 0.0f, 0, 1.0f); ShadeVertsTransformPos(fg_dl, vtx_idx_begin, fg_dl->_VtxCurrentIdx, pivot_in, label_cos_a, label_sin_a, pivot_out); } + label_name = label_name_eol + 1; } } @@ -3068,6 +3465,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label PopClipRect(); table->TempData->AngledHeadersExtraWidth = ImMax(0.0f, max_x - table->Columns[table->RightMostEnabledColumn].MaxX); } + //------------------------------------------------------------------------- // [SECTION] Tables: Context Menu //------------------------------------------------------------------------- @@ -3075,6 +3473,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label // - TableBeginContextMenuPopup() [Internal] // - TableDrawDefaultContextMenu() [Internal] //------------------------------------------------------------------------- + // Use -1 to open menu not specific to a given column. void ImGui::TableOpenContextMenu(int column_n) { @@ -3094,6 +3493,7 @@ void ImGui::TableOpenContextMenu(int column_n) OpenPopupEx(context_menu_id, ImGuiPopupFlags_None); } } + bool ImGui::TableBeginContextMenuPopup(ImGuiTable* table) { if (!table->IsContextPopupOpen || table->InstanceCurrent != table->InstanceInteracted) @@ -3104,6 +3504,7 @@ bool ImGui::TableBeginContextMenuPopup(ImGuiTable* table) table->IsContextPopupOpen = false; return false; } + // Output context menu into current window (generally a popup) // FIXME-TABLE: Ideally this should be writable by the user. Full programmatic access to that data? // Sections to display are pulled from 'flags_for_section_to_display', which is typically == table->Flags. @@ -3118,9 +3519,11 @@ void ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return; + bool want_separator = false; const int column_n = (table->ContextPopupColumn >= 0 && table->ContextPopupColumn < table->ColumnsCount) ? table->ContextPopupColumn : -1; ImGuiTableColumn* column = (column_n != -1) ? &table->Columns[column_n] : NULL; + // Sizing if (flags_for_section_to_display & ImGuiTableFlags_Resizable) { @@ -3130,6 +3533,7 @@ void ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags if (MenuItem(LocalizeGetMsg(ImGuiLocKey_TableSizeOne), NULL, false, can_resize)) // "###SizeOne" TableSetColumnWidthAutoSingle(table, column_n); } + const char* size_all_desc; if (table->ColumnsEnabledFixedCount == table->ColumnsEnabledCount && (table->Flags & ImGuiTableFlags_SizingMask_) != ImGuiTableFlags_SizingFixedSame) size_all_desc = LocalizeGetMsg(ImGuiLocKey_TableSizeAllFit); // "###SizeAll" All fixed @@ -3139,6 +3543,7 @@ void ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags TableSetColumnWidthAutoAll(table); want_separator = true; } + // Ordering if (flags_for_section_to_display & ImGuiTableFlags_Reorderable) { @@ -3146,9 +3551,11 @@ void ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags table->IsResetDisplayOrderRequest = true; want_separator = true; } + // Reset all (should work but seems unnecessary/noisy to expose?) //if (MenuItem("Reset all")) // table->IsResetAllRequest = true; + // Sorting // (modify TableOpenContextMenu() to add _Sortable flag if enabling this) #if 0 @@ -3157,6 +3564,7 @@ void ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags if (want_separator) Separator(); want_separator = true; + bool append_to_sort_specs = g.IO.KeyShift; if (MenuItem("Sort in Ascending Order", NULL, column->SortOrder != -1 && column->SortDirection == ImGuiSortDirection_Ascending, (column->Flags & ImGuiTableColumnFlags_NoSortAscending) == 0)) TableSetColumnSortDirection(table, column_n, ImGuiSortDirection_Ascending, append_to_sort_specs); @@ -3164,21 +3572,25 @@ void ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags TableSetColumnSortDirection(table, column_n, ImGuiSortDirection_Descending, append_to_sort_specs); } #endif + // Hiding / Visibility if (flags_for_section_to_display & ImGuiTableFlags_Hideable) { if (want_separator) Separator(); want_separator = true; + PushItemFlag(ImGuiItemFlags_AutoClosePopups, false); for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) { ImGuiTableColumn* other_column = &table->Columns[other_column_n]; if (other_column->Flags & ImGuiTableColumnFlags_Disabled) continue; + const char* name = TableGetColumnName(table, other_column_n); if (name == NULL || name[0] == 0) name = ""; + // Make sure we can't hide the last active column bool menu_item_active = (other_column->Flags & ImGuiTableColumnFlags_NoHide) ? false : true; if (other_column->IsUserEnabled && table->ColumnsEnabledCount <= 1) @@ -3189,6 +3601,7 @@ void ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags PopItemFlag(); } } + //------------------------------------------------------------------------- // [SECTION] Tables: Settings (.ini data) //------------------------------------------------------------------------- @@ -3214,6 +3627,7 @@ void ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags // [Main] 3: TableSaveSettings() When table properties are modified, serialize Table data into bound or new TableSettings, mark .ini as dirty. // [Main] 4: TableSettingsHandler_WriteAll() When .ini file is dirty (which can come from other source), save TableSettings into .ini file. //------------------------------------------------------------------------- + // Clear and initialize empty settings instance static void TableSettingsInit(ImGuiTableSettings* settings, ImGuiID id, int columns_count, int columns_count_max) { @@ -3226,10 +3640,12 @@ static void TableSettingsInit(ImGuiTableSettings* settings, ImGuiID id, int colu settings->ColumnsCountMax = (ImGuiTableColumnIdx)columns_count_max; settings->WantApply = true; } + static size_t TableSettingsCalcChunkSize(int columns_count) { return sizeof(ImGuiTableSettings) + (size_t)columns_count * sizeof(ImGuiTableColumnSettings); } + ImGuiTableSettings* ImGui::TableSettingsCreate(ImGuiID id, int columns_count) { ImGuiContext& g = *GImGui; @@ -3237,6 +3653,7 @@ ImGuiTableSettings* ImGui::TableSettingsCreate(ImGuiID id, int columns_count) TableSettingsInit(settings, id, columns_count, columns_count); return settings; } + // Find existing settings ImGuiTableSettings* ImGui::TableSettingsFindByID(ImGuiID id) { @@ -3247,6 +3664,7 @@ ImGuiTableSettings* ImGui::TableSettingsFindByID(ImGuiID id) return settings; return NULL; } + // Get settings for a given table, NULL if none ImGuiTableSettings* ImGui::TableGetBoundSettings(ImGuiTable* table) { @@ -3261,6 +3679,7 @@ ImGuiTableSettings* ImGui::TableGetBoundSettings(ImGuiTable* table) } return NULL; } + // Restore initial state of table (with or without saved settings) void ImGui::TableResetSettings(ImGuiTable* table) { @@ -3269,11 +3688,13 @@ void ImGui::TableResetSettings(ImGuiTable* table) table->IsSettingsRequestLoad = false; // Don't reload from ini table->SettingsLoadedFlags = ImGuiTableFlags_None; // Mark as nothing loaded so our initialized data becomes authoritative } + void ImGui::TableSaveSettings(ImGuiTable* table) { table->IsSettingsDirty = false; if (table->Flags & ImGuiTableFlags_NoSavedSettings) return; + // Bind or create settings data ImGuiContext& g = *GImGui; ImGuiTableSettings* settings = TableGetBoundSettings(table); @@ -3283,11 +3704,13 @@ void ImGui::TableSaveSettings(ImGuiTable* table) table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings); } settings->ColumnsCount = (ImGuiTableColumnIdx)table->ColumnsCount; + // Serialize ImGuiTable/ImGuiTableColumn into ImGuiTableSettings/ImGuiTableColumnSettings IM_ASSERT(settings->ID == table->ID); IM_ASSERT(settings->ColumnsCount == table->ColumnsCount && settings->ColumnsCountMax >= settings->ColumnsCount); ImGuiTableColumn* column = table->Columns.Data; ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); + bool save_ref_scale = false; settings->SaveFlags = ImGuiTableFlags_None; for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++) @@ -3302,6 +3725,7 @@ void ImGui::TableSaveSettings(ImGuiTable* table) column_settings->IsStretch = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? 1 : 0; if ((column->Flags & ImGuiTableColumnFlags_WidthStretch) == 0) save_ref_scale = true; + // We skip saving some data in the .ini file when they are unnecessary to restore our state. // Note that fixed width where initial width was derived from auto-fit will always be saved as InitStretchWeightOrWidth will be 0.0f. // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet so it's always saved when present. @@ -3316,14 +3740,17 @@ void ImGui::TableSaveSettings(ImGuiTable* table) } settings->SaveFlags &= table->Flags; settings->RefScale = save_ref_scale ? table->RefScale : 0.0f; + MarkIniSettingsDirty(); } + void ImGui::TableLoadSettings(ImGuiTable* table) { ImGuiContext& g = *GImGui; table->IsSettingsRequestLoad = false; if (table->Flags & ImGuiTableFlags_NoSavedSettings) return; + // Bind settings ImGuiTableSettings* settings; if (table->SettingsOffset == -1) @@ -3339,8 +3766,10 @@ void ImGui::TableLoadSettings(ImGuiTable* table) { settings = TableGetBoundSettings(table); } + table->SettingsLoadedFlags = settings->SaveFlags; table->RefScale = settings->RefScale; + // Initialize default columns settings for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { @@ -3348,6 +3777,7 @@ void ImGui::TableLoadSettings(ImGuiTable* table) TableInitColumnDefaults(table, column, ~0); column->AutoFitQueue = 0x00; } + // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); ImU64 display_order_mask = 0; @@ -3356,6 +3786,7 @@ void ImGui::TableLoadSettings(ImGuiTable* table) int column_n = column_settings->Index; if (column_n < 0 || column_n >= table->ColumnsCount) continue; + ImGuiTableColumn* column = &table->Columns[column_n]; if (settings->SaveFlags & ImGuiTableFlags_Resizable) { @@ -3372,15 +3803,18 @@ void ImGui::TableLoadSettings(ImGuiTable* table) column->SortOrder = column_settings->SortOrder; column->SortDirection = column_settings->SortDirection; } + // Validate and fix invalid display order data const ImU64 expected_display_order_mask = (settings->ColumnsCount == 64) ? ~0 : ((ImU64)1 << settings->ColumnsCount) - 1; if (display_order_mask != expected_display_order_mask) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) table->Columns[column_n].DisplayOrder = (ImGuiTableColumnIdx)column_n; + // Rebuild index for (int column_n = 0; column_n < table->ColumnsCount; column_n++) table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n; } + static void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*) { ImGuiContext& g = *ctx; @@ -3389,6 +3823,7 @@ static void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandle table->SettingsOffset = -1; g.SettingsTables.clear(); } + // Apply to existing windows (if any) static void TableSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*) { @@ -3400,12 +3835,14 @@ static void TableSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandle table->SettingsOffset = -1; } } + static void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) { ImGuiID id = 0; int columns_count = 0; if (sscanf(name, "0x%08X,%d", &id, &columns_count) < 2) return NULL; + if (ImGuiTableSettings* settings = ImGui::TableSettingsFindByID(id)) { if (settings->ColumnsCountMax >= columns_count) @@ -3417,13 +3854,16 @@ static void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, } return ImGui::TableSettingsCreate(id, columns_count); } + static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line) { // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" ImGuiTableSettings* settings = (ImGuiTableSettings*)entry; float f = 0.0f; int column_n = 0, r = 0, n = 0; + if (sscanf(line, "RefScale=%f", &f) == 1) { settings->RefScale = f; return; } + if (sscanf(line, "Column %d%n", &column_n, &r) == 1) { if (column_n < 0 || column_n >= settings->ColumnsCount) @@ -3440,6 +3880,7 @@ static void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, if (sscanf(line, "Sort=%d%c%n", &n, &c, &r) == 2) { line = ImStrSkipBlank(line + r); column->SortOrder = (ImGuiTableColumnIdx)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; } } } + static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) { ImGuiContext& g = *ctx; @@ -3447,6 +3888,7 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle { if (settings->ID == 0) // Skip ditched settings continue; + // TableSaveSettings() may clear some of those flags when we establish that the data can be stripped // (e.g. Order was unchanged) const bool save_size = (settings->SaveFlags & ImGuiTableFlags_Resizable) != 0; @@ -3454,6 +3896,7 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle const bool save_order = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0; const bool save_sort = (settings->SaveFlags & ImGuiTableFlags_Sortable) != 0; // We need to save the [Table] entry even if all the bools are false, since this records a table with "default settings". + buf->reserve(buf->size() + 30 + settings->ColumnsCount * 50); // ballpark reserve buf->appendf("[%s][0x%08X,%d]\n", handler->TypeName, settings->ID, settings->ColumnsCount); if (settings->RefScale != 0.0f) @@ -3477,6 +3920,7 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle buf->append("\n"); } } + void ImGui::TableSettingsAddSettingsHandler() { ImGuiSettingsHandler ini_handler; @@ -3489,6 +3933,7 @@ void ImGui::TableSettingsAddSettingsHandler() ini_handler.WriteAllFn = TableSettingsHandler_WriteAll; AddSettingsHandler(&ini_handler); } + //------------------------------------------------------------------------- // [SECTION] Tables: Garbage Collection //------------------------------------------------------------------------- @@ -3496,6 +3941,7 @@ void ImGui::TableSettingsAddSettingsHandler() // - TableGcCompactTransientBuffers() [Internal] // - TableGcCompactSettings() [Internal] //------------------------------------------------------------------------- + // Remove Table (currently only used by TestEngine) void ImGui::TableRemove(ImGuiTable* table) { @@ -3507,6 +3953,7 @@ void ImGui::TableRemove(ImGuiTable* table) g.Tables.Remove(table->ID, table); g.TablesLastTimeActive[table_idx] = -1.0f; } + // Free up/compact internal Table buffers for when it gets unused void ImGui::TableGcCompactTransientBuffers(ImGuiTable* table) { @@ -3522,11 +3969,13 @@ void ImGui::TableGcCompactTransientBuffers(ImGuiTable* table) table->Columns[n].NameOffset = -1; g.TablesLastTimeActive[g.Tables.GetIndex(table)] = -1.0f; } + void ImGui::TableGcCompactTransientBuffers(ImGuiTableTempData* temp_data) { temp_data->DrawSplitter.ClearFreeMemory(); temp_data->LastTimeActive = -1.0f; } + // Compact and remove unused settings data (currently only used by TestEngine) void ImGui::TableGcCompactSettings() { @@ -3544,12 +3993,16 @@ void ImGui::TableGcCompactSettings() memcpy(new_chunk_stream.alloc_chunk(TableSettingsCalcChunkSize(settings->ColumnsCount)), settings, TableSettingsCalcChunkSize(settings->ColumnsCount)); g.SettingsTables.swap(new_chunk_stream); } + + //------------------------------------------------------------------------- // [SECTION] Tables: Debugging //------------------------------------------------------------------------- // - DebugNodeTable() [Internal] //------------------------------------------------------------------------- + #ifndef IMGUI_DISABLE_DEBUG_TOOLS + static const char* DebugNodeTableGetSizingPolicyDesc(ImGuiTableFlags sizing_policy) { sizing_policy &= ImGuiTableFlags_SizingMask_; @@ -3559,6 +4012,7 @@ static const char* DebugNodeTableGetSizingPolicyDesc(ImGuiTableFlags sizing_poli if (sizing_policy == ImGuiTableFlags_SizingStretchSame) { return "StretchSame"; } return "N/A"; } + void ImGui::DebugNodeTable(ImGuiTable* table) { ImGuiContext& g = *GImGui; @@ -3580,6 +4034,7 @@ void ImGui::DebugNodeTable(ImGuiTable* table) g.DebugBreakInTable = table->ID; SameLine(); } + bool clear_settings = SmallButton("Clear settings"); BulletText("OuterRect: Pos: (%.1f,%.1f) Size: (%.1f,%.1f) Sizing: '%s'", table->OuterRect.Min.x, table->OuterRect.Min.y, table->OuterRect.GetWidth(), table->OuterRect.GetHeight(), DebugNodeTableGetSizingPolicyDesc(table->Flags)); BulletText("ColumnsGivenWidth: %.1f, ColumnsAutoFitWidth: %.1f, InnerWidth: %.1f%s", table->ColumnsGivenWidth, table->ColumnsAutoFitWidth, table->InnerWidth, table->InnerWidth == 0.0f ? " (auto)" : ""); @@ -3631,6 +4086,7 @@ void ImGui::DebugNodeTable(ImGuiTable* table) table->IsResetAllRequest = true; TreePop(); } + void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings) { if (!TreeNode((void*)(intptr_t)settings->ID, "Settings 0x%08X (%d columns)", settings->ID, settings->ColumnsCount)) @@ -3648,10 +4104,15 @@ void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings) } TreePop(); } + #else // #ifndef IMGUI_DISABLE_DEBUG_TOOLS + void ImGui::DebugNodeTable(ImGuiTable*) {} void ImGui::DebugNodeTableSettings(ImGuiTableSettings*) {} + #endif + + //------------------------------------------------------------------------- // [SECTION] Columns, BeginColumns, EndColumns, etc. // (This is a legacy API, prefer using BeginTable/EndTable!) @@ -3675,6 +4136,7 @@ void ImGui::DebugNodeTableSettings(ImGuiTableSettings*) {} // - EndColumns() // - Columns() //------------------------------------------------------------------------- + // [Internal] Small optimization to avoid calls to PopClipRect/SetCurrentChannel/PushClipRect in sequences, // they would meddle many times with the underlying ImDrawCmd. // Instead, we do a preemptive overwrite of clipping rectangle _without_ altering the command-buffer and let @@ -3686,25 +4148,31 @@ void ImGui::SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& window->DrawList->_CmdHeader.ClipRect = clip_rect_vec4; window->DrawList->_ClipRectStack.Data[window->DrawList->_ClipRectStack.Size - 1] = clip_rect_vec4; } + int ImGui::GetColumnIndex() { ImGuiWindow* window = GetCurrentWindowRead(); return window->DC.CurrentColumns ? window->DC.CurrentColumns->Current : 0; } + int ImGui::GetColumnsCount() { ImGuiWindow* window = GetCurrentWindowRead(); return window->DC.CurrentColumns ? window->DC.CurrentColumns->Count : 1; } + float ImGui::GetColumnOffsetFromNorm(const ImGuiOldColumns* columns, float offset_norm) { return offset_norm * (columns->OffMaxX - columns->OffMinX); } + float ImGui::GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset) { return offset / (columns->OffMaxX - columns->OffMinX); } + static const float COLUMNS_HIT_RECT_HALF_THICKNESS = 4.0f; + static float GetDraggedColumnOffset(ImGuiOldColumns* columns, int column_index) { // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing @@ -3713,29 +4181,36 @@ static float GetDraggedColumnOffset(ImGuiOldColumns* columns, int column_index) ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(column_index > 0); // We are not supposed to drag column 0. IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index)); + float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + ImTrunc(COLUMNS_HIT_RECT_HALF_THICKNESS * g.CurrentDpiScale) - window->Pos.x; x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing); if ((columns->Flags & ImGuiOldColumnFlags_NoPreserveWidths)) x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing); + return x; } + float ImGui::GetColumnOffset(int column_index) { ImGuiWindow* window = GetCurrentWindowRead(); ImGuiOldColumns* columns = window->DC.CurrentColumns; if (columns == NULL) return 0.0f; + if (column_index < 0) column_index = columns->Current; IM_ASSERT(column_index < columns->Columns.Size); + const float t = columns->Columns[column_index].OffsetNorm; const float x_offset = ImLerp(columns->OffMinX, columns->OffMaxX, t); return x_offset; } + static float GetColumnWidthEx(ImGuiOldColumns* columns, int column_index, bool before_resize = false) { if (column_index < 0) column_index = columns->Current; + float offset_norm; if (before_resize) offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize; @@ -3743,6 +4218,7 @@ static float GetColumnWidthEx(ImGuiOldColumns* columns, int column_index, bool b offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm; return ImGui::GetColumnOffsetFromNorm(columns, offset_norm); } + float ImGui::GetColumnWidth(int column_index) { ImGuiContext& g = *GImGui; @@ -3750,45 +4226,56 @@ float ImGui::GetColumnWidth(int column_index) ImGuiOldColumns* columns = window->DC.CurrentColumns; if (columns == NULL) return GetContentRegionAvail().x; + if (column_index < 0) column_index = columns->Current; return GetColumnOffsetFromNorm(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm); } + void ImGui::SetColumnOffset(int column_index, float offset) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiOldColumns* columns = window->DC.CurrentColumns; IM_ASSERT(columns != NULL); + if (column_index < 0) column_index = columns->Current; IM_ASSERT(column_index < columns->Columns.Size); + const bool preserve_width = !(columns->Flags & ImGuiOldColumnFlags_NoPreserveWidths) && (column_index < columns->Count - 1); const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f; + if (!(columns->Flags & ImGuiOldColumnFlags_NoForceWithinWindow)) offset = ImMin(offset, columns->OffMaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index)); columns->Columns[column_index].OffsetNorm = GetColumnNormFromOffset(columns, offset - columns->OffMinX); + if (preserve_width) SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width)); } + void ImGui::SetColumnWidth(int column_index, float width) { ImGuiWindow* window = GetCurrentWindowRead(); ImGuiOldColumns* columns = window->DC.CurrentColumns; IM_ASSERT(columns != NULL); + if (column_index < 0) column_index = columns->Current; SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width); } + void ImGui::PushColumnClipRect(int column_index) { ImGuiWindow* window = GetCurrentWindowRead(); ImGuiOldColumns* columns = window->DC.CurrentColumns; if (column_index < 0) column_index = columns->Current; + ImGuiOldColumnData* column = &columns->Columns[column_index]; PushClipRect(column->ClipRect.Min, column->ClipRect.Max, false); } + // Get into the columns background draw command (which is generally the same draw command as before we called BeginColumns) void ImGui::PushColumnsBackground() { @@ -3796,48 +4283,59 @@ void ImGui::PushColumnsBackground() ImGuiOldColumns* columns = window->DC.CurrentColumns; if (columns->Count == 1) return; + // Optimization: avoid SetCurrentChannel() + PushClipRect() columns->HostBackupClipRect = window->ClipRect; SetWindowClipRectBeforeSetChannel(window, columns->HostInitialClipRect); columns->Splitter.SetCurrentChannel(window->DrawList, 0); } + void ImGui::PopColumnsBackground() { ImGuiWindow* window = GetCurrentWindowRead(); ImGuiOldColumns* columns = window->DC.CurrentColumns; if (columns->Count == 1) return; + // Optimization: avoid PopClipRect() + SetCurrentChannel() SetWindowClipRectBeforeSetChannel(window, columns->HostBackupClipRect); columns->Splitter.SetCurrentChannel(window->DrawList, columns->Current + 1); } + ImGuiOldColumns* ImGui::FindOrCreateColumns(ImGuiWindow* window, ImGuiID id) { // We have few columns per window so for now we don't need bother much with turning this into a faster lookup. for (int n = 0; n < window->ColumnsStorage.Size; n++) if (window->ColumnsStorage[n].ID == id) return &window->ColumnsStorage[n]; + window->ColumnsStorage.push_back(ImGuiOldColumns()); ImGuiOldColumns* columns = &window->ColumnsStorage.back(); columns->ID = id; return columns; } + ImGuiID ImGui::GetColumnsID(const char* str_id, int columns_count) { ImGuiWindow* window = GetCurrentWindow(); + // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget. // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer. PushID(0x11223347 + (str_id ? 0 : columns_count)); ImGuiID id = window->GetID(str_id ? str_id : "columns"); PopID(); + return id; } + void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiOldColumnFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(columns_count >= 1); IM_ASSERT(window->DC.CurrentColumns == NULL); // Nested columns are currently not supported + // Acquire storage for the columns set ImGuiID id = GetColumnsID(str_id, columns_count); ImGuiOldColumns* columns = FindOrCreateColumns(window, id); @@ -3847,11 +4345,13 @@ void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiOldColumnFl columns->Flags = flags; window->DC.CurrentColumns = columns; window->DC.NavIsScrollPushableX = false; // Shortcut for NavUpdateCurrentWindowIsScrollPushableX(); + columns->HostCursorPosY = window->DC.CursorPos.y; columns->HostCursorMaxPosX = window->DC.CursorMaxPos.x; columns->HostInitialClipRect = window->ClipRect; columns->HostBackupParentWorkRect = window->ParentWorkRect; window->ParentWorkRect = window->WorkRect; + // Set state for first column // We aim so that the right-most column will have the same clipping width as other after being clipped by parent ClipRect const float column_padding = g.Style.ItemSpacing.x; @@ -3861,9 +4361,11 @@ void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiOldColumnFl columns->OffMinX = window->DC.Indent.x - column_padding + ImMax(column_padding - window->WindowPadding.x, 0.0f); columns->OffMaxX = ImMax(ImMin(max_1, max_2) - window->Pos.x, columns->OffMinX + 1.0f); columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y; + // Clear data if columns count changed if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1) columns->Columns.resize(0); + // Initialize default widths columns->IsFirstFrame = (columns->Columns.Size == 0); if (columns->Columns.Size == 0) @@ -3876,6 +4378,7 @@ void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiOldColumnFl columns->Columns.push_back(column); } } + for (int n = 0; n < columns_count; n++) { // Compute clipping rectangle @@ -3885,12 +4388,14 @@ void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiOldColumnFl column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX); column->ClipRect.ClipWithFull(window->ClipRect); } + if (columns->Count > 1) { columns->Splitter.Split(window->DrawList, 1 + columns->Count); columns->Splitter.SetCurrentChannel(window->DrawList, 1); PushColumnClipRect(0); } + // We don't generally store Indent.x inside ColumnsOffset because it may be manipulated by the user. float offset_0 = GetColumnOffset(columns->Current); float offset_1 = GetColumnOffset(columns->Current + 1); @@ -3901,28 +4406,35 @@ void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiOldColumnFl window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding; window->WorkRect.Max.y = window->ContentRegionRect.Max.y; } + void ImGui::NextColumn() { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems || window->DC.CurrentColumns == NULL) return; + ImGuiContext& g = *GImGui; ImGuiOldColumns* columns = window->DC.CurrentColumns; + if (columns->Count == 1) { window->DC.CursorPos.x = IM_TRUNC(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); IM_ASSERT(columns->Current == 0); return; } + // Next column if (++columns->Current == columns->Count) columns->Current = 0; + PopItemWidth(); + // Optimization: avoid PopClipRect() + SetCurrentChannel() + PushClipRect() // (which would needlessly attempt to update commands in the wrong channel, then pop or overwrite them), ImGuiOldColumnData* column = &columns->Columns[columns->Current]; SetWindowClipRectBeforeSetChannel(window, column->ClipRect); columns->Splitter.SetCurrentChannel(window->DrawList, columns->Current + 1); + const float column_padding = g.Style.ItemSpacing.x; columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); if (columns->Current > 0) @@ -3942,6 +4454,7 @@ void ImGui::NextColumn() window->DC.CursorPos.y = columns->LineMinY; window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); window->DC.CurrLineTextBaseOffset = 0.0f; + // FIXME-COLUMNS: Share code with BeginColumns() - move code on columns setup. float offset_0 = GetColumnOffset(columns->Current); float offset_1 = GetColumnOffset(columns->Current + 1); @@ -3949,23 +4462,27 @@ void ImGui::NextColumn() PushItemWidth(width * 0.65f); window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding; } + void ImGui::EndColumns() { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); ImGuiOldColumns* columns = window->DC.CurrentColumns; IM_ASSERT(columns != NULL); + PopItemWidth(); if (columns->Count > 1) { PopClipRect(); columns->Splitter.Merge(window->DrawList); } + const ImGuiOldColumnFlags flags = columns->Flags; columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); window->DC.CursorPos.y = columns->LineMaxY; if (!(flags & ImGuiOldColumnFlags_GrowParentContentsSize)) window->DC.CursorMaxPos.x = columns->HostCursorMaxPosX; // Restore cursor max pos, as columns don't grow parent + // Draw columns borders and handle resize // The IsBeingResized flag ensure we preserve pre-resize columns width so back-and-forth are not lossy bool is_being_resized = false; @@ -3984,6 +4501,7 @@ void ImGui::EndColumns() const ImRect column_hit_rect(ImVec2(x - column_hit_hw, y1), ImVec2(x + column_hit_hw, y2)); if (!ItemAdd(column_hit_rect, column_id, NULL, ImGuiItemFlags_NoNav)) continue; + bool hovered = false, held = false; if (!(flags & ImGuiOldColumnFlags_NoResize)) { @@ -3993,11 +4511,13 @@ void ImGui::EndColumns() if (held && !(column->Flags & ImGuiOldColumnFlags_NoResize)) dragging_column = n; } + // Draw column const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); const float xi = IM_TRUNC(x); window->DrawList->AddLine(ImVec2(xi, y1 + 1.0f), ImVec2(xi, y2), col); } + // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. if (dragging_column != -1) { @@ -4010,6 +4530,7 @@ void ImGui::EndColumns() } } columns->IsBeingResized = is_being_resized; + window->WorkRect = window->ParentWorkRect; window->ParentWorkRect = columns->HostBackupParentWorkRect; window->DC.CurrentColumns = NULL; @@ -4017,19 +4538,25 @@ void ImGui::EndColumns() window->DC.CursorPos.x = IM_TRUNC(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); NavUpdateCurrentWindowIsScrollPushableX(); } + void ImGui::Columns(int columns_count, const char* id, bool borders) { ImGuiWindow* window = GetCurrentWindow(); IM_ASSERT(columns_count >= 1); + ImGuiOldColumnFlags flags = (borders ? 0 : ImGuiOldColumnFlags_NoBorder); //flags |= ImGuiOldColumnFlags_NoPreserveWidths; // NB: Legacy behavior ImGuiOldColumns* columns = window->DC.CurrentColumns; if (columns != NULL && columns->Count == columns_count && columns->Flags == flags) return; + if (columns != NULL) EndColumns(); + if (columns_count != 1) BeginColumns(id, columns_count, flags); } + //------------------------------------------------------------------------- + #endif // #ifndef IMGUI_DISABLE diff --git a/external/reshade/deps/imgui/imgui_widgets.cpp b/external/reshade/deps/imgui/imgui_widgets.cpp index 3f5d4e8..f0da0b2 100644 --- a/external/reshade/deps/imgui/imgui_widgets.cpp +++ b/external/reshade/deps/imgui/imgui_widgets.cpp @@ -1,7 +1,10 @@ -// dear imgui, v1.91b +// dear imgui, v1.92.2b // (widgets code) + /* + Index of this file: + // [SECTION] Forward Declarations // [SECTION] Widgets: Text, etc. // [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.) @@ -26,21 +29,28 @@ Index of this file: // [SECTION] Widgets: BeginTabBar, EndTabBar, etc. // [SECTION] Widgets: BeginTabItem, EndTabItem, etc. // [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc. + */ + #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif + #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif + #include "imgui.h" #ifndef IMGUI_DISABLE #include "imgui_internal.h" + // System includes #include // intptr_t + //------------------------------------------------------------------------- // Warnings //------------------------------------------------------------------------- + // Visual Studio warnings #ifdef _MSC_VER #pragma warning (disable: 4127) // condition expression is constant @@ -51,6 +61,7 @@ Index of this file: #pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). #pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). #endif + // Clang/GCC warnings with -Weverything #if defined(__clang__) #if __has_warning("-Wunknown-warning-option") @@ -82,12 +93,15 @@ Index of this file: #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers #endif + //------------------------------------------------------------------------- // Data //------------------------------------------------------------------------- + // Widgets static const float DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f; // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior. static const float DRAG_MOUSE_THRESHOLD_FACTOR = 0.50f; // Multiplier for the default value of io.MouseDragThreshold to make DragFloat/DragInt react faster to mouse drags. + // Those MIN/MAX values are not define because we need to point to them static const signed char IM_S8_MIN = -128; static const signed char IM_S8_MAX = 127; @@ -114,13 +128,16 @@ static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull); #else static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); #endif + //------------------------------------------------------------------------- // [SECTION] Forward Declarations //------------------------------------------------------------------------- + // For InputTextEx() static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false); static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); + //------------------------------------------------------------------------- // [SECTION] Widgets: Text, etc. //------------------------------------------------------------------------- @@ -139,19 +156,23 @@ static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, // - BulletText() // - BulletTextV() //------------------------------------------------------------------------- + void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; ImGuiContext& g = *GImGui; + // Accept null ranges if (text == text_end) text = text_end = ""; + // Calculate length const char* text_begin = text; if (text_end == NULL) text_end = text + ImStrlen(text); // FIXME-OPT + const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); const float wrap_pos_x = window->DC.TextWrapPos; const bool wrap_enabled = (wrap_pos_x >= 0.0f); @@ -160,10 +181,12 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) // Common case const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f; const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width); + ImRect bb(text_pos, text_pos + text_size); ItemSize(text_size, 0.0f); if (!ItemAdd(bb, 0)) return; + // Render (we don't hide text after ## in this end-user function) RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width); } @@ -177,6 +200,7 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) const char* line = text; const float line_height = GetTextLineHeight(); ImVec2 text_size(0, 0); + // Lines to skip (can't skip when logging text) ImVec2 pos = text_pos; if (!g.LogEnabled) @@ -198,6 +222,7 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) pos.y += lines_skipped * line_height; } } + // Lines to render if (line < text_end) { @@ -206,6 +231,7 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) { if (IsClippedEx(line_rect, 0)) break; + const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line); if (!line_end) line_end = text_end; @@ -216,6 +242,7 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) line_rect.Max.y += line_height; pos.y += line_height; } + // Count remaining lines int lines_skipped = 0; while (line < text_end) @@ -231,15 +258,18 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) pos.y += lines_skipped * line_height; } text_size.y = (pos - text_pos).y; + ImRect bb(text_pos, text_pos + text_size); ItemSize(text_size, 0.0f); ItemAdd(bb, 0); } } + void ImGui::TextUnformatted(const char* text, const char* text_end) { TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); } + void ImGui::Text(const char* fmt, ...) { va_list args; @@ -247,15 +277,18 @@ void ImGui::Text(const char* fmt, ...) TextV(fmt, args); va_end(args); } + void ImGui::TextV(const char* fmt, va_list args) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; + const char* text, *text_end; ImFormatStringToTempBufferV(&text, &text_end, fmt, args); TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); } + void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) { va_list args; @@ -263,12 +296,14 @@ void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) TextColoredV(col, fmt, args); va_end(args); } + void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args) { PushStyleColor(ImGuiCol_Text, col); TextV(fmt, args); PopStyleColor(); } + void ImGui::TextDisabled(const char* fmt, ...) { va_list args; @@ -276,6 +311,7 @@ void ImGui::TextDisabled(const char* fmt, ...) TextDisabledV(fmt, args); va_end(args); } + void ImGui::TextDisabledV(const char* fmt, va_list args) { ImGuiContext& g = *GImGui; @@ -283,6 +319,7 @@ void ImGui::TextDisabledV(const char* fmt, va_list args) TextV(fmt, args); PopStyleColor(); } + void ImGui::TextWrapped(const char* fmt, ...) { va_list args; @@ -290,6 +327,7 @@ void ImGui::TextWrapped(const char* fmt, ...) TextWrappedV(fmt, args); va_end(args); } + void ImGui::TextWrappedV(const char* fmt, va_list args) { ImGuiContext& g = *GImGui; @@ -300,6 +338,47 @@ void ImGui::TextWrappedV(const char* fmt, va_list args) if (need_backup) PopTextWrapPos(); } + +void ImGui::TextAligned(float align_x, float size_x, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextAlignedV(align_x, size_x, fmt, args); + va_end(args); +} + +// align_x: 0.0f = left, 0.5f = center, 1.0f = right. +// size_x : 0.0f = shortcut for GetContentRegionAvail().x +// FIXME-WIP: Works but API is likely to be reworked. This is designed for 1 item on the line. (#7024) +void ImGui::TextAlignedV(float align_x, float size_x, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + const char* text, *text_end; + ImFormatStringToTempBufferV(&text, &text_end, fmt, args); + const ImVec2 text_size = CalcTextSize(text, text_end); + size_x = CalcItemSize(ImVec2(size_x, 0.0f), 0.0f, text_size.y).x; + + ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + ImVec2 pos_max(pos.x + size_x, window->ClipRect.Max.y); + ImVec2 size(ImMin(size_x, text_size.x), text_size.y); + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, pos.x + text_size.x); + window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, pos.x + text_size.x); + if (align_x > 0.0f && text_size.x < size_x) + pos.x += ImTrunc((size_x - text_size.x) * align_x); + RenderTextEllipsis(window->DrawList, pos, pos_max, pos_max.x, text, text_end, &text_size); + + const ImVec2 backup_max_pos = window->DC.CursorMaxPos; + ItemSize(size); + ItemAdd(ImRect(pos, pos + size), 0); + window->DC.CursorMaxPos.x = backup_max_pos.x; // Cancel out extending content size because right-aligned text would otherwise mess it up. + + if (size_x < text_size.x && IsItemHovered(ImGuiHoveredFlags_NoNavOverride | ImGuiHoveredFlags_AllowWhenDisabled | ImGuiHoveredFlags_ForTooltip)) + SetTooltip("%.*s", (int)(text_end - text), text); +} + void ImGui::LabelText(const char* label, const char* fmt, ...) { va_list args; @@ -307,30 +386,36 @@ void ImGui::LabelText(const char* label, const char* fmt, ...) LabelTextV(label, fmt, args); va_end(args); } + // Add a label+text combo aligned to other label+value widgets void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; + ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const float w = CalcItemWidth(); + const char* value_text_begin, *value_text_end; ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args); const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false); const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImVec2 pos = window->DC.CursorPos; const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2)); const ImRect total_bb(pos, pos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), ImMax(value_size.y, label_size.y) + style.FramePadding.y * 2)); ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, 0)) return; + // Render RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f)); if (label_size.x > 0.0f) RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); } + void ImGui::BulletText(const char* fmt, ...) { va_list args; @@ -338,14 +423,17 @@ void ImGui::BulletText(const char* fmt, ...) BulletTextV(fmt, args); va_end(args); } + // Text with a little bullet aligned to the typical tree node. void ImGui::BulletTextV(const char* fmt, va_list args) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; + ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; + const char* text_begin, *text_end; ImFormatStringToTempBufferV(&text_begin, &text_end, fmt, args); const ImVec2 label_size = CalcTextSize(text_begin, text_end, false); @@ -356,11 +444,13 @@ void ImGui::BulletTextV(const char* fmt, va_list args) const ImRect bb(pos, pos + total_size); if (!ItemAdd(bb, 0)) return; + // Render ImU32 text_col = GetColorU32(ImGuiCol_Text); RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), text_col); RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text_begin, text_end, false); } + //------------------------------------------------------------------------- // [SECTION] Widgets: Main //------------------------------------------------------------------------- @@ -385,6 +475,7 @@ void ImGui::BulletTextV(const char* fmt, va_list args) // - Bullet() // - Hyperlink() //------------------------------------------------------------------------- + // The ButtonBehavior() function is key to many interactions and used by many/most widgets. // Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_), // this code is a little complex. @@ -438,11 +529,12 @@ void ImGui::BulletTextV(const char* fmt, va_list args) // ... - - - - // Frame N + RepeatDelay + RepeatRate*N true true - true //------------------------------------------------------------------------------------------------------------------------------------------------- + // - FIXME: For refactor we could output flags, incl mouse hovered vs nav keyboard vs nav triggered etc. // And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);' // For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading. // - Since v1.91.2 (Sept 2024) we included io.ConfigDebugHighlightIdConflicts feature. -// One idiom which was previously valid which will now emit a warning is when using multiple overlayed ButtonBehavior() +// One idiom which was previously valid which will now emit a warning is when using multiple overlaid ButtonBehavior() // with same ID and different MouseButton (see #8030). You can fix it by: // (1) switching to use a single ButtonBehavior() with multiple _MouseButton flags. // or (2) surrounding those calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag() @@ -450,29 +542,39 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); + // Default behavior inherited from item flags // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that. ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags); if (flags & ImGuiButtonFlags_AllowOverlap) item_flags |= ImGuiItemFlags_AllowOverlap; + if (item_flags & ImGuiItemFlags_NoFocus) + flags |= ImGuiButtonFlags_NoFocus | ImGuiButtonFlags_NoNavFocus; + // Default only reacts to left mouse button if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0) flags |= ImGuiButtonFlags_MouseButtonLeft; + // Default behavior requires click + release inside bounding box if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0) flags |= (item_flags & ImGuiItemFlags_ButtonRepeat) ? ImGuiButtonFlags_PressedOnClick : ImGuiButtonFlags_PressedOnDefault_; + ImGuiWindow* backup_hovered_window = g.HoveredWindow; const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindowDockTree == window->RootWindowDockTree; if (flatten_hovered_children) g.HoveredWindow = window; + #ifdef IMGUI_ENABLE_TEST_ENGINE // Alternate registration spot, for when caller didn't use ItemAdd() if (g.LastItemData.ID != id) IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, NULL); #endif + bool pressed = false; bool hovered = ItemHoverable(bb, id, item_flags); - // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button + + // Special mode for Drag and Drop used by openables (tree nodes, tabs etc.) + // where holding the button pressed for a long time while drag a payload item triggers the button. if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) { @@ -485,13 +587,16 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool FocusWindow(window); } } + if (flatten_hovered_children) g.HoveredWindow = backup_hovered_window; + // Mouse handling const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id; if (hovered) { IM_ASSERT(id != 0); // Lazily check inside rare path. + // Poll mouse buttons // - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId. // - Technically we only need some values in one code path, but since this is gated by hovered test this is fine. @@ -503,6 +608,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool if (IsMouseClicked(button, ImGuiInputFlags_None, test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; } if (IsMouseReleased(button, test_owner_id) && mouse_button_released == -1) { mouse_button_released = button; } } + // Process initial action const bool mods_ok = !(flags & ImGuiButtonFlags_NoKeyModsAllowed) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt); if (mods_ok) @@ -520,7 +626,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool SetFocusID(id, window); FocusWindow(window); } - else + else if (!(flags & ImGuiButtonFlags_NoFocus)) { FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child } @@ -538,7 +644,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool SetFocusID(id, window); FocusWindow(window); } - else + else if (!(flags & ImGuiButtonFlags_NoFocus)) { FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child } @@ -556,15 +662,18 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool ClearActiveID(); } } + // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. if (g.ActiveId == id && (item_flags & ImGuiItemFlags_ButtonRepeat)) if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, ImGuiInputFlags_Repeat, test_owner_id)) pressed = true; } + if (pressed && g.IO.ConfigNavCursorVisibleAuto) g.NavCursorVisible = false; } + // Keyboard/Gamepad navigation handling // We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse. if (g.NavId == id && g.NavCursorVisible && g.NavHighlightItemUnderNav) @@ -595,6 +704,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool g.ActiveIdFromShortcut = true; } } + // Process while held bool held = false; if (g.ActiveId == id) @@ -603,6 +713,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool { if (g.ActiveIdIsJustActivated) g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; + const int mouse_button = g.ActiveIdMouseButton; if (mouse_button == -1) { @@ -642,49 +753,63 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool if (pressed) g.ActiveIdHasBeenPressedBefore = true; } + // Activation highlight (this may be a remote activation) if (g.NavHighlightActivatedId == id) hovered = true; + if (out_hovered) *out_hovered = hovered; if (out_held) *out_held = held; + return pressed; } + bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 pos = window->DC.CursorPos; if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y; ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); + const ImRect bb(pos, pos + size); ItemSize(size, style.FramePadding.y); if (!ItemAdd(bb, id)) return false; + bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + // Render const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); RenderNavCursor(bb, id); RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + if (g.LogEnabled) LogSetNextTextDecoration("[", "]"); RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); + // Automatically close popups //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) // CloseCurrentPopup(); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return pressed; } + bool ImGui::Button(const char* label, const ImVec2& size_arg) { return ButtonEx(label, size_arg, ImGuiButtonFlags_None); } + // Small buttons fits within text without additional vertical spacing. bool ImGui::SmallButton(const char* label) { @@ -695,6 +820,7 @@ bool ImGui::SmallButton(const char* label) g.Style.FramePadding.y = backup_padding_y; return pressed; } + // Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack. // Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id) bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags) @@ -703,53 +829,65 @@ bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiBut ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size. IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f); + const ImGuiID id = window->GetID(str_id); ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); ItemSize(size); if (!ItemAdd(bb, id, NULL, (flags & ImGuiButtonFlags_EnableNav) ? ImGuiItemFlags_None : ImGuiItemFlags_NoNav)) return false; + bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); RenderNavCursor(bb, id); + IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags); return pressed; } + bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + const ImGuiID id = window->GetID(str_id); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); const float default_size = GetFrameHeight(); ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f); if (!ItemAdd(bb, id)) return false; + bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + // Render const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); const ImU32 text_col = GetColorU32(ImGuiCol_Text); RenderNavCursor(bb, id); RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding); RenderArrow(window->DrawList, bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), text_col, dir); + IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags); return pressed; } + bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir) { float sz = GetFrameHeight(); return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None); } + // Button to close a window bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + // Tweak 1: Shrink hit-testing area if button covers an abnormally large proportion of the visible region. That's in order to facilitate moving the window away. (#3825) // This may better be applied as a general hit-rect reduction mechanism for all widgets to ensure the area to move window is always accessible? const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize)); @@ -757,36 +895,44 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea(); if (area_to_visible_ratio < 1.5f) bb_interact.Expand(ImTrunc(bb_interact.GetSize() * -0.25f)); + // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window. // (this isn't the common behavior of buttons, but it doesn't affect the user because navigation tends to keep items visible in scrolling layer). bool is_clipped = !ItemAdd(bb_interact, id); + bool hovered, held; bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held); if (is_clipped) return pressed; + // Render ImU32 bg_col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); if (hovered) window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); - ImU32 cross_col = GetColorU32(ImGuiCol_Text); - ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); - float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; - window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f); - window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f); + const ImU32 cross_col = GetColorU32(ImGuiCol_Text); + const ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); + const float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; + const float cross_thickness = 1.0f; // FIXME-DPI + window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, cross_thickness); + window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, cross_thickness); + return pressed; } + // The Collapse button also functions as a Dock Menu button. bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize)); bool is_clipped = !ItemAdd(bb, id); bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None); if (is_clipped) return pressed; + // Render //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed); ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); @@ -794,19 +940,24 @@ bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_no if (hovered || held) window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); + if (dock_node) RenderArrowDockMenu(window->DrawList, bb.Min, g.FontSize, text_col); else RenderArrow(window->DrawList, bb.Min, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); + // Switch to moving the window after mouse is moved beyond the initial drag threshold if (IsItemActive() && IsMouseDragging(0)) StartMouseMovingWindowOrNode(window, dock_node, true); // Undock from window/collapse menu button + return pressed; } + ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis) { return window->GetID(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY"); } + // Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set. ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis) { @@ -822,11 +973,13 @@ ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis) else return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y + border_top, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size); } + void ImGui::Scrollbar(ImGuiAxis axis) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; const ImGuiID id = GetWindowScrollbarID(window, axis); + // Calculate scrollbar bounding box ImRect bb = GetWindowScrollbarRect(window, axis); ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone; @@ -849,6 +1002,7 @@ void ImGui::Scrollbar(ImGuiAxis axis) ScrollbarEx(bb, id, axis, &scroll, (ImS64)size_visible, (ImS64)size_contents, rounding_corners); window->Scroll[axis] = (float)scroll; } + // Vertical/Horizontal scrollbar // The entire piece of code below is rather confusing because: // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab) @@ -861,22 +1015,28 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; + const float bb_frame_width = bb_frame.GetWidth(); const float bb_frame_height = bb_frame.GetHeight(); if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f) return false; + // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the window resize grab) float alpha = 1.0f; if ((axis == ImGuiAxis_Y) && bb_frame_height < bb_frame_width) alpha = ImSaturate(bb_frame_height / ImMax(bb_frame_width * 2.0f, 1.0f)); if (alpha <= 0.0f) return false; + const ImGuiStyle& style = g.Style; const bool allow_interaction = (alpha >= 1.0f); + ImRect bb = bb_frame; bb.Expand(ImVec2(-ImClamp(IM_TRUNC((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_TRUNC((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f))); + // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight(); + // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount) // But we maintain a minimum size in pixel to allow for the user to still aim inside. IM_ASSERT(ImMax(size_contents_v, size_visible_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers. @@ -884,11 +1044,13 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 const float grab_h_minsize = ImMin(bb.GetSize()[axis], style.GrabMinSize); const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), grab_h_minsize, scrollbar_size_v); const float grab_h_norm = grab_h_pixels / scrollbar_size_v; + // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar(). bool held = false; bool hovered = false; ItemAdd(bb_frame, id, NULL, ImGuiItemFlags_NoNav); ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus); + const ImS64 scroll_max = ImMax((ImS64)1, size_contents_v - size_visible_v); float scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max); float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space @@ -896,8 +1058,10 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 { const float scrollbar_pos_v = bb.Min[axis]; const float mouse_pos_v = g.IO.MousePos[axis]; + // Click position in scrollbar normalized space (0.0f->1.0f) const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); + const int held_dir = (clicked_v_norm < grab_v_norm) ? -1 : (clicked_v_norm > grab_v_norm + grab_h_norm) ? +1 : 0; if (g.ActiveIdIsJustActivated) { @@ -906,6 +1070,7 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 g.ScrollbarSeekMode = scroll_to_clicked_location ? 0 : (short)held_dir; g.ScrollbarClickDeltaToGrabCenter = (held_dir == 0 && !g.IO.KeyShift) ? clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f : 0.0f; } + // Apply scroll (p_scroll_v will generally point on one member of window->Scroll) // It is ok to modify Scroll here because we are being called in Begin() after the calculation of ContentSize and before setting up our starting position if (g.ScrollbarSeekMode == 0) @@ -923,13 +1088,16 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 *p_scroll_v = ImClamp(*p_scroll_v + (ImS64)(page_dir * size_visible_v), (ImS64)0, scroll_max); } } + // Update values for rendering scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max); grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; + // Update distance to grab now that we have seek'ed and saturated //if (seek_absolute) // g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f; } + // Render const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg); const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha); @@ -940,76 +1108,90 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 else grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels); window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding); + return held; } -// - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples + +// - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above. -void ImGui::ImageWithBg(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) +void ImGui::ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; + const ImVec2 padding(g.Style.ImageBorderSize, g.Style.ImageBorderSize); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f); ItemSize(bb); if (!ItemAdd(bb, 0)) return; + // Render if (g.Style.ImageBorderSize > 0.0f) window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, ImDrawFlags_None, g.Style.ImageBorderSize); if (bg_col.w > 0.0f) window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); - window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); } -void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1) + +void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1) { - ImageWithBg(user_texture_id, image_size, uv0, uv1); + ImageWithBg(tex_ref, image_size, uv0, uv1); } + // 1.91.9 (February 2025) removed 'tint_col' and 'border_col' parameters, made border size not depend on color value. (#8131, #8238) #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) +void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) { ImGuiContext& g = *GImGui; PushStyleVar(ImGuiStyleVar_ImageBorderSize, (border_col.w > 0.0f) ? ImMax(1.0f, g.Style.ImageBorderSize) : 0.0f); // Preserve legacy behavior where border is always visible when border_col's Alpha is >0.0f PushStyleColor(ImGuiCol_Border, border_col); - ImageWithBg(user_texture_id, image_size, uv0, uv1, ImVec4(0, 0, 0, 0), tint_col); + ImageWithBg(tex_ref, image_size, uv0, uv1, ImVec4(0, 0, 0, 0), tint_col); PopStyleColor(); PopStyleVar(); } #endif -bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) + +bool ImGui::ImageButtonEx(ImGuiID id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + const ImVec2 padding = g.Style.FramePadding; const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f); ItemSize(bb); if (!ItemAdd(bb, id)) return false; + bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + // Render const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); RenderNavCursor(bb, id); RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); if (bg_col.w > 0.0f) window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); - window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + return pressed; } + // - ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button. // - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. (#8165) // FIXME: Maybe that's not the best design? -bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) +bool ImGui::ImageButton(const char* str_id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; - return ImageButtonEx(window->GetID(str_id), user_texture_id, image_size, uv0, uv1, bg_col, tint_col); + + return ImageButtonEx(window->GetID(str_id), tex_ref, image_size, uv0, uv1, bg_col, tint_col); } + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Legacy API obsoleted in 1.89. Two differences with new ImageButton() // - old ImageButton() used ImTextureID as item id (created issue with multiple buttons with same image, transient texture id values, opaque computation of ID) @@ -1031,15 +1213,18 @@ bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const I } */ #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + bool ImGui::Checkbox(const char* label, bool* v) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const ImVec2 label_size = CalcTextSize(label, NULL, true); + const float square_sz = GetFrameHeight(); const ImVec2 pos = window->DC.CursorPos; const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); @@ -1052,23 +1237,28 @@ bool ImGui::Checkbox(const char* label, bool* v) IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); return false; } + // Range-Selection/Multi-selection support (header) bool checked = *v; if (is_multi_select) MultiSelectItemHeader(id, &checked, NULL); + bool hovered, held; bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + // Range-Selection/Multi-selection support (footer) if (is_multi_select) MultiSelectItemFooter(id, &checked, &pressed); else if (pressed) checked = !checked; + if (*v != checked) { *v = checked; pressed = true; // return value MarkItemEdited(id); } + const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); const bool mixed_value = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; if (is_visible) @@ -1094,9 +1284,11 @@ bool ImGui::Checkbox(const char* label, bool* v) LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]"); if (is_visible && label_size.x > 0.0f) RenderText(label_pos, label); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); return pressed; } + template bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value) { @@ -1112,6 +1304,7 @@ bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value) else { pressed = Checkbox(label, &all_on); + } if (pressed) { @@ -1122,31 +1315,38 @@ bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value) } return pressed; } + bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value) { return CheckboxFlagsT(label, flags, flags_value); } + bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) { return CheckboxFlagsT(label, flags, flags_value); } + bool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value) { return CheckboxFlagsT(label, flags, flags_value); } + bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value) { return CheckboxFlagsT(label, flags, flags_value); } + bool ImGui::RadioButton(const char* label, bool active) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const ImVec2 label_size = CalcTextSize(label, NULL, true); + const float square_sz = GetFrameHeight(); const ImVec2 pos = window->DC.CursorPos; const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); @@ -1154,14 +1354,17 @@ bool ImGui::RadioButton(const char* label, bool active) ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, id)) return false; + ImVec2 center = check_bb.GetCenter(); center.x = IM_ROUND(center.x); center.y = IM_ROUND(center.y); const float radius = (square_sz - 1.0f) * 0.5f; + bool hovered, held; bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); if (pressed) MarkItemEdited(id); + RenderNavCursor(total_bb, id); const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius); window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment); @@ -1170,19 +1373,23 @@ bool ImGui::RadioButton(const char* label, bool active) const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f)); window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark)); } + if (style.FrameBorderSize > 0.0f) { window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), num_segment, style.FrameBorderSize); window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), num_segment, style.FrameBorderSize); } + ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); if (g.LogEnabled) LogRenderedText(&label_pos, active ? "(x)" : "( )"); if (label_size.x > 0.0f) RenderText(label_pos, label); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return pressed; } + // FIXME: This would work nicely if it was a public template, e.g. 'template RadioButton(const char* label, T* v, T v_button)', but I'm not sure how we would expose it.. bool ImGui::RadioButton(const char* label, int* v, int v_button) { @@ -1191,28 +1398,34 @@ bool ImGui::RadioButton(const char* label, int* v, int v_button) *v = v_button; return pressed; } + // size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; + ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; + ImVec2 pos = window->DC.CursorPos; ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f); ImRect bb(pos, pos + size); ItemSize(size, style.FramePadding.y); if (!ItemAdd(bb, 0)) return; + // Fraction < 0.0f will display an indeterminate progress bar animation // The value must be animated along with time, so e.g. passing '-1.0f * ImGui::GetTime()' as fraction works. const bool is_indeterminate = (fraction < 0.0f); if (!is_indeterminate) fraction = ImSaturate(fraction); + // Out of courtesy we accept a NaN fraction without crashing float fill_n0 = 0.0f; float fill_n1 = (fraction == fraction) ? fraction : 0.0f; + if (is_indeterminate) { const float fill_width_n = 0.2f; @@ -1220,10 +1433,12 @@ void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* over fill_n1 = ImSaturate(fill_n0 + fill_width_n); fill_n0 = ImSaturate(fill_n0); } + // Render RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_n0, fill_n1, style.FrameRounding); + // Default displaying the fraction as percentage string, but user can override it // Don't display text for indeterminate bars by default char overlay_buf[32]; @@ -1234,6 +1449,7 @@ void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* over ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f); overlay = overlay_buf; } + ImVec2 overlay_size = CalcTextSize(overlay, NULL); if (overlay_size.x > 0.0f) { @@ -1242,11 +1458,13 @@ void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* over } } } + void ImGui::Bullet() { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; + ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), g.FontSize); @@ -1257,11 +1475,13 @@ void ImGui::Bullet() SameLine(0, style.FramePadding.x * 2); return; } + // Render and stay on same line ImU32 text_col = GetColorU32(ImGuiCol_Text); RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), text_col); SameLine(0, style.FramePadding.x * 2.0f); } + // This is provided as a convenience for being an often requested feature. // FIXME-STYLE: we delayed adding as there is a larger plan to revamp the styling system. // Because of this we currently don't provide many styling options for this widget @@ -1271,20 +1491,25 @@ bool ImGui::TextLink(const char* label) ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; const ImGuiID id = window->GetID(label); const char* label_end = FindRenderedTextEnd(label); + ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); ImVec2 size = CalcTextSize(label, label_end, true); ImRect bb(pos, pos + size); ItemSize(size, 0.0f); if (!ItemAdd(bb, id)) return false; + bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held); RenderNavCursor(bb, id); + if (hovered) SetMouseCursor(ImGuiMouseCursor_Hand); + ImVec4 text_colf = g.Style.Colors[ImGuiCol_TextLink]; ImVec4 line_colf = text_colf; { @@ -1301,22 +1526,26 @@ bool ImGui::TextLink(const char* label) v = ImSaturate(v - 0.20f); ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z); } - float line_y = bb.Max.y + ImFloor(g.Font->Descent * g.FontScale * 0.20f); - window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode. + + float line_y = bb.Max.y + ImFloor(g.FontBaked->Descent * g.FontBakedScale * 0.20f); + window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode // FIXME-DPI + PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf)); RenderText(bb.Min, label, label_end); PopStyleColor(); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return pressed; } -void ImGui::TextLinkOpenURL(const char* label, const char* url) + +bool ImGui::TextLinkOpenURL(const char* label, const char* url) { ImGuiContext& g = *GImGui; if (url == NULL) url = label; - if (TextLink(label)) - if (g.PlatformIO.Platform_OpenInShellFn != NULL) - g.PlatformIO.Platform_OpenInShellFn(&g, url); + bool pressed = TextLink(label); + if (pressed && g.PlatformIO.Platform_OpenInShellFn != NULL) + g.PlatformIO.Platform_OpenInShellFn(&g, url); SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when we same as label if (BeginPopupContextItem()) { @@ -1324,7 +1553,9 @@ void ImGui::TextLinkOpenURL(const char* label, const char* url) SetClipboardText(url); EndPopup(); } + return pressed; } + //------------------------------------------------------------------------- // [SECTION] Widgets: Low-level Layout helpers //------------------------------------------------------------------------- @@ -1337,6 +1568,7 @@ void ImGui::TextLinkOpenURL(const char* label, const char* url) // - SplitterBehavior() [Internal] // - ShrinkWidths() [Internal] //------------------------------------------------------------------------- + void ImGui::Spacing() { ImGuiWindow* window = GetCurrentWindow(); @@ -1344,20 +1576,24 @@ void ImGui::Spacing() return; ItemSize(ImVec2(0, 0)); } + void ImGui::Dummy(const ImVec2& size) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); ItemSize(size); ItemAdd(bb, 0); } + void ImGui::NewLine() { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; + ImGuiContext& g = *GImGui; const ImGuiLayoutType backup_layout_type = window->DC.LayoutType; window->DC.LayoutType = ImGuiLayoutType_Vertical; @@ -1368,15 +1604,18 @@ void ImGui::NewLine() ItemSize(ImVec2(0.0f, g.FontSize)); window->DC.LayoutType = backup_layout_type; } + void ImGui::AlignTextToFramePadding() { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; + ImGuiContext& g = *GImGui; window->DC.CurrLineSize.y = ImMax(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2); window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y); } + // Horizontal/vertical separating line // FIXME: Surprisingly, this seemingly trivial widget is a victim of many different legacy/tricky layout issues. // Note how thickness == 1.0f is handled specifically as not moving CursorPos by 'thickness', but other values are. @@ -1385,9 +1624,11 @@ void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness) ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; + ImGuiContext& g = *GImGui; IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))); // Check that only 1 option is selected IM_ASSERT(thickness > 0.0f); + if (flags & ImGuiSeparatorFlags_Vertical) { // Vertical separator, for menu bars (use current line height). @@ -1397,6 +1638,7 @@ void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness) ItemSize(ImVec2(thickness, 0.0f)); if (!ItemAdd(bb, 0)) return; + // Draw window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator)); if (g.LogEnabled) @@ -1407,6 +1649,7 @@ void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness) // Horizontal Separator float x1 = window->DC.CursorPos.x; float x2 = window->WorkRect.Max.x; + // Preserve legacy behavior inside Columns() // Before Tables API happened, we relied on Separator() to span all columns of a Columns() set. // We currently don't need to provide the same feature for tables because tables naturally have border features. @@ -1417,17 +1660,20 @@ void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness) x2 = window->Pos.x + window->Size.x; PushColumnsBackground(); } + // We don't provide our width to the layout so that it doesn't get feed back into AutoFit // FIXME: This prevents ->CursorMaxPos based bounding box evaluation from working (e.g. TableEndCell) const float thickness_for_layout = (thickness == 1.0f) ? 0.0f : thickness; // FIXME: See 1.70/1.71 Separator() change: makes legacy 1-px separator not affect layout yet. Should change. const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness)); ItemSize(ImVec2(0.0f, thickness_for_layout)); + if (ItemAdd(bb, 0)) { // Draw window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator)); if (g.LogEnabled) LogRenderedText(&bb.Min, "--------------------------------\n"); + } if (columns) { @@ -1436,28 +1682,35 @@ void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness) } } } + void ImGui::Separator() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return; + // Those flags should eventually be configurable by the user // FIXME: We cannot g.Style.SeparatorTextBorderSize for thickness as it relates to SeparatorText() which is a decorated separator, not defaulting to 1.0f. ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal; + // Only applies to legacy Columns() api as they relied on Separator() a lot. if (window->DC.CurrentColumns) flags |= ImGuiSeparatorFlags_SpanAllColumns; + SeparatorEx(flags, 1.0f); } + void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiStyle& style = g.Style; + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImVec2 pos = window->DC.CursorPos; const ImVec2 padding = style.SeparatorTextPadding; + const float separator_thickness = style.SeparatorTextBorderSize; const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(label_size.y + padding.y * 2.0f, separator_thickness)); const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y)); @@ -1465,13 +1718,17 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end ItemSize(min_size, text_baseline_y); if (!ItemAdd(bb, id)) return; + const float sep1_x1 = pos.x; const float sep2_x2 = bb.Max.x; const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.99999f); + const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f); const ImVec2 label_pos(pos.x + padding.x + ImMax(0.0f, (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN + // This allows using SameLine() to position something in the 'extra_w' window->DC.CursorPosPrevLine.x = label_pos.x + label_size.x; + const ImU32 separator_col = GetColorU32(ImGuiCol_Separator); if (label_size.x > 0.0f) { @@ -1483,7 +1740,7 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness); if (g.LogEnabled) LogSetNextTextDecoration("---", NULL); - RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, bb.Max.x, label, label_end, &label_size); + RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, label, label_end, &label_size); } else { @@ -1493,11 +1750,13 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness); } } + void ImGui::SeparatorText(const char* label) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; + // The SeparatorText() vs SeparatorTextEx() distinction is designed to be considerate that we may want: // - allow separator-text to be draggable items (would require a stable ID + a noticeable highlight) // - this high-level entry point to allow formatting? (which in turns may require ID separate from formatted string) @@ -1506,13 +1765,16 @@ void ImGui::SeparatorText(const char* label) // and then we can turn this into a format function. SeparatorTextEx(0, label, FindRenderedTextEnd(label), 0.0f); } + // Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise. bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay, ImU32 bg_col) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + if (!ItemAdd(bb, id, NULL, ImGuiItemFlags_NoNav)) return false; + // FIXME: AFAIK the only leftover reason for passing ImGuiButtonFlags_AllowOverlap here is // to allow caller of SplitterBehavior() to call SetItemAllowOverlap() after the item. // Nowadays we would instead want to use SetNextItemAllowOverlap() before the item. @@ -1520,18 +1782,22 @@ bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS button_flags |= ImGuiButtonFlags_AllowOverlap; #endif + bool hovered, held; ImRect bb_interact = bb; bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f)); ButtonBehavior(bb_interact, id, &hovered, &held, button_flags); if (hovered) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb + if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay)) SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); + ImRect bb_render = bb; if (held) { float mouse_delta = (g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min)[axis]; + // Minimum pane size float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1); float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2); @@ -1539,6 +1805,7 @@ bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float mouse_delta = -size_1_maximum_delta; if (mouse_delta > size_2_maximum_delta) mouse_delta = size_2_maximum_delta; + // Apply resize if (mouse_delta != 0.0f) { @@ -1548,13 +1815,16 @@ bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float MarkItemEdited(id); } } + // Render at new position if (bg_col & IM_COL32_A_MASK) window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, bg_col, 0.0f); const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f); + return held; } + static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs) { const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs; @@ -1563,30 +1833,36 @@ static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs) return d; return (b->Index - a->Index); } + // Shrink excess width from a set of item, by removing width from the larger items first. // Set items Width to -1.0f to disable shrinking this item. -void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess) +void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess, float width_min) { if (count == 1) { if (items[0].Width >= 0.0f) - items[0].Width = ImMax(items[0].Width - width_excess, 1.0f); + items[0].Width = ImMax(items[0].Width - width_excess, width_min); return; } - ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer); + ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer); // Sort largest first, smallest last. int count_same_width = 1; - while (width_excess > 0.0f && count_same_width < count) + while (width_excess > 0.001f && count_same_width < count) { while (count_same_width < count && items[0].Width <= items[count_same_width].Width) count_same_width++; float max_width_to_remove_per_item = (count_same_width < count && items[count_same_width].Width >= 0.0f) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f); + max_width_to_remove_per_item = ImMin(items[0].Width - width_min, max_width_to_remove_per_item); if (max_width_to_remove_per_item <= 0.0f) break; - float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item); + float base_width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item); for (int item_n = 0; item_n < count_same_width; item_n++) - items[item_n].Width -= width_to_remove_per_item; - width_excess -= width_to_remove_per_item * count_same_width; + { + float width_to_remove_for_this_item = ImMin(base_width_to_remove_per_item, items[item_n].Width - width_min); + items[item_n].Width -= width_to_remove_for_this_item; + width_excess -= width_to_remove_for_this_item; + } } + // Round width and redistribute remainder // Ensure that e.g. the right-most tab of a shrunk tab-bar always reaches exactly at the same distance from the right-most edge of the tab bar separator. width_excess = 0.0f; @@ -1604,6 +1880,7 @@ void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_exc width_excess -= width_to_add; } } + //------------------------------------------------------------------------- // [SECTION] Widgets: ComboBox //------------------------------------------------------------------------- @@ -1615,6 +1892,7 @@ void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_exc // - EndComboPreview() [Internal] // - Combo() //------------------------------------------------------------------------- + static float CalcMaxPopupHeightFromItemCount(int items_count) { ImGuiContext& g = *GImGui; @@ -1622,19 +1900,23 @@ static float CalcMaxPopupHeightFromItemCount(int items_count) return FLT_MAX; return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2); } + bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); + ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.HasFlags; g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values if (window->SkipItems) return false; + const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together if (flags & ImGuiComboFlags_WidthFitPreview) IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0); + const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); const ImVec2 label_size = CalcTextSize(label, NULL, true); const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, true).x : 0.0f; @@ -1644,6 +1926,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, id, &bb)) return false; + // Open on click bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held); @@ -1654,6 +1937,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF OpenPopupEx(popup_id, ImGuiPopupFlags_None); popup_open = true; } + // Render shape const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size); @@ -1669,6 +1953,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f); } RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding); + // Custom preview if (flags & ImGuiComboFlags_CustomPreview) { @@ -1676,6 +1961,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF IM_ASSERT(preview_value == NULL || preview_value[0] == 0); preview_value = NULL; } + // Render preview and label if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview)) { @@ -1685,11 +1971,14 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF } if (label_size.x > 0) RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label); + if (!popup_open) return false; + g.NextWindowData.HasFlags = backup_next_window_data_flags; return BeginComboPopup(popup_id, bb, flags); } + bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags) { ImGuiContext& g = *GImGui; @@ -1698,6 +1987,7 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags g.NextWindowData.ClearFlags(); return false; } + // Set popup size float w = bb.GetWidth(); if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint) @@ -1720,9 +2010,11 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags constraint_max.y = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items); SetNextWindowSizeConstraints(constraint_min, constraint_max); } + // This is essentially a specialized version of BeginPopupEx() char name[16]; ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth + // Set position given a custom constraint (peak into expected window size so we can position it) // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function? // FIXME: This might be moved to Begin() or at least around the same spot where Tooltips and other Popups are calling FindBestWindowPosForPopupEx()? @@ -1736,6 +2028,7 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags ImVec2 pos = FindBestWindowPosForPopupEx(bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, bb, ImGuiPopupPositionPolicy_ComboBox); SetNextWindowPos(pos); } + // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx() ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove; PushStyleVarX(ImGuiStyleVar_WindowPadding, g.Style.FramePadding.x); // Horizontally align ourselves with the framed text @@ -1750,12 +2043,14 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags g.BeginComboDepth++; return true; } + void ImGui::EndCombo() { ImGuiContext& g = *GImGui; EndPopup(); g.BeginComboDepth--; } + // Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements // (Experimental, see GitHub issues: #1658, #4168) bool ImGui::BeginComboPreview() @@ -1763,11 +2058,13 @@ bool ImGui::BeginComboPreview() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; + if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)) return false; IM_ASSERT(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y); // Didn't call after BeginCombo/EndCombo block or forgot to pass ImGuiComboFlags_CustomPreview flag? if (!window->ClipRect.Overlaps(preview_data->PreviewRect)) // Narrower test (optional) return false; + // FIXME: This could be contained in a PushWorkRect() api preview_data->BackupCursorPos = window->DC.CursorPos; preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos; @@ -1779,13 +2076,16 @@ bool ImGui::BeginComboPreview() window->DC.LayoutType = ImGuiLayoutType_Horizontal; window->DC.IsSameLine = false; PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true); + return true; } + void ImGui::EndComboPreview() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; + // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future ImDrawList* draw_list = window->DrawList; if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y) @@ -1803,12 +2103,14 @@ void ImGui::EndComboPreview() window->DC.IsSameLine = false; preview_data->PreviewRect = ImRect(); } + // Getter for the old Combo() API: const char*[] static const char* Items_ArrayGetter(void* data, int idx) { const char* const* items = (const char* const*)data; return items[idx]; } + // Getter for the old Combo() API: "item1\0item2\0item3\0" static const char* Items_SingleStringGetter(void* data, int idx) { @@ -1824,19 +2126,24 @@ static const char* Items_SingleStringGetter(void* data, int idx) } return *p ? p : NULL; } + // Old API, prefer using BeginCombo() nowadays if you can. bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int popup_max_height_in_items) { ImGuiContext& g = *GImGui; + // Call the getter to obtain the preview string which is a parameter to BeginCombo() const char* preview_value = NULL; if (*current_item >= 0 && *current_item < items_count) preview_value = getter(user_data, *current_item); + // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here. if (popup_max_height_in_items != -1 && !(g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint)) SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); + if (!BeginCombo(label, preview_value, ImGuiComboFlags_None)) return false; + // Display items bool value_changed = false; ImGuiListClipper clipper; @@ -1848,6 +2155,7 @@ bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(vo const char* item_text = getter(user_data, i); if (item_text == NULL) item_text = "*Unknown item*"; + PushID(i); const bool item_selected = (i == *current_item); if (Selectable(item_text, item_selected) && *current_item != i) @@ -1859,17 +2167,21 @@ bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(vo SetItemDefaultFocus(); PopID(); } + EndCombo(); if (value_changed) MarkItemEdited(g.LastItemData.ID); + return value_changed; } + // Combo box helper allowing to pass an array of strings. bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items) { const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items); return value_changed; } + // Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0" bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items) { @@ -1883,7 +2195,9 @@ bool ImGui::Combo(const char* label, int* current_item, const char* items_separa bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items); return value_changed; } + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + struct ImGuiGetNameFromIndexOldToNewCallbackData { void* UserData; bool (*OldCallback)(void*, int, const char**); }; static const char* ImGuiGetNameFromIndexOldToNewCallback(void* user_data, int idx) { @@ -1892,6 +2206,7 @@ static const char* ImGuiGetNameFromIndexOldToNewCallback(void* user_data, int id data->OldCallback(data->UserData, idx, &s); return s; } + bool ImGui::ListBox(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int height_in_items) { ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter }; @@ -1902,7 +2217,9 @@ bool ImGui::Combo(const char* label, int* current_item, bool (*old_getter)(void* ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter }; return Combo(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, popup_max_height_in_items); } + #endif + //------------------------------------------------------------------------- // [SECTION] Data Type and Data Formatting Helpers [Internal] //------------------------------------------------------------------------- @@ -1915,6 +2232,7 @@ bool ImGui::Combo(const char* label, int* current_item, bool (*old_getter)(void* // - GetMinimumStepAtDecimalPrecision // - RoundScalarWithFormat<>() //------------------------------------------------------------------------- + static const ImGuiDataTypeInfo GDataTypeInfo[] = { { sizeof(char), "S8", "%d", "%d" }, // ImGuiDataType_S8 @@ -1936,11 +2254,13 @@ static const ImGuiDataTypeInfo GDataTypeInfo[] = { 0, "char*","%s", "%s" }, // ImGuiDataType_String }; IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); + const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type) { IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); return &GDataTypeInfo[data_type]; } + int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format) { // Signedness doesn't matter when pushing integer arguments @@ -1963,6 +2283,7 @@ int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type IM_ASSERT(0); return 0; } + void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2) { IM_ASSERT(op == '+' || op == '-'); @@ -2012,6 +2333,7 @@ void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const } IM_ASSERT(0); } + // User can input math operators (e.g. +100) to edit a numerical values. // NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess.. bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format, void* p_data_when_empty) @@ -2020,6 +2342,7 @@ bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type); ImGuiDataTypeStorage data_backup; memcpy(&data_backup, p_data, type_info->Size); + while (ImCharIsBlankA(*buf)) buf++; if (!buf[0]) @@ -2031,6 +2354,7 @@ bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void } return false; } + // Sanitize format // - For float/double we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in, so force them into %f and %lf // - In theory could treat empty format as using default, but this would only cover rare/bizarre case of using InputScalar() + integer + format string without %. @@ -2039,6 +2363,7 @@ bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void format = type_info->ScanFmt; else format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_ARRAYSIZE(format_sanitized)); + // Small types need a 32-bit buffer to receive the result from scanf() int v32 = 0; if (sscanf(buf, format, type_info->Size >= 4 ? p_data : &v32) < 1) @@ -2056,8 +2381,10 @@ bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void else IM_ASSERT(0); } + return memcmp(&data_backup, p_data, type_info->Size) != 0; } + template static int DataTypeCompareT(const T* lhs, const T* rhs) { @@ -2065,6 +2392,7 @@ static int DataTypeCompareT(const T* lhs, const T* rhs) if (*lhs > *rhs) return +1; return 0; } + int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2) { switch (data_type) @@ -2084,6 +2412,7 @@ int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const voi IM_ASSERT(0); return 0; } + template static bool DataTypeClampT(T* v, const T* v_min, const T* v_max) { @@ -2092,6 +2421,7 @@ static bool DataTypeClampT(T* v, const T* v_min, const T* v_max) if (v_max && *v > *v_max) { *v = *v_max; return true; } return false; } + bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max) { switch (data_type) @@ -2111,11 +2441,13 @@ bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_m IM_ASSERT(0); return false; } + bool ImGui::DataTypeIsZero(ImGuiDataType data_type, const void* p_data) { ImGuiContext& g = *GImGui; return DataTypeCompare(data_type, p_data, &g.DataTypeZeroValue) == 0; } + static float GetMinimumStepAtDecimalPrecision(int decimal_precision) { static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f }; @@ -2123,6 +2455,7 @@ static float GetMinimumStepAtDecimalPrecision(int decimal_precision) return FLT_MIN; return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision); } + template TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v) { @@ -2131,10 +2464,12 @@ TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, const char* fmt_start = ImParseFormatFindStart(format); if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string return v; + // Sanitize format char fmt_sanitized[32]; ImParseFormatSanitizeForPrinting(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized)); fmt_start = fmt_sanitized; + // Format value with our rounding, and read back char v_str[64]; ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v); @@ -2142,8 +2477,10 @@ TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, while (*p == ' ') p++; v = (TYPE)ImAtof(p); + return v; } + //------------------------------------------------------------------------- // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc. //------------------------------------------------------------------------- @@ -2162,6 +2499,7 @@ TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, // - DragInt4() // - DragIntRange2() //------------------------------------------------------------------------- + // This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls) template bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags) @@ -2172,9 +2510,11 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const const bool is_wrapped = is_bounded && (flags & ImGuiSliderFlags_WrapAround); const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0; const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); + // Default tweak speed if (v_speed == 0.0f && is_bounded && (v_max - v_min < FLT_MAX)) v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio); + // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings float adjust_delta = 0.0f; if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) @@ -2195,12 +2535,15 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); } adjust_delta *= v_speed; + // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter. if (axis == ImGuiAxis_Y) adjust_delta = -adjust_delta; + // For logarithmic use our range is effectively 0..1 so scale the delta into that range if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) // Epsilon to avoid /0 adjust_delta /= (float)(v_max - v_min); + // Clear current value on activation // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300. const bool is_just_activated = g.ActiveIdIsJustActivated; @@ -2215,10 +2558,13 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const g.DragCurrentAccum += adjust_delta; g.DragCurrentAccumDirty = true; } + if (!g.DragCurrentAccumDirty) return false; + TYPE v_cur = *v; FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f; + float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true const float zero_deadzone_halfsize = 0.0f; // Drag widgets have no deadzone (as it doesn't make sense) if (is_logarithmic) @@ -2226,6 +2572,7 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1; logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision); + // Convert to parametric space, apply delta, convert back float v_old_parametric = ScaleRatioFromValueT(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); float v_new_parametric = v_old_parametric + g.DragCurrentAccum; @@ -2236,9 +2583,11 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const { v_cur += (SIGNEDTYPE)g.DragCurrentAccum; } + // Round to user desired precision based on format string if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat)) v_cur = RoundScalarWithFormatT(format, data_type, v_cur); + // Preserve remainder after rounding has been applied. This also allow slow tweaking of values. g.DragCurrentAccumDirty = false; if (is_logarithmic) @@ -2251,9 +2600,11 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const { g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v); } + // Lose zero sign for float/double if (v_cur == (TYPE)-0) v_cur = (TYPE)0; + if (*v != v_cur && is_bounded) { if (is_wrapped) @@ -2273,16 +2624,19 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const v_cur = v_max; } } + // Apply result if (*v == v_cur) return false; *v = v_cur; return true; } + bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert. IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); + ImGuiContext& g = *GImGui; if (g.ActiveId == id) { @@ -2296,6 +2650,7 @@ bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v return false; if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) return false; + switch (data_type) { case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = DragBehaviorT(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS8*) p_min : IM_S8_MIN, p_max ? *(const ImS8*)p_max : IM_S8_MAX, format, flags); if (r) *(ImS8*)p_v = (ImS8)v32; return r; } @@ -2313,6 +2668,7 @@ bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v IM_ASSERT(0); return false; } + // Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a Drag widget, p_min and p_max are optional. // Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly. bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) @@ -2320,20 +2676,25 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const float w = CalcItemWidth(); + const ImVec2 label_size = CalcTextSize(label, NULL, true); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0)) return false; + // Default format string when passing NULL if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; + const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags); bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); if (!temp_input_is_active) @@ -2347,6 +2708,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, if (make_active && temp_input_allowed) if ((clicked && g.IO.KeyCtrl) || double_clicked || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput))) temp_input_is_active = true; + // (Optional) simple click (without moving) turns Drag into an InputText if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active) if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) @@ -2355,9 +2717,11 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, g.NavActivateFlags = ImGuiActivateFlags_PreferInput; temp_input_is_active = true; } + // Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert) if (make_active) memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size); + if (make_active && !temp_input_is_active) { SetActiveID(id, window); @@ -2366,6 +2730,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); } } + if (temp_input_is_active) { // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp) @@ -2380,30 +2745,37 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, } return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL); } + // Draw frame const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); RenderNavCursor(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); + // Drag behavior const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags); if (value_changed) MarkItemEdited(id); + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. char value_buf[64]; const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); + if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0)); return value_changed; } + bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; bool value_changed = false; BeginGroup(); @@ -2421,102 +2793,125 @@ bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data p_data = (void*)((char*)p_data + type_size); } PopID(); + const char* label_end = FindRenderedTextEnd(label); if (label != label_end) { SameLine(0, g.Style.ItemInnerSpacing.x); TextEx(label, label_end); } + EndGroup(); return value_changed; } + bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, flags); } + bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, flags); } + bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, flags); } + bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, flags); } + // NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this. bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, ImGuiSliderFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; PushID(label); BeginGroup(); PushMultiItemsWidths(2, CalcItemWidth()); + float min_min = (v_min >= v_max) ? -FLT_MAX : v_min; float min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max); ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0); bool value_changed = DragScalar("##min", ImGuiDataType_Float, v_current_min, v_speed, &min_min, &min_max, format, min_flags); PopItemWidth(); SameLine(0, g.Style.ItemInnerSpacing.x); + float max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min); float max_max = (v_min >= v_max) ? FLT_MAX : v_max; ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0); value_changed |= DragScalar("##max", ImGuiDataType_Float, v_current_max, v_speed, &max_min, &max_max, format_max ? format_max : format, max_flags); PopItemWidth(); SameLine(0, g.Style.ItemInnerSpacing.x); + TextEx(label, FindRenderedTextEnd(label)); EndGroup(); PopID(); + return value_changed; } + // NB: v_speed is float to allow adjusting the drag speed with more precision bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format, flags); } + bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format, flags); } + bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format, flags); } + bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format, flags); } + // NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this. bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max, ImGuiSliderFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; PushID(label); BeginGroup(); PushMultiItemsWidths(2, CalcItemWidth()); + int min_min = (v_min >= v_max) ? INT_MIN : v_min; int min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max); ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0); bool value_changed = DragInt("##min", v_current_min, v_speed, min_min, min_max, format, min_flags); PopItemWidth(); SameLine(0, g.Style.ItemInnerSpacing.x); + int max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min); int max_max = (v_min >= v_max) ? INT_MAX : v_max; ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0); value_changed |= DragInt("##max", v_current_max, v_speed, max_min, max_max, format_max ? format_max : format, max_flags); PopItemWidth(); SameLine(0, g.Style.ItemInnerSpacing.x); + TextEx(label, FindRenderedTextEnd(label)); EndGroup(); PopID(); + return value_changed; } + //------------------------------------------------------------------------- // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc. //------------------------------------------------------------------------- @@ -2539,6 +2934,7 @@ bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_ // - VSliderFloat() // - VSliderInt() //------------------------------------------------------------------------- + // Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT) template float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize) @@ -2546,20 +2942,25 @@ float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, T if (v_min == v_max) return 0.0f; IM_UNUSED(data_type); + const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min); if (is_logarithmic) { bool flipped = v_max < v_min; + if (flipped) // Handle the case where the range is backwards ImSwap(v_min, v_max); + // Fudge min/max to avoid getting close to log(0) FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min; FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max; + // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) if ((v_min == 0.0f) && (v_max < 0.0f)) v_min_fudged = -logarithmic_zero_epsilon; else if ((v_max == 0.0f) && (v_min < 0.0f)) v_max_fudged = -logarithmic_zero_epsilon; + float result; if (v_clamped <= v_min_fudged) result = 0.0f; // Workaround for values that are in-range but below our fudge @@ -2581,6 +2982,7 @@ float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, T result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged)); else result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged)); + return flipped ? (1.0f - result) : result; } else @@ -2589,6 +2991,7 @@ float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, T return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min)); } } + // Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT) template TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize) @@ -2599,19 +3002,24 @@ TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, T return v_min; if (t >= 1.0f) return v_max; + TYPE result = (TYPE)0; if (is_logarithmic) { // Fudge min/max to avoid getting silly results close to zero FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min; FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max; + const bool flipped = v_max < v_min; // Check if range is "backwards" if (flipped) ImSwap(v_min_fudged, v_max_fudged); + // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) if ((v_max == 0.0f) && (v_min < 0.0f)) v_max_fudged = -logarithmic_zero_epsilon; + float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range + if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts { float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space @@ -2647,18 +3055,22 @@ TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, T result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5))); } } + return result; } + // FIXME: Try to move more of the code into shared SliderBehavior() template bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb) { ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; + const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0; const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); const float v_range_f = (float)(v_min < v_max ? v_max - v_min : v_min - v_max); // We don't need high precision for what we do with it. + // Calculate bounds const float grab_padding = 2.0f; // FIXME: Should be part of style. const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f; @@ -2669,6 +3081,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ const float slider_usable_sz = slider_sz - grab_sz; const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f; const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f; + float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true float zero_deadzone_halfsize = 0.0f; // Only valid when is_logarithmic is true if (is_logarithmic) @@ -2678,6 +3091,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision); zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5f) / ImMax(slider_usable_sz, 1.0f); } + // Process interacting with the slider bool value_changed = false; if (g.ActiveId == id) @@ -2716,6 +3130,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation g.SliderCurrentAccumDirty = false; } + float input_delta = (axis == ImGuiAxis_X) ? GetNavTweakPressedAmount(axis) : -GetNavTweakPressedAmount(axis); if (input_delta != 0.0f) { @@ -2737,9 +3152,11 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ } if (tweak_fast) input_delta *= 10.0f; + g.SliderCurrentAccum += input_delta; g.SliderCurrentAccumDirty = true; } + float delta = g.SliderCurrentAccum; if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) { @@ -2748,6 +3165,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ else if (g.SliderCurrentAccumDirty) { clicked_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits { set_new_value = false; @@ -2758,28 +3176,35 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ set_new_value = true; float old_clicked_t = clicked_t; clicked_t = ImSaturate(clicked_t + delta); + // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator TYPE v_new = ScaleValueFromRatioT(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat)) v_new = RoundScalarWithFormatT(format, data_type, v_new); float new_clicked_t = ScaleRatioFromValueT(data_type, v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + if (delta > 0) g.SliderCurrentAccum -= ImMin(new_clicked_t - old_clicked_t, delta); else g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta); } + g.SliderCurrentAccumDirty = false; } } + if (set_new_value) if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) set_new_value = false; + if (set_new_value) { TYPE v_new = ScaleValueFromRatioT(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + // Round to user desired precision based on format string if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat)) v_new = RoundScalarWithFormatT(format, data_type, v_new); + // Apply result if (*v != v_new) { @@ -2788,6 +3213,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ } } } + if (slider_sz < 1.0f) { *out_grab_bb = ImRect(bb.Min, bb.Min); @@ -2804,8 +3230,10 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ else *out_grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz * 0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz * 0.5f); } + return value_changed; } + // For 32-bit and larger types, slider bounds are limited to half the natural type range. // So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok. // It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders. @@ -2814,6 +3242,7 @@ bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert. IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); IM_ASSERT((flags & ImGuiSliderFlags_WrapAround) == 0); // Not supported by SliderXXX(), only by DragXXX() + switch (data_type) { case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_S32, &v32, *(const ImS8*)p_min, *(const ImS8*)p_max, format, flags, out_grab_bb); if (r) *(ImS8*)p_v = (ImS8)v32; return r; } @@ -2843,6 +3272,7 @@ bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type IM_ASSERT(0); return false; } + // Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a slider, they are all required. // Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly. bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) @@ -2850,20 +3280,25 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const float w = CalcItemWidth(); + const ImVec2 label_size = CalcTextSize(label, NULL, true); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0)) return false; + // Default format string when passing NULL if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; + const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags); bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); if (!temp_input_is_active) @@ -2876,9 +3311,11 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat if (make_active && temp_input_allowed) if ((clicked && g.IO.KeyCtrl) || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput))) temp_input_is_active = true; + // Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert) if (make_active) memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size); + if (make_active && !temp_input_is_active) { SetActiveID(id, window); @@ -2887,41 +3324,50 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); } } + if (temp_input_is_active) { // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp) const bool clamp_enabled = (flags & ImGuiSliderFlags_ClampOnInput) != 0; return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL); } + // Draw frame const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); RenderNavCursor(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); + // Slider behavior ImRect grab_bb; const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags, &grab_bb); if (value_changed) MarkItemEdited(id); + // Render grab if (grab_bb.Max.x > grab_bb.Min.x) window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. char value_buf[64]; const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); + if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0)); return value_changed; } + // Add multiple sliders on 1 line for compact edition of multiple components bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, ImGuiSliderFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; bool value_changed = false; BeginGroup(); @@ -2939,31 +3385,38 @@ bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, i v = (void*)((char*)v + type_size); } PopID(); + const char* label_end = FindRenderedTextEnd(label); if (label != label_end) { SameLine(0, g.Style.ItemInnerSpacing.x); TextEx(label, label_end); } + EndGroup(); return value_changed; } + bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, flags); } + bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, flags); } + bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, flags); } + bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, flags); } + bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags) { if (format == NULL) @@ -2974,39 +3427,49 @@ bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, fl *v_rad = v_deg * (2 * IM_PI) / 360.0f; return value_changed; } + bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format, flags); } + bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format, flags); } + bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format, flags); } + bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format, flags); } + bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + ItemSize(bb, style.FramePadding.y); if (!ItemAdd(frame_bb, id)) return false; + // Default format string when passing NULL if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; + const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags); const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id); if (clicked || g.NavActivateId == id) @@ -3018,18 +3481,22 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d FocusWindow(window); g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); } + // Draw frame const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); RenderNavCursor(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); + // Slider behavior ImRect grab_bb; const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags | ImGuiSliderFlags_Vertical, &grab_bb); if (value_changed) MarkItemEdited(id); + // Render grab if (grab_bb.Max.y > grab_bb.Min.y) window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. // For the vertical slider we allow centered text to overlap the frame padding char value_buf[64]; @@ -3037,16 +3504,20 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.0f)); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + return value_changed; } + bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, flags); } + bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags); } + //------------------------------------------------------------------------- // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc. //------------------------------------------------------------------------- @@ -3069,6 +3540,7 @@ bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, // - InputInt4() // - InputDouble() //------------------------------------------------------------------------- + // We don't use strchr() because our strings are usually very short and often start with '%' const char* ImParseFormatFindStart(const char* fmt) { @@ -3082,6 +3554,7 @@ const char* ImParseFormatFindStart(const char* fmt) } return fmt; } + const char* ImParseFormatFindEnd(const char* fmt) { // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format. @@ -3098,6 +3571,7 @@ const char* ImParseFormatFindEnd(const char* fmt) } return fmt; } + // Extract the format out of a format string with leading or trailing decorations // fmt = "blah blah" -> return "" // fmt = "%.3f" -> return fmt @@ -3114,6 +3588,7 @@ const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_ ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size)); return buf; } + // Sanitize format // - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi // - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi. @@ -3130,6 +3605,7 @@ void ImParseFormatSanitizeForPrinting(const char* fmt_in, char* fmt_out, size_t } *fmt_out = 0; // Zero-terminate } + // - For scanning we need to remove all width and precision fields and flags "%+3.7f" -> "%f". BUT don't strip types like "%I64d" which includes digits. ! "%07I64d" -> "%I64d" const char* ImParseFormatSanitizeForScanning(const char* fmt_in, char* fmt_out, size_t fmt_out_size) { @@ -3150,6 +3626,7 @@ const char* ImParseFormatSanitizeForScanning(const char* fmt_in, char* fmt_out, *fmt_out = 0; // Zero-terminate return fmt_out_begin; } + template static const char* ImAtoi(const char* src, TYPE* output) { @@ -3162,6 +3639,7 @@ static const char* ImAtoi(const char* src, TYPE* output) *output = negative ? -v : v; return src; } + // Parse display precision back from the display format string // FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed. int ImParseFormatPrecision(const char* fmt, int default_precision) @@ -3185,6 +3663,7 @@ int ImParseFormatPrecision(const char* fmt, int default_precision) precision = -1; return (precision == INT_MAX) ? default_precision : precision; } + // Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets) // FIXME: Facilitate using this in variety of other situations. // FIXME: Among other things, setting ImGuiItemFlags_AllowDuplicateId in LastItemData is currently correct but @@ -3197,6 +3676,7 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* const bool init = (g.TempInputId != id); if (init) ClearActiveID(); + g.CurrentWindow->DC.CursorPos = bb.Min; g.LastItemData.ItemFlags |= ImGuiItemFlags_AllowDuplicateId; bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem); @@ -3208,6 +3688,7 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* } return value_changed; } + // Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set! // This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility. // However this may not be ideal for all uses, as some user code may break on out of bound values. @@ -3224,6 +3705,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG format = type_info->PrintFmt; DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format); ImStrTrimBlanks(data_buf); + ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; g.LastItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; // Because TempInputText() uses ImGuiInputTextFlags_MergedItem it doesn't submit a new item, so we poke LastItemData. bool value_changed = false; @@ -3233,6 +3715,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG size_t data_type_size = type_info->Size; ImGuiDataTypeStorage data_backup; memcpy(&data_backup, p_data, data_type_size); + // Apply new value (or operations) then clamp DataTypeApplyFromText(data_buf, data_type, p_data, format, NULL); if (p_clamp_min || p_clamp_max) @@ -3241,6 +3724,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG ImSwap(p_clamp_min, p_clamp_max); DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max); } + // Only mark as edited if new value is different g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; value_changed = memcmp(&data_backup, p_data, data_type_size) != 0; @@ -3249,12 +3733,14 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG } return value_changed; } + void ImGui::SetNextItemRefVal(ImGuiDataType data_type, void* p_data) { ImGuiContext& g = *GImGui; g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasRefVal; memcpy(&g.NextItemData.RefVal, p_data, DataTypeGetInfo(data_type)->Size); } + // Note: p_data, p_step, p_step_fast are _pointers_ to a memory address holding the data. For an Input widget, p_step and p_step_fast are optional. // Read code of e.g. InputFloat(), InputInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly. bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags) @@ -3262,21 +3748,27 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; IM_ASSERT((flags & ImGuiInputTextFlags_EnterReturnsTrue) == 0); // Not supported by InputScalar(). Please open an issue if you this would be useful to you. Otherwise use IsItemDeactivatedAfterEdit()! + if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; + void* p_data_default = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasRefVal) ? &g.NextItemData.RefVal : &g.DataTypeZeroValue; + char buf[64]; if ((flags & ImGuiInputTextFlags_DisplayEmptyRefVal) && DataTypeCompare(data_type, p_data, p_data_default) == 0) buf[0] = 0; else DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format); + // Disable the MarkItemEdited() call in InputText but keep ImGuiItemStatusFlags_Edited. // We call MarkItemEdited() ourselves by comparing the actual data rather than the string. g.NextItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; + bool value_changed = false; if (p_step == NULL) { @@ -3286,12 +3778,14 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data else { const float button_size = GetFrameHeight(); + BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive() PushID(label); SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2)); if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable); + // Step buttons const ImVec2 backup_frame_padding = style.FramePadding; style.FramePadding.x = style.FramePadding.y; @@ -3313,6 +3807,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data PopItemFlag(); if (flags & ImGuiInputTextFlags_ReadOnly) EndDisabled(); + const char* label_end = FindRenderedTextEnd(label); if (label != label_end) { @@ -3320,19 +3815,24 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data TextEx(label, label_end); } style.FramePadding = backup_frame_padding; + PopID(); EndGroup(); } + g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; if (value_changed) MarkItemEdited(g.LastItemData.ID); + return value_changed; } + bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; bool value_changed = false; BeginGroup(); @@ -3350,53 +3850,65 @@ bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* p_dat p_data = (void*)((char*)p_data + type_size); } PopID(); + const char* label_end = FindRenderedTextEnd(label); if (label != label_end) { SameLine(0.0f, g.Style.ItemInnerSpacing.x); TextEx(label, label_end); } + EndGroup(); return value_changed; } + bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags) { return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), format, flags); } + bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags) { return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags); } + bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags) { return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags); } + bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags) { return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags); } + bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags) { // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes. const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d"; return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step > 0 ? &step : NULL), (void*)(step_fast > 0 ? &step_fast : NULL), format, flags); } + bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags) { return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags); } + bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags) { return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags); } + bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags) { return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags); } + bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags) { return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step > 0.0 ? &step : NULL), (void*)(step_fast > 0.0 ? &step_fast : NULL), format, flags); } + //------------------------------------------------------------------------- // [SECTION] Widgets: InputText, InputTextMultiline, InputTextWithHint //------------------------------------------------------------------------- @@ -3410,25 +3922,30 @@ bool ImGui::InputDouble(const char* label, double* v, double step, double step_f // - InputTextEx() [Internal] // - DebugNodeInputTextState() [Internal] //------------------------------------------------------------------------- + namespace ImStb { #include "imstb_textedit.h" } + bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() return InputTextEx(label, NULL, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data); } + bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data); } + bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() or InputTextEx() manually if you need multi-line + hint. return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data); } -// This is only used in the path where the multiline widget is inactivate. + +// This is only used in the path where the multiline widget is inactive. static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) { int line_count = 0; @@ -3447,15 +3964,19 @@ static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** *out_text_end = s; return line_count; } + // FIXME: Ideally we'd share code with ImFont::CalcTextSizeA() static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining, ImVec2* out_offset, bool stop_on_new_line) { ImGuiContext& g = *ctx; - ImFont* font = g.Font; + //ImFont* font = g.Font; + ImFontBaked* baked = g.FontBaked; const float line_height = g.FontSize; - const float scale = line_height / font->FontSize; + const float scale = line_height / baked->Size; + ImVec2 text_size = ImVec2(0, 0); float line_width = 0.0f; + const char* s = text_begin; while (s < text_end) { @@ -3464,6 +3985,7 @@ static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, c s += 1; else s += ImTextCharFromUtf8(&c, s, text_end); + if (c == '\n') { text_size.x = ImMax(text_size.x, line_width); @@ -3475,19 +3997,25 @@ static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, c } if (c == '\r') continue; - const float char_width = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX) * scale; - line_width += char_width; + + line_width += baked->GetCharAdvance((ImWchar)c) * scale; } + if (text_size.x < line_width) text_size.x = line_width; + if (out_offset) *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n + if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n text_size.y += line_height; + if (remaining) *remaining = s; + return text_size; } + // Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) // With our UTF-8 use of stb_textedit: // - STB_TEXTEDIT_GETCHAR is nothing more than a a "GETBYTE". It's only used to compare to ascii or to copy blocks of text so we are fine. @@ -3497,7 +4025,7 @@ namespace ImStb { static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->TextLen; } static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->TextLen); return obj->TextSrc[idx]; } -static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; } +static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.FontBaked->GetCharAdvance((ImWchar)c) * g.FontBakedScale; } static char STB_TEXTEDIT_NEWLINE = '\n'; static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx) { @@ -3511,8 +4039,10 @@ static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* ob r->ymax = size.y; r->num_chars = (int)(text_remaining - (text + line_start_idx)); } + #define IMSTB_TEXTEDIT_GETNEXTCHARINDEX IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL #define IMSTB_TEXTEDIT_GETPREVCHARINDEX IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL + static int IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx) { if (idx >= obj->TextLen) @@ -3520,6 +4050,7 @@ static int IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL(ImGuiInputTextState* obj, int id unsigned int c; return idx + ImTextCharFromUtf8(&c, obj->TextSrc + idx, obj->TextSrc + obj->TextLen); } + static int IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx) { if (idx <= 0) @@ -3527,6 +4058,7 @@ static int IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL(ImGuiInputTextState* obj, int id const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, obj->TextSrc + idx); return (int)(p - obj->TextSrc); } + static bool ImCharIsSeparatorW(unsigned int c) { static const unsigned int separator_list[] = @@ -3540,15 +4072,18 @@ static bool ImCharIsSeparatorW(unsigned int c) return true; return false; } + static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx) { // When ImGuiInputTextFlags_Password is set, we don't want actions such as CTRL+Arrow to leak the fact that underlying data are blanks or separators. if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0) return 0; + const char* curr_p = obj->TextSrc + idx; const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p); unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextSrc + obj->TextLen); unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextSrc + obj->TextLen); + bool prev_white = ImCharIsBlankW(prev_c); bool prev_separ = ImCharIsSeparatorW(prev_c); bool curr_white = ImCharIsBlankW(curr_c); @@ -3559,10 +4094,12 @@ static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) { if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0) return 0; + const char* curr_p = obj->TextSrc + idx; const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p); unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextSrc + obj->TextLen); unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextSrc + obj->TextLen); + bool prev_white = ImCharIsBlankW(prev_c); bool prev_separ = ImCharIsSeparatorW(prev_c); bool curr_white = ImCharIsBlankW(curr_c); @@ -3595,6 +4132,7 @@ static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx) static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { ImGuiContext& g = *obj->Ctx; if (g.IO.ConfigMacOSXBehaviors) return STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx); else return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx); } #define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h #define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL + static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n) { // Offset remaining text (+ copy zero terminator) @@ -3605,13 +4143,16 @@ static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n) obj->Edited = true; obj->TextLen -= n; } + static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const char* new_text, int new_text_len) { const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0; const int text_len = obj->TextLen; IM_ASSERT(pos <= text_len); + if (!is_resizable && (new_text_len + obj->TextLen + 1 > obj->BufCapacity)) return false; + // Grow internal buffer if needed IM_ASSERT(obj->TextSrc == obj->TextA.Data); if (new_text_len + text_len + 1 > obj->TextA.Size) @@ -3621,15 +4162,19 @@ static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ch obj->TextA.resize(text_len + ImClamp(new_text_len, 32, ImMax(256, new_text_len)) + 1); obj->TextSrc = obj->TextA.Data; } + char* text = obj->TextA.Data; if (pos != text_len) memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos)); memcpy(text + pos, new_text, (size_t)new_text_len); + obj->Edited = true; obj->TextLen += new_text_len; obj->TextA[obj->TextLen] = '\0'; + return true; } + // We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols) #define STB_TEXTEDIT_K_LEFT 0x200000 // keyboard input to move cursor left #define STB_TEXTEDIT_K_RIGHT 0x200001 // keyboard input to move cursor right @@ -3648,9 +4193,11 @@ static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ch #define STB_TEXTEDIT_K_PGUP 0x20000E // keyboard input to move cursor up a page #define STB_TEXTEDIT_K_PGDOWN 0x20000F // keyboard input to move cursor down a page #define STB_TEXTEDIT_K_SHIFT 0x400000 + #define IMSTB_TEXTEDIT_IMPLEMENTATION #define IMSTB_TEXTEDIT_memmove memmove #include "imstb_textedit.h" + // stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling // the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?) static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len) @@ -3668,7 +4215,9 @@ static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* st } IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace() } + } // namespace ImStb + // We added an extra indirection where 'Stb' is heap-allocated, in order facilitate the work of bindings generators. ImGuiInputTextState::ImGuiInputTextState() { @@ -3676,16 +4225,19 @@ ImGuiInputTextState::ImGuiInputTextState() Stb = IM_NEW(ImStbTexteditState); memset(Stb, 0, sizeof(*Stb)); } + ImGuiInputTextState::~ImGuiInputTextState() { IM_DELETE(Stb); } + void ImGuiInputTextState::OnKeyPressed(int key) { stb_textedit_key(this, Stb, key); CursorFollow = true; CursorAnimReset(); } + void ImGuiInputTextState::OnCharPressed(unsigned int c) { // Convert the key to a UTF8 byte sequence. @@ -3696,6 +4248,7 @@ void ImGuiInputTextState::OnCharPressed(unsigned int c) CursorFollow = true; CursorAnimReset(); } + // Those functions are not inlined in imgui_internal.h, allowing us to hide ImStbTexteditState from that header. void ImGuiInputTextState::CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking void ImGuiInputTextState::CursorClamp() { Stb->cursor = ImMin(Stb->cursor, TextLen); Stb->select_start = ImMin(Stb->select_start, TextLen); Stb->select_end = ImMin(Stb->select_end, TextLen); } @@ -3708,10 +4261,12 @@ void ImGuiInputTextState::SelectAll() { Stb->select_start void ImGuiInputTextState::ReloadUserBufAndSelectAll() { WantReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; } void ImGuiInputTextState::ReloadUserBufAndKeepSelection() { WantReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; } void ImGuiInputTextState::ReloadUserBufAndMoveToEnd() { WantReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; } + ImGuiInputTextCallbackData::ImGuiInputTextCallbackData() { memset(this, 0, sizeof(*this)); } + // Public API to manipulate UTF-8 text from within a callback. // FIXME: The existence of this rarely exercised code path is a bit of a nuisance. // Historically they existed because STB_TEXTEDIT_INSERTCHARS() etc. worked on our ImWchar @@ -3722,6 +4277,7 @@ void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count) char* dst = Buf + pos; const char* src = Buf + pos + bytes_count; memmove(dst, src, BufTextLen - bytes_count - pos + 1); + if (CursorPos >= pos + bytes_count) CursorPos -= bytes_count; else if (CursorPos >= pos) @@ -3730,58 +4286,78 @@ void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count) BufDirty = true; BufTextLen -= bytes_count; } + void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end) { // Accept null ranges if (new_text == new_text_end) return; + + ImGuiContext& g = *Ctx; + ImGuiInputTextState* obj = &g.InputTextState; + IM_ASSERT(obj->ID != 0 && g.ActiveId == obj->ID); + // Grow internal buffer if needed const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0; const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)ImStrlen(new_text); - if (new_text_len + BufTextLen >= BufSize) + if (new_text_len + BufTextLen + 1 > obj->TextA.Size && (Flags & ImGuiInputTextFlags_ReadOnly) == 0) { if (!is_resizable) return; - ImGuiContext& g = *Ctx; - ImGuiInputTextState* edit_state = &g.InputTextState; - IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID); - IM_ASSERT(Buf == edit_state->TextA.Data); + + IM_ASSERT(Buf == obj->TextA.Data); int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; - edit_state->TextA.resize(new_buf_size + 1); - edit_state->TextSrc = edit_state->TextA.Data; - Buf = edit_state->TextA.Data; - BufSize = edit_state->BufCapacity = new_buf_size; + obj->TextA.resize(new_buf_size + 1); + obj->TextSrc = obj->TextA.Data; + Buf = obj->TextA.Data; + BufSize = obj->BufCapacity = new_buf_size; } + if (BufTextLen != pos) memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos)); memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char)); Buf[BufTextLen + new_text_len] = '\0'; + if (CursorPos >= pos) CursorPos += new_text_len; SelectionStart = SelectionEnd = CursorPos; BufDirty = true; BufTextLen += new_text_len; } + void ImGui::PushPasswordFont() { ImGuiContext& g = *GImGui; - ImFont* in_font = g.Font; - ImFont* out_font = &g.InputTextPasswordFont; - ImFontGlyph* glyph = in_font->FindGlyph('*'); - out_font->FontSize = in_font->FontSize; - out_font->Scale = in_font->Scale; - out_font->Ascent = in_font->Ascent; - out_font->Descent = in_font->Descent; - out_font->ContainerAtlas = in_font->ContainerAtlas; - out_font->FallbackGlyph = glyph; - out_font->FallbackAdvanceX = glyph->AdvanceX; - IM_ASSERT(out_font->Glyphs.Size == 0 && out_font->IndexAdvanceX.Size == 0 && out_font->IndexLookup.Size == 0); - PushFont(out_font); + ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked; + IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0); + ImFontGlyph* glyph = g.FontBaked->FindGlyph('*'); + g.InputTextPasswordFontBackupFlags = g.Font->Flags; + backup->FallbackGlyphIndex = g.FontBaked->FallbackGlyphIndex; + backup->FallbackAdvanceX = g.FontBaked->FallbackAdvanceX; + backup->IndexLookup.swap(g.FontBaked->IndexLookup); + backup->IndexAdvanceX.swap(g.FontBaked->IndexAdvanceX); + g.Font->Flags |= ImFontFlags_NoLoadGlyphs; + g.FontBaked->FallbackGlyphIndex = g.FontBaked->Glyphs.index_from_ptr(glyph); + g.FontBaked->FallbackAdvanceX = glyph->AdvanceX; } + +void ImGui::PopPasswordFont() +{ + ImGuiContext& g = *GImGui; + ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked; + g.Font->Flags = g.InputTextPasswordFontBackupFlags; + g.FontBaked->FallbackGlyphIndex = backup->FallbackGlyphIndex; + g.FontBaked->FallbackAdvanceX = backup->FallbackAdvanceX; + g.FontBaked->IndexLookup.swap(backup->IndexLookup); + g.FontBaked->IndexAdvanceX.swap(backup->IndexAdvanceX); + IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0); +} + // Return false to discard a character. static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard) { unsigned int c = *p_char; + // Filter non-printable (NB: isprint is unreliable! see #2467) bool apply_named_filters = true; if (c < 0x20) @@ -3799,18 +4375,22 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im return false; apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted. } + if (input_source_is_clipboard == false) { // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817) if (c == 127) return false; + // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME) if (c >= 0xE000 && c <= 0xF8FF) return false; } + // Filter Unicode ranges we are not handling in this build if (c > IM_UNICODE_CODEPOINT_MAX) return false; + // Generic named filters if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint))) { @@ -3825,33 +4405,41 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint)) if (c == '.' || c == ',') c = c_decimal_point; + // Full-width -> half-width conversion for numeric fields (https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block) // While this is mostly convenient, this has the side-effect for uninformed users accidentally inputting full-width characters that they may // scratch their head as to why it works in numerical fields vs in generic text fields it would require support in the font. if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | ImGuiInputTextFlags_CharsHexadecimal)) if (c >= 0xFF01 && c <= 0xFF5E) c = c - 0xFF01 + 0x21; + // Allow 0-9 . - + * / if (flags & ImGuiInputTextFlags_CharsDecimal) if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/')) return false; + // Allow 0-9 . - + * / e E if (flags & ImGuiInputTextFlags_CharsScientific) if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E')) return false; + // Allow 0-9 a-F A-F if (flags & ImGuiInputTextFlags_CharsHexadecimal) if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F')) return false; + // Turn a-z into A-Z if (flags & ImGuiInputTextFlags_CharsUppercase) if (c >= 'a' && c <= 'z') c += (unsigned int)('A' - 'a'); + if (flags & ImGuiInputTextFlags_CharsNoBlank) if (ImCharIsBlankW(c)) return false; + *p_char = c; } + // Custom callback filter if (flags & ImGuiInputTextFlags_CallbackCharFilter) { @@ -3868,8 +4456,10 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im if (!callback_data.EventChar) return false; } + return true; } + // Find the shortest single replacement we can make to get from old_buf to new_buf // Note that this doesn't directly alter state->TextA, state->TextLen. They are expected to be made valid separately. // FIXME: Ideally we should transition toward (1) making InsertChars()/DeleteChars() update undo-stack (2) discourage (and keep reconcile) or obsolete (and remove reconcile) accessing buffer directly. @@ -3882,11 +4472,13 @@ static void InputTextReconcileUndoState(ImGuiInputTextState* state, const char* break; if (first_diff == old_length && first_diff == new_length) return; + int old_last_diff = old_length - 1; int new_last_diff = new_length - 1; for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--) if (old_buf[old_last_diff] != new_buf[new_last_diff]) break; + const int insert_len = new_last_diff - first_diff + 1; const int delete_len = old_last_diff - first_diff + 1; if (insert_len > 0 || delete_len > 0) @@ -3894,6 +4486,7 @@ static void InputTextReconcileUndoState(ImGuiInputTextState* state, const char* for (int i = 0; i < delete_len; i++) p[i] = old_buf[first_diff + i]; } + // As InputText() retain textual data and we currently provide a path for user to not retain it (via local variables) // we need some form of hook to reapply data back to user buffer on deactivation frame. (#4714) // It would be more desirable that we discourage users from taking advantage of the "user not retaining data" trick, @@ -3917,6 +4510,7 @@ void ImGui::InputTextDeactivateHook(ImGuiID id) memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->TextLen + 1); } } + // Edit a string of text // - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!". // This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match @@ -3930,23 +4524,29 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + IM_ASSERT(buf != NULL && buf_size >= 0); IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) IM_ASSERT(!((flags & ImGuiInputTextFlags_ElideLeft) && (flags & ImGuiInputTextFlags_Multiline))); // Multiline will not work with left-trimming + ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; const ImGuiStyle& style = g.Style; + const bool RENDER_SELECTION_WHEN_INACTIVE = false; const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; + if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope (including the scrollbar) BeginGroup(); const ImGuiID id = window->GetID(label); const ImVec2 label_size = CalcTextSize(label, NULL, true); const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? g.FontSize * 8.0f : label_size.y) + style.FramePadding.y * 2.0f); // Arbitrary default of 8 lines high for multi-line const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); const ImRect total_bb(frame_bb.Min, frame_bb.Min + total_size); + ImGuiWindow* draw_window = window; ImVec2 inner_size = frame_size; ImGuiLastItemData item_data_backup; @@ -3961,13 +4561,16 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } item_data_backup = g.LastItemData; window->DC.CursorPos = backup_pos; + // Prevent NavActivation from Tabbing when our widget accepts Tab inputs: this allows cycling through widgets without stopping. if (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_FromTabbing) && (flags & ImGuiInputTextFlags_AllowTabInput)) g.NavActivateId = 0; + // Prevent NavActivate reactivating in BeginChild() when we are already active. const ImGuiID backup_activate_id = g.NavActivateId; if (g.ActiveId == id) // Prevent reactivation g.NavActivateId = 0; + // We reproduce the contents of BeginChildFrame() in order to provide 'label' so our window internal data are easier to read/debug. PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]); PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding); @@ -3996,14 +4599,17 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable)) return false; } + // Ensure mouse cursor is set even after switching to keyboard/gamepad mode. May generalize further? (#6417) bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags | ImGuiItemFlags_NoNavDisableMouseHover); if (hovered) SetMouseCursor(ImGuiMouseCursor_TextInput); if (hovered && g.NavHighlightItemUnderNav) hovered = false; + // We are only allowed to access the state if we are already the active widget. ImGuiInputTextState* state = GetInputTextState(id); + if (g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) flags |= ImGuiInputTextFlags_ReadOnly; const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0; @@ -4012,13 +4618,17 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0; if (is_resizable) IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag! + const bool input_requested_by_nav = (g.ActiveId != id) && ((g.NavActivateId == id) && ((g.NavActivateFlags & ImGuiActivateFlags_PreferInput) || (g.NavInputSource == ImGuiInputSource_Keyboard))); + const bool user_clicked = hovered && io.MouseClicked[0]; const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y); const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y); bool clear_active_id = false; bool select_all = false; + float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX; + const bool init_reload_from_user_buf = (state != NULL && state->WantReloadUserBuf); const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state. const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav); @@ -4041,19 +4651,23 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Access state even if we don't own it yet. state = &g.InputTextState; state->CursorAnimReset(); + // Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714) InputTextDeactivateHook(state->ID); + // Take a copy of the initial buffer value. // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode) const int buf_len = (int)ImStrlen(buf); IM_ASSERT(buf_len + 1 <= buf_size && "Is your input buffer properly zero-terminated?"); state->TextToRevertTo.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. memcpy(state->TextToRevertTo.Data, buf, buf_len + 1); + // Preserve cursor position and undo/redo stack if we come back to same widget // FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate? bool recycle_state = (state->ID == id && !init_changed_specs); if (recycle_state && (state->TextLen != buf_len || (state->TextA.Data == NULL || strncmp(state->TextA.Data, buf, buf_len) != 0))) recycle_state = false; + // Start edition state->ID = id; state->TextLen = buf_len; @@ -4062,16 +4676,19 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string. memcpy(state->TextA.Data, buf, state->TextLen + 1); } + // Find initial scroll position for right alignment state->Scroll = ImVec2(0.0f, 0.0f); if (flags & ImGuiInputTextFlags_ElideLeft) state->Scroll.x += ImMax(0.0f, CalcTextSize(buf).x - frame_size.x + style.FramePadding.x * 2.0f); + // Recycle existing cursor/selection/undo stack but clamp position // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. if (recycle_state) state->CursorClamp(); else stb_textedit_initialize_state(state->Stb, !is_multiline); + if (!is_multiline) { if (flags & ImGuiInputTextFlags_AutoSelectAll) @@ -4081,9 +4698,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (user_clicked && io.KeyCtrl) select_all = true; } + if (flags & ImGuiInputTextFlags_AlwaysOverwrite) state->Stb->insert_mode = 1; // stb field name is indeed incorrect (see #2863) } + const bool is_osx = io.ConfigMacOSXBehaviors; if (g.ActiveId != id && init_make_active) { @@ -4095,7 +4714,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (g.ActiveId == id) { // Declare some inputs, the other are registered and polled via Shortcut() routing system. - // FIXME: The reason we don't use Shortcut() is we would need a routing flag to specify multiple mods, or to all mods combinaison into individual shortcuts. + // FIXME: The reason we don't use Shortcut() is we would need a routing flag to specify multiple mods, or to all mods combination into individual shortcuts. const ImGuiKey always_owned_keys[] = { ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_Enter, ImGuiKey_KeypadEnter, ImGuiKey_Delete, ImGuiKey_Backspace, ImGuiKey_Home, ImGuiKey_End }; for (ImGuiKey key : always_owned_keys) SetKeyOwner(key, id); @@ -4116,9 +4735,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // FIXME: May be a problem to always steal Alt on OSX, would ideally still allow an uninterrupted Alt down-up to toggle menu if (is_osx) SetKeyOwner(ImGuiMod_Alt, id); + // Expose scroll in a manner that is agnostic to us using a child window if (is_multiline && state != NULL) state->Scroll.y = draw_window->Scroll.y; + // Read-only mode always ever read from source buffer. Refresh TextLen when active. if (is_readonly && state != NULL) state->TextLen = (int)ImStrlen(buf); @@ -4127,23 +4748,29 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } if (state != NULL) state->TextSrc = is_readonly ? buf : state->TextA.Data; + // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function) if (g.ActiveId == id && state == NULL) ClearActiveID(); + // Release focus when we click outside if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560 clear_active_id = true; + // Lock the decision of whether we are going to take the path displaying the cursor or selection bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active); bool render_selection = state && (state->HasSelection() || select_all) && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor); bool value_changed = false; bool validated = false; + // Select the buffer to render. const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state; bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); + // Password pushes a temporary font with only a fallback glyph if (is_password && !is_displaying_hint) PushPasswordFont(); + // Process mouse inputs and character inputs if (g.ActiveId == id) { @@ -4151,12 +4778,15 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->Edited = false; state->BufCapacity = buf_size; state->Flags = flags; + // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. // Down the line we should have a cleaner library-wide concept of Selected vs Active. g.ActiveIdAllowOverlap = !io.MouseDown[0]; + // Edit in progress const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->Scroll.x; const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f)); + if (select_all) { state->SelectAll(); @@ -4216,6 +4846,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } if (state->SelectedAllMouseLock && !io.MouseDown[0]) state->SelectedAllMouseLock = false; + // We expect backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336) // (For Tab and Enter: Win32/SFML/Allegro are sending both keys and chars, GLFW and SDL are only sending keys. For Space they all send all threes) if ((flags & ImGuiInputTextFlags_AllowTabInput) && !is_readonly) @@ -4233,6 +4864,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } */ } + // Process regular text input (before we check for Return because using some IME will effectively send a Return?) // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters. const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeyCtrl); @@ -4248,20 +4880,25 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) state->OnCharPressed(c); } + // Consume characters io.InputQueueCharacters.resize(0); } } + // Process other shortcuts/key-presses bool revert_edit = false; if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) { IM_ASSERT(state != NULL); + const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1); state->Stb->row_count_per_page = row_count_per_page; + const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl const bool is_startend_key_down = is_osx && io.KeyCtrl && !io.KeySuper && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End + // Using Shortcut() with ImGuiInputFlags_RouteFocused (default policy) to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: former would be handled by InputText) // Otherwise we could simply assume that we own the keys as we are active. const ImGuiInputFlags f_repeat = ImGuiInputFlags_Repeat; @@ -4271,11 +4908,13 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const bool is_undo = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable; const bool is_redo = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Y, f_repeat, id) || Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable; const bool is_select_all = Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, 0, id); + // We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful. const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; const bool is_enter_pressed = IsKeyPressed(ImGuiKey_Enter, true) || IsKeyPressed(ImGuiKey_KeypadEnter, true); const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false)); const bool is_cancel = Shortcut(ImGuiKey_Escape, f_repeat, id) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, f_repeat, id)); + // FIXME: Should use more Shortcut() and reduce IsKeyPressed()+SetKeyOwner(), but requires modifiers combination to be taken account of. // FIXME-OSX: Missing support for Alt(option)+Right/Left = go to end of line, or next line if already in end of line. if (IsKeyPressed(ImGuiKey_LeftArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } @@ -4311,7 +4950,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { // Determine if we turn Enter into a \n character bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; - if (!is_multiline || is_gamepad_validate || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl)) + if (!is_multiline || is_gamepad_validate || (ctrl_enter_for_new_line != io.KeyCtrl)) { validated = true; if (io.ConfigInputTextEnterKeepActive && !is_multiline) @@ -4406,9 +5045,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } } } + // Update render selection flag after events have been handled, so selection highlight can be displayed during the same frame. render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor); } + // Process callbacks and apply result back to user's buffer. const char* apply_new_text = NULL; int apply_new_text_length = 0; @@ -4431,12 +5072,14 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { apply_new_text = state->TextToRevertTo.Data; apply_new_text_length = state->TextToRevertTo.Size - 1; + // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents. // Push records into the undo stack so we can CTRL+Z the revert operation itself value_changed = true; stb_textedit_replace(state, state->Stb, state->TextToRevertTo.Data, state->TextToRevertTo.Size - 1); } } + // FIXME-OPT: We always reapply the live buffer back to the input buffer before clearing ActiveId, // even though strictly speaking it wasn't modified on this frame. Should mark dirty state from the stb_textedit callbacks. // If we do that, need to ensure that as special case, 'validated == true' also writes back. @@ -4448,10 +5091,12 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { // Apply current edited text immediately. // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer + // User callback if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0) { IM_ASSERT(callback != NULL); + // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment. ImGuiInputTextFlags event_flag = 0; ImGuiKey event_key = ImGuiKey_None; @@ -4478,6 +5123,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { event_flag = ImGuiInputTextFlags_CallbackAlways; } + if (event_flag) { ImGuiInputTextCallbackData callback_data; @@ -4485,21 +5131,26 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ callback_data.EventFlag = event_flag; callback_data.Flags = flags; callback_data.UserData = callback_user_data; + // FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925 char* callback_buf = is_readonly ? buf : state->TextA.Data; IM_ASSERT(callback_buf == state->TextSrc); state->CallbackTextBackup.resize(state->TextLen + 1); memcpy(state->CallbackTextBackup.Data, callback_buf, state->TextLen + 1); + callback_data.EventKey = event_key; callback_data.Buf = callback_buf; callback_data.BufTextLen = state->TextLen; callback_data.BufSize = state->BufCapacity; callback_data.BufDirty = false; + const int utf8_cursor_pos = callback_data.CursorPos = state->Stb->cursor; const int utf8_selection_start = callback_data.SelectionStart = state->Stb->select_start; const int utf8_selection_end = callback_data.SelectionEnd = state->Stb->select_end; + // Call user code callback(&callback_data); + // Read back what user may have modified callback_buf = is_readonly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback IM_ASSERT(callback_data.Buf == callback_buf); // Invalid to modify those fields @@ -4519,6 +5170,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } } } + // Will copy result string if modified if (!is_readonly && strcmp(state->TextSrc, buf) != 0) { @@ -4528,6 +5180,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } } } + // Handle reapplying final data on deactivation (see InputTextDeactivateHook() for details) if (g.InputTextDeactivatedState.ID == id) { @@ -4540,6 +5193,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } g.InputTextDeactivatedState.ID = 0; } + // Copy result to user buffer. This can currently only happen when (g.ActiveId == id) if (apply_new_text != NULL) { @@ -4564,37 +5218,41 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ IM_ASSERT(apply_new_text_length <= buf_size); } //IMGUI_DEBUG_PRINT("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length); + // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size. ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size)); } + // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) // Otherwise request text input ahead for next frame. if (g.ActiveId == id && clear_active_id) ClearActiveID(); - else if (g.ActiveId == id) - g.WantTextInputNextFrame = 1; + // Render frame if (!is_multiline) { RenderNavCursor(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); } + const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + inner_size.x, frame_bb.Min.y + inner_size.y); // Not using frame_bb.Max because we have adjusted size ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; ImVec2 text_size(0.0f, 0.0f); + // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether. // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash. const int buf_display_max_length = 2 * 1024 * 1024; const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595 const char* buf_display_end = NULL; // We have specialized paths below for setting the length + // Display hint when contents is empty // At this point we need to handle the possibility that a callback could have modified the underlying buffer (#8368) const bool new_is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); if (new_is_displaying_hint != is_displaying_hint) { if (is_password && !is_displaying_hint) - PopFont(); + PopPasswordFont(); is_displaying_hint = new_is_displaying_hint; if (is_password && !is_displaying_hint) PushPasswordFont(); @@ -4604,6 +5262,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ buf_display = hint; buf_display_end = hint + ImStrlen(hint); } + // Render text. We currently only render selection when the widget is active or while scrolling. // FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive. if (render_cursor || render_selection) @@ -4611,6 +5270,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ IM_ASSERT(state != NULL); if (!is_displaying_hint) buf_display_end = buf_display + state->TextLen; + // Render text (with cursor and selection) // This is going to be messy. We need to: // - Display the text (this alone can be more easily clipped) @@ -4621,12 +5281,14 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const char* text_begin = buf_display; const char* text_end = text_begin + state->TextLen; ImVec2 cursor_offset, select_start_offset; + { // Find lines numbers straddling cursor and selection min position int cursor_line_no = render_cursor ? -1 : -1000; int selmin_line_no = render_selection ? -1 : -1000; const char* cursor_ptr = render_cursor ? text_begin + state->Stb->cursor : NULL; const char* selmin_ptr = render_selection ? text_begin + ImMin(state->Stb->select_start, state->Stb->select_end) : NULL; + // Count lines and find line number for cursor and selection ends int line_count = 1; if (is_multiline) @@ -4642,6 +5304,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ cursor_line_no = line_count; if (selmin_line_no == -1) selmin_line_no = line_count; + // Calculate 2d position by finding the beginning of the line and measuring distance cursor_offset.x = InputTextCalcTextSize(&g, ImStrbol(cursor_ptr, text_begin), cursor_ptr).x; cursor_offset.y = cursor_line_no * g.FontSize; @@ -4650,10 +5313,12 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ select_start_offset.x = InputTextCalcTextSize(&g, ImStrbol(selmin_ptr, text_begin), selmin_ptr).x; select_start_offset.y = selmin_line_no * g.FontSize; } + // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224) if (is_multiline) text_size = ImVec2(inner_size.x, line_count * g.FontSize); } + // Scroll if (render_cursor && state->CursorFollow) { @@ -4671,6 +5336,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { state->Scroll.y = 0.0f; } + // Vertical scroll if (is_multiline) { @@ -4684,14 +5350,17 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ draw_pos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag draw_window->Scroll.y = scroll_y; } + state->CursorFollow = false; } + // Draw selection const ImVec2 draw_scroll = ImVec2(state->Scroll.x, 0.0f); if (render_selection) { const char* text_selected_begin = text_begin + ImMin(state->Stb->select_start, state->Stb->select_end); const char* text_selected_end = text_begin + ImMax(state->Stb->select_start, state->Stb->select_end); + ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests. float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. float bg_offy_dn = is_multiline ? 0.0f : 2.0f; @@ -4708,7 +5377,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ else { ImVec2 rect_size = InputTextCalcTextSize(&g, p, text_selected_end, &p, NULL, true); - if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines + if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.FontBaked->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn)); rect.ClipWith(clip_rect); if (rect.Overlaps(clip_rect)) @@ -4718,6 +5387,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ rect_pos.y += g.FontSize; } } + // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash. // FIXME-OPT: Multiline could submit a smaller amount of contents to AddText() since we already iterated through it. if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) @@ -4725,6 +5395,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); } + // Draw blinking cursor if (render_cursor) { @@ -4733,14 +5404,19 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll); ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) - draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); + draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_InputTextCursor), 1.0f); // FIXME-DPI: Cursor thickness (#7031) + // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) - if (!is_readonly) + // This is required for some backends (SDL3) to start emitting character/text inputs. + // As per #6341, make sure we don't set that on the deactivating frame. + if (!is_readonly && g.ActiveId == id) { - g.PlatformImeData.WantVisible = true; - g.PlatformImeData.InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize); - g.PlatformImeData.InputLineHeight = g.FontSize; - g.PlatformImeViewport = window->Viewport->ID; + ImGuiPlatformImeData* ime_data = &g.PlatformImeData; // (this is a public struct, passed to io.Platform_SetImeDataFn() handler) + ime_data->WantVisible = true; + ime_data->WantTextInput = true; + ime_data->InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize); + ime_data->InputLineHeight = g.FontSize; + ime_data->ViewportId = window->Viewport->ID; } } } @@ -4753,18 +5429,22 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ buf_display_end = buf_display + state->TextLen; else if (!is_displaying_hint) buf_display_end = buf_display + ImStrlen(buf_display); + if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) { // Find render position for right alignment if (flags & ImGuiInputTextFlags_ElideLeft) draw_pos.x = ImMin(draw_pos.x, frame_bb.Max.x - CalcTextSize(buf_display, NULL).x - style.FramePadding.x); + const ImVec2 draw_scroll = /*state ? ImVec2(state->Scroll.x, 0.0f) :*/ ImVec2(0.0f, 0.0f); // Preserve scroll when inactive? ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); } } + if (is_password && !is_displaying_hint) - PopFont(); + PopPasswordFont(); + if (is_multiline) { // For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (see #4761, #7870)... @@ -4772,6 +5452,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ g.NextItemData.ItemFlags |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop; EndChild(); item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow); + // ...and then we need to undo the group overriding last item data, which gets a bit messy as EndGroup() tries to forward scrollbar being active... // FIXME: This quite messy/tricky, should attempt to get rid of the child window. EndGroup(); @@ -4784,22 +5465,27 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } if (state) state->TextSrc = NULL; + // Log as text if (g.LogEnabled && (!is_password || is_displaying_hint)) { LogSetNextTextDecoration("{", "}"); LogRenderedText(&draw_pos, buf_display, buf_display_end); } + if (label_size.x > 0) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + if (value_changed) MarkItemEdited(id); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable); if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0) return validated; else return value_changed; } + void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) { #ifndef IMGUI_DISABLE_DEBUG_TOOLS @@ -4809,7 +5495,7 @@ void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId); DebugLocateItemOnHover(state->ID); Text("CurLenA: %d, Cursor: %d, Selection: %d..%d", state->TextLen, stb_state->cursor, stb_state->select_start, stb_state->select_end); - Text("BufCapacityA: %d", state->BufCapacity); + Text("BufCapacity: %d", state->BufCapacity); Text("(Internal Buffer: TextA Size: %d, Capacity: %d)", state->TextA.Size, state->TextA.Capacity); Text("has_preferred_x: %d (%.2f)", stb_state->has_preferred_x, stb_state->preferred_x); Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point); @@ -4836,6 +5522,7 @@ void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) IM_UNUSED(state); #endif } + //------------------------------------------------------------------------- // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. //------------------------------------------------------------------------- @@ -4850,10 +5537,12 @@ void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) // - ColorEditOptionsPopup() [Internal] // - ColorPickerOptionsPopup() [Internal] //------------------------------------------------------------------------- + bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags) { return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha); } + static void ColorEditRestoreH(const float* col, float* H) { ImGuiContext& g = *GImGui; @@ -4862,6 +5551,7 @@ static void ColorEditRestoreH(const float* col, float* H) return; *H = g.ColorEditSavedHue; } + // ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation. // Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting. static void ColorEditRestoreHS(const float* col, float* H, float* S, float* V) @@ -4870,14 +5560,17 @@ static void ColorEditRestoreHS(const float* col, float* H, float* S, float* V) IM_ASSERT(g.ColorEditCurrentID != 0); if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0))) return; + // When S == 0, H is undefined. // When H == 1 it wraps around to 0. if (*S == 0.0f || (*H == 0.0f && g.ColorEditSavedHue == 1)) *H = g.ColorEditSavedHue; + // When V == 0, S is undefined. if (*V == 0.0f) *S = g.ColorEditSavedSat; } + // Edit colors components (each component in 0.0f..1.0f range). // See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. // With typical options: Left-click on color square to open color picker. Right-click to open option menu. CTRL+Click over input fields to edit them and TAB to go to next item. @@ -4886,24 +5579,29 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const float square_sz = GetFrameHeight(); const char* label_display_end = FindRenderedTextEnd(label); float w_full = CalcItemWidth(); g.NextItemData.ClearFlags(); + BeginGroup(); PushID(label); const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0); if (set_current_color_edit_id) g.ColorEditCurrentID = window->IDStack.back(); + // If we're not showing any slider there's no point in doing any HSV conversions const ImGuiColorEditFlags flags_untouched = flags; if (flags & ImGuiColorEditFlags_NoInputs) flags = (flags & (~ImGuiColorEditFlags_DisplayMask_)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions; + // Context menu: display and modify options (before defaults are applied) if (!(flags & ImGuiColorEditFlags_NoOptions)) ColorEditOptionsPopup(col, flags); + // Read stored options if (!(flags & ImGuiColorEditFlags_DisplayMask_)) flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DisplayMask_); @@ -4916,12 +5614,14 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_)); IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check that only 1 is selected IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected + const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0; const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0; const int components = alpha ? 4 : 3; const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x); const float w_inputs = ImMax(w_full - w_button, 1.0f); w_full = w_inputs + w_button; + // Convert to the formats we need float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f }; if ((flags & ImGuiColorEditFlags_InputHSV) && (flags & ImGuiColorEditFlags_DisplayRGB)) @@ -4933,15 +5633,19 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag ColorEditRestoreHS(col, &f[0], &f[1], &f[2]); } int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) }; + bool value_changed = false; bool value_changed_as_float = false; + const ImVec2 pos = window->DC.CursorPos; const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f; window->DC.CursorPos.x = pos.x + inputs_offset_x; + if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) { // RGB/HSV 0..255 Sliders const float w_items = w_inputs - style.ItemInnerSpacing.x * (components - 1); + const bool hide_prefix = (IM_TRUNC(w_items / components) <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x); static const char* ids[4] = { "##X", "##Y", "##Z", "##W" }; static const char* fmt_table_int[3][4] = @@ -4957,6 +5661,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA }; const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1; + float prev_split = 0.0f; for (int n = 0; n < components; n++) { @@ -4965,6 +5670,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag float next_split = IM_TRUNC(w_items * (n + 1) / components); SetNextItemWidth(ImMax(next_split - prev_split, 1.0f)); prev_split = next_split; + // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0. if (flags & ImGuiColorEditFlags_Float) { @@ -5006,11 +5712,13 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag if (!(flags & ImGuiColorEditFlags_NoOptions)) OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight); } + ImGuiWindow* picker_active_window = NULL; if (!(flags & ImGuiColorEditFlags_NoSmallPreview)) { const float button_offset_x = ((flags & ImGuiColorEditFlags_NoInputs) || (style.ColorButtonPosition == ImGuiDir_Left)) ? 0.0f : w_inputs + style.ItemInnerSpacing.x; window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y); + const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f); if (ColorButton("##ColorButton", col_v4, flags)) { @@ -5024,6 +5732,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag } if (!(flags & ImGuiColorEditFlags_NoOptions)) OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight); + if (BeginPopup("picker")) { if (g.CurrentWindow->BeginCount == 1) @@ -5042,6 +5751,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag EndPopup(); } } + if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel)) { // Position not necessarily next to last submitted button (e.g. if style.ColorButtonPosition == ImGuiDir_Left), @@ -5050,6 +5760,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag window->DC.CursorPos.x = pos.x + ((flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x); TextEx(label, label_display_end); } + // Convert back if (value_changed && picker_active_window == NULL) { @@ -5066,16 +5777,19 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag } if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV)) ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); + col[0] = f[0]; col[1] = f[1]; col[2] = f[2]; if (alpha) col[3] = f[3]; } + if (set_current_color_edit_id) g.ColorEditCurrentID = 0; PopID(); EndGroup(); + // Drag and Drop Target // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test. if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget()) @@ -5091,18 +5805,23 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag memcpy((float*)col, payload->Data, sizeof(float) * components); value_changed = accepted_drag_drop = true; } + // Drag-drop payloads are always RGB if (accepted_drag_drop && (flags & ImGuiColorEditFlags_InputHSV)) ColorConvertRGBtoHSV(col[0], col[1], col[2], col[0], col[1], col[2]); EndDragDropTarget(); } + // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4(). if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window) g.LastItemData.ID = g.ActiveId; + if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId MarkItemEdited(g.LastItemData.ID); + return value_changed; } + bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags) { float col4[4] = { col[0], col[1], col[2], 1.0f }; @@ -5111,6 +5830,7 @@ bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags fl col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2]; return true; } + // Helper for ColorPicker4() static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w, float alpha) { @@ -5120,6 +5840,7 @@ static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32(0,0,0,alpha8)); ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32(255,255,255,alpha8)); } + // Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. // (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.) // FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..) @@ -5130,22 +5851,28 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImDrawList* draw_list = window->DrawList; ImGuiStyle& style = g.Style; ImGuiIO& io = g.IO; + const float width = CalcItemWidth(); const bool is_readonly = ((g.NextItemData.ItemFlags | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0; g.NextItemData.ClearFlags(); + PushID(label); const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0); if (set_current_color_edit_id) g.ColorEditCurrentID = window->IDStack.back(); BeginGroup(); + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) flags |= ImGuiColorEditFlags_NoSmallPreview; + // Context menu: display and store options. if (!(flags & ImGuiColorEditFlags_NoOptions)) ColorPickerOptionsPopup(col, flags); + // Read stored options if (!(flags & ImGuiColorEditFlags_PickerMask_)) flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_; @@ -5155,6 +5882,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected if (!(flags & ImGuiColorEditFlags_NoOptions)) flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar); + // Setup int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4; bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha); @@ -5165,17 +5893,21 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x; float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x; float bars_triangles_half_sz = IM_TRUNC(bars_width * 0.20f); + float backup_initial_col[4]; memcpy(backup_initial_col, col, components * sizeof(float)); + float wheel_thickness = sv_picker_size * 0.08f; float wheel_r_outer = sv_picker_size * 0.50f; float wheel_r_inner = wheel_r_outer - wheel_thickness; ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size * 0.5f); + // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic. float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f); ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point. ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point. ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point. + float H = col[0], S = col[1], V = col[2]; float R = col[0], G = col[1], B = col[2]; if (flags & ImGuiColorEditFlags_InputRGB) @@ -5188,7 +5920,9 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl { ColorConvertHSVtoRGB(H, S, V, R, G, B); } + bool value_changed = false, value_changed_h = false, value_changed_sv = false; + PushItemFlag(ImGuiItemFlags_NoNav, true); if (flags & ImGuiColorEditFlags_PickerHueWheel) { @@ -5238,6 +5972,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl } if (!(flags & ImGuiColorEditFlags_NoOptions)) OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight); + // Hue bar logic SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y)); InvisibleButton("hue", ImVec2(bars_width, sv_picker_size)); @@ -5247,6 +5982,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl value_changed = value_changed_h = true; } } + // Alpha bar logic if (alpha_bar) { @@ -5259,11 +5995,13 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl } } PopItemFlag(); // ImGuiItemFlags_NoNav + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) { SameLine(0, style.ItemInnerSpacing.x); BeginGroup(); } + if (!(flags & ImGuiColorEditFlags_NoLabel)) { const char* label_display_end = FindRenderedTextEnd(label); @@ -5274,12 +6012,14 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl TextEx(label, label_display_end); } } + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) { PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); if ((flags & ImGuiColorEditFlags_NoLabel)) Text("Current"); + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaMask_ | ImGuiColorEditFlags_NoTooltip; ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)); if (ref_col != NULL) @@ -5295,6 +6035,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl PopItemFlag(); EndGroup(); } + // Convert back color to RGB if (value_changed_h || value_changed_sv) { @@ -5313,6 +6054,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl col[2] = V; } } + // R,G,B and H,S,V slider color editor bool value_changed_fix_hue_wrap = false; if ((flags & ImGuiColorEditFlags_NoInputs) == 0) @@ -5334,6 +6076,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_DisplayHex); PopItemWidth(); } + // Try to cancel hue wrap (after ColorEdit4 call), if any if (value_changed_fix_hue_wrap && (flags & ImGuiColorEditFlags_InputRGB)) { @@ -5347,6 +6090,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]); } } + if (value_changed) { if (flags & ImGuiColorEditFlags_InputRGB) @@ -5365,15 +6109,19 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl ColorConvertHSVtoRGB(H, S, V, R, G, B); } } + const int style_alpha8 = IM_F32_TO_INT8_SAT(style.Alpha); const ImU32 col_black = IM_COL32(0,0,0,style_alpha8); const ImU32 col_white = IM_COL32(255,255,255,style_alpha8); const ImU32 col_midgrey = IM_COL32(128,128,128,style_alpha8); const ImU32 col_hues[6 + 1] = { IM_COL32(255,0,0,style_alpha8), IM_COL32(255,255,0,style_alpha8), IM_COL32(0,255,0,style_alpha8), IM_COL32(0,255,255,style_alpha8), IM_COL32(0,0,255,style_alpha8), IM_COL32(255,0,255,style_alpha8), IM_COL32(255,0,0,style_alpha8) }; + ImVec4 hue_color_f(1, 1, 1, style.Alpha); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z); ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f); ImU32 user_col32_striped_of_alpha = ColorConvertFloat4ToU32(ImVec4(R, G, B, style.Alpha)); // Important: this is still including the main rendering/style alpha!! + ImVec2 sv_cursor_pos; + if (flags & ImGuiColorEditFlags_PickerHueWheel) { // Render Hue Wheel @@ -5387,11 +6135,13 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc); draw_list->PathStroke(col_white, 0, wheel_thickness); const int vert_end_idx = draw_list->VtxBuffer.Size; + // Paint colors over existing vertices ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner); ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner); ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col_hues[n], col_hues[n + 1]); } + // Render Cursor + preview on Hue Wheel float cos_hue_angle = ImCos(H * 2.0f * IM_PI); float sin_hue_angle = ImSin(H * 2.0f * IM_PI); @@ -5401,6 +6151,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments); draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments); draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments); + // Render SV triangle (rotated according to hue) ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle); ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle); @@ -5421,6 +6172,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f); sv_cursor_pos.x = ImClamp(IM_ROUND(picker_pos.x + ImSaturate(S) * sv_picker_size), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much sv_cursor_pos.y = ImClamp(IM_ROUND(picker_pos.y + ImSaturate(1 - V) * sv_picker_size), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2); + // Render Hue Bar for (int i = 0; i < 6; ++i) draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), col_hues[i], col_hues[i], col_hues[i + 1], col_hues[i + 1]); @@ -5428,12 +6180,14 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f); RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha); } + // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range) float sv_cursor_rad = value_changed_sv ? wheel_thickness * 0.55f : wheel_thickness * 0.40f; int sv_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(sv_cursor_rad); // Lock segment count so the +1 one matches others. draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, user_col32_striped_of_alpha, sv_cursor_segments); draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, col_midgrey, sv_cursor_segments); draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, sv_cursor_segments); + // Render alpha bar if (alpha_bar) { @@ -5445,16 +6199,21 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f); RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha); } + EndGroup(); + if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0) value_changed = false; if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId MarkItemEdited(g.LastItemData.ID); + if (set_current_color_edit_id) g.ColorEditCurrentID = 0; PopID(); + return value_changed; } + // A little color square. Return true when clicked. // FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip. // 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip. @@ -5464,6 +6223,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; const ImGuiID id = window->GetID(desc_id); const float default_size = GetFrameHeight(); @@ -5472,13 +6232,17 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f); if (!ItemAdd(bb, id)) return false; + bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held); + if (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaOpaque)) flags &= ~(ImGuiColorEditFlags_AlphaNoBg | ImGuiColorEditFlags_AlphaPreviewHalf); + ImVec4 col_rgb = col; if (flags & ImGuiColorEditFlags_InputHSV) ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z); + ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f); float grid_step = ImMin(size.x, size.y) / 2.99f; float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); @@ -5513,8 +6277,9 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl if (g.Style.FrameBorderSize > 0.0f) RenderFrameBorder(bb.Min, bb.Max, rounding); else - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color buttons are often in need of some sort of border + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color buttons are often in need of some sort of border // FIXME-DPI } + // Drag and Drop Source // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test. if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource()) @@ -5528,11 +6293,14 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl TextEx("Color"); EndDragDropSource(); } + // Tooltip if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered && IsItemHovered(ImGuiHoveredFlags_ForTooltip)) ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_AlphaMask_)); + return pressed; } + // Initialize/override default color options void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) { @@ -5551,10 +6319,12 @@ void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check only 1 option is selected g.ColorEditOptions = flags; } + // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags) { ImGuiContext& g = *GImGui; + if (!BeginTooltipEx(ImGuiTooltipFlags_OverridePrevious, ImGuiWindowFlags_None)) return; const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text; @@ -5563,6 +6333,7 @@ void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags TextEx(text, text_end); Separator(); } + ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2); ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); @@ -5585,12 +6356,14 @@ void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags } EndTooltip(); } + void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) { bool allow_opt_inputs = !(flags & ImGuiColorEditFlags_DisplayMask_); bool allow_opt_datatype = !(flags & ImGuiColorEditFlags_DataTypeMask_); if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context")) return; + ImGuiContext& g = *GImGui; PushItemFlag(ImGuiItemFlags_NoMarkEdited, true); ImGuiColorEditFlags opts = g.ColorEditOptions; @@ -5606,6 +6379,7 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Uint8; if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Float; } + if (allow_opt_inputs || allow_opt_datatype) Separator(); if (Button("Copy as..", ImVec2(-1, 0))) @@ -5631,16 +6405,19 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) } EndPopup(); } + g.ColorEditOptions = opts; PopItemFlag(); EndPopup(); } + void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags) { bool allow_opt_picker = !(flags & ImGuiColorEditFlags_PickerMask_); bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar); if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context")) return; + ImGuiContext& g = *GImGui; PushItemFlag(ImGuiItemFlags_NoMarkEdited, true); if (allow_opt_picker) @@ -5674,6 +6451,7 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl PopItemFlag(); EndPopup(); } + //------------------------------------------------------------------------- // [SECTION] Widgets: TreeNode, CollapsingHeader, etc. //------------------------------------------------------------------------- @@ -5681,6 +6459,7 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl // - TreeNodeV() // - TreeNodeEx() // - TreeNodeExV() +// - TreeNodeStoreStackData() [Internal] // - TreeNodeBehavior() [Internal] // - TreePush() // - TreePop() @@ -5688,6 +6467,7 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl // - SetNextItemOpen() // - CollapsingHeader() //------------------------------------------------------------------------- + bool ImGui::TreeNode(const char* str_id, const char* fmt, ...) { va_list args; @@ -5696,6 +6476,7 @@ bool ImGui::TreeNode(const char* str_id, const char* fmt, ...) va_end(args); return is_open; } + bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...) { va_list args; @@ -5704,6 +6485,7 @@ bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...) va_end(args); return is_open; } + bool ImGui::TreeNode(const char* label) { ImGuiWindow* window = GetCurrentWindow(); @@ -5712,14 +6494,17 @@ bool ImGui::TreeNode(const char* label) ImGuiID id = window->GetID(label); return TreeNodeBehavior(id, ImGuiTreeNodeFlags_None, label, NULL); } + bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args) { return TreeNodeExV(str_id, 0, fmt, args); } + bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args) { return TreeNodeExV(ptr_id, 0, fmt, args); } + bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) { ImGuiWindow* window = GetCurrentWindow(); @@ -5728,6 +6513,7 @@ bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) ImGuiID id = window->GetID(label); return TreeNodeBehavior(id, flags, label, NULL); } + bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) { va_list args; @@ -5736,6 +6522,7 @@ bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* va_end(args); return is_open; } + bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) { va_list args; @@ -5744,46 +6531,55 @@ bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* va_end(args); return is_open; } + bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiID id = window->GetID(str_id); const char* label, *label_end; ImFormatStringToTempBufferV(&label, &label_end, fmt, args); return TreeNodeBehavior(id, flags, label, label_end); } + bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiID id = window->GetID(ptr_id); const char* label, *label_end; ImFormatStringToTempBufferV(&label, &label_end, fmt, args); return TreeNodeBehavior(id, flags, label, label_end); } + bool ImGui::TreeNodeGetOpen(ImGuiID storage_id) { ImGuiContext& g = *GImGui; ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage; return storage->GetInt(storage_id, 0) != 0; } + void ImGui::TreeNodeSetOpen(ImGuiID storage_id, bool open) { ImGuiContext& g = *GImGui; ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage; storage->SetInt(storage_id, open ? 1 : 0); } + bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags) { if (flags & ImGuiTreeNodeFlags_Leaf) return true; + // We only write to the tree storage if the user clicks, or explicitly use the SetNextItemOpen function ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiStorage* storage = window->DC.StateStorage; + bool is_open; if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasOpen) { @@ -5811,42 +6607,59 @@ bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags) { is_open = storage->GetInt(storage_id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0; } + // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior). // NB- If we are above max depth we still allow manually opened nodes to be logged. if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && (window->DC.TreeDepth - g.LogDepthRef) < g.LogDepthToExpand) is_open = true; + return is_open; } + // Store ImGuiTreeNodeStackData for just submitted node. // Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase. -static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags) +static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags, float x1) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + g.TreeNodeStack.resize(g.TreeNodeStack.Size + 1); - ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.back(); + ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; tree_node_data->ID = g.LastItemData.ID; tree_node_data->TreeFlags = flags; tree_node_data->ItemFlags = g.LastItemData.ItemFlags; tree_node_data->NavRect = g.LastItemData.NavRect; + + // Initially I tried to latch value for GetColorU32(ImGuiCol_TreeLines) but it's not a good trade-off for very large trees. + const bool draw_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) != 0; + tree_node_data->DrawLinesX1 = draw_lines ? (x1 + g.FontSize * 0.5f + g.Style.FramePadding.x) : +FLT_MAX; + tree_node_data->DrawLinesTableColumn = (draw_lines && g.CurrentTable) ? (ImGuiTableColumnIdx)g.CurrentTable->CurrentColumn : -1; + tree_node_data->DrawLinesToNodesY2 = -FLT_MAX; window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth); + if (flags & ImGuiTreeNodeFlags_DrawLinesToNodes) + window->DC.TreeRecordsClippedNodesY2Mask |= (1 << window->DC.TreeDepth); } + // When using public API, currently 'id == storage_id' is always true, but we separate the values to facilitate advanced user code doing storage queries outside of UI loop. bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0; const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y)); + if (!label_end) label_end = FindRenderedTextEnd(label); const ImVec2 label_size = CalcTextSize(label, label_end, false); + const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2); // Collapsing arrow width + Spacing const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset); // Latch before ItemSize changes it const float text_width = g.FontSize + label_size.x + padding.x * 2; // Include collapsing arrow + // We vertically grow up to current line height up the typical widget height. const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2); const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL); @@ -5862,15 +6675,19 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l frame_bb.Min.x -= outer_extend; frame_bb.Max.x += outer_extend; } + ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y); ItemSize(ImVec2(text_width, frame_height), padding.y); + // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing ImRect interact_bb = frame_bb; if ((flags & (ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanLabelWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0) interact_bb.Max.x = frame_bb.Min.x + text_width + (label_size.x > 0.0f ? style.ItemSpacing.x * 2.0f : 0.0f); + // Compute open and multi-select states before ItemAdd() as it clear NextItem data. ImGuiID storage_id = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasStorageID) ? g.NextItemData.StorageId : id; bool is_open = TreeNodeUpdateNextOpen(storage_id, flags); + bool is_visible; if (span_all_columns || span_all_columns_label) { @@ -5889,47 +6706,65 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l } g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; g.LastItemData.DisplayRect = frame_bb; - // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsBackHere enabled: + + // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsToParent enabled: // Store data for the current depth to allow returning to this node from any child item. // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop(). - // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsBackHere by default or move it to ImGuiStyle. + // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsToParent by default or move it to ImGuiStyle. bool store_tree_node_stack_data = false; + if ((flags & ImGuiTreeNodeFlags_DrawLinesMask_) == 0) + flags |= g.Style.TreeLinesFlags; + const bool draw_tree_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) && (frame_bb.Min.y < window->ClipRect.Max.y) && (g.Style.TreeLinesSize > 0.0f); if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) { - if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && is_open && !g.NavIdIsAlive) + store_tree_node_stack_data = draw_tree_lines; + if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsToParent) && !g.NavIdIsAlive) if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) store_tree_node_stack_data = true; } + const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; if (!is_visible) { - if (store_tree_node_stack_data && is_open) - TreeNodeStoreStackData(flags); // Call before TreePushOverrideID() + if ((flags & ImGuiTreeNodeFlags_DrawLinesToNodes) && (window->DC.TreeRecordsClippedNodesY2Mask & (1 << (window->DC.TreeDepth - 1)))) + { + ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; + parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, window->DC.CursorPos.y); // Don't need to aim to mid Y position as we are clipped anyway. + if (frame_bb.Min.y >= window->ClipRect.Max.y) + window->DC.TreeRecordsClippedNodesY2Mask &= ~(1 << (window->DC.TreeDepth - 1)); // Done + } + if (is_open && store_tree_node_stack_data) + TreeNodeStoreStackData(flags, text_pos.x - text_offset_x); // Call before TreePushOverrideID() if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) TreePushOverrideID(id); IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); return is_open; } + if (span_all_columns || span_all_columns_label) { TablePushBackgroundChannel(); g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect; g.LastItemData.ClipRect = window->ClipRect; } + ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None; if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap)) button_flags |= ImGuiButtonFlags_AllowOverlap; if (!is_leaf) button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; + // We allow clicking on the arrow section with keyboard modifiers held, in order to easily // allow browsing a tree while preserving selection with code implementing multi-selection patterns. // When clicking on the rest of the tree node we always disallow keyboard modifiers. const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x; const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x; const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2); + const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0; if (is_multi_select) // We absolutely need to distinguish open vs select so _OpenOnArrow comes by default flags |= (flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 ? ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick : ImGuiTreeNodeFlags_OpenOnArrow; + // Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags. // Some alteration have subtle effects (e.g. toggle on MouseUp vs MouseDown events) due to requirements for multi-selection and drag and drop support. // - Single-click on label = Toggle on MouseUp (default, when _OpenOnArrow=0) @@ -5945,8 +6780,12 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; else button_flags |= ImGuiButtonFlags_PressedOnClickRelease; + if (flags & ImGuiTreeNodeFlags_NoNavFocus) + button_flags |= ImGuiButtonFlags_NoNavFocus; + bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0; const bool was_selected = selected; + // Multi-selection support (header) if (is_multi_select) { @@ -5960,6 +6799,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l if (window != g.HoveredWindow || !is_mouse_x_over_arrow) button_flags |= ImGuiButtonFlags_NoKeyModsAllowed; } + bool hovered, held; bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags); bool toggled = false; @@ -5982,6 +6822,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l else pressed = false; // Cancel press so it doesn't trigger selection. } + if (g.NavId == id && g.NavMoveDir == ImGuiDir_Left && is_open) { toggled = true; @@ -5994,6 +6835,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l NavClearPreferredPosForAxis(ImGuiAxis_X); NavMoveRequestCancel(); } + if (toggled) { is_open = !is_open; @@ -6001,6 +6843,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen; } } + // Multi-selection support (footer) if (is_multi_select) { @@ -6009,8 +6852,10 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l if (pressed) SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, interact_bb); } + if (selected != was_selected) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; + // Render { const ImU32 text_col = GetColorU32(ImGuiCol_Text); @@ -6023,6 +6868,8 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding); RenderNavCursor(frame_bb, id, nav_render_cursor_flags); + if (span_all_columns && !span_all_columns_label) + TablePopBackgroundChannel(); if (flags & ImGuiTreeNodeFlags_Bullet) RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col); else if (!is_leaf) @@ -6043,6 +6890,8 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false); } RenderNavCursor(frame_bb, id, nav_render_cursor_flags); + if (span_all_columns && !span_all_columns_label) + TablePopBackgroundChannel(); if (flags & ImGuiTreeNodeFlags_Bullet) RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col); else if (!is_leaf) @@ -6050,23 +6899,87 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l if (g.LogEnabled) LogSetNextTextDecoration(">", NULL); } - if (span_all_columns && !span_all_columns_label) - TablePopBackgroundChannel(); + + if (draw_tree_lines) + TreeNodeDrawLineToChildNode(ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.5f)); + // Label if (display_frame) RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); else RenderText(text_pos, label, label_end, false); + if (span_all_columns_label) TablePopBackgroundChannel(); } - if (store_tree_node_stack_data && is_open) - TreeNodeStoreStackData(flags); // Call before TreePushOverrideID() + + if (is_open && store_tree_node_stack_data) + TreeNodeStoreStackData(flags, text_pos.x - text_offset_x); // Call before TreePushOverrideID() if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) TreePushOverrideID(id); // Could use TreePush(label) but this avoid computing twice + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); return is_open; } + +// Draw horizontal line from our parent node +// This is only called for visible child nodes so we are not too fussy anymore about performances +void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->DC.TreeDepth == 0 || (window->DC.TreeHasStackDataDepthMask & (1 << (window->DC.TreeDepth - 1))) == 0) + return; + + ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; + float x1 = ImTrunc(parent_data->DrawLinesX1); + float x2 = ImTrunc(target_pos.x - g.Style.ItemInnerSpacing.x); + float y = ImTrunc(target_pos.y); + float rounding = (g.Style.TreeLinesRounding > 0.0f) ? ImMin(x2 - x1, g.Style.TreeLinesRounding) : 0.0f; + parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, y - rounding); + if (x1 >= x2) + return; + if (rounding > 0.0f) + { + x1 += 0.5f + rounding; + window->DrawList->PathArcToFast(ImVec2(x1, y - rounding), rounding, 6, 3); + if (x1 < x2) + window->DrawList->PathLineTo(ImVec2(x2, y)); + window->DrawList->PathStroke(GetColorU32(ImGuiCol_TreeLines), ImDrawFlags_None, g.Style.TreeLinesSize); + } + else + { + window->DrawList->AddLine(ImVec2(x1, y), ImVec2(x2, y), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + } +} + +// Draw vertical line of the hierarchy +void ImGui::TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + float y1 = ImMax(data->NavRect.Max.y, window->ClipRect.Min.y); + float y2 = data->DrawLinesToNodesY2; + if (data->TreeFlags & ImGuiTreeNodeFlags_DrawLinesFull) + { + float y2_full = window->DC.CursorPos.y; + if (g.CurrentTable) + y2_full = ImMax(g.CurrentTable->RowPosY2, y2_full); + y2_full = ImTrunc(y2_full - g.Style.ItemSpacing.y - g.FontSize * 0.5f); + if (y2 + (g.Style.ItemSpacing.y + g.Style.TreeLinesRounding) < y2_full) // FIXME: threshold to use ToNodes Y2 instead of Full Y2 when close by ItemSpacing.y + y2 = y2_full; + } + y2 = ImMin(y2, window->ClipRect.Max.y); + if (y2 <= y1) + return; + float x = ImTrunc(data->DrawLinesX1); + if (data->DrawLinesTableColumn != -1) + TablePushColumnChannel(data->DrawLinesTableColumn); + window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + if (data->DrawLinesTableColumn != -1) + TablePopColumnChannel(); +} + void ImGui::TreePush(const char* str_id) { ImGuiWindow* window = GetCurrentWindow(); @@ -6074,6 +6987,7 @@ void ImGui::TreePush(const char* str_id) window->DC.TreeDepth++; PushID(str_id); } + void ImGui::TreePush(const void* ptr_id) { ImGuiWindow* window = GetCurrentWindow(); @@ -6081,6 +6995,7 @@ void ImGui::TreePush(const void* ptr_id) window->DC.TreeDepth++; PushID(ptr_id); } + void ImGui::TreePushOverrideID(ImGuiID id) { ImGuiContext& g = *GImGui; @@ -6089,35 +7004,46 @@ void ImGui::TreePushOverrideID(ImGuiID id) window->DC.TreeDepth++; PushOverrideID(id); } + void ImGui::TreePop() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; Unindent(); + window->DC.TreeDepth--; ImU32 tree_depth_mask = (1 << window->DC.TreeDepth); - if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) // Only set during request + + if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) { - ImGuiTreeNodeStackData* data = &g.TreeNodeStack.back(); + const ImGuiTreeNodeStackData* data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; IM_ASSERT(data->ID == window->IDStack.back()); - if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) - { - // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled) + + // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsToParent is enabled) + if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsToParent) if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, data); - } + + // Draw hierarchy lines + if (data->DrawLinesX1 != +FLT_MAX && window->DC.CursorPos.y >= window->ClipRect.Min.y) + TreeNodeDrawLineToTreePop(data); + g.TreeNodeStack.pop_back(); window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask; + window->DC.TreeRecordsClippedNodesY2Mask &= ~tree_depth_mask; } + IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much. PopID(); } + // Horizontal distance preceding label when using TreeNode() or Bullet() float ImGui::GetTreeNodeToLabelSpacing() { ImGuiContext& g = *GImGui; return g.FontSize + (g.Style.FramePadding.x * 2.0f); } + // Set next TreeNode/CollapsingHeader open state. void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond) { @@ -6128,6 +7054,7 @@ void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond) g.NextItemData.OpenVal = is_open; g.NextItemData.OpenCond = (ImU8)(cond ? cond : ImGuiCond_Always); } + // Set next TreeNode/CollapsingHeader storage id. void ImGui::SetNextItemStorageID(ImGuiID storage_id) { @@ -6137,6 +7064,7 @@ void ImGui::SetNextItemStorageID(ImGuiID storage_id) g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasStorageID; g.NextItemData.StorageId = storage_id; } + // CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag). // This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode(). bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) @@ -6147,6 +7075,7 @@ bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) ImGuiID id = window->GetID(label); return TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader, label); } + // p_visible == NULL : regular collapsing header // p_visible != NULL && *p_visible == true : show a small close button on the corner of the header, clicking the button will set *p_visible = false // p_visible != NULL && *p_visible == false : do not show the header at all @@ -6156,8 +7085,10 @@ bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFl ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + if (p_visible && !*p_visible) return false; + ImGuiID id = window->GetID(label); flags |= ImGuiTreeNodeFlags_CollapsingHeader; if (p_visible) @@ -6178,13 +7109,16 @@ bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFl *p_visible = false; g.LastItemData = last_item_backup; } + return is_open; } + //------------------------------------------------------------------------- // [SECTION] Widgets: Selectable //------------------------------------------------------------------------- // - Selectable() //------------------------------------------------------------------------- + // Tip: pass a non-visible label (e.g. "##hello") then you can use the space to draw other text or image. // But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id. // With this scheme, ImGuiSelectableFlags_SpanAllColumns and ImGuiSelectableFlags_AllowOverlap are also frequently used flags. @@ -6194,8 +7128,10 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; + // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle. ImGuiID id = window->GetID(label); ImVec2 label_size = CalcTextSize(label, NULL, true); @@ -6203,6 +7139,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl ImVec2 pos = window->DC.CursorPos; pos.y += window->DC.CurrLineTextBaseOffset; ItemSize(size, 0.0f); + // Fill horizontal space // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets. const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0; @@ -6210,6 +7147,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x; if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth)) size.x = ImMax(label_size.x, max_x - min_x); + // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable. // FIXME: Not part of layout so not included in clipper calculation, but ItemSize currently doesn't allow offsetting CursorPos. ImRect bb(min_x, pos.y, min_x + size.x, pos.y + size.y); @@ -6225,6 +7163,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl bb.Max.y += (spacing_y - spacing_U); } //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); } + const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0; const ImGuiItemFlags extra_item_flags = disabled_item ? (ImGuiItemFlags)ImGuiItemFlags_Disabled : ImGuiItemFlags_None; bool is_visible; @@ -6243,13 +7182,16 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl { is_visible = ItemAdd(bb, id, NULL, extra_item_flags); } + const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0; if (!is_visible) if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(bb)) // Extra layer of "no logic clip" for box-select support (would be more overhead to add to ItemAdd) return false; + const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; if (disabled_item && !disabled_global) // Only testing this as an optimization BeginDisabled(); + // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only, // which would be advantageous since most selectable are not selected. if (span_all_columns) @@ -6261,6 +7203,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect; g.LastItemData.ClipRect = window->ClipRect; } + // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries ImGuiButtonFlags button_flags = 0; if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; } @@ -6269,6 +7212,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; } if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; } if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; } + // Multi-selection support (header) const bool was_selected = selected; if (is_multi_select) @@ -6276,8 +7220,10 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // Handle multi-select + alter button flags for it MultiSelectItemHeader(id, &selected, &button_flags); } + bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + // Multi-selection support (footer) if (is_multi_select) { @@ -6296,6 +7242,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (g.NavJustMovedToId == id) selected = pressed = true; } + // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with keyboard/gamepad if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover))) { @@ -6308,8 +7255,10 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl } if (pressed) MarkItemEdited(id); + if (selected != was_selected) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; + // Render if (is_visible) { @@ -6328,6 +7277,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl RenderNavCursor(bb, id, nav_render_cursor_flags); } } + if (span_all_columns) { if (g.CurrentTable) @@ -6335,20 +7285,25 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl else if (window->DC.CurrentColumns) PopColumnsBackground(); } + // Text stays at the submission position. Alignment/clipping extents ignore SpanAllColumns. if (is_visible) RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, NULL, &label_size, style.SelectableTextAlign, &bb); + // Automatically close popups if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups)) CloseCurrentPopup(); + if (disabled_item && !disabled_global) EndDisabled(); + // Selectable() always returns a pressed state! // Users of BeginMultiSelect()/EndMultiSelect() scope: you may call ImGui::IsItemToggledSelection() to retrieve // selection toggle, only useful if you need that state updated (e.g. for rendering purpose) before reaching EndMultiSelect(). IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return pressed; //-V1020 } + bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) { if (Selectable(label, *p_selected, flags, size_arg)) @@ -6358,9 +7313,12 @@ bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags } return false; } + + //------------------------------------------------------------------------- // [SECTION] Widgets: Typing-Select support //------------------------------------------------------------------------- + // [Experimental] Currently not exposed in public API. // Consume character inputs and return search request, if any. // This would typically only be called on the focused window or location you want to grab inputs for, e.g. @@ -6373,6 +7331,7 @@ ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags f ImGuiContext& g = *GImGui; ImGuiTypingSelectState* data = &g.TypingSelectState; ImGuiTypingSelectRequest* out_request = &data->Request; + // Clear buffer const float TYPING_SELECT_RESET_TIMER = 1.80f; // FIXME: Potentially move to IO config. const int TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK = 4; // Lock single char matching when repeating same char 4 times @@ -6389,6 +7348,7 @@ ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags f if (clear_buffer) data->Clear(); } + // Append to buffer const int buffer_max_len = IM_ARRAYSIZE(data->SearchBuffer) - 1; int buffer_len = (int)ImStrlen(data->SearchBuffer); @@ -6415,6 +7375,7 @@ ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags f select_request = true; } g.IO.InputQueueCharacters.resize(0); + // Handle backspace if ((flags & ImGuiTypingSelectFlags_AllowBackspace) && IsKeyPressed(ImGuiKey_Backspace, ImGuiInputFlags_Repeat)) { @@ -6422,6 +7383,7 @@ ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags f *p = 0; buffer_len = (int)(p - data->SearchBuffer); } + // Return request if any if (buffer_len == 0) return NULL; @@ -6437,6 +7399,7 @@ ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags f out_request->SelectRequest = (data->LastRequestFrame == g.FrameCount); out_request->SingleCharMode = false; out_request->SingleCharSize = 0; + // Calculate if buffer contains the same character repeated. // - This can be used to implement a special search mode on first character. // - Performed on UTF-8 codepoint for correctness. @@ -6455,8 +7418,10 @@ ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags f out_request->SingleCharSize = (ImS8)c0_len; data->SingleCharModeLock |= (single_char_count >= TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK); // From now on we stop search matching to lock to single char mode. } + return out_request; } + static int ImStrimatchlen(const char* s1, const char* s1_end, const char* s2) { int match_len = 0; @@ -6464,6 +7429,7 @@ static int ImStrimatchlen(const char* s1, const char* s1_end, const char* s2) match_len++; return match_len; } + // Default handler for finding a result for typing-select. You may implement your own. // You might want to display a tooltip to visualize the current request SearchBuffer // When SingleCharMode is set: @@ -6483,12 +7449,14 @@ int ImGui::TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, SetNavCursorVisibleAfterMove(); return idx; } + // Special handling when a single character is repeated: perform search on a single letter and goes to next. int ImGui::TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx) { // FIXME: Assume selection user data is index. Would be extremely practical. //if (nav_item_idx == -1) // nav_item_idx = (int)g.NavLastValidSelectionUserData; + int first_match_idx = -1; bool return_next_match = false; for (int idx = 0; idx < items_count; idx++) @@ -6507,6 +7475,7 @@ int ImGui::TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, in } return first_match_idx; // First result } + int ImGui::TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data) { int longest_match_idx = -1; @@ -6524,6 +7493,7 @@ int ImGui::TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int i } return longest_match_idx; } + void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data) { #ifndef IMGUI_DISABLE_DEBUG_TOOLS @@ -6534,6 +7504,7 @@ void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data) IM_UNUSED(data); #endif } + //------------------------------------------------------------------------- // [SECTION] Widgets: Box-Select support // This has been extracted away from Multi-Select logic in the hope that it could eventually be used elsewhere, but hasn't been yet. @@ -6547,6 +7518,7 @@ void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data) // - BeginBoxSelect() [Internal] // - EndBoxSelect() [Internal] //------------------------------------------------------------------------- + // Call on the initial click. static void BoxSelectPreStartDrag(ImGuiID id, ImGuiSelectionUserData clicked_item) { @@ -6560,6 +7532,7 @@ static void BoxSelectPreStartDrag(ImGuiID id, ImGuiSelectionUserData clicked_ite bs->StartPosRel = bs->EndPosRel = ImGui::WindowPosAbsToRel(g.CurrentWindow, g.IO.MousePos); bs->ScrollAccum = ImVec2(0.0f, 0.0f); } + static void BoxSelectActivateDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -6572,6 +7545,7 @@ static void BoxSelectActivateDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window) if (bs->IsStartedFromVoid && (bs->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0) bs->RequestClear = true; } + static void BoxSelectDeactivateDrag(ImGuiBoxSelectState* bs) { ImGuiContext& g = *GImGui; @@ -6583,6 +7557,7 @@ static void BoxSelectDeactivateDrag(ImGuiBoxSelectState* bs) } bs->ID = 0; } + static void BoxSelectScrollWithMouseDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window, const ImRect& inner_r) { ImGuiContext& g = *GImGui; @@ -6594,9 +7569,11 @@ static void BoxSelectScrollWithMouseDrag(ImGuiBoxSelectState* bs, ImGuiWindow* w const float scroll_curr = window->Scroll[n]; if (dist == 0.0f || (dist < 0.0f && scroll_curr < 0.0f) || (dist > 0.0f && scroll_curr >= window->ScrollMax[n])) continue; + const float speed_multiplier = ImLinearRemapClamp(g.FontSize, g.FontSize * 5.0f, 1.0f, 4.0f, ImAbs(dist)); // x1 to x4 depending on distance const float scroll_step = g.FontSize * 35.0f * speed_multiplier * ImSign(dist) * g.IO.DeltaTime; bs->ScrollAccum[n] += scroll_step; + // Accumulate into a stored value so we can handle high-framerate const float scroll_step_i = ImFloor(bs->ScrollAccum[n]); if (scroll_step_i == 0.0f) @@ -6608,6 +7585,7 @@ static void BoxSelectScrollWithMouseDrag(ImGuiBoxSelectState* bs, ImGuiWindow* w bs->ScrollAccum[n] -= scroll_step_i; } } + bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags) { ImGuiContext& g = *GImGui; @@ -6615,6 +7593,7 @@ bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiI KeepAliveID(box_select_id); if (bs->ID != box_select_id) return false; + // IsStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry. bs->UnclipMode = false; bs->RequestClear = false; @@ -6624,6 +7603,7 @@ bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiI BoxSelectDeactivateDrag(bs); if (!bs->IsActive) return false; + // Current frame absolute prev/current rectangles are used to toggle selection. // They are derived from positions relative to scrolling space. ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel); @@ -6635,6 +7615,7 @@ bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiI bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs); bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs); bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs); + // Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper) // Storing an extra rect used by widgets supporting box-select. if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d) @@ -6644,11 +7625,13 @@ bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiI bs->UnclipRect = bs->BoxSelectRectPrev; // FIXME-OPT: UnclipRect x coordinates could be intersection of Prev and Curr rect on X axis. bs->UnclipRect.Add(bs->BoxSelectRectCurr); } + //GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); //GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); //GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f); return true; } + void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flags) { ImGuiContext& g = *GImGui; @@ -6656,12 +7639,14 @@ void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flag ImGuiBoxSelectState* bs = &g.BoxSelectState; IM_ASSERT(bs->IsActive); bs->UnclipMode = false; + // Render selection rectangle bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view ImRect box_select_r = bs->BoxSelectRectCurr; box_select_r.ClipWith(scope_rect); window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling - window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT: Styling + window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT FIXME-DPI: Styling + // Scroll const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0; if (enable_scroll) @@ -6673,6 +7658,7 @@ void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flag BoxSelectScrollWithMouseDrag(bs, window, scroll_r); } } + //------------------------------------------------------------------------- // [SECTION] Widgets: Multi-Select support //------------------------------------------------------------------------- @@ -6685,6 +7671,7 @@ void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flag // - MultiSelectItemFooter() [Internal] // - DebugNodeMultiSelectState() [Internal] //------------------------------------------------------------------------- + static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSelectIO* io) { ImGuiContext& g = *GImGui; @@ -6695,6 +7682,7 @@ static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSe if (req.Type == ImGuiSelectionRequestType_SetRange) IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetRange %" IM_PRId64 "..%" IM_PRId64 " (0x%" IM_PRIX64 "..0x%" IM_PRIX64 ") = %d (dir %d)\n", function, req.RangeFirstItem, req.RangeLastItem, req.RangeFirstItem, req.RangeLastItem, req.Selected, req.RangeDirection); } } + static ImRect CalcScopeRect(ImGuiMultiSelectTempData* ms, ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -6709,11 +7697,13 @@ static ImRect CalcScopeRect(ImGuiMultiSelectTempData* ms, ImGuiWindow* window) ImRect scope_rect = window->InnerClipRect; if (g.CurrentTable != NULL) scope_rect = g.CurrentTable->HostClipRect; + // Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect? scope_rect.Min = ImMin(scope_rect.Min + ImVec2(window->DecoInnerSizeX1, window->DecoInnerSizeY1), scope_rect.Max); return scope_rect; } } + // Return ImGuiMultiSelectIO structure. // Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect(). // Passing 'selection_size' and 'items_count' parameters is currently optional. @@ -6726,6 +7716,7 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + if (++g.MultiSelectTempDataStacked > g.MultiSelectTempData.Size) g.MultiSelectTempData.resize(g.MultiSelectTempDataStacked, ImGuiMultiSelectTempData()); ImGuiMultiSelectTempData* ms = &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1]; @@ -6737,11 +7728,13 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel flags &= ~(ImGuiMultiSelectFlags_BoxSelect2d | ImGuiMultiSelectFlags_BoxSelect1d); if (flags & ImGuiMultiSelectFlags_BoxSelect2d) flags &= ~ImGuiMultiSelectFlags_BoxSelect1d; + // FIXME: Workaround to the fact we override CursorMaxPos, meaning size measurement are lost. (#8250) // They should perhaps be stacked properly? if (ImGuiTable* table = g.CurrentTable) if (table->CurrentColumn != -1) TableEndCell(table); // This is currently safe to call multiple time. If that properly is lost we can extract the "save measurement" part of it. + // FIXME: BeginFocusScope() const ImGuiID id = window->IDStack.back(); ms->Clear(); @@ -6753,10 +7746,12 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel PushFocusScope(ms->FocusScopeId); if (flags & ImGuiMultiSelectFlags_ScopeWindow) // Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items. window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main; + // Use copy of keyboard mods at the time of the request, otherwise we would requires mods to be held for an extra frame. ms->KeyMods = g.NavJustMovedToId ? (g.NavJustMovedToIsTabbing ? 0 : g.NavJustMovedToKeyMods) : g.IO.KeyMods; if (flags & ImGuiMultiSelectFlags_NoRangeSelect) ms->KeyMods &= ~ImGuiMod_Shift; + // Bind storage ImGuiMultiSelectState* storage = g.MultiSelectStorage.GetOrAddByKey(id); storage->ID = id; @@ -6764,12 +7759,14 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel storage->LastSelectionSize = selection_size; storage->Window = window; ms->Storage = storage; + // Output to user ms->IO.Requests.resize(0); ms->IO.RangeSrcItem = storage->RangeSrcItem; ms->IO.NavIdItem = storage->NavIdItem; ms->IO.NavIdSelected = (storage->NavIdSelected == 1) ? true : false; ms->IO.ItemsCount = items_count; + // Clear when using Navigation to move within the scope // (we compare FocusScopeId so it possible to use multiple selections inside a same window) bool request_clear = false; @@ -6789,6 +7786,7 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0) request_clear = true; } + // Box-select handling: update active state. ImGuiBoxSelectState* bs = &g.BoxSelectState; if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) @@ -6797,6 +7795,7 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel if (BeginBoxSelect(CalcScopeRect(ms, window), window, ms->BoxSelectId, flags)) request_clear |= bs->RequestClear; } + if (ms->IsFocused) { // Shortcut: Clear selection (Escape) @@ -6812,11 +7811,13 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel BoxSelectDeactivateDrag(bs); } } + // Shortcut: Select all (CTRL+A) if (!(flags & ImGuiMultiSelectFlags_SingleSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll)) if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_A)) request_select_all = true; } + if (request_clear || request_select_all) { MultiSelectAddSetAll(ms, request_select_all); @@ -6825,10 +7826,13 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel } ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1; ms->LastSubmittedItem = ImGuiSelectionUserData_Invalid; + if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO); + return &ms->IO; } + // Return updated ImGuiMultiSelectIO structure. // Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect(). ImGuiMultiSelectIO* ImGui::EndMultiSelect() @@ -6840,11 +7844,12 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect() IM_ASSERT_USER_ERROR(ms->FocusScopeId == g.CurrentFocusScopeId, "EndMultiSelect() FocusScope mismatch!"); IM_ASSERT(g.CurrentMultiSelect != NULL && storage->Window == g.CurrentWindow); IM_ASSERT(g.MultiSelectTempDataStacked > 0 && &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] == g.CurrentMultiSelect); + ImRect scope_rect = CalcScopeRect(ms, window); if (ms->IsFocused) { // We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here. - if (ms->IO.RangeSrcReset || (ms->RangeSrcPassedBy == false && ms->IO.RangeSrcItem != ImGuiSelectionUserData_Invalid)) // Can't read storage->RangeSrcItem here -> we want the state at begining of the scope (see tests for easy failure) + if (ms->IO.RangeSrcReset || (ms->RangeSrcPassedBy == false && ms->IO.RangeSrcItem != ImGuiSelectionUserData_Invalid)) // Can't read storage->RangeSrcItem here -> we want the state at beginning of the scope (see tests for easy failure) { IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset RangeSrcItem.\n"); // Will set be to NavId. storage->RangeSrcItem = ImGuiSelectionUserData_Invalid; @@ -6855,11 +7860,14 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect() storage->NavIdItem = ImGuiSelectionUserData_Invalid; storage->NavIdSelected = -1; } + if ((ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) && GetBoxSelectState(ms->BoxSelectId)) EndBoxSelect(scope_rect, ms->Flags); } + if (ms->IsEndIO == false) ms->IO.Requests.resize(0); + // Clear selection when clicking void? // We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection! // The InnerRect test is necessary for non-child/decorated windows. @@ -6879,26 +7887,33 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect() SetNavID(0, ImGuiNavLayer_Main, ms->FocusScopeId, ImRect(g.IO.MousePos, g.IO.MousePos)); // Automatically switch FocusScope for initial click from void to box-select. } } + if (ms->Flags & ImGuiMultiSelectFlags_ClearOnClickVoid) if (IsMouseReleased(0) && IsMouseDragPastThreshold(0) == false && g.IO.KeyMods == ImGuiMod_None) MultiSelectAddSetAll(ms, false); } + // Courtesy nav wrapping helper flag if (ms->Flags & ImGuiMultiSelectFlags_NavWrapX) { IM_ASSERT(ms->Flags & ImGuiMultiSelectFlags_ScopeWindow); // Only supported at window scope ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX); } + // Unwind window->DC.CursorMaxPos = ImMax(ms->BackupCursorMaxPos, window->DC.CursorMaxPos); PopFocusScope(); + if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) DebugLogMultiSelectRequests("EndMultiSelect", &ms->IO); + ms->FocusScopeId = 0; ms->Flags = ImGuiMultiSelectFlags_None; g.CurrentMultiSelect = (--g.MultiSelectTempDataStacked > 0) ? &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] : NULL; + return &ms->IO; } + void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data) { // Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code! @@ -6906,6 +7921,7 @@ void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_d ImGuiContext& g = *GImGui; g.NextItemData.SelectionUserData = selection_user_data; g.NextItemData.FocusScopeId = g.CurrentFocusScopeId; + if (ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect) { // Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping) @@ -6918,6 +7934,7 @@ void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_d g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData; } } + // In charge of: // - Applying SetAll for submitted items. // - Applying SetRange for submitted items and record end points. @@ -6926,17 +7943,20 @@ void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags { ImGuiContext& g = *GImGui; ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect; + bool selected = *p_selected; if (ms->IsFocused) { ImGuiMultiSelectState* storage = ms->Storage; ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData; IM_ASSERT(g.NextItemData.FocusScopeId == g.CurrentFocusScopeId && "Forgot to call SetNextItemSelectionUserData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope"); + // Apply SetAll (Clear/SelectAll) requests requested by BeginMultiSelect(). // This is only useful if the user hasn't processed them already, and this only works if the user isn't using the clipper. // If you are using a clipper you need to process the SetAll request after calling BeginMultiSelect() if (ms->LoopRequestSetAll != -1) selected = (ms->LoopRequestSetAll == 1); + // When using SHIFT+Nav: because it can incur scrolling we cannot afford a frame of lag with the selection highlight (otherwise scrolling would happen before selection) // For this to work, we need someone to set 'RangeSrcPassedBy = true' at some point (either clipper either SetNextItemSelectionUserData() function) if (ms->IsKeyboardSetRange) @@ -6965,6 +7985,7 @@ void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags } *p_selected = selected; } + // Alter button behavior flags // To handle drag and drop of multiple items we need to avoid clearing selection on click. // Enabling this test makes actions using CTRL+SHIFT delay their effect on MouseUp which is annoying, but it allows drag and drop of multiple items. @@ -6979,6 +8000,7 @@ void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags *p_button_flags = button_flags; } } + // In charge of: // - Auto-select on navigation. // - Box-select toggle handling. @@ -6990,23 +8012,29 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + bool selected = *p_selected; bool pressed = *p_pressed; ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect; ImGuiMultiSelectState* storage = ms->Storage; if (pressed) ms->IsFocused = true; + bool hovered = false; if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) hovered = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); if (!ms->IsFocused && !hovered) return; + ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData; + ImGuiMultiSelectFlags flags = ms->Flags; const bool is_singleselect = (flags & ImGuiMultiSelectFlags_SingleSelect) != 0; bool is_ctrl = (ms->KeyMods & ImGuiMod_Ctrl) != 0; bool is_shift = (ms->KeyMods & ImGuiMod_Shift) != 0; + bool apply_to_range_src = false; + if (g.NavId == id && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) apply_to_range_src = true; if (ms->IsEndIO == false) @@ -7014,6 +8042,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) ms->IO.Requests.resize(0); ms->IsEndIO = true; } + // Auto-select as you navigate a list if (g.NavJustMovedToId == id) { @@ -7033,11 +8062,13 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) apply_to_range_src = true; // Since if (pressed) {} main block is not running we update this } } + if (apply_to_range_src) { storage->RangeSrcItem = item_data; storage->RangeSelected = selected; // Will be updated at the end of this function anyway. } + // Box-select toggle handling if (ms->BoxSelectId != 0) if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId)) @@ -7059,6 +8090,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) storage->LastSelectionSize = ImMax(storage->LastSelectionSize + 1, 1); } } + // Right-click handling. // FIXME-MULTISELECT: Currently filtered out by ImGuiMultiSelectFlags_NoAutoSelect but maybe should be moved to Selectable(). See https://github.com/ocornut/imgui/pull/5816 if (hovered && IsMouseClicked(1) && (flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) @@ -7072,10 +8104,12 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) is_ctrl = is_shift = false; } } + // Unlike Space, Enter doesn't alter selection (but can still return a press) unless current item is not selected. // The later, "unless current item is not select", may become optional? It seems like a better default if Enter doesn't necessarily open something // (unlike e.g. Windows explorer). For use case where Enter always open something, we might decide to make this optional? const bool enter_pressed = pressed && (g.NavActivateId == id) && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput); + // Alter selection if (pressed && (!enter_pressed || !selected)) { @@ -7084,6 +8118,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) if (selected == false && !g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1) BoxSelectPreStartDrag(ms->BoxSelectId, item_data); + //---------------------------------------------------------------------------------------- // ACTION | Begin | Pressed/Activated | End //---------------------------------------------------------------------------------------- @@ -7100,6 +8135,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) // Mouse Pressed: Shift | n/a | Dst=item, Sel=1, => Clear + SetRange 1 // Mouse Pressed: Ctrl+Shift | n/a | Dst=item, Sel=!Sel => SetRange Src-Dst //---------------------------------------------------------------------------------------- + if ((flags & ImGuiMultiSelectFlags_NoAutoClear) == 0) { bool request_clear = false; @@ -7112,6 +8148,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) if (request_clear) MultiSelectAddSetAll(ms, false); } + int range_direction; bool range_selected; if (is_shift && !is_singleselect) @@ -7149,9 +8186,11 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) } MultiSelectAddSetRange(ms, range_selected, range_direction, storage->RangeSrcItem, item_data); } + // Update/store the selection state of the Source item (used by CTRL+SHIFT, when Source is unselected we perform a range unselect) if (storage->RangeSrcItem == item_data) storage->RangeSelected = selected ? 1 : 0; + // Update/store the selection state of focused item if (g.NavId == id) { @@ -7161,15 +8200,18 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) if (storage->NavIdItem == item_data) ms->NavIdPassedBy = true; ms->LastSubmittedItem = item_data; + *p_selected = selected; *p_pressed = pressed; } + void ImGui::MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected) { ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, selected, 0, ImGuiSelectionUserData_Invalid, ImGuiSelectionUserData_Invalid }; ms->IO.Requests.resize(0); // Can always clear previous requests ms->IO.Requests.push_back(req); // Add new request } + void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item) { // Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges) @@ -7182,9 +8224,11 @@ void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, return; } } + ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item }; ms->IO.Requests.push_back(req); // Add new request } + void ImGui::DebugNodeMultiSelectState(ImGuiMultiSelectState* storage) { #ifndef IMGUI_DISABLE_DEBUG_TOOLS @@ -7202,12 +8246,14 @@ void ImGui::DebugNodeMultiSelectState(ImGuiMultiSelectState* storage) IM_UNUSED(storage); #endif } + //------------------------------------------------------------------------- // [SECTION] Widgets: Multi-Select helpers //------------------------------------------------------------------------- // - ImGuiSelectionBasicStorage // - ImGuiSelectionExternalStorage //------------------------------------------------------------------------- + ImGuiSelectionBasicStorage::ImGuiSelectionBasicStorage() { Size = 0; @@ -7216,28 +8262,33 @@ ImGuiSelectionBasicStorage::ImGuiSelectionBasicStorage() AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; }; _SelectionOrder = 1; // Always >0 } + void ImGuiSelectionBasicStorage::Clear() { Size = 0; _SelectionOrder = 1; // Always >0 _Storage.Data.resize(0); } + void ImGuiSelectionBasicStorage::Swap(ImGuiSelectionBasicStorage& r) { ImSwap(Size, r.Size); ImSwap(_SelectionOrder, r._SelectionOrder); _Storage.Data.swap(r._Storage.Data); } + bool ImGuiSelectionBasicStorage::Contains(ImGuiID id) const { return _Storage.GetInt(id, 0) != 0; } + static int IMGUI_CDECL PairComparerByValueInt(const void* lhs, const void* rhs) { int lhs_v = ((const ImGuiStoragePair*)lhs)->val_i; int rhs_v = ((const ImGuiStoragePair*)rhs)->val_i; return (lhs_v > rhs_v ? +1 : lhs_v < rhs_v ? -1 : 0); } + // GetNextSelectedItem() is an abstraction allowing us to change our underlying actual storage system without impacting user. // (e.g. store unselected vs compact down, compact down on demand, use raw ImVector instead of ImGuiStorage...) bool ImGuiSelectionBasicStorage::GetNextSelectedItem(void** opaque_it, ImGuiID* out_id) @@ -7259,12 +8310,14 @@ bool ImGuiSelectionBasicStorage::GetNextSelectedItem(void** opaque_it, ImGuiID* _Storage.BuildSortByKey(); return has_more; } + void ImGuiSelectionBasicStorage::SetItemSelected(ImGuiID id, bool selected) { int* p_int = _Storage.GetIntRef(id, 0); if (selected && *p_int == 0) { *p_int = _SelectionOrder++; Size++; } else if (!selected && *p_int != 0) { *p_int = 0; Size--; } } + // Optimized for batch edits (with same value of 'selected') static void ImGuiSelectionBasicStorage_BatchSetItemSelected(ImGuiSelectionBasicStorage* selection, ImGuiID id, bool selected, int size_before_amends, int selection_order) { @@ -7279,12 +8332,14 @@ static void ImGuiSelectionBasicStorage_BatchSetItemSelected(ImGuiSelectionBasicS it->val_i = selected ? selection_order : 0; // Modify in-place. selection->Size += selected ? +1 : -1; } + static void ImGuiSelectionBasicStorage_BatchFinish(ImGuiSelectionBasicStorage* selection, bool selected, int size_before_amends) { ImGuiStorage* storage = &selection->_Storage; if (selected && selection->Size != size_before_amends) storage->BuildSortByKey(); // When done selecting: sort everything } + // Apply requests coming from BeginMultiSelect() and EndMultiSelect(). // - Enable 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen. // - Honoring SetRange requests requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem. @@ -7308,6 +8363,7 @@ void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) // Other scheme may handle SetAll differently. IM_ASSERT(ms_io->ItemsCount != -1 && "Missing value for items_count in BeginMultiSelect() call!"); IM_ASSERT(AdapterIndexToStorageId != NULL); + // This is optimized/specialized to cope with very large selections (e.g. 100k+ items) // - A simpler version could call SetItemSelected() directly instead of ImGuiSelectionBasicStorage_BatchSetItemSelected() + ImGuiSelectionBasicStorage_BatchFinish(). // - Optimized select can append unsorted, then sort in a second pass. Optimized unselect can clear in-place then compact in a second pass. @@ -7357,12 +8413,15 @@ void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) } } } + //------------------------------------------------------------------------- + ImGuiSelectionExternalStorage::ImGuiSelectionExternalStorage() { UserData = NULL; AdapterSetItemSelected = NULL; } + // Apply requests coming from BeginMultiSelect() and EndMultiSelect(). // We also pull 'ms_io->ItemsCount' as passed for BeginMultiSelect() for consistency with ImGuiSelectionBasicStorage // This makes no assumption about underlying storage. @@ -7379,6 +8438,7 @@ void ImGuiSelectionExternalStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) AdapterSetItemSelected(this, idx, req.Selected); } } + //------------------------------------------------------------------------- // [SECTION] Widgets: ListBox //------------------------------------------------------------------------- @@ -7386,6 +8446,7 @@ void ImGuiSelectionExternalStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) // - EndListBox() // - ListBox() //------------------------------------------------------------------------- + // This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label. // This handle some subtleties with capturing info from the label. // If you don't need a label you can pretty much directly use ImGui::BeginChild() with ImGuiChildFlags_FrameStyle. @@ -7397,9 +8458,11 @@ bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + const ImGuiStyle& style = g.Style; const ImGuiID id = GetID(label); const ImVec2 label_size = CalcTextSize(label, NULL, true); + // Size default to hold ~7.25 items. // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. ImVec2 size = ImTrunc(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f)); @@ -7407,6 +8470,7 @@ bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); g.NextItemData.ClearFlags(); + if (!IsRectVisible(bb.Min, bb.Max)) { ItemSize(bb.GetSize(), style.FramePadding.y); @@ -7414,6 +8478,7 @@ bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values return false; } + // FIXME-OPT: We could omit the BeginGroup() if label_size.x == 0.0f but would need to omit the EndGroup() as well. BeginGroup(); if (label_size.x > 0.0f) @@ -7423,35 +8488,43 @@ bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size); AlignTextToFramePadding(); } + BeginChild(id, frame_bb.GetSize(), ImGuiChildFlags_FrameStyle); return true; } + void ImGui::EndListBox() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) && "Mismatched BeginListBox/EndListBox calls. Did you test the return value of BeginListBox?"); IM_UNUSED(window); + EndChild(); EndGroup(); // This is only required to be able to do IsItemXXX query on the whole ListBox including label } + bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items) { const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items); return value_changed; } + // This is merely a helper around BeginListBox(), EndListBox(). // Considering using those directly to submit custom data or store selection differently. bool ImGui::ListBox(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int height_in_items) { ImGuiContext& g = *GImGui; + // Calculate size from "height_in_items" if (height_in_items < 0) height_in_items = ImMin(items_count, 7); float height_in_items_f = height_in_items + 0.25f; ImVec2 size(0.0f, ImTrunc(GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f)); + if (!BeginListBox(label, size)) return false; + // Assume all items have even height (= 1 line of text). If you need items of different height, // you can create a custom version of ListBox() in your code without using the clipper. bool value_changed = false; @@ -7464,6 +8537,7 @@ bool ImGui::ListBox(const char* label, int* current_item, const char* (*getter)( const char* item_text = getter(user_data, i); if (item_text == NULL) item_text = "*Unknown item*"; + PushID(i); const bool item_selected = (i == *current_item); if (Selectable(item_text, item_selected)) @@ -7476,10 +8550,13 @@ bool ImGui::ListBox(const char* label, int* current_item, const char* (*getter)( PopID(); } EndListBox(); + if (value_changed) MarkItemEdited(g.LastItemData.ID); + return value_changed; } + //------------------------------------------------------------------------- // [SECTION] Widgets: PlotLines, PlotHistogram //------------------------------------------------------------------------- @@ -7492,16 +8569,20 @@ bool ImGui::ListBox(const char* label, int* current_item, const char* (*getter)( // - ImPlot https://github.com/epezent/implot // - others https://github.com/ocornut/imgui/wiki/Useful-Extensions //------------------------------------------------------------------------- + int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, const ImVec2& size_arg) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return -1; + const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0)); @@ -7510,6 +8591,7 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get return -1; bool hovered; ButtonBehavior(frame_bb, id, &hovered, NULL); + // Determine scale from values if not specified if (scale_min == FLT_MAX || scale_max == FLT_MAX) { @@ -7528,19 +8610,23 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get if (scale_max == FLT_MAX) scale_max = v_max; } + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1; int idx_hovered = -1; if (values_count >= values_count_min) { int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); + // Tooltip on hover if (hovered && inner_bb.Contains(g.IO.MousePos)) { const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f); const int v_idx = (int)(t * item_count); IM_ASSERT(v_idx >= 0 && v_idx < values_count); + const float v0 = values_getter(data, (v_idx + values_offset) % values_count); const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count); if (plot_type == ImGuiPlotType_Lines) @@ -7549,14 +8635,18 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get SetTooltip("%d: %8.4g", v_idx, v0); idx_hovered = v_idx; } + const float t_step = 1.0f / (float)res_w; const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min)); + float v0 = values_getter(data, (0 + values_offset) % values_count); float t0 = 0.0f; ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (1 + scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands + const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram); const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered); + for (int n = 0; n < res_w; n++) { const float t1 = t0 + t_step; @@ -7564,6 +8654,7 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get IM_ASSERT(v1_idx >= 0 && v1_idx < values_count); const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count); const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) ); + // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU. ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0); ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t)); @@ -7577,67 +8668,83 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get pos1.x -= 1.0f; window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base); } + t0 = t1; tp0 = tp1; } } + // Text overlay if (overlay_text) RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f, 0.0f)); + if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); + // Return hovered index or -1 if none are hovered. // This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx(). return idx_hovered; } + struct ImGuiPlotArrayGetterData { const float* Values; int Stride; + ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; } }; + static float Plot_ArrayGetter(void* data, int idx) { ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data; const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride); return v; } + void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) { ImGuiPlotArrayGetterData data(values, stride); PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); } + void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) { PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); } + void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) { ImGuiPlotArrayGetterData data(values, stride); PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); } + void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) { PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); } + //------------------------------------------------------------------------- // [SECTION] Widgets: Value helpers // Those is not very useful, legacy API. //------------------------------------------------------------------------- // - Value() //------------------------------------------------------------------------- + void ImGui::Value(const char* prefix, bool b) { Text("%s: %s", prefix, (b ? "true" : "false")); } + void ImGui::Value(const char* prefix, int v) { Text("%s: %d", prefix, v); } + void ImGui::Value(const char* prefix, unsigned int v) { Text("%s: %d", prefix, v); } + void ImGui::Value(const char* prefix, float v, const char* float_format) { if (float_format) @@ -7651,6 +8758,7 @@ void ImGui::Value(const char* prefix, float v, const char* float_format) Text("%s: %.3f", prefix, v); } } + //------------------------------------------------------------------------- // [SECTION] MenuItem, BeginMenu, EndMenu, etc. //------------------------------------------------------------------------- @@ -7664,6 +8772,7 @@ void ImGui::Value(const char* prefix, float v, const char* float_format) // - MenuItemEx() [Internal] // - MenuItem() //------------------------------------------------------------------------- + // Helpers for internal use void ImGuiMenuColumns::Update(float spacing, bool window_reappearing) { @@ -7675,6 +8784,7 @@ void ImGuiMenuColumns::Update(float spacing, bool window_reappearing) TotalWidth = NextTotalWidth; NextTotalWidth = 0; } + void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets) { ImU16 offset = 0; @@ -7695,6 +8805,7 @@ void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets) } NextTotalWidth = offset; } + float ImGuiMenuColumns::DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark) { Widths[0] = ImMax(Widths[0], (ImU16)w_icon); @@ -7704,6 +8815,7 @@ float ImGuiMenuColumns::DeclColumns(float w_icon, float w_label, float w_shortcu CalcNextTotalWidth(false); return (float)ImMax(TotalWidth, NextTotalWidth); } + // FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere.. // Currently the main responsibility of this function being to setup clip-rect + horizontal layout + menu navigation layer. // Ideally we also want this to be responsible for claiming space out of the main window scrolling rectangle, in which case ImGuiWindowFlags_MenuBar will become unnecessary. @@ -7715,9 +8827,11 @@ bool ImGui::BeginMenuBar() return false; if (!(window->Flags & ImGuiWindowFlags_MenuBar)) return false; + IM_ASSERT(!window->DC.MenuBarAppending); BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore PushID("##MenuBar"); + // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect. // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy. const float border_top = ImMax(IM_ROUND(window->WindowBorderSize * 0.5f - window->TitleBarHeight), 0.0f); @@ -7726,6 +8840,7 @@ bool ImGui::BeginMenuBar() ImRect clip_rect(ImFloor(bar_rect.Min.x + border_half), ImFloor(bar_rect.Min.y + border_top), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, border_half))), ImFloor(bar_rect.Max.y)); clip_rect.ClipWith(window->OuterRectClipped); PushClipRect(clip_rect.Min, clip_rect.Max, false); + // We overwrite CursorMaxPos because BeginGroup sets it to CursorPos (essentially the .EmitItem hack in EndMenuBar() would need something analogous here, maybe a BeginGroupEx() with flags). window->DC.CursorPos = window->DC.CursorMaxPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y); window->DC.LayoutType = ImGuiLayoutType_Horizontal; @@ -7735,15 +8850,18 @@ bool ImGui::BeginMenuBar() AlignTextToFramePadding(); return true; } + void ImGui::EndMenuBar() { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; ImGuiContext& g = *GImGui; + IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'" IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); IM_ASSERT(window->DC.MenuBarAppending); + // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings. if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) { @@ -7769,9 +8887,12 @@ void ImGui::EndMenuBar() NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); // Repeat } } + PopClipRect(); PopID(); + IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'" window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->Pos.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos. + // FIXME: Extremely confusing, cleanup by (a) working on WorkRect stack system (b) not using a Group confusingly here. ImGuiGroupData& group_data = g.GroupStack.back(); group_data.EmitItem = false; @@ -7784,12 +8905,14 @@ void ImGui::EndMenuBar() window->DC.MenuBarAppending = false; window->DC.CursorMaxPos = restore_cursor_max_pos; } + // Important: calling order matters! // FIXME: Somehow overlapping with docking tech. // FIXME: The "rect-cut" aspect of this could be formalized into a lower-level helper (rect-cut: https://halt.software/dead-simple-layouts) bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, ImGuiDir dir, float axis_size, ImGuiWindowFlags window_flags) { IM_ASSERT(dir != ImGuiDir_None); + ImGuiWindow* bar_window = FindWindowByName(name); ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport()); if (bar_window == NULL || bar_window->BeginCount == 0) @@ -7804,26 +8927,32 @@ bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, Im size[axis] = axis_size; SetNextWindowPos(pos); SetNextWindowSize(size); + // Report our size into work area (for next frame) using actual window size if (dir == ImGuiDir_Up || dir == ImGuiDir_Left) viewport->BuildWorkInsetMin[axis] += axis_size; else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right) viewport->BuildWorkInsetMax[axis] += axis_size; } + window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking; SetNextWindowViewport(viewport->ID); // Enforce viewport so we don't create our own viewport when ImGuiConfigFlags_ViewportsNoMerge is set. PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint bool is_open = Begin(name, NULL, window_flags); PopStyleVar(2); + return is_open; } + bool ImGui::BeginMainMenuBar() { ImGuiContext& g = *GImGui; ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport(); + // Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change SetCurrentViewport(NULL, viewport); + // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set. // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea? // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings. @@ -7837,11 +8966,13 @@ bool ImGui::BeginMainMenuBar() End(); return false; } + // Temporarily disable _NoSavedSettings, in the off-chance that tables or child windows submitted within the menu-bar may want to use settings. (#8356) g.CurrentWindow->Flags &= ~ImGuiWindowFlags_NoSavedSettings; BeginMenuBar(); return is_open; } + void ImGui::EndMainMenuBar() { ImGuiContext& g = *GImGui; @@ -7850,20 +8981,25 @@ void ImGui::EndMainMenuBar() IM_ASSERT_USER_ERROR(0, "Calling EndMainMenuBar() not from a menu-bar!"); // Not technically testing that it is the main menu bar return; } + EndMenuBar(); g.CurrentWindow->Flags |= ImGuiWindowFlags_NoSavedSettings; // Restore _NoSavedSettings (#8356) + // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window // FIXME: With this strategy we won't be able to restore a NULL focus. if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest && g.ActiveId == 0) FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild); + End(); } + static bool IsRootOfOpenMenuSet() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if ((g.OpenPopupStack.Size <= g.BeginPopupStack.Size) || (window->Flags & ImGuiWindowFlags_ChildMenu)) return false; + // Initially we used 'upper_popup->OpenParentId == window->IDStack.back()' to differentiate multiple menu sets from each others // (e.g. inside menu bar vs loose menu items) based on parent ID. // This would however prevent the use of e.g. PushID() user code submitting menus. @@ -7880,20 +9016,24 @@ static bool IsRootOfOpenMenuSet() return false; return upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu) && ImGui::IsWindowChildOf(upper_popup->Window, window, true, false); } + bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None); + // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu) // The first menu in a hierarchy isn't so hovering doesn't get across (otherwise e.g. resizing borders with ImGuiButtonFlags_FlattenChildren would react), but top-most BeginMenu() will bypass that limitation. ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus; if (window->Flags & ImGuiWindowFlags_ChildMenu) window_flags |= ImGuiWindowFlags_ChildWindow; + // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin(). // We are relying on a O(N) search - so O(N log N) over the frame - which seems like the most efficient for the expected small amount of BeginMenu() calls per frame. // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used. @@ -7905,14 +9045,18 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) g.NextWindowData.ClearFlags(); // we behave like Begin() and need to consume those values return menu_is_open; } + // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu g.MenusIdSubmittedThisFrame.push_back(id); + ImVec2 label_size = CalcTextSize(label, NULL, true); + // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent without always being a Child window) // This is only done for items for the menu set and not the full parent window. const bool menuset_is_open = IsRootOfOpenMenuSet(); if (menuset_is_open) PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true); + // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu, // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup(). // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering. @@ -7922,6 +9066,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) BeginDisabled(); const ImGuiMenuColumns* offsets = &window->DC.MenuColumns; bool pressed; + // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another. const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) @@ -7960,9 +9105,11 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) } if (!enabled) EndDisabled(); + const bool hovered = (g.HoveredId == id) && enabled && !g.NavHighlightItemUnderNav; if (menuset_is_open) PopItemFlag(); + bool want_open = false; bool want_open_nav_init = false; bool want_close = false; @@ -7990,11 +9137,13 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) moving_toward_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_toward_child_menu ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG] } + // The 'HovereWindow == window' check creates an inconsistency (e.g. moving away from menu slowly tends to hit same window, whereas moving away fast does not) // But we also need to not close the top-menu menu when moving over void. Perhaps we should extend the triangle check to a larger polygon. // (Remember to test this on BeginPopup("A")->BeginMenu("B") sequence which behaves slightly differently as B isn't a Child of A and hovering isn't shared.) if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu && !g.NavHighlightItemUnderNav && g.ActiveId == 0) want_close = true; + // Open // (note: at this point 'hovered' actually includes the NavDisableMouseHover == false test) if (!menu_is_open && pressed) // Click/activate to open @@ -8028,12 +9177,15 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) NavMoveRequestCancel(); } } + if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }' want_close = true; if (want_close && IsPopupOpen(id, ImGuiPopupFlags_None)) ClosePopupToLevel(g.BeginPopupStack.Size, true); + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0)); PopID(); + if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size) { // Don't reopen/recycle same menu level in the same frame if it is a different menu ID, first close the other menu and yield for a frame. @@ -8044,6 +9196,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) menu_is_open = true; OpenPopup(label, ImGuiPopupFlags_NoReopen);// | (want_open_nav_init ? ImGuiPopupFlags_NoReopenAlwaysNavInit : 0)); } + if (menu_is_open) { ImGuiLastItemData last_item_in_parent = g.LastItemData; @@ -8060,6 +9213,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) FocusWindow(g.CurrentWindow, ImGuiFocusRequestFlags_UnlessBelowModal); NavInitWindow(g.CurrentWindow, false); } + // Restore LastItemData so IsItemXXXX functions can work after BeginMenu()/EndMenu() // (This fixes using IsItemClicked() and IsItemHovered(), but IsItemHovered() also relies on its support for ImGuiItemFlags_NoWindowHoverableCheck) g.LastItemData = last_item_in_parent; @@ -8071,12 +9225,15 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) { g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values } + return menu_is_open; } + bool ImGui::BeginMenu(const char* label, bool enabled) { return BeginMenuEx(label, NULL, enabled); } + void ImGui::EndMenu() { // Nav: When a left move request our menu failed, close ourselves. @@ -8091,27 +9248,33 @@ void ImGui::EndMenu() ClosePopupToLevel(g.BeginPopupStack.Size - 1, true); NavMoveRequestCancel(); } + EndPopup(); } + bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut, bool selected, bool enabled) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; ImVec2 pos = window->DC.CursorPos; ImVec2 label_size = CalcTextSize(label, NULL, true); + // See BeginMenuEx() for comments about this. const bool menuset_is_open = IsRootOfOpenMenuSet(); if (menuset_is_open) PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true); + // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73), // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only. bool pressed; PushID(label); if (!enabled) BeginDisabled(); + // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another. const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SetNavIdOnHover; const ImGuiMenuColumns* offsets = &window->DC.MenuColumns; @@ -8162,12 +9325,15 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut PopID(); if (menuset_is_open) PopItemFlag(); + return pressed; } + bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) { return MenuItemEx(label, NULL, shortcut, selected, enabled); } + bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) { if (MenuItemEx(label, NULL, shortcut, p_selected ? *p_selected : false, enabled)) @@ -8178,6 +9344,7 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, } return false; } + //------------------------------------------------------------------------- // [SECTION] Widgets: BeginTabBar, EndTabBar, etc. //------------------------------------------------------------------------- @@ -8204,13 +9371,17 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, // - TabBarScrollingButtons() [Internal] // - TabBarTabListPopupButton() [Internal] //------------------------------------------------------------------------- + struct ImGuiTabBarSection { int TabCount; // Number of tabs in this section. float Width; // Sum of width of tabs in this section (after shrinking down) + float WidthAfterShrinkMinWidth; float Spacing; // Horizontal spacing at the end of the section. + ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); } }; + namespace ImGui { static void TabBarLayout(ImGuiTabBar* tab_bar); @@ -8221,16 +9392,19 @@ namespace ImGui static ImGuiTabItem* TabBarScrollingButtons(ImGuiTabBar* tab_bar); static ImGuiTabItem* TabBarTabListPopupButton(ImGuiTabBar* tab_bar); } + ImGuiTabBar::ImGuiTabBar() { memset(this, 0, sizeof(*this)); CurrFrameVisible = PrevFrameVisible = -1; LastTabItemIdx = -1; } + static inline int TabItemGetSectionIdx(const ImGuiTabItem* tab) { return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; } + static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs) { const ImGuiTabItem* a = (const ImGuiTabItem*)lhs; @@ -8241,17 +9415,20 @@ static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs return a_section - b_section; return (int)(a->IndexDuringLayout - b->IndexDuringLayout); } + static int IMGUI_CDECL TabItemComparerByBeginOrder(const void* lhs, const void* rhs) { const ImGuiTabItem* a = (const ImGuiTabItem*)lhs; const ImGuiTabItem* b = (const ImGuiTabItem*)rhs; return (int)(a->BeginOrder - b->BeginOrder); } + static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref) { ImGuiContext& g = *GImGui; return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index); } + static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar) { ImGuiContext& g = *GImGui; @@ -8259,35 +9436,41 @@ static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar) return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar)); return ImGuiPtrOrIndex(tab_bar); } + bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; + ImGuiID id = window->GetID(str_id); ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id); ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->WorkRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2); tab_bar->ID = id; - tab_bar->SeparatorMinX = tab_bar->BarRect.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f); - tab_bar->SeparatorMaxX = tab_bar->BarRect.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f); + tab_bar->SeparatorMinX = tab_bar_bb.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f); + tab_bar->SeparatorMaxX = tab_bar_bb.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f); //if (g.NavWindow && IsWindowChildOf(g.NavWindow, window, false, false)) flags |= ImGuiTabBarFlags_IsFocused; return BeginTabBarEx(tab_bar, tab_bar_bb, flags); } + bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; + IM_ASSERT(tab_bar->ID != 0); if ((flags & ImGuiTabBarFlags_DockNode) == 0) PushOverrideID(tab_bar->ID); + // Add to stack g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar)); g.CurrentTabBar = tab_bar; tab_bar->Window = window; + // Append with multiple BeginTabBar()/EndTabBar() pairs. tab_bar->BackupCursorPos = window->DC.CursorPos; if (tab_bar->CurrFrameVisible == g.FrameCount) @@ -8296,14 +9479,17 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG tab_bar->BeginCount++; return true; } + // Ensure correct ordering when toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable))) if ((flags & ImGuiTabBarFlags_DockNode) == 0) // FIXME: TabBar with DockNode can now be hybrid ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder); tab_bar->TabsAddedNew = false; + // Flags if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0) flags |= ImGuiTabBarFlags_FittingPolicyDefault_; + tab_bar->Flags = flags; tab_bar->BarRect = tab_bar_bb; tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab() @@ -8316,8 +9502,10 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG tab_bar->TabsActiveCount = 0; tab_bar->LastTabItemIdx = -1; tab_bar->BeginCount = 1; + // Set cursor pos in a way which only be used in the off-chance the user erroneously submits item before BeginTabItem(): items will overlap window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY); + // Draw separator // (it would be misleading to draw this in EndTabBar() suggesting that it may be drawn over tabs, as tab bar are appendable) const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected); @@ -8328,21 +9516,25 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG } return true; } + void ImGui::EndTabBar() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return; + ImGuiTabBar* tab_bar = g.CurrentTabBar; if (tab_bar == NULL) { IM_ASSERT_USER_ERROR(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!"); return; } + // Fallback in case no TabItem have been submitted if (tab_bar->WantLayout) TabBarLayout(tab_bar); + // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed(). const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount); if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing) @@ -8356,23 +9548,32 @@ void ImGui::EndTabBar() } if (tab_bar->BeginCount > 1) window->DC.CursorPos = tab_bar->BackupCursorPos; + tab_bar->LastTabItemIdx = -1; if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) PopID(); + g.CurrentTabBarStack.pop_back(); g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back()); } + // Scrolling happens only in the central section (leading/trailing sections are not scrolling) static float TabBarCalcScrollableWidth(ImGuiTabBar* tab_bar, ImGuiTabBarSection* sections) { return tab_bar->BarRect.GetWidth() - sections[0].Width - sections[2].Width - sections[1].Spacing; } + // This is called only once a frame before by the first call to ItemTab() // The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions. static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) { ImGuiContext& g = *GImGui; tab_bar->WantLayout = false; + + // Track selected tab when resizing our parent down + const bool scroll_to_selected_tab = (tab_bar->BarRectPrevWidth > tab_bar->BarRect.GetWidth()); + tab_bar->BarRectPrevWidth = tab_bar->BarRect.GetWidth(); + // Garbage collect by compacting list // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section) int tab_dst_n = 0; @@ -8391,8 +9592,10 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) } if (tab_dst_n != tab_src_n) tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n]; + tab = &tab_bar->Tabs[tab_dst_n]; tab->IndexDuringLayout = (ImS16)tab_dst_n; + // We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another) int curr_tab_section_n = TabItemGetSectionIdx(tab); if (tab_dst_n > 0) @@ -8404,16 +9607,20 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) if (prev_tab_section_n == 2 && curr_tab_section_n != 2) need_sort_by_section = true; } + sections[curr_tab_section_n].TabCount++; tab_dst_n++; } if (tab_bar->Tabs.Size != tab_dst_n) tab_bar->Tabs.resize(tab_dst_n); + if (need_sort_by_section) ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection); + // Calculate spacing between sections sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; + // Setup next selected tab ImGuiID scroll_to_tab_id = 0; if (tab_bar->NextSelectedTabId) @@ -8422,6 +9629,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab_bar->NextSelectedTabId = 0; scroll_to_tab_id = tab_bar->SelectedTabId; } + // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot). if (tab_bar->ReorderRequestTabId != 0) { @@ -8430,15 +9638,21 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) scroll_to_tab_id = tab_bar->ReorderRequestTabId; tab_bar->ReorderRequestTabId = 0; } + // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!) const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0; if (tab_list_popup_button) if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x! scroll_to_tab_id = tab_bar->SelectedTabId = tab_to_select->ID; + // Leading/Trailing tabs will be shrink only if central one aren't visible anymore, so layout the shrink data as: leading, trailing, central // (whereas our tabs are stored as: leading, central, trailing) int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount }; g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); + + // Minimum shrink width + const float shrink_min_width = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed) ? g.Style.TabMinWidthShrink : 1.0f; + // Compute ideal tabs widths + store them into shrink buffer ImGuiTabItem* most_recently_selected_tab = NULL; int curr_section_n = -1; @@ -8447,22 +9661,29 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) { ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible); + if ((most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) && !(tab->Flags & ImGuiTabItemFlags_Button)) most_recently_selected_tab = tab; if (tab->ID == tab_bar->SelectedTabId) found_selected_tab_id = true; if (scroll_to_tab_id == 0 && g.NavJustMovedToId == tab->ID) scroll_to_tab_id = tab->ID; + // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar. // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet, // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window. const char* tab_name = TabBarGetTabName(tab_bar, tab); const bool has_close_button_or_unsaved_marker = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) == 0 || (tab->Flags & ImGuiTabItemFlags_UnsavedDocument); tab->ContentWidth = (tab->RequestedWidth >= 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button_or_unsaved_marker).x; + if ((tab->Flags & ImGuiTabItemFlags_Button) == 0) + tab->ContentWidth = ImMax(tab->ContentWidth, g.Style.TabMinWidthBase); + int section_n = TabItemGetSectionIdx(tab); ImGuiTabBarSection* section = §ions[section_n]; section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f); + section->WidthAfterShrinkMinWidth += ImMin(tab->ContentWidth, shrink_min_width) + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f); curr_section_n = section_n; + // Store data so we can build an array sorted by width if we need to shrink tabs down IM_MSVC_WARNING_SUPPRESS(6385); ImGuiShrinkWidthItem* shrink_width_item = &g.ShrinkWidthBuffer[shrink_buffer_indexes[section_n]++]; @@ -8470,19 +9691,31 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) shrink_width_item->Width = shrink_width_item->InitialWidth = tab->ContentWidth; tab->Width = ImMax(tab->ContentWidth, 1.0f); } + // Compute total ideal width (used for e.g. auto-resizing a window) + float width_all_tabs_after_min_width_shrink = 0.0f; tab_bar->WidthAllTabsIdeal = 0.0f; for (int section_n = 0; section_n < 3; section_n++) + { tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing; + width_all_tabs_after_min_width_shrink += sections[section_n].WidthAfterShrinkMinWidth + sections[section_n].Spacing; + } + // Horizontal scrolling buttons - // (note that TabBarScrollButtons() will alter BarRect.Max.x) - if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll)) + // Important: note that TabBarScrollButtons() will alter BarRect.Max.x. + const bool can_scroll = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) || (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed); + const float width_all_tabs_to_use_for_scroll = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) ? tab_bar->WidthAllTabs : width_all_tabs_after_min_width_shrink; + tab_bar->ScrollButtonEnabled = ((width_all_tabs_to_use_for_scroll > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && can_scroll); + if (tab_bar->ScrollButtonEnabled) if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar)) { scroll_to_tab_id = scroll_and_select_tab->ID; if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0) tab_bar->SelectedTabId = scroll_to_tab_id; } + if (scroll_to_tab_id == 0 && scroll_to_selected_tab) + scroll_to_tab_id = tab_bar->SelectedTabId; + // Shrink widths if full tabs don't fit in their allocated space float section_0_w = sections[0].Width + sections[0].Spacing; float section_1_w = sections[1].Width + sections[1].Spacing; @@ -8493,12 +9726,15 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) width_excess = ImMax(section_1_w - (tab_bar->BarRect.GetWidth() - section_0_w - section_2_w), 0.0f); // Excess used to shrink central section else width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section + // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore - if (width_excess >= 1.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible)) + const bool can_shrink = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyShrink) || (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed); + if (width_excess >= 1.0f && (can_shrink || !central_section_is_visible)) { int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount); int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0); - ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess); + ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess, shrink_min_width); + // Apply shrunk values into tabs and sections for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++) { @@ -8506,12 +9742,14 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) float shrinked_width = IM_TRUNC(g.ShrinkWidthBuffer[tab_n].Width); if (shrinked_width < 0.0f) continue; + shrinked_width = ImMax(1.0f, shrinked_width); int section_n = TabItemGetSectionIdx(tab); sections[section_n].Width -= (tab->Width - shrinked_width); tab->Width = shrinked_width; } } + // Layout all active tabs int section_tab_index = 0; float tab_offset = 0.0f; @@ -8521,6 +9759,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) ImGuiTabBarSection* section = §ions[section_n]; if (section_n == 2) tab_offset = ImMin(ImMax(0.0f, tab_bar->BarRect.GetWidth() - section->Width), tab_offset); + for (int tab_n = 0; tab_n < section->TabCount; tab_n++) { ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n]; @@ -8532,23 +9771,28 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab_offset += section->Spacing; section_tab_index += section->TabCount; } + // Clear name buffers tab_bar->TabsNames.Buf.resize(0); + // If we have lost the selected tab, select the next most recently active one if (found_selected_tab_id == false) tab_bar->SelectedTabId = 0; if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL) scroll_to_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID; + // Lock in visible tab tab_bar->VisibleTabId = tab_bar->SelectedTabId; tab_bar->VisibleTabWasSubmitted = false; + // CTRL+TAB can override visible tab temporarily if (g.NavWindowingTarget != NULL && g.NavWindowingTarget->DockNode && g.NavWindowingTarget->DockNode->TabBar == tab_bar) tab_bar->VisibleTabId = scroll_to_tab_id = g.NavWindowingTarget->TabId; + // Apply request requests if (scroll_to_tab_id != 0) TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections); - else if ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) && IsMouseHoveringRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, true) && IsWindowContentHoverable(g.CurrentWindow)) + else if (tab_bar->ScrollButtonEnabled && IsMouseHoveringRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, true) && IsWindowContentHoverable(g.CurrentWindow)) { const float wheel = g.IO.MouseWheelRequestAxisSwap ? g.IO.MouseWheel : g.IO.MouseWheelH; const ImGuiKey wheel_key = g.IO.MouseWheelRequestAxisSwap ? ImGuiKey_MouseWheelY : ImGuiKey_MouseWheelX; @@ -8560,6 +9804,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) } SetKeyOwner(wheel_key, tab_bar->ID); } + // Update scrolling tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim); tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget); @@ -8578,12 +9823,14 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) } tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing; tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing; + // Actual layout in host window (we don't do it in BeginTabBar() so as not to waste an extra frame) ImGuiWindow* window = g.CurrentWindow; window->DC.CursorPos = tab_bar->BarRect.Min; ItemSize(ImVec2(tab_bar->WidthAllTabs, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y); window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, tab_bar->BarRect.Min.x + tab_bar->WidthAllTabsIdeal); } + // Dockable windows uses Name/ID in the global namespace. Non-dockable items use the ID stack. static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window) { @@ -8601,11 +9848,13 @@ static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, I return window->GetID(label); } } + static float ImGui::TabBarCalcMaxTabWidth() { ImGuiContext& g = *GImGui; return g.FontSize * 20.0f; } + ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id) { if (tab_id != 0) @@ -8614,6 +9863,7 @@ ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id) return &tab_bar->Tabs[n]; return NULL; } + // Order = visible order, not submission order! (which is tab->BeginOrder) ImGuiTabItem* ImGui::TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order) { @@ -8621,6 +9871,7 @@ ImGuiTabItem* ImGui::TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order) return NULL; return &tab_bar->Tabs[order]; } + // FIXME: See references to #2304 in TODO.txt ImGuiTabItem* ImGui::TabBarFindMostRecentlySelectedTabForActiveWindow(ImGuiTabBar* tab_bar) { @@ -8634,12 +9885,14 @@ ImGuiTabItem* ImGui::TabBarFindMostRecentlySelectedTabForActiveWindow(ImGuiTabBa } return most_recently_selected_tab; } + ImGuiTabItem* ImGui::TabBarGetCurrentTab(ImGuiTabBar* tab_bar) { if (tab_bar->LastTabItemIdx < 0 || tab_bar->LastTabItemIdx >= tab_bar->Tabs.Size) return NULL; return &tab_bar->Tabs[tab_bar->LastTabItemIdx]; } + const char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) { if (tab->Window) @@ -8649,6 +9902,7 @@ const char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) IM_ASSERT(tab->NameOffset < tab_bar->TabsNames.Buf.Size); return tab_bar->TabsNames.Buf.Data + tab->NameOffset; } + // The purpose of this call is to register tab in advance so we can control their order at the time they appear. // Otherwise calling this is unnecessary as tabs are appending as needed by the BeginTabItem() function. void ImGui::TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGuiWindow* window) @@ -8656,8 +9910,10 @@ void ImGui::TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGu ImGuiContext& g = *GImGui; IM_ASSERT(TabBarFindTabByID(tab_bar, window->TabId) == NULL); IM_ASSERT(g.CurrentTabBar != tab_bar); // Can't work while the tab bar is active as our tab doesn't have an X offset yet, in theory we could/should test something like (tab_bar->CurrFrameVisible < g.FrameCount) but we'd need to solve why triggers the commented early-out assert in BeginTabBarEx() (probably dock node going from implicit to explicit in same frame) + if (!window->HasCloseButton) tab_flags |= ImGuiTabItemFlags_NoCloseButton; // Set _NoCloseButton immediately because it will be used for first-frame width calculation. + ImGuiTabItem new_tab; new_tab.ID = window->TabId; new_tab.Flags = tab_flags; @@ -8667,6 +9923,7 @@ void ImGui::TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGu new_tab.Window = window; // Required so tab bar layout can compute the tab width before tab submission tab_bar->Tabs.push_back(new_tab); } + // The *TabId fields are already set by the docking system _before_ the actual TabItem was created, so we clear them regardless. void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) { @@ -8676,11 +9933,13 @@ void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) if (tab_bar->SelectedTabId == tab_id) { tab_bar->SelectedTabId = 0; } if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; } } + // Called on manual closure attempt void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) { if (tab->Flags & ImGuiTabItemFlags_Button) return; // A button appended with TabItemButton(). + if ((tab->Flags & (ImGuiTabItemFlags_UnsavedDocument | ImGuiTabItemFlags_NoAssumedClosure)) == 0) { // This will remove a frame of lag for selecting another tab on closure. @@ -8699,11 +9958,13 @@ void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) TabBarQueueFocus(tab_bar, tab); } } + static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling) { scrolling = ImMin(scrolling, tab_bar->WidthAllTabs - tab_bar->BarRect.GetWidth()); return ImMax(scrolling, 0.0f); } + // Note: we may scroll to tab that are not selected! e.g. using keyboard arrow keys static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections) { @@ -8712,11 +9973,14 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGui return; if (tab->Flags & ImGuiTabItemFlags_SectionMask_) return; + ImGuiContext& g = *GImGui; float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar) int order = TabBarGetTabOrder(tab_bar, tab); + // Scrolling happens only in the central section (leading/trailing sections are not scrolling) float scrollable_width = TabBarCalcScrollableWidth(tab_bar, sections); + // We make all tabs positions all relative Sections[0].Width to make code simpler float tab_x1 = tab->Offset - sections[0].Width + (order > sections[0].TabCount - 1 ? -margin : 0.0f); float tab_x2 = tab->Offset - sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - sections[2].TabCount ? margin : 1.0f); @@ -8734,16 +9998,19 @@ static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGui tab_bar->ScrollingTarget = tab_x2 - scrollable_width; } } + void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) { tab_bar->NextSelectedTabId = tab->ID; } + void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, const char* tab_name) { IM_ASSERT((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0); // Only supported for manual/explicit tab bars ImGuiID tab_id = TabBarCalcTabID(tab_bar, tab_name, NULL); tab_bar->NextSelectedTabId = tab_id; } + void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset) { IM_ASSERT(offset != 0); @@ -8751,14 +10018,17 @@ void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offs tab_bar->ReorderRequestTabId = tab->ID; tab_bar->ReorderRequestOffset = (ImS16)offset; } + void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* src_tab, ImVec2 mouse_pos) { ImGuiContext& g = *GImGui; IM_ASSERT(tab_bar->ReorderRequestTabId == 0); if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0) return; + const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0; const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0); + // Count number of contiguous tabs we are crossing over const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1; const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab); @@ -8772,6 +10042,7 @@ void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* s if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_)) break; dst_idx = i; + // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered. const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x; const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x; @@ -8779,18 +10050,22 @@ void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* s if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2)) break; } + if (dst_idx != src_idx) TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx); } + bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar) { ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId); if (tab1 == NULL || (tab1->Flags & ImGuiTabItemFlags_NoReorder)) return false; + //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools int tab2_order = TabBarGetTabOrder(tab_bar, tab1) + tab_bar->ReorderRequestOffset; if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size) return false; + // Reordered tabs must share the same section // (Note: TabBarQueueReorderFromMousePos() also has a similar test but since we allow direct calls to TabBarQueueReorder() we do it here too) ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order]; @@ -8798,30 +10073,37 @@ bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar) return false; if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_)) return false; + ImGuiTabItem item_tmp = *tab1; ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2; ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1; const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset; memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem)); *tab2 = item_tmp; + if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings) MarkIniSettingsDirty(); return true; } + static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f); const float scrolling_buttons_width = arrow_button_size.x * 2.0f; + const ImVec2 backup_cursor_pos = window->DC.CursorPos; //window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x, tab_bar->BarRect.Max.y), IM_COL32(255,0,0,255)); + int select_dir = 0; ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text]; arrow_col.w *= 0.5f; + PushStyleColor(ImGuiCol_Text, arrow_col); PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); - PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); + PushItemFlag(ImGuiItemFlags_ButtonRepeat | ImGuiItemFlags_NoNav, true); const float backup_repeat_delay = g.IO.KeyRepeatDelay; const float backup_repeat_rate = g.IO.KeyRepeatRate; g.IO.KeyRepeatDelay = 0.250f; @@ -8837,17 +10119,20 @@ static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) PopStyleColor(2); g.IO.KeyRepeatRate = backup_repeat_rate; g.IO.KeyRepeatDelay = backup_repeat_delay; + ImGuiTabItem* tab_to_scroll_to = NULL; if (select_dir != 0) if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId)) { int selected_order = TabBarGetTabOrder(tab_bar, tab_item); int target_order = selected_order + select_dir; + // Skip tab item buttons until another tab item is found or end is reached while (tab_to_scroll_to == NULL) { // If we are at the end of the list, still scroll to make our tab visible tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order]; + // Cross through buttons // (even if first/last item is a button, return it so we can update the scroll) if (tab_to_scroll_to->Flags & ImGuiTabItemFlags_Button) @@ -8860,23 +10145,28 @@ static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) } window->DC.CursorPos = backup_cursor_pos; tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f; + return tab_to_scroll_to; } + static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; + // We use g.Style.FramePadding.y to match the square ArrowButton size const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y; const ImVec2 backup_cursor_pos = window->DC.CursorPos; window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y); tab_bar->BarRect.Min.x += tab_list_popup_button_width; + ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text]; arrow_col.w *= 0.5f; PushStyleColor(ImGuiCol_Text, arrow_col); PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLargest); PopStyleColor(2); + ImGuiTabItem* tab_to_select = NULL; if (open) { @@ -8885,15 +10175,18 @@ static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar) ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; if (tab->Flags & ImGuiTabItemFlags_Button) continue; + const char* tab_name = TabBarGetTabName(tab_bar, tab); if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID)) tab_to_select = tab; } EndCombo(); } + window->DC.CursorPos = backup_cursor_pos; return tab_to_select; } + //------------------------------------------------------------------------- // [SECTION] Widgets: BeginTabItem, EndTabItem, etc. //------------------------------------------------------------------------- @@ -8906,12 +10199,14 @@ static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar) // - TabItemBackground() [Internal] // - TabItemLabelAndCloseButton() [Internal] //------------------------------------------------------------------------- + bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; + ImGuiTabBar* tab_bar = g.CurrentTabBar; if (tab_bar == NULL) { @@ -8919,6 +10214,7 @@ bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags f return false; } IM_ASSERT((flags & ImGuiTabItemFlags_Button) == 0); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead! + bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL); if (ret && !(flags & ImGuiTabItemFlags_NoPushId)) { @@ -8927,12 +10223,14 @@ bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags f } return ret; } + void ImGui::EndTabItem() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return; + ImGuiTabBar* tab_bar = g.CurrentTabBar; if (tab_bar == NULL) { @@ -8944,12 +10242,14 @@ void ImGui::EndTabItem() if (!(tab->Flags & ImGuiTabItemFlags_NoPushId)) PopID(); } + bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; + ImGuiTabBar* tab_bar = g.CurrentTabBar; if (tab_bar == NULL) { @@ -8958,12 +10258,14 @@ bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags) } return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL); } + void ImGui::TabItemSpacing(const char* str_id, ImGuiTabItemFlags flags, float width) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return; + ImGuiTabBar* tab_bar = g.CurrentTabBar; if (tab_bar == NULL) { @@ -8973,6 +10275,7 @@ void ImGui::TabItemSpacing(const char* str_id, ImGuiTabItemFlags flags, float SetNextItemWidth(width); TabItemEx(tab_bar, str_id, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder | ImGuiTabItemFlags_Invisible, NULL); } + bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window) { // Layout whole tab bar if not already done @@ -8986,8 +10289,10 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; + const ImGuiStyle& style = g.Style; const ImGuiID id = TabBarCalcTabID(tab_bar, label, docked_window); + // If the user called us with *p_open == false, we early out and don't render. // We make a call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID. IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); @@ -8996,13 +10301,16 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav); return false; } + IM_ASSERT(!p_open || !(flags & ImGuiTabItemFlags_Button)); IM_ASSERT((flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)); // Can't use both Leading and Trailing + // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented) if (flags & ImGuiTabItemFlags_NoCloseButton) p_open = NULL; else if (p_open == NULL) flags |= ImGuiTabItemFlags_NoCloseButton; + // Acquire tab data ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id); bool tab_is_new = false; @@ -9014,6 +10322,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, tab_bar->TabsAddedNew = tab_is_new = true; } tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab); + // Calculate tab contents size ImVec2 size = TabItemCalcSize(label, (p_open != NULL) || (flags & ImGuiTabItemFlags_UnsavedDocument)); tab->RequestedWidth = -1.0f; @@ -9023,6 +10332,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, tab->Width = ImMax(1.0f, size.x); tab->ContentWidth = size.x; tab->BeginOrder = tab_bar->TabsActiveCount++; + const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount); const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0; const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount); @@ -9031,6 +10341,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, tab->LastFrameVisible = g.FrameCount; tab->Flags = flags; tab->Window = docked_window; + // Append name _WITH_ the zero-terminator // (regular tabs are permitted in a DockNode tab bar, but window tabs not permitted in a non-DockNode tab bar) if (docked_window != NULL) @@ -9043,6 +10354,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, tab->NameOffset = (ImS32)tab_bar->TabsNames.size(); tab_bar->TabsNames.append(label, label + ImStrlen(label) + 1); } + // Update selected tab if (!is_tab_button) { @@ -9052,15 +10364,18 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // _SetSelected can only be passed on explicit tab bar TabBarQueueFocus(tab_bar, tab); } + // Lock visibility // (Note: tab_contents_visible != tab_selected... because CTRL+TAB operations may preview some tabs without selecting them!) bool tab_contents_visible = (tab_bar->VisibleTabId == id); if (tab_contents_visible) tab_bar->VisibleTabWasSubmitted = true; + // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing && docked_window == NULL) if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs)) tab_contents_visible = true; + // Note that tab_is_new is not necessarily the same as tab_appearing! When a tab bar stops being submitted // and then gets submitted again, the tabs will have 'tab_appearing=true' but 'tab_is_new=false'. if (tab_appearing && (!tab_bar_appearing || tab_is_new)) @@ -9070,10 +10385,13 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, return false; return tab_contents_visible; } + if (tab_bar->SelectedTabId == id) tab->LastFrameSelected = g.FrameCount; + // Backup current layout position const ImVec2 backup_main_cursor_pos = window->DC.CursorPos; + // Layout const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0; size.x = tab->Width; @@ -9083,13 +10401,16 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f); ImVec2 pos = window->DC.CursorPos; ImRect bb(pos, pos + size); + // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation) const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX); if (want_clip_rect) PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true); + ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos; ItemSize(bb.GetSize(), style.FramePadding.y); window->DC.CursorMaxPos = backup_cursor_max_pos; + if (!ItemAdd(bb, id)) { if (want_clip_rect) @@ -9097,6 +10418,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, window->DC.CursorPos = backup_main_cursor_pos; return tab_contents_visible; } + // Click to Select a tab ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap); if (g.DragDropActive && !g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW)) // FIXME: May be an opt-in property of the payload to disable this @@ -9108,10 +10430,12 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); if (pressed && !is_tab_button) TabBarQueueFocus(tab_bar, tab); + // Transfer active id window so the active id is not owned by the dock host (as StartMouseMovingWindow() // will only do it on the drag). This allows FocusWindow() to be more conservative in how it clears active id. if (held && docked_window && g.ActiveId == id && g.ActiveIdIsJustActivated) g.ActiveIdWindow = docked_window; + // Drag and drop a single floating window node moves it ImGuiDockNode* node = docked_window ? docked_window->DockNode : NULL; const bool single_floating_window_node = node && node->IsFloatingNode() && (node->Windows.Size == 1); @@ -9141,6 +10465,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos); } } + // Extract a Dockable window out of it's tab bar const bool can_undock = docked_window != NULL && !(docked_window->Flags & ImGuiWindowFlags_NoMove) && !(node->MergedFlags & ImGuiDockNodeFlags_NoUndocking); if (can_undock) @@ -9153,6 +10478,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, float threshold_x = (threshold_base * 2.2f); float threshold_y = (threshold_base * 1.5f) + ImClamp((ImFabs(g.IO.MouseDragMaxDistanceAbs[0].x) - threshold_base * 2.0f) * 0.20f, 0.0f, threshold_base * 4.0f); //GetForegroundDrawList()->AddRect(ImVec2(bb.Min.x - threshold_x, bb.Min.y - threshold_y), ImVec2(bb.Max.x + threshold_x, bb.Max.y + threshold_y), IM_COL32_WHITE); // [DEBUG] + float distance_from_edge_y = ImMax(bb.Min.y - g.IO.MousePos.y, g.IO.MousePos.y - bb.Max.y); if (distance_from_edge_y >= threshold_y) undocking_tab = true; @@ -9160,6 +10486,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, if ((drag_dir < 0 && TabBarGetTabOrder(tab_bar, tab) == 0) || (drag_dir > 0 && TabBarGetTabOrder(tab_bar, tab) == tab_bar->Tabs.Size - 1)) undocking_tab = true; } + if (undocking_tab) { // Undock @@ -9173,6 +10500,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, } } } + #if 0 if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth) { @@ -9182,6 +10510,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive)); } #endif + // Render tab shape const bool is_visible = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) && !(flags & ImGuiTabItemFlags_Invisible); if (is_visible) @@ -9208,12 +10537,15 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, } } RenderNavCursor(bb, id); + // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget. const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); if (tab_bar->SelectedTabId != tab->ID && hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button) TabBarQueueFocus(tab_bar, tab); + if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; + // Render tab label, process close button const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, docked_window ? docked_window->ID : id) : 0; bool just_closed; @@ -9224,10 +10556,12 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, *p_open = false; TabBarCloseTab(tab_bar, tab); } + // Forward Hovered state so IsItemHovered() after Begin() can work (even though we are technically hovering our parent) // That state is copied to window->DockTabItemStatusFlags by our caller. if (docked_window && (hovered || g.HoveredId == close_button_id)) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + // Tooltip // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok) // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores) @@ -9237,15 +10571,18 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip)) SetItemTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label); } + // Restore main window position so user can draw there if (want_clip_rect) PopClipRect(); window->DC.CursorPos = backup_main_cursor_pos; + IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected if (is_tab_button) return pressed; return tab_contents_visible; } + // [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed. // To use it to need to call the function SetTabItemClosed() between BeginTabBar() and EndTabBar(). // Tabs closed by the close button will automatically be flagged to avoid this issue. @@ -9271,6 +10608,7 @@ void ImGui::SetTabItemClosed(const char* label) } } } + ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker) { ImGuiContext& g = *GImGui; @@ -9282,10 +10620,12 @@ ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsave size.x += g.Style.FramePadding.x + 1.0f; return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y); } + ImVec2 ImGui::TabItemCalcSize(ImGuiWindow* window) { return TabItemCalcSize(window->Name, window->HasCloseButton || (window->Flags & ImGuiWindowFlags_UnsavedDocument)); } + void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col) { // While rendering tabs, we trim 1 pixel off the top of our bounding box so they can fit within a regular frame height while looking "detached" from it. @@ -9310,18 +10650,22 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize); } } + // Render text label (with custom clipping) + Unsaved Document marker + Close Button logic // We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter. void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped) { ImGuiContext& g = *GImGui; ImVec2 label_size = CalcTextSize(label, NULL, true); + if (out_just_closed) *out_just_closed = false; if (out_text_clipped) *out_text_clipped = false; + if (bb.GetWidth() <= 1.0f) return; + // In Style V2 we'll have full override of all colors per state (e.g. focused, selected) // But right now if you want to alter text color of tabs this is what you need to do. #if 0 @@ -9329,17 +10673,20 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, if (!is_contents_visible) g.Style.Alpha *= 0.7f; #endif + // Render text label (with clipping + alpha gradient) + unsaved marker - ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y); - ImRect text_ellipsis_clip_bb = text_pixel_clip_bb; + ImRect text_ellipsis_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y); + // Return clipped state ignoring the close button if (out_text_clipped) { - *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_pixel_clip_bb.Max.x; + *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_ellipsis_clip_bb.Max.x; //draw_list->AddCircle(text_ellipsis_clip_bb.Min, 3.0f, *out_text_clipped ? IM_COL32(255, 0, 0, 255) : IM_COL32(0, 255, 0, 255)); } + const float button_sz = g.FontSize; const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x - button_sz), bb.Min.y + frame_padding.y); + // Close Button & Unsaved Marker // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap() // 'hovered' will be true when hovering the Tab but NOT when hovering the close button @@ -9348,6 +10695,7 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, bool close_button_pressed = false; bool close_button_visible = false; bool is_hovered = g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id; // Any interaction account for this too. + if (close_button_id != 0) { if (is_contents_visible) @@ -9355,6 +10703,7 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, else close_button_visible = (g.Style.TabCloseButtonMinWidthUnselected < 0.0f) ? true : (is_hovered && bb.GetWidth() >= ImMax(button_sz, g.Style.TabCloseButtonMinWidthUnselected)); } + // When tabs/document is unsaved, the unsaved marker takes priority over the close button. const bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x) && (!close_button_visible || !is_hovered); if (unsaved_marker_visible) @@ -9368,27 +10717,40 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, if (CloseButton(close_button_id, button_pos)) close_button_pressed = true; g.LastItemData = last_item_backup; + // Close with middle mouse button if (is_hovered && !(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2)) close_button_pressed = true; } + // This is all rather complicated // (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position) // FIXME: if FramePadding is noticeably large, ellipsis_max_x will be wrong here (e.g. #3497), maybe for consistency that parameter of RenderTextEllipsis() shouldn't exist.. - float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f; + float ellipsis_max_x = text_ellipsis_clip_bb.Max.x; if (close_button_visible || unsaved_marker_visible) { - text_pixel_clip_bb.Max.x -= close_button_visible ? (button_sz) : (button_sz * 0.80f); - text_ellipsis_clip_bb.Max.x -= unsaved_marker_visible ? (button_sz * 0.80f) : 0.0f; - ellipsis_max_x = text_pixel_clip_bb.Max.x; + const bool visible_without_hover = unsaved_marker_visible || (is_contents_visible ? g.Style.TabCloseButtonMinWidthSelected : g.Style.TabCloseButtonMinWidthUnselected) < 0.0f; + if (visible_without_hover) + { + text_ellipsis_clip_bb.Max.x -= button_sz * 0.90f; + ellipsis_max_x -= button_sz * 0.90f; + } + else + { + text_ellipsis_clip_bb.Max.x -= button_sz * 1.00f; + } } LogSetNextTextDecoration("/", "\\"); - RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size); + RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, ellipsis_max_x, label, NULL, &label_size); + #if 0 if (!is_contents_visible) g.Style.Alpha = backup_alpha; #endif + if (out_just_closed) *out_just_closed = close_button_pressed; } + + #endif // #ifndef IMGUI_DISABLE diff --git a/external/reshade/deps/imgui/imstb_rectpack.h b/external/reshade/deps/imgui/imstb_rectpack.h index 7d19023..f6917e7 100644 --- a/external/reshade/deps/imgui/imstb_rectpack.h +++ b/external/reshade/deps/imgui/imstb_rectpack.h @@ -61,27 +61,36 @@ // LICENSE // // See end of file for license information. + ////////////////////////////////////////////////////////////////////////////// // // INCLUDE SECTION // + #ifndef STB_INCLUDE_STB_RECT_PACK_H #define STB_INCLUDE_STB_RECT_PACK_H + #define STB_RECT_PACK_VERSION 1 + #ifdef STBRP_STATIC #define STBRP_DEF static #else #define STBRP_DEF extern #endif + #ifdef __cplusplus extern "C" { #endif + typedef struct stbrp_context stbrp_context; typedef struct stbrp_node stbrp_node; typedef struct stbrp_rect stbrp_rect; + typedef int stbrp_coord; + #define STBRP__MAXVAL 0x7fffffff // Mostly for internal use, but this is the maximum supported coordinate value. + STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); // Assign packed locations to rectangles. The rectangles are of type // 'stbrp_rect' defined below, stored in the array 'rects', and there @@ -106,16 +115,22 @@ STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int n // // The function returns 1 if all of the rectangles were successfully // packed and 0 otherwise. + struct stbrp_rect { // reserved for your use: int id; + // input: stbrp_coord w, h; + // output: stbrp_coord x, y; int was_packed; // non-zero if valid packing + }; // 16 bytes, nominally + + STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); // Initialize a rectangle packer to: // pack a rectangle that is 'width' by 'height' in dimensions @@ -136,29 +151,37 @@ STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, // // If you do #2, then the non-quantized algorithm will be used, but the algorithm // may run out of temporary storage and be unable to pack some rectangles. + STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); // Optionally call this function after init but before doing any packing to // change the handling of the out-of-temp-memory scenario, described above. // If you call init again, this will be reset to the default (false). + + STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); // Optionally select which packing heuristic the library should use. Different // heuristics will produce better/worse results for different data sets. // If you call init again, this will be reset to the default. + enum { STBRP_HEURISTIC_Skyline_default=0, STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, STBRP_HEURISTIC_Skyline_BF_sortHeight }; + + ////////////////////////////////////////////////////////////////////////////// // // the details of the following structures don't matter to you, but they must // be visible so you can handle the memory allocations for them + struct stbrp_node { stbrp_coord x,y; stbrp_node *next; }; + struct stbrp_context { int width; @@ -171,23 +194,29 @@ struct stbrp_context stbrp_node *free_head; stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' }; + #ifdef __cplusplus } #endif + #endif + ////////////////////////////////////////////////////////////////////////////// // // IMPLEMENTATION SECTION // + #ifdef STB_RECT_PACK_IMPLEMENTATION #ifndef STBRP_SORT #include #define STBRP_SORT qsort #endif + #ifndef STBRP_ASSERT #include #define STBRP_ASSERT assert #endif + #ifdef _MSC_VER #define STBRP__NOTUSED(v) (void)(v) #define STBRP__CDECL __cdecl @@ -195,10 +224,12 @@ struct stbrp_context #define STBRP__NOTUSED(v) (void)sizeof(v) #define STBRP__CDECL #endif + enum { STBRP__INIT_skyline = 1 }; + STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) { switch (context->init_mode) { @@ -210,6 +241,7 @@ STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) STBRP_ASSERT(0); } } + STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) { if (allow_out_of_mem) @@ -225,12 +257,15 @@ STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_ou // I.e. num_nodes * align >= width // align >= width / num_nodes // align = ceil(width/num_nodes) + context->align = (context->width + context->num_nodes-1) / context->num_nodes; } } + STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) { int i; + for (i=0; i < num_nodes-1; ++i) nodes[i].next = &nodes[i+1]; nodes[i].next = NULL; @@ -242,6 +277,7 @@ STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, context->height = height; context->num_nodes = num_nodes; stbrp_setup_allow_out_of_mem(context, 0); + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) context->extra[0].x = 0; context->extra[0].y = 0; @@ -250,14 +286,18 @@ STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, context->extra[1].y = (1<<30); context->extra[1].next = NULL; } + // find minimum y position if it starts at x1 static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) { stbrp_node *node = first; int x1 = x0 + width; int min_y, visited_width, waste_area; + STBRP__NOTUSED(c); + STBRP_ASSERT(first->x <= x0); + #if 0 // skip in case we're past the node while (node->next->x <= x0) @@ -265,7 +305,9 @@ static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0 #else STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency #endif + STBRP_ASSERT(node->x <= x0); + min_y = 0; waste_area = 0; visited_width = 0; @@ -291,29 +333,35 @@ static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0 } node = node->next; } + *pwaste = waste_area; return min_y; } + typedef struct { int x,y; stbrp_node **prev_link; } stbrp__findresult; + static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) { int best_waste = (1<<30), best_x, best_y = (1 << 30); stbrp__findresult fr; stbrp_node **prev, *node, *tail, **best = NULL; + // align to multiple of c->align width = (width + c->align - 1); width -= width % c->align; STBRP_ASSERT(width % c->align == 0); + // if it can't possibly fit, bail immediately if (width > c->width || height > c->height) { fr.prev_link = NULL; fr.x = fr.y = 0; return fr; } + node = c->active_head; prev = &c->active_head; while (node->x + width <= c->width) { @@ -339,7 +387,9 @@ static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int widt prev = &node->next; node = node->next; } + best_x = (best == NULL) ? 0 : (*best)->x; + // if doing best-fit (BF), we also have to try aligning right edge to each node position // // e.g, if fitting @@ -356,6 +406,7 @@ static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int widt // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned // // This makes BF take about 2x the time + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { tail = c->active_head; node = c->active_head; @@ -388,16 +439,19 @@ static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int widt tail = tail->next; } } + fr.prev_link = best; fr.x = best_x; fr.y = best_y; return fr; } + static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) { // find best position according to heuristic stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); stbrp_node *node, *cur; + // bail if: // 1. it failed // 2. the best node doesn't fit (we don't always check this) @@ -406,14 +460,18 @@ static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, i res.prev_link = NULL; return res; } + // on success, create new node node = context->free_head; node->x = (stbrp_coord) res.x; node->y = (stbrp_coord) (res.y + height); + context->free_head = node->next; + // insert the new node into the right starting point, and // let 'cur' point to the remaining nodes needing to be // stiched back in + cur = *res.prev_link; if (cur->x < res.x) { // preserve the existing one, so start testing with the next one @@ -423,6 +481,7 @@ static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, i } else { *res.prev_link = node; } + // from here, traverse cur and free the nodes, until we get to one // that shouldn't be freed while (cur->next && cur->next->x <= res.x + width) { @@ -432,10 +491,13 @@ static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, i context->free_head = cur; cur = next; } + // stitch the list back in node->next = cur; + if (cur->x < res.x + width) cur->x = (stbrp_coord) (res.x + width); + #ifdef _DEBUG cur = context->active_head; while (cur->x < context->width) { @@ -443,6 +505,7 @@ static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, i cur = cur->next; } STBRP_ASSERT(cur->next == NULL); + { int count=0; cur = context->active_head; @@ -458,8 +521,10 @@ static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, i STBRP_ASSERT(count == context->num_nodes+2); } #endif + return res; } + static int STBRP__CDECL rect_height_compare(const void *a, const void *b) { const stbrp_rect *p = (const stbrp_rect *) a; @@ -470,21 +535,26 @@ static int STBRP__CDECL rect_height_compare(const void *a, const void *b) return 1; return (p->w > q->w) ? -1 : (p->w < q->w); } + static int STBRP__CDECL rect_original_order(const void *a, const void *b) { const stbrp_rect *p = (const stbrp_rect *) a; const stbrp_rect *q = (const stbrp_rect *) b; return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); } + STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) { int i, all_rects_packed = 1; + // we use the 'was_packed' field internally to allow sorting/unsorting for (i=0; i < num_rects; ++i) { rects[i].was_packed = i; } + // sort according to heuristic STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + for (i=0; i < num_rects; ++i) { if (rects[i].w == 0 || rects[i].h == 0) { rects[i].x = rects[i].y = 0; // empty rect needs no space @@ -498,18 +568,22 @@ STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int nu } } } + // unsort STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + // set was_packed flags and all_rects_packed status for (i=0; i < num_rects; ++i) { rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); if (!rects[i].was_packed) all_rects_packed = 0; } + // return the all_rects_packed status return all_rects_packed; } #endif + /* ------------------------------------------------------------------------------ This software is available under 2 licenses -- choose whichever you prefer. diff --git a/external/reshade/deps/imgui/imstb_textedit.h b/external/reshade/deps/imgui/imstb_textedit.h index 7feb4d4..33eef70 100644 --- a/external/reshade/deps/imgui/imstb_textedit.h +++ b/external/reshade/deps/imgui/imstb_textedit.h @@ -7,6 +7,7 @@ // - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925) // Grep for [DEAR IMGUI] to find the changes. // - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_* + // stb_textedit.h - v1.14 - public domain - Sean Barrett // Development of this library was sponsored by RAD Game Tools // @@ -140,6 +141,7 @@ // with previous char) // STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character // (return type is int, -1 means not valid to insert) +// (not supported if you want to use UTF-8, see below) // STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based // STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize // as manually wordwrapping for end-of-line positioning @@ -177,6 +179,13 @@ // STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text // STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text // +// To support UTF-8: +// +// STB_TEXTEDIT_GETPREVCHARINDEX returns index of previous character +// STB_TEXTEDIT_GETNEXTCHARINDEX returns index of next character +// Do NOT define STB_TEXTEDIT_KEYTOTEXT. +// Instead, call stb_textedit_text() directly for text contents. +// // Keyboard input must be encoded as a single integer value; e.g. a character code // and some bitflags that represent shift states. to simplify the interface, SHIFT must // be a bitflag, so we can test the shifted state of cursor movements to allow selection, @@ -249,8 +258,10 @@ // if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are // transformed into text and stb_textedit_text() is automatically called. // -// text: [DEAR IMGUI] added 2024-09 -// call this to text inputs sent to the textfield. +// text: (added 2025) +// call this to directly send text input the textfield, which is required +// for UTF-8 support, because stb_textedit_key() + STB_TEXTEDIT_KEYTOTEXT() +// cannot infer text length. // // // When rendering, you can read the cursor position and selection state from @@ -273,14 +284,18 @@ // If it's run in a widget that *has* cached the layout, then this is less // efficient, but it's not horrible on modern computers. But you wouldn't // want to edit million-line files with it. + + //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //// //// Header-file mode //// //// + #ifndef INCLUDE_IMSTB_TEXTEDIT_H #define INCLUDE_IMSTB_TEXTEDIT_H + //////////////////////////////////////////////////////////////////////// // // STB_TexteditState @@ -289,6 +304,7 @@ // per-textfield; it includes cursor position, selection state, // and undo state. // + #ifndef IMSTB_TEXTEDIT_UNDOSTATECOUNT #define IMSTB_TEXTEDIT_UNDOSTATECOUNT 99 #endif @@ -301,6 +317,7 @@ #ifndef IMSTB_TEXTEDIT_POSITIONTYPE #define IMSTB_TEXTEDIT_POSITIONTYPE int #endif + typedef struct { // private data @@ -309,6 +326,7 @@ typedef struct IMSTB_TEXTEDIT_POSITIONTYPE delete_length; int char_storage; } StbUndoRecord; + typedef struct { // private data @@ -317,26 +335,32 @@ typedef struct short undo_point, redo_point; int undo_char_point, redo_char_point; } StbUndoState; + typedef struct STB_TexteditState { ///////////////////// // // public data // + int cursor; // position of the text cursor within the string + int select_start; // selection start point int select_end; // selection start and end point in characters; if equal, no selection. // note that start may be less than or greater than end (e.g. when // dragging the mouse, start is where the initial click was, and you // can drag in either direction) + unsigned char insert_mode; // each textfield keeps its own insert mode state. to keep an app-wide // insert mode, copy this value in/out of the app state + int row_count_per_page; // page size in number of row. // this value MUST be set to >0 for pageup or pagedown in multilines documents. + ///////////////////// // // private data @@ -349,12 +373,15 @@ typedef struct STB_TexteditState float preferred_x; // this determines where the cursor up/down tries to seek to along x StbUndoState undostate; } STB_TexteditState; + + //////////////////////////////////////////////////////////////////////// // // StbTexteditRow // // Result of layout query, used by stb_textedit to determine where // the text in each row is. + // result of layout query typedef struct { @@ -364,23 +391,41 @@ typedef struct int num_chars; } StbTexteditRow; #endif //INCLUDE_IMSTB_TEXTEDIT_H + + //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //// //// Implementation mode //// //// + + // implementation isn't include-guarded, since it might have indirectly // included just the "header" portion #ifdef IMSTB_TEXTEDIT_IMPLEMENTATION + #ifndef IMSTB_TEXTEDIT_memmove #include #define IMSTB_TEXTEDIT_memmove memmove #endif + +// [DEAR IMGUI] +// Functions must be implemented for UTF8 support +// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit. +// There is not necessarily a '[DEAR IMGUI]' at the usage sites. +#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX +#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(OBJ, IDX) ((IDX) - 1) +#endif +#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX +#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(OBJ, IDX) ((IDX) + 1) +#endif + ///////////////////////////////////////////////////////////////////////////// // // Mouse input handling // + // traverse the layout to locate the nearest character to a display position static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y) { @@ -388,27 +433,35 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y) int n = STB_TEXTEDIT_STRINGLEN(str); float base_y = 0, prev_x; int i=0, k; + r.x0 = r.x1 = 0; r.ymin = r.ymax = 0; r.num_chars = 0; + // search rows to find one that straddles 'y' while (i < n) { STB_TEXTEDIT_LAYOUTROW(&r, str, i); if (r.num_chars <= 0) return n; + if (i==0 && y < base_y + r.ymin) return 0; + if (y < base_y + r.ymax) break; + i += r.num_chars; base_y += r.baseline_y_delta; } + // below all text, return 'after' last character if (i >= n) return n; + // check if it's before the beginning of the line if (x < r.x0) return i; + // check if it's before the end of the line if (x < r.x1) { // search characters in row for one that straddles 'x' @@ -425,12 +478,14 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y) } // shouldn't happen, but if it does, fall through to end-of-line case } + // if the last character is a newline, return that. otherwise return 'after' the last character if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) return i+r.num_chars-1; else return i+r.num_chars; } + // API click: on mouse down, move the cursor to the clicked location, and reset the selection static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) { @@ -442,15 +497,18 @@ static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *st STB_TEXTEDIT_LAYOUTROW(&r, str, 0); y = r.ymin; } + state->cursor = stb_text_locate_coord(str, x, y); state->select_start = state->cursor; state->select_end = state->cursor; state->has_preferred_x = 0; } + // API drag: on mouse drag, move the cursor and selection endpoint to the clicked location static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) { int p = 0; + // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse // goes off the top or bottom of the text if( state->single_line ) @@ -459,21 +517,26 @@ static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *sta STB_TEXTEDIT_LAYOUTROW(&r, str, 0); y = r.ymin; } + if (state->select_start == state->select_end) state->select_start = state->cursor; + p = stb_text_locate_coord(str, x, y); state->cursor = state->select_end = p; } + ///////////////////////////////////////////////////////////////////////////// // // Keyboard input handling // + // forward declarations static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state); static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state); static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length); static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length); static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length); + typedef struct { float x,y; // position of n'th character @@ -481,6 +544,7 @@ typedef struct int first_char, length; // first char of row, and length int prev_first; // first char of previous row } StbFindState; + // find the x/y location of a character, and remember info about the previous row in // case we get a move-up event (for page up, we'll have to rescan) static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING *str, int n, int single_line) @@ -489,6 +553,7 @@ static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING int prev_start = 0; int z = STB_TEXTEDIT_STRINGLEN(str); int i=0, first; + if (n == z && single_line) { // special case if it's at the end (may not be needed?) STB_TEXTEDIT_LAYOUTROW(&r, str, 0); @@ -499,8 +564,10 @@ static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING find->x = r.x1; return; } + // search rows to find the one that straddles character n find->y = 0; + for(;;) { STB_TEXTEDIT_LAYOUTROW(&r, str, i); if (n < i + r.num_chars) @@ -516,16 +583,20 @@ static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING break; // [DEAR IMGUI] } } + find->first_char = first = i; find->length = r.num_chars; find->height = r.ymax - r.ymin; find->prev_first = prev_start; + // now scan to find xpos find->x = r.x0; for (i=0; first+i < n; i = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, first + i) - first) find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); } + #define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) + // make the selection/cursor state valid if client altered the string static void stb_textedit_clamp(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) { @@ -539,6 +610,7 @@ static void stb_textedit_clamp(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *st } if (state->cursor > n) state->cursor = n; } + // delete characters while updating undo static void stb_textedit_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len) { @@ -546,6 +618,7 @@ static void stb_textedit_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *s STB_TEXTEDIT_DELETECHARS(str, where, len); state->has_preferred_x = 0; } + // delete the section static void stb_textedit_delete_selection(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) { @@ -561,6 +634,7 @@ static void stb_textedit_delete_selection(IMSTB_TEXTEDIT_STRING *str, STB_Texted state->has_preferred_x = 0; } } + // canoncialize the selection so start <= end static void stb_textedit_sortselection(STB_TexteditState *state) { @@ -570,6 +644,7 @@ static void stb_textedit_sortselection(STB_TexteditState *state) state->select_start = temp; } } + // move cursor to first character of selection static void stb_textedit_move_to_first(STB_TexteditState *state) { @@ -580,6 +655,7 @@ static void stb_textedit_move_to_first(STB_TexteditState *state) state->has_preferred_x = 0; } } + // move cursor to last character of selection static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) { @@ -591,47 +667,46 @@ static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditSt state->has_preferred_x = 0; } } -// [DEAR IMGUI] -// Functions must be implemented for UTF8 support -// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit. -// There is not necessarily a '[DEAR IMGUI]' at the usage sites. -#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX -#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx) (idx - 1) -#endif -#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX -#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx) (idx + 1) -#endif + #ifdef STB_TEXTEDIT_IS_SPACE static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx ) { return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; } + #ifndef STB_TEXTEDIT_MOVEWORDLEFT static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c ) { - --c; // always move at least one character - while( c >= 0 && !is_word_boundary( str, c ) ) - --c; + c = IMSTB_TEXTEDIT_GETPREVCHARINDEX( str, c ); // always move at least one character + while (c >= 0 && !is_word_boundary(str, c)) + c = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, c); + if( c < 0 ) c = 0; + return c; } #define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous #endif + #ifndef STB_TEXTEDIT_MOVEWORDRIGHT static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c ) { const int len = STB_TEXTEDIT_STRINGLEN(str); - ++c; // always move at least one character + c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c); // always move at least one character while( c < len && !is_word_boundary( str, c ) ) - ++c; + c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c); + if( c > len ) c = len; + return c; } #define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next #endif + #endif + // update selection and cursor to match each other static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) { @@ -640,6 +715,7 @@ static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) else state->cursor = state->select_end; } + // API cut: delete selection static int stb_textedit_cut(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) { @@ -650,6 +726,7 @@ static int stb_textedit_cut(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state } return 0; } + // API paste: replace existing selection with passed-in text static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE *text, int len) { @@ -666,15 +743,19 @@ static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditS // note: paste failure will leave deleted selection, may be restored with an undo (see https://github.com/nothings/stb/issues/734 for details) return 0; } + #ifndef STB_TEXTEDIT_KEYTYPE #define STB_TEXTEDIT_KEYTYPE int #endif + +// API key: process text input // [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility. static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len) { // can't add newline in single-line mode if (text[0] == '\n' && state->single_line) return; + if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { stb_text_makeundo_replace(str, state, state->cursor, 1, 1); STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); @@ -682,8 +763,7 @@ static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* sta state->cursor += text_len; state->has_preferred_x = 0; } - } - else { + } else { stb_textedit_delete_selection(str, state); // implicitly clamps if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) { stb_text_makeundo_insert(state, state->cursor, text_len); @@ -692,6 +772,7 @@ static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* sta } } } + // API key: process a keyboard input static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key) { @@ -699,6 +780,7 @@ retry: switch (key) { default: { #ifdef STB_TEXTEDIT_KEYTOTEXT + // This is not suitable for UTF-8 support. int c = STB_TEXTEDIT_KEYTOTEXT(key); if (c > 0) { IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c; @@ -707,19 +789,23 @@ retry: #endif break; } + #ifdef STB_TEXTEDIT_K_INSERT case STB_TEXTEDIT_K_INSERT: state->insert_mode = !state->insert_mode; break; #endif + case STB_TEXTEDIT_K_UNDO: stb_text_undo(str, state); state->has_preferred_x = 0; break; + case STB_TEXTEDIT_K_REDO: stb_text_redo(str, state); state->has_preferred_x = 0; break; + case STB_TEXTEDIT_K_LEFT: // if currently there's a selection, move cursor to start of selection if (STB_TEXT_HAS_SELECTION(state)) @@ -729,6 +815,7 @@ retry: state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor); state->has_preferred_x = 0; break; + case STB_TEXTEDIT_K_RIGHT: // if currently there's a selection, move cursor to end of selection if (STB_TEXT_HAS_SELECTION(state)) @@ -738,6 +825,7 @@ retry: stb_textedit_clamp(str, state); state->has_preferred_x = 0; break; + case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: stb_textedit_clamp(str, state); stb_textedit_prep_selection_at_cursor(state); @@ -747,6 +835,7 @@ retry: state->cursor = state->select_end; state->has_preferred_x = 0; break; + #ifdef STB_TEXTEDIT_MOVEWORDLEFT case STB_TEXTEDIT_K_WORDLEFT: if (STB_TEXT_HAS_SELECTION(state)) @@ -756,14 +845,18 @@ retry: stb_textedit_clamp( str, state ); } break; + case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: if( !STB_TEXT_HAS_SELECTION( state ) ) stb_textedit_prep_selection_at_cursor(state); + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); state->select_end = state->cursor; + stb_textedit_clamp( str, state ); break; #endif + #ifdef STB_TEXTEDIT_MOVEWORDRIGHT case STB_TEXTEDIT_K_WORDRIGHT: if (STB_TEXT_HAS_SELECTION(state)) @@ -773,14 +866,18 @@ retry: stb_textedit_clamp( str, state ); } break; + case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: if( !STB_TEXT_HAS_SELECTION( state ) ) stb_textedit_prep_selection_at_cursor(state); + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); state->select_end = state->cursor; + stb_textedit_clamp( str, state ); break; #endif + case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: stb_textedit_prep_selection_at_cursor(state); // move selection right @@ -789,6 +886,7 @@ retry: state->cursor = state->select_end; state->has_preferred_x = 0; break; + case STB_TEXTEDIT_K_DOWN: case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: case STB_TEXTEDIT_K_PGDOWN: @@ -798,33 +896,41 @@ retry: int i, j, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGDOWN; int row_count = is_page ? state->row_count_per_page : 1; + if (!is_page && state->single_line) { // on windows, up&down in single-line behave like left&right key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); goto retry; } + if (sel) stb_textedit_prep_selection_at_cursor(state); else if (STB_TEXT_HAS_SELECTION(state)) stb_textedit_move_to_last(str, state); + // compute current position of cursor point stb_textedit_clamp(str, state); stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + for (j = 0; j < row_count; ++j) { float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; int start = find.first_char + find.length; + if (find.length == 0) break; + // [DEAR IMGUI] // going down while being on the last line shouldn't bring us to that line end if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE) break; + // now find character position down a row state->cursor = start; STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); x = row.x0; - for (i=0; i < row.num_chars; ++i) { + for (i=0; i < row.num_chars; ) { float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); + int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE) break; @@ -832,19 +938,24 @@ retry: x += dx; if (x > goal_x) break; - state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); + i += next - state->cursor; + state->cursor = next; } stb_textedit_clamp(str, state); + state->has_preferred_x = 1; state->preferred_x = goal_x; + if (sel) state->select_end = state->cursor; + // go to next line find.first_char = find.first_char + find.length; find.length = row.num_chars; } break; } + case STB_TEXTEDIT_K_UP: case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: case STB_TEXTEDIT_K_PGUP: @@ -854,29 +965,36 @@ retry: int i, j, prev_scan, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGUP; int row_count = is_page ? state->row_count_per_page : 1; + if (!is_page && state->single_line) { // on windows, up&down become left&right key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); goto retry; } + if (sel) stb_textedit_prep_selection_at_cursor(state); else if (STB_TEXT_HAS_SELECTION(state)) stb_textedit_move_to_first(state); + // compute current position of cursor point stb_textedit_clamp(str, state); stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + for (j = 0; j < row_count; ++j) { float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; + // can only go up if there's a previous row if (find.prev_first == find.first_char) break; + // now find character position up a row state->cursor = find.prev_first; STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); x = row.x0; - for (i=0; i < row.num_chars; ++i) { + for (i=0; i < row.num_chars; ) { float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); + int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE) break; @@ -884,23 +1002,33 @@ retry: x += dx; if (x > goal_x) break; - state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); + i += next - state->cursor; + state->cursor = next; } stb_textedit_clamp(str, state); + state->has_preferred_x = 1; state->preferred_x = goal_x; + if (sel) state->select_end = state->cursor; + // go to previous line // (we need to scan previous line the hard way. maybe we could expose this as a new API function?) prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0; - while (prev_scan > 0 && STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE) - --prev_scan; + while (prev_scan > 0) + { + int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, prev_scan); + if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE) + break; + prev_scan = prev; + } find.first_char = find.prev_first; find.prev_first = prev_scan; } break; } + case STB_TEXTEDIT_K_DELETE: case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: if (STB_TEXT_HAS_SELECTION(state)) @@ -912,6 +1040,7 @@ retry: } state->has_preferred_x = 0; break; + case STB_TEXTEDIT_K_BACKSPACE: case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: if (STB_TEXT_HAS_SELECTION(state)) @@ -926,6 +1055,7 @@ retry: } state->has_preferred_x = 0; break; + #ifdef STB_TEXTEDIT_K_TEXTSTART2 case STB_TEXTEDIT_K_TEXTSTART2: #endif @@ -933,6 +1063,7 @@ retry: state->cursor = state->select_start = state->select_end = 0; state->has_preferred_x = 0; break; + #ifdef STB_TEXTEDIT_K_TEXTEND2 case STB_TEXTEDIT_K_TEXTEND2: #endif @@ -941,6 +1072,7 @@ retry: state->select_start = state->select_end = 0; state->has_preferred_x = 0; break; + #ifdef STB_TEXTEDIT_K_TEXTSTART2 case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: #endif @@ -949,6 +1081,7 @@ retry: state->cursor = state->select_end = 0; state->has_preferred_x = 0; break; + #ifdef STB_TEXTEDIT_K_TEXTEND2 case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: #endif @@ -957,6 +1090,8 @@ retry: state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); state->has_preferred_x = 0; break; + + #ifdef STB_TEXTEDIT_K_LINESTART2 case STB_TEXTEDIT_K_LINESTART2: #endif @@ -966,9 +1101,10 @@ retry: if (state->single_line) state->cursor = 0; else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) - --state->cursor; + state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor); state->has_preferred_x = 0; break; + #ifdef STB_TEXTEDIT_K_LINEEND2 case STB_TEXTEDIT_K_LINEEND2: #endif @@ -977,12 +1113,13 @@ retry: stb_textedit_clamp(str, state); stb_textedit_move_to_first(state); if (state->single_line) - state->cursor = n; + state->cursor = n; else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) - ++state->cursor; + state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); state->has_preferred_x = 0; break; } + #ifdef STB_TEXTEDIT_K_LINESTART2 case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: #endif @@ -992,10 +1129,11 @@ retry: if (state->single_line) state->cursor = 0; else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) - --state->cursor; + state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor); state->select_end = state->cursor; state->has_preferred_x = 0; break; + #ifdef STB_TEXTEDIT_K_LINEEND2 case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: #endif @@ -1006,23 +1144,26 @@ retry: if (state->single_line) state->cursor = n; else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) - ++state->cursor; + state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); state->select_end = state->cursor; state->has_preferred_x = 0; break; } } } + ///////////////////////////////////////////////////////////////////////////// // // Undo processing // // @OPTIMIZE: the undo/redo buffer should be circular + static void stb_textedit_flush_redo(StbUndoState *state) { state->redo_point = IMSTB_TEXTEDIT_UNDOSTATECOUNT; state->redo_char_point = IMSTB_TEXTEDIT_UNDOCHARCOUNT; } + // discard the oldest entry in the undo list static void stb_textedit_discard_undo(StbUndoState *state) { @@ -1041,6 +1182,7 @@ static void stb_textedit_discard_undo(StbUndoState *state) IMSTB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0]))); } } + // discard the oldest entry in the redo list--it's bad if this // ever happens, but because undo & redo have to store the actual // characters in different cases, the redo character buffer can @@ -1048,6 +1190,7 @@ static void stb_textedit_discard_undo(StbUndoState *state) static void stb_textedit_discard_redo(StbUndoState *state) { int k = IMSTB_TEXTEDIT_UNDOSTATECOUNT-1; + if (state->redo_point <= k) { // if the k'th undo state has characters, clean those up if (state->undo_rec[k].char_storage >= 0) { @@ -1068,37 +1211,46 @@ static void stb_textedit_discard_redo(StbUndoState *state) IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin); IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= buf_end); IMSTB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size); + // now move redo_point to point to the new one ++state->redo_point; } } + static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars) { // any time we create a new undo record, we discard redo stb_textedit_flush_redo(state); + // if we have no free records, we have to make room, by sliding the // existing records down if (state->undo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT) stb_textedit_discard_undo(state); + // if the characters to store won't possibly fit in the buffer, we can't undo if (numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT) { state->undo_point = 0; state->undo_char_point = 0; return NULL; } + // if we don't have enough free characters in the buffer, we have to make room while (state->undo_char_point + numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT) stb_textedit_discard_undo(state); + return &state->undo_rec[state->undo_point++]; } + static IMSTB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len) { StbUndoRecord *r = stb_text_create_undo_record(state, insert_len); if (r == NULL) return NULL; + r->where = pos; r->insert_length = (IMSTB_TEXTEDIT_POSITIONTYPE) insert_len; r->delete_length = (IMSTB_TEXTEDIT_POSITIONTYPE) delete_len; + if (insert_len == 0) { r->char_storage = -1; return NULL; @@ -1108,33 +1260,40 @@ static IMSTB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos return &state->undo_char[r->char_storage]; } } + static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) { StbUndoState *s = &state->undostate; StbUndoRecord u, *r; if (s->undo_point == 0) return; + // we need to do two things: apply the undo record, and create a redo record u = s->undo_rec[s->undo_point-1]; r = &s->undo_rec[s->redo_point-1]; r->char_storage = -1; + r->insert_length = u.delete_length; r->delete_length = u.insert_length; r->where = u.where; + if (u.delete_length) { // if the undo record says to delete characters, then the redo record will // need to re-insert the characters that get deleted, so we need to store // them. + // there are three cases: // there's enough room to store the characters // characters stored for *redoing* don't leave room for redo // characters stored for *undoing* don't leave room for redo // if the last is true, we have to bail + if (s->undo_char_point + u.delete_length >= IMSTB_TEXTEDIT_UNDOCHARCOUNT) { // the undo records take up too much character space; there's no space to store the redo characters r->insert_length = 0; } else { int i; + // there's definitely room to store the characters eventually while (s->undo_char_point + u.delete_length > s->redo_char_point) { // should never happen: @@ -1144,43 +1303,55 @@ static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) stb_textedit_discard_redo(s); } r = &s->undo_rec[s->redo_point-1]; + r->char_storage = s->redo_char_point - u.delete_length; s->redo_char_point = s->redo_char_point - u.delete_length; + // now save the characters for (i=0; i < u.delete_length; ++i) s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); } + // now we can carry out the deletion STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); } + // check type of recorded action: if (u.insert_length) { // easy case: was a deletion, so we need to insert n characters STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); s->undo_char_point -= u.insert_length; } + state->cursor = u.where + u.insert_length; + s->undo_point--; s->redo_point--; } + static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) { StbUndoState *s = &state->undostate; StbUndoRecord *u, r; if (s->redo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT) return; + // we need to do two things: apply the redo record, and create an undo record u = &s->undo_rec[s->undo_point]; r = s->undo_rec[s->redo_point]; + // we KNOW there must be room for the undo record, because the redo record // was derived from an undo record + u->delete_length = r.insert_length; u->insert_length = r.delete_length; u->where = r.where; u->char_storage = -1; + if (r.delete_length) { // the redo record requires us to delete characters, so the undo record // needs to store the characters + if (s->undo_char_point + u->insert_length > s->redo_char_point) { u->insert_length = 0; u->delete_length = 0; @@ -1188,25 +1359,32 @@ static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) int i; u->char_storage = s->undo_char_point; s->undo_char_point = s->undo_char_point + u->insert_length; + // now save the characters for (i=0; i < u->insert_length; ++i) s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); } + STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); } + if (r.insert_length) { // easy case: need to insert n characters STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); s->redo_char_point += r.insert_length; } + state->cursor = r.where + r.insert_length; + s->undo_point++; s->redo_point++; } + static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length) { stb_text_createundo(&state->undostate, where, 0, length); } + static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length) { int i; @@ -1216,6 +1394,7 @@ static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditSta p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); } } + static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length) { int i; @@ -1225,6 +1404,7 @@ static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditSt p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); } } + // reset the state to default static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line) { @@ -1242,23 +1422,29 @@ static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_lin state->insert_mode = 0; state->row_count_per_page = 0; } + // API initialize static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) { stb_textedit_clear_state(state, is_single_line); } + #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-qual" #endif + static int stb_textedit_paste(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE const *ctext, int len) { return stb_textedit_paste_internal(str, state, (IMSTB_TEXTEDIT_CHARTYPE *) ctext, len); } + #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic pop #endif + #endif//IMSTB_TEXTEDIT_IMPLEMENTATION + /* ------------------------------------------------------------------------------ This software is available under 2 licenses -- choose whichever you prefer. diff --git a/external/reshade/deps/imgui/imstb_truetype.h b/external/reshade/deps/imgui/imstb_truetype.h index deed47b..cf33289 100644 --- a/external/reshade/deps/imgui/imstb_truetype.h +++ b/external/reshade/deps/imgui/imstb_truetype.h @@ -2,6 +2,7 @@ // This is a slightly modified version of stb_truetype.h 1.26. // Mostly fixing for compiler and static analyzer warnings. // Grep for [DEAR IMGUI] to find the changes. + // stb_truetype.h - v1.26 - public domain // authored from 2009-2021 by Sean Barrett / RAD Game Tools // @@ -269,6 +270,7 @@ // Pool allocations: 7.72 s 6.34 s // Inline sort : 6.54 s 5.65 s // New rasterizer : 5.63 s 5.00 s + ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// //// @@ -280,10 +282,13 @@ #if 0 #define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation #include "stb_truetype.h" + unsigned char ttf_buffer[1<<20]; unsigned char temp_bitmap[512*512]; + stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs GLuint ftex; + void my_stbtt_initfont(void) { fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); @@ -295,6 +300,7 @@ void my_stbtt_initfont(void) // can free temp_bitmap at this point glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } + void my_stbtt_print(float x, float y, char *text) { // assume orthographic projection with units = screen pixels, origin at top left @@ -327,15 +333,20 @@ void my_stbtt_print(float x, float y, char *text) #include #define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation #include "stb_truetype.h" + char ttf_buffer[1<<25]; + int main(int argc, char **argv) { stbtt_fontinfo font; unsigned char *bitmap; int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + for (j=0; j < h; ++j) { for (i=0; i < w; ++i) putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); @@ -365,17 +376,21 @@ int main(int argc, char **argv) #if 0 char buffer[24<<20]; unsigned char screen[20][79]; + int main(int arg, char **argv) { stbtt_fontinfo font; int i,j,ascent,baseline,ch=0; float scale, xpos=2; // leave a little padding in case the character extends left char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); stbtt_InitFont(&font, buffer, 0); + scale = stbtt_ScaleForPixelHeight(&font, 15); stbtt_GetFontVMetrics(&font, &ascent,0,0); baseline = (int) (ascent*scale); + while (text[ch]) { int advance,lsb,x0,y0,x1,y1; float x_shift = xpos - (float) floor(xpos); @@ -391,14 +406,18 @@ int main(int arg, char **argv) xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); ++ch; } + for (j=0; j < 20; ++j) { for (i=0; i < 78; ++i) putchar(" .:ioVM@"[screen[j][i]>>5]); putchar('\n'); } + return 0; } #endif + + ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// //// @@ -407,6 +426,7 @@ int main(int arg, char **argv) //// The following sections allow you to supply alternate definitions //// of C library functions used by stb_truetype, e.g. if you don't //// link with the C runtime library. + #ifdef STB_TRUETYPE_IMPLEMENTATION // #define your own (u)stbtt_int8/16/32 before including to override this #ifndef stbtt_uint8 @@ -417,68 +437,83 @@ int main(int arg, char **argv) typedef unsigned int stbtt_uint32; typedef signed int stbtt_int32; #endif + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h #ifndef STBTT_ifloor #include #define STBTT_ifloor(x) ((int) floor(x)) #define STBTT_iceil(x) ((int) ceil(x)) #endif + #ifndef STBTT_sqrt #include #define STBTT_sqrt(x) sqrt(x) #define STBTT_pow(x,y) pow(x,y) #endif + #ifndef STBTT_fmod #include #define STBTT_fmod(x,y) fmod(x,y) #endif + #ifndef STBTT_cos #include #define STBTT_cos(x) cos(x) #define STBTT_acos(x) acos(x) #endif + #ifndef STBTT_fabs #include #define STBTT_fabs(x) fabs(x) #endif + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h #ifndef STBTT_malloc #include #define STBTT_malloc(x,u) ((void)(u),malloc(x)) #define STBTT_free(x,u) ((void)(u),free(x)) #endif + #ifndef STBTT_assert #include #define STBTT_assert(x) assert(x) #endif + #ifndef STBTT_strlen #include #define STBTT_strlen(x) strlen(x) #endif + #ifndef STBTT_memcpy #include #define STBTT_memcpy memcpy #define STBTT_memset memset #endif #endif + /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// //// //// INTERFACE //// //// + #ifndef __STB_INCLUDE_STB_TRUETYPE_H__ #define __STB_INCLUDE_STB_TRUETYPE_H__ + #ifdef STBTT_STATIC #define STBTT_DEF static #else #define STBTT_DEF extern #endif + #ifdef __cplusplus extern "C" { #endif + // private structure typedef struct { @@ -486,17 +521,20 @@ typedef struct int cursor; int size; } stbtt__buf; + ////////////////////////////////////////////////////////////////////////////// // // TEXTURE BAKING API // // If you use this API, you only have to call two functions ever. // + typedef struct { unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap float xoff,yoff,xadvance; } stbtt_bakedchar; + STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) float pixel_height, // height of font in pixels unsigned char *pixels, int pw, int ph, // bitmap to be filled in @@ -506,11 +544,13 @@ STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // fo // if return is negative, returns the negative of the number of characters that fit // if return is 0, no characters fit and no rows were used // This uses a very crappy packing. + typedef struct { float x0,y0,s0,t0; // top-left float x1,y1,s1,t1; // bottom-right } stbtt_aligned_quad; + STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above int char_index, // character to display float *xpos, float *ypos, // pointers to current position in screen pixel space @@ -525,25 +565,31 @@ STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int p // see discussion of "BASELINE" above. // // It's inefficient; you might want to c&p it and optimize it. + STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); // Query the font vertical metrics without having to create a font first. + + ////////////////////////////////////////////////////////////////////////////// // // NEW TEXTURE BAKING API // // This provides options for packing multiple fonts into one atlas, not // perfectly but better than nothing. + typedef struct { unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap float xoff,yoff,xadvance; float xoff2,yoff2; } stbtt_packedchar; + typedef struct stbtt_pack_context stbtt_pack_context; typedef struct stbtt_fontinfo stbtt_fontinfo; #ifndef STB_RECT_PACK_VERSION typedef struct stbrp_rect stbrp_rect; #endif + STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); // Initializes a packing context stored in the passed-in stbtt_pack_context. // Future calls using this context will pack characters into the bitmap passed @@ -554,9 +600,12 @@ STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, i // bilinear filtering). // // Returns 0 on failure, 1 on success. + STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); // Cleans up the packing context and frees all memory. + #define STBTT_POINT_SIZE(x) (-(x)) + STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); // Creates character bitmaps from the font_index'th font found in fontdata (use @@ -571,6 +620,7 @@ STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char // and pass that result as 'font_size': // ..., 20 , ... // font max minus min y is 20 pixels tall // ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + typedef struct { float font_size; @@ -580,11 +630,13 @@ typedef struct stbtt_packedchar *chardata_for_range; // output unsigned char h_oversample, v_oversample; // don't set these, they're used internally } stbtt_pack_range; + STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); // Creates character bitmaps from multiple ranges of characters stored in // ranges. This will usually create a better-packed bitmap than multiple // calls to stbtt_PackFontRange. Note that you can call this multiple // times within a single PackBegin/PackEnd. + STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); // Oversampling a font increases the quality by allowing higher-quality subpixel // positioning, and is especially valuable at smaller text sizes. @@ -600,16 +652,19 @@ STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h // // To use with PackFontRangesGather etc., you must set it before calls // call to PackFontRangesGatherRects. + STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); // If skip != 0, this tells stb_truetype to skip any codepoints for which // there is no corresponding glyph. If skip=0, which is the default, then // codepoints without a glyph received the font's "missing character" glyph, // typically an empty box by convention. + STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above int char_index, // character to display float *xpos, float *ypos, // pointers to current position in screen pixel space stbtt_aligned_quad *q, // output: quad to draw int align_to_integer); + STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); @@ -622,6 +677,7 @@ STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, cons // then call RenderIntoRects repeatedly. This may result in a // better packing than calling PackFontRanges multiple times // (or it may not). + // this is an opaque structure that you shouldn't mess with which holds // all the context needed from PackBegin to PackEnd. struct stbtt_pack_context { @@ -636,23 +692,27 @@ struct stbtt_pack_context { unsigned char *pixels; void *nodes; }; + ////////////////////////////////////////////////////////////////////////////// // // FONT LOADING // // + STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); // This function will determine the number of fonts in a font file. TrueType // collection (.ttc) files may contain multiple fonts, while TrueType font // (.ttf) files only contain one font. The number of fonts can be used for // indexing with the previous function where the index is between zero and one // less than the total fonts. If an error occurs, -1 is returned. + STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); // Each .ttf/.ttc file may have more than one font. Each font has a sequential // index number starting from 0. Call this function to get the font offset for // a given index; it returns -1 if the index is out of range. A regular .ttf // file will only define one font and it always be at offset 0, so it will // return '0' for index 0, and -1 for all other indices. + // The following structure is defined publicly so you can declare one on // the stack or as a global or etc, but you should treat it as opaque. struct stbtt_fontinfo @@ -660,10 +720,13 @@ struct stbtt_fontinfo void * userdata; unsigned char * data; // pointer to .ttf file int fontstart; // offset of start of font + int numGlyphs; // number of glyphs, needed for range checking + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf int index_map; // a cmap mapping for our chosen character encoding int indexToLocFormat; // format needed to map from glyph index to glyph + stbtt__buf cff; // cff font data stbtt__buf charstrings; // the charstring index stbtt__buf gsubrs; // global charstring subroutines index @@ -671,25 +734,32 @@ struct stbtt_fontinfo stbtt__buf fontdicts; // array of font dicts stbtt__buf fdselect; // map from glyph to fontdict }; + STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); // Given an offset into the file that defines a font, this function builds // the necessary cached info for the rest of the system. You must allocate // the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't // need to do anything special to free it, because the contents are pure // value data with no additional data structures. Returns 0 on failure. + + ////////////////////////////////////////////////////////////////////////////// // // CHARACTER TO GLYPH-INDEX CONVERSIOn + STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); // If you're going to perform multiple operations on the same character // and you want a speed-up, call this function with the character you're // going to process, then use glyph-based functions instead of the // codepoint-based functions. // Returns 0 if the character codepoint is not defined in the font. + + ////////////////////////////////////////////////////////////////////////////// // // CHARACTER PROPERTIES // + STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); // computes a scale factor to produce a font whose "height" is 'pixels' tall. // Height is measured as the distance from the highest ascender to the lowest @@ -697,10 +767,12 @@ STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixe // and computing: // scale = pixels / (ascent - descent) // so if you prefer to measure height by the ascent only, use a similar calculation. + STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); // computes a scale factor to produce a font whose EM size is mapped to // 'pixels' tall. This is probably what traditional APIs compute, but // I'm not positive. + STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); // ascent is the coordinate above the baseline the font extends; descent // is the coordinate below the baseline the font extends (i.e. it is typically negative) @@ -708,41 +780,51 @@ STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, in // so you should advance the vertical position by "*ascent - *descent + *lineGap" // these are expressed in unscaled coordinates, so you must multiply by // the scale factor for a given size + STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); // analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 // table (specific to MS/Windows TTF files). // // Returns 1 on success (table present), 0 on failure. + STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); // the bounding box around all possible characters + STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); // leftSideBearing is the offset from the current horizontal position to the left edge of the character // advanceWidth is the offset from the current horizontal position to the next horizontal position // these are expressed in unscaled coordinates + STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); // an additional amount to add to the 'advance' value between ch1 and ch2 + STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); // Gets the bounding box of the visible part of the glyph, in unscaled coordinates + STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); // as above, but takes one or more glyph indices for greater efficiency + typedef struct stbtt_kerningentry { int glyph1; // use stbtt_FindGlyphIndex int glyph2; int advance; } stbtt_kerningentry; + STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); // Retrieves a complete list of all of the kerning pairs provided by the font // stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. // The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + ////////////////////////////////////////////////////////////////////////////// // // GLYPH SHAPES (you probably don't need these, but they have to go before // the bitmaps for C declaration-order reasons) // + #ifndef STBTT_vmove // you can predefine these to use different values (but why?) enum { STBTT_vmove=1, @@ -751,6 +833,7 @@ STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningen STBTT_vcubic }; #endif + #ifndef stbtt_vertex // you can predefine this to use different values // (we share this with other code at RAD) #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file @@ -760,8 +843,10 @@ STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningen unsigned char type,padding; } stbtt_vertex; #endif + STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); // returns non-zero if nothing is drawn for this glyph + STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); // returns # of vertices and fills *vertices with the pointer to them @@ -773,19 +858,24 @@ STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, s // draws a line from previous endpoint to its x,y; a curveto // draws a quadratic bezier from previous endpoint to // its x,y, using cx,cy as the bezier control point. + STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); // frees the data allocated above + STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl); STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); // fills svg with the character's SVG data. // returns data size or 0 if SVG not found. + ////////////////////////////////////////////////////////////////////////////// // // BITMAP RENDERING // + STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); // frees the bitmap allocated below + STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); // allocates a large-enough single-channel 8bpp bitmap and renders the // specified character/glyph at the specified scale into it, with @@ -794,29 +884,36 @@ STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, fl // which is stored left-to-right, top-to-bottom. // // xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); // the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel // shift for the character + STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); // the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap // in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap // is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the // width and height and positioning info for it first. + STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); // same as stbtt_MakeCodepointBitmap, but you can specify a subpixel // shift for the character + STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); // same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering // is performed (see stbtt_PackSetOversampling) + STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); // get the bbox of the bitmap centered around the glyph origin; so the // bitmap width is ix1-ix0, height is iy1-iy0, and location to place // the bitmap top left is (leftSideBearing*scale,iy0). // (Note that the bitmap uses y-increases-down, but the shape uses // y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); // same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel // shift for the character + // the following functions are equivalent to the above functions, but operate // on glyph indices instead of Unicode codepoints (for efficiency) STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); @@ -826,12 +923,15 @@ STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigne STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + // @TODO: don't expose this structure typedef struct { int w,h,stride; unsigned char *pixels; } stbtt__bitmap; + // rasterize a shape with quadratic beziers into a bitmap STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into float flatness_in_pixels, // allowable error of curve in pixels @@ -842,11 +942,14 @@ STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap int x_off, int y_off, // another translation applied to input int invert, // if non-zero, vertically flip shape void *userdata); // context for to STBTT_MALLOC + ////////////////////////////////////////////////////////////////////////////// // // Signed Distance Function (or Field) rendering + STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); // frees the SDF bitmap allocated below + STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); // These functions compute a discretized SDF field for a single character, suitable for storing @@ -895,6 +998,9 @@ STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, floa // // The algorithm has not been optimized at all, so expect it to be slow // if computing lots of characters or very large sizes. + + + ////////////////////////////////////////////////////////////////////////////// // // Finding the right font... @@ -915,6 +1021,8 @@ STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, floa // stbtt_GetFontNameString() lets you get any of the various strings // from the file yourself and do your own comparisons on them. // You have to have called stbtt_InitFont() first. + + STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); // returns the offset (not index) of the font that matches, or -1 if none // if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". @@ -925,9 +1033,11 @@ STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char * #define STBTT_MACSTYLE_ITALIC 2 #define STBTT_MACSTYLE_UNDERSCORE 4 #define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); // returns 1/0 whether the first string interpreted as utf8 is identical to // the second string interpreted as big-endian utf16... useful for strings from next func + STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); // returns the string (which may be big-endian double byte, e.g. for unicode) // and puts the length in bytes in *length. @@ -935,12 +1045,14 @@ STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *l // some of the values for the IDs are below; for more see the truetype spec: // http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html // http://www.microsoft.com/typography/otspec/name.htm + enum { // platformID STBTT_PLATFORM_ID_UNICODE =0, STBTT_PLATFORM_ID_MAC =1, STBTT_PLATFORM_ID_ISO =2, STBTT_PLATFORM_ID_MICROSOFT =3 }; + enum { // encodingID for STBTT_PLATFORM_ID_UNICODE STBTT_UNICODE_EID_UNICODE_1_0 =0, STBTT_UNICODE_EID_UNICODE_1_1 =1, @@ -948,18 +1060,21 @@ enum { // encodingID for STBTT_PLATFORM_ID_UNICODE STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 }; + enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT STBTT_MS_EID_SYMBOL =0, STBTT_MS_EID_UNICODE_BMP =1, STBTT_MS_EID_SHIFTJIS =2, STBTT_MS_EID_UNICODE_FULL =10 }; + enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 }; + enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, @@ -969,6 +1084,7 @@ enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D }; + enum { // languageID for STBTT_PLATFORM_ID_MAC STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, @@ -978,57 +1094,72 @@ enum { // languageID for STBTT_PLATFORM_ID_MAC STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 }; + #ifdef __cplusplus } #endif + #endif // __STB_INCLUDE_STB_TRUETYPE_H__ + /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// //// //// IMPLEMENTATION //// //// + #ifdef STB_TRUETYPE_IMPLEMENTATION + #ifndef STBTT_MAX_OVERSAMPLE #define STBTT_MAX_OVERSAMPLE 8 #endif + #if STBTT_MAX_OVERSAMPLE > 255 #error "STBTT_MAX_OVERSAMPLE cannot be > 255" #endif + typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + #ifndef STBTT_RASTERIZER_VERSION #define STBTT_RASTERIZER_VERSION 2 #endif + #ifdef _MSC_VER #define STBTT__NOTUSED(v) (void)(v) #else #define STBTT__NOTUSED(v) (void)sizeof(v) #endif + ////////////////////////////////////////////////////////////////////////// // // stbtt__buf helpers to parse data from file // + static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) { if (b->cursor >= b->size) return 0; return b->data[b->cursor++]; } + static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) { if (b->cursor >= b->size) return 0; return b->data[b->cursor]; } + static void stbtt__buf_seek(stbtt__buf *b, int o) { STBTT_assert(!(o > b->size || o < 0)); b->cursor = (o > b->size || o < 0) ? b->size : o; } + static void stbtt__buf_skip(stbtt__buf *b, int o) { stbtt__buf_seek(b, b->cursor + o); } + static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) { stbtt_uint32 v = 0; @@ -1038,6 +1169,7 @@ static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) v = (v << 8) | stbtt__buf_get8(b); return v; } + static stbtt__buf stbtt__new_buf(const void *p, size_t size) { stbtt__buf r; @@ -1047,8 +1179,10 @@ static stbtt__buf stbtt__new_buf(const void *p, size_t size) r.cursor = 0; return r; } + #define stbtt__buf_get16(b) stbtt__buf_get((b), 2) #define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) { stbtt__buf r = stbtt__new_buf(NULL, 0); @@ -1057,6 +1191,7 @@ static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) r.size = s; return r; } + static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) { int count, start, offsize; @@ -1070,6 +1205,7 @@ static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) } return stbtt__buf_range(b, start, b->cursor - start); } + static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) { int b0 = stbtt__buf_get8(b); @@ -1081,6 +1217,7 @@ static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) STBTT_assert(0); return 0; } + static void stbtt__cff_skip_operand(stbtt__buf *b) { int v, b0 = stbtt__buf_peek8(b); STBTT_assert(b0 >= 28); @@ -1095,6 +1232,7 @@ static void stbtt__cff_skip_operand(stbtt__buf *b) { stbtt__cff_int(b); } } + static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) { stbtt__buf_seek(b, 0); @@ -1109,6 +1247,7 @@ static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) } return stbtt__buf_range(b, 0, 0); } + static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) { int i; @@ -1116,11 +1255,13 @@ static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uin for (i = 0; i < outcount && operands.cursor < operands.size; i++) out[i] = stbtt__cff_int(&operands); } + static int stbtt__cff_index_count(stbtt__buf *b) { stbtt__buf_seek(b, 0); return stbtt__buf_get16(b); } + static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) { int count, offsize, start, end; @@ -1134,21 +1275,27 @@ static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) end = stbtt__buf_get(&b, offsize); return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); } + ////////////////////////////////////////////////////////////////////////// // // accessors to parse data from file // + // on platforms that don't allow misaligned reads, if we want to allow // truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + #define ttBYTE(p) (* (stbtt_uint8 *) (p)) #define ttCHAR(p) (* (stbtt_int8 *) (p)) #define ttFixed(p) ttLONG(p) + static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + #define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) #define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + static int stbtt__isfont(stbtt_uint8 *font) { // check the version number @@ -1159,6 +1306,7 @@ static int stbtt__isfont(stbtt_uint8 *font) if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts return 0; } + // @OPTIMIZE: binary search static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) { @@ -1172,11 +1320,13 @@ static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, } return 0; } + static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) { // if it's just a font, there's only one valid index if (stbtt__isfont(font_collection)) return index == 0 ? 0 : -1; + // check if it's a TTC if (stbtt_tag(font_collection, "ttcf")) { // version 1? @@ -1189,11 +1339,13 @@ static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, } return -1; } + static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) { // if it's just a font, there's only one valid font if (stbtt__isfont(font_collection)) return 1; + // check if it's a TTC if (stbtt_tag(font_collection, "ttcf")) { // version 1? @@ -1203,6 +1355,7 @@ static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) } return 0; } + static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) { stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; @@ -1215,6 +1368,7 @@ static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) stbtt__buf_seek(&cff, private_loc[1]+subrsoff); return stbtt__cff_get_index(&cff); } + // since most people won't use this, find this table the first time it's needed static int stbtt__get_svg(stbtt_fontinfo *info) { @@ -1230,13 +1384,16 @@ static int stbtt__get_svg(stbtt_fontinfo *info) } return info->svg; } + static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) { stbtt_uint32 cmap, t; stbtt_int32 i,numTables; + info->data = data; info->fontstart = fontstart; info->cff = stbtt__new_buf(NULL, 0); + cmap = stbtt__find_table(data, fontstart, "cmap"); // required info->loca = stbtt__find_table(data, fontstart, "loca"); // required info->head = stbtt__find_table(data, fontstart, "head"); // required @@ -1245,6 +1402,7 @@ static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, in info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required info->kern = stbtt__find_table(data, fontstart, "kern"); // not required info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + if (!cmap || !info->head || !info->hhea || !info->hmtx) return 0; if (info->glyf) { @@ -1255,16 +1413,21 @@ static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, in stbtt__buf b, topdict, topdictidx; stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; stbtt_uint32 cff; + cff = stbtt__find_table(data, fontstart, "CFF "); if (!cff) return 0; + info->fontdicts = stbtt__new_buf(NULL, 0); info->fdselect = stbtt__new_buf(NULL, 0); + // @TODO this should use size from table (not 512MB) info->cff = stbtt__new_buf(data+cff, 512*1024*1024); b = info->cff; + // read the header stbtt__buf_skip(&b, 2); stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + // @TODO the name INDEX could list multiple fonts, // but we just use the first one. stbtt__cff_get_index(&b); // name INDEX @@ -1272,14 +1435,17 @@ static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, in topdict = stbtt__cff_index_get(topdictidx, 0); stbtt__cff_get_index(&b); // string INDEX info->gsubrs = stbtt__cff_get_index(&b); + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); info->subrs = stbtt__get_subrs(b, topdict); + // we only support Type 2 charstrings if (cstype != 2) return 0; if (charstrings == 0) return 0; + if (fdarrayoff) { // looks like a CID font if (!fdselectoff) return 0; @@ -1287,15 +1453,19 @@ static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, in info->fontdicts = stbtt__cff_get_index(&b); info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); } + stbtt__buf_seek(&b, charstrings); info->charstrings = stbtt__cff_get_index(&b); } + t = stbtt__find_table(data, fontstart, "maxp"); if (t) info->numGlyphs = ttUSHORT(data+t+4); else info->numGlyphs = 0xffff; + info->svg = -1; + // find a cmap encoding table we understand *now* to avoid searching // later. (todo: could make this installable) // the same regardless of glyph. @@ -1323,13 +1493,16 @@ static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, in } if (info->index_map == 0) return 0; + info->indexToLocFormat = ttUSHORT(data+info->head + 50); return 1; } + STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) { stbtt_uint8 *data = info->data; stbtt_uint32 index_map = info->index_map; + stbtt_uint16 format = ttUSHORT(data + index_map + 0); if (format == 0) { // apple byte encoding stbtt_int32 bytes = ttUSHORT(data + index_map + 2); @@ -1350,15 +1523,19 @@ STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codep stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + // do a binary search of the segments stbtt_uint32 endCount = index_map + 14; stbtt_uint32 search = endCount; + if (unicode_codepoint > 0xffff) return 0; + // they lie from endCount .. endCount + segCount // but searchRange is the nearest power of two, so... if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) search += rangeShift*2; + // now decrement to bias correctly to find smallest search -= 2; while (entrySelector) { @@ -1370,16 +1547,20 @@ STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codep --entrySelector; } search += 2; + { stbtt_uint16 offset, start, last; stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); last = ttUSHORT(data + endCount + 2*item); if (unicode_codepoint < start || unicode_codepoint > last) return 0; + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); if (offset == 0) return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); } } else if (format == 12 || format == 13) { @@ -1409,10 +1590,12 @@ STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codep STBTT_assert(0); return 0; } + STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) { return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); } + static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) { v->type = type; @@ -1421,12 +1604,16 @@ static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, st v->cx = (stbtt_int16) cx; v->cy = (stbtt_int16) cy; } + static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) { int g1,g2; + STBTT_assert(!info->cff.size); + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + if (info->indexToLocFormat == 0) { g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; @@ -1434,9 +1621,12 @@ static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); } + return g1==g2 ? -1 : g1; // if length is 0, return -1 } + static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) { if (info->cff.size) { @@ -1444,6 +1634,7 @@ STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int } else { int g = stbtt__GetGlyfOffset(info, glyph_index); if (g < 0) return 0; + if (x0) *x0 = ttSHORT(info->data + g + 2); if (y0) *y0 = ttSHORT(info->data + g + 4); if (x1) *x1 = ttSHORT(info->data + g + 6); @@ -1451,10 +1642,12 @@ STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int } return 1; } + STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) { return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); } + STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) { stbtt_int16 numberOfContours; @@ -1466,6 +1659,7 @@ STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) numberOfContours = ttSHORT(info->data + g); return numberOfContours == 0; } + static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) { @@ -1481,6 +1675,7 @@ static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_ } return num_vertices; } + static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) { stbtt_int16 numberOfContours; @@ -1489,9 +1684,13 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s stbtt_vertex *vertices=0; int num_vertices=0; int g = stbtt__GetGlyfOffset(info, glyph_index); + *pvertices = NULL; + if (g < 0) return 0; + numberOfContours = ttSHORT(data + g); + if (numberOfContours > 0) { stbtt_uint8 flags=0,flagcount; stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; @@ -1500,18 +1699,25 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s endPtsOfContours = (data + g + 10); ins = ttUSHORT(data + g + 10 + numberOfContours * 2); points = data + g + 10 + numberOfContours * 2 + 2 + ins; + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); if (vertices == 0) return 0; + next_move = 0; flagcount=0; + // in first pass, we load uninterpreted data into the allocated array // above, shifted to the end of the array so we won't overwrite it when // we create our final data starting from the front + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + // first load flags + for (i=0; i < n; ++i) { if (flagcount == 0) { flags = *points++; @@ -1521,6 +1727,7 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s --flagcount; vertices[off+i].type = flags; } + // now load x coordinates x=0; for (i=0; i < n; ++i) { @@ -1536,6 +1743,7 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s } vertices[off+i].x = (stbtt_int16) x; } + // now load y coordinates y=0; for (i=0; i < n; ++i) { @@ -1551,6 +1759,7 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s } vertices[off+i].y = (stbtt_int16) y; } + // now convert them to our format num_vertices=0; sx = sy = cx = cy = scx = scy = 0; @@ -1558,9 +1767,11 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s flags = vertices[off+i].type; x = (stbtt_int16) vertices[off+i].x; y = (stbtt_int16) vertices[off+i].y; + if (next_move == i) { if (i != 0) num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + // now start the new one start_off = !(flags & 1); if (start_off) { @@ -1614,8 +1825,10 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s int comp_num_verts = 0, i; stbtt_vertex *comp_verts = 0, *tmp = 0; float mtx[6] = {1,0,0,1,0,0}, m, n; + flags = ttSHORT(comp); comp+=2; gidx = ttSHORT(comp); comp+=2; + if (flags & 2) { // XY values if (flags & 1) { // shorts mtx[4] = ttSHORT(comp); comp+=2; @@ -1642,9 +1855,11 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; } + // Find transformation scales. m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + // Get indexed glyph. comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); if (comp_num_verts > 0) { @@ -1679,9 +1894,11 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s } else { // numberOfCounters == 0, do nothing } + *pvertices = vertices; return num_vertices; } + typedef struct { int bounds; @@ -1689,10 +1906,13 @@ typedef struct float first_x, first_y; float x, y; stbtt_int32 min_x, max_x, min_y, max_y; + stbtt_vertex *pvertices; int num_vertices; } stbtt__csctx; + #define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) { if (x > c->max_x || !c->started) c->max_x = x; @@ -1701,6 +1921,7 @@ static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) if (y < c->min_y || !c->started) c->min_y = y; c->started = 1; } + static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) { if (c->bounds) { @@ -1716,11 +1937,13 @@ static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stb } c->num_vertices++; } + static void stbtt__csctx_close_shape(stbtt__csctx *ctx) { if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); } + static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) { stbtt__csctx_close_shape(ctx); @@ -1728,12 +1951,14 @@ static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) ctx->first_y = ctx->y = ctx->y + dy; stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); } + static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) { ctx->x += dx; ctx->y += dy; stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); } + static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) { float cx1 = ctx->x + dx1; @@ -1744,6 +1969,7 @@ static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, flo ctx->y = cy2 + dy3; stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); } + static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) { int count = stbtt__cff_index_count(&idx); @@ -1757,10 +1983,12 @@ static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) return stbtt__new_buf(NULL, 0); return stbtt__cff_index_get(idx, n); } + static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) { stbtt__buf fdselect = info->fdselect; int nranges, start, end, v, fmt, fdselector = -1, i; + stbtt__buf_seek(&fdselect, 0); fmt = stbtt__buf_get8(&fdselect); if (fmt == 0) { @@ -1783,6 +2011,7 @@ static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int gly if (fdselector == -1) return stbtt__new_buf(NULL, 0); // [DEAR IMGUI] fixed, see #6007 and nothings/stb#1422 return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); } + static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) { int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; @@ -1790,7 +2019,9 @@ static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, st float s[48]; stbtt__buf subr_stack[10], subrs = info->subrs, b; float f; + #define STBTT__CSERR(s) (0) + // this currently ignores the initial width value, which isn't needed if we have hmtx b = stbtt__cff_index_get(info->charstrings, glyph_index); while (b.cursor < b.size) { @@ -1806,12 +2037,14 @@ static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, st in_header = 0; stbtt__buf_skip(&b, (maskbits + 7) / 8); break; + case 0x01: // hstem case 0x03: // vstem case 0x12: // hstemhm case 0x17: // vstemhm maskbits += (sp / 2); break; + case 0x15: // rmoveto in_header = 0; if (sp < 2) return STBTT__CSERR("rmoveto stack"); @@ -1827,13 +2060,16 @@ static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, st if (sp < 1) return STBTT__CSERR("hmoveto stack"); stbtt__csctx_rmove_to(c, s[sp-1], 0); break; + case 0x05: // rlineto if (sp < 2) return STBTT__CSERR("rlineto stack"); for (; i + 1 < sp; i += 2) stbtt__csctx_rline_to(c, s[i], s[i+1]); break; + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical // starting from a different place. + case 0x07: // vlineto if (sp < 1) return STBTT__CSERR("vlineto stack"); goto vlineto; @@ -1849,6 +2085,7 @@ static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, st i++; } break; + case 0x1F: // hvcurveto if (sp < 4) return STBTT__CSERR("hvcurveto stack"); goto hvcurveto; @@ -1864,11 +2101,13 @@ static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, st i += 4; } break; + case 0x08: // rrcurveto if (sp < 6) return STBTT__CSERR("rcurveline stack"); for (; i + 5 < sp; i += 6) stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); break; + case 0x18: // rcurveline if (sp < 8) return STBTT__CSERR("rcurveline stack"); for (; i + 5 < sp - 2; i += 6) @@ -1876,6 +2115,7 @@ static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, st if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); stbtt__csctx_rline_to(c, s[i], s[i+1]); break; + case 0x19: // rlinecurve if (sp < 8) return STBTT__CSERR("rlinecurve stack"); for (; i + 1 < sp - 6; i += 2) @@ -1883,6 +2123,7 @@ static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, st if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); break; + case 0x1A: // vvcurveto case 0x1B: // hhcurveto if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); @@ -1896,6 +2137,7 @@ static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, st f = 0.0; } break; + case 0x0A: // callsubr if (!has_subrs) { if (info->fdselect.size) @@ -1913,14 +2155,17 @@ static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, st b.cursor = 0; clear_stack = 0; break; + case 0x0B: // return if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); b = subr_stack[--subr_stack_height]; clear_stack = 0; break; + case 0x0E: // endchar stbtt__csctx_close_shape(c); return 1; + case 0x0C: { // two-byte escape float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; float dx, dy; @@ -1940,6 +2185,7 @@ static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, st stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); break; + case 0x23: // flex if (sp < 13) return STBTT__CSERR("flex stack"); dx1 = s[0]; @@ -1958,6 +2204,7 @@ static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, st stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); break; + case 0x24: // hflex1 if (sp < 9) return STBTT__CSERR("hflex1 stack"); dx1 = s[0]; @@ -1972,6 +2219,7 @@ static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, st stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); break; + case 0x25: // flex1 if (sp < 11) return STBTT__CSERR("flex1 stack"); dx1 = s[0]; @@ -1994,13 +2242,16 @@ static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, st stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); break; + default: return STBTT__CSERR("unimplemented"); } } break; + default: if (b0 != 255 && b0 != 28 && b0 < 32) return STBTT__CSERR("reserved operator"); + // push immediate if (b0 == 255) { f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; @@ -2016,8 +2267,10 @@ static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, st if (clear_stack) sp = 0; } return STBTT__CSERR("no endchar"); + #undef STBTT__CSERR } + static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) { // runs the charstring twice, once to count and once to output (to avoid realloc) @@ -2034,6 +2287,7 @@ static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, s *pvertices = NULL; return 0; } + static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) { stbtt__csctx c = STBTT__CSCTX_INIT(1); @@ -2044,6 +2298,7 @@ static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, in if (y1) *y1 = r ? c.max_y : 0; return r ? c.num_vertices : 0; } + STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) { if (!info->cff.size) @@ -2051,6 +2306,7 @@ STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, s else return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); } + STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) { stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); @@ -2062,9 +2318,11 @@ STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_inde if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); } } + STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) { stbtt_uint8 *data = info->data + info->kern; + // we only look at the first table. it must be 'horizontal' and format 0. if (!info->kern) return 0; @@ -2072,12 +2330,15 @@ STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) return 0; if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format return 0; + return ttUSHORT(data+10); } + STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) { stbtt_uint8 *data = info->data + info->kern; int k, length; + // we only look at the first table. it must be 'horizontal' and format 0. if (!info->kern) return 0; @@ -2085,22 +2346,27 @@ STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningent return 0; if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format return 0; + length = ttUSHORT(data+10); if (table_length < length) length = table_length; + for (k = 0; k < length; k++) { table[k].glyph1 = ttUSHORT(data+18+(k*6)); table[k].glyph2 = ttUSHORT(data+20+(k*6)); table[k].advance = ttSHORT(data+22+(k*6)); } + return length; } + static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) { stbtt_uint8 *data = info->data + info->kern; stbtt_uint32 needle, straw; int l, r, m; + // we only look at the first table. it must be 'horizontal' and format 0. if (!info->kern) return 0; @@ -2108,6 +2374,7 @@ static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1 return 0; if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format return 0; + l = 0; r = ttUSHORT(data+10) - 1; needle = glyph1 << 16 | glyph2; @@ -2123,12 +2390,14 @@ static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1 } return 0; } + static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) { stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); switch (coverageFormat) { case 1: { stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + // Binary search. stbtt_int32 l=0, r=glyphCount-1, m; int straw, needle=glyph; @@ -2148,9 +2417,11 @@ static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph } break; } + case 2: { stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); stbtt_uint8 *rangeArray = coverageTable + 4; + // Binary search. stbtt_int32 l=0, r=rangeCount-1, m; int strawStart, strawEnd, needle=glyph; @@ -2171,10 +2442,13 @@ static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph } break; } + default: return -1; // unsupported } + return -1; } + static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) { stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); @@ -2184,13 +2458,16 @@ static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); break; } + case 2: { stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); stbtt_uint8 *classRangeRecords = classDefTable + 4; + // Binary search. stbtt_int32 l=0, r=classRangeCount-1, m; int strawStart, strawEnd, needle=glyph; @@ -2209,14 +2486,18 @@ static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) } break; } + default: return -1; // Unsupported definition type, return an error. } + // "All glyphs not assigned to a class fall into class 0". (OpenType spec) return 0; } + // Define to STBTT_assert(x) if you want to break on unimplemented formats. #define STBTT_GPOS_TODO_assert(x) + static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) { stbtt_uint16 lookupListOffset; @@ -2224,21 +2505,28 @@ static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, in stbtt_uint16 lookupCount; stbtt_uint8 *data; stbtt_int32 i, sti; + if (!info->gpos) return 0; + data = info->data + info->gpos; + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + lookupListOffset = ttUSHORT(data+8); lookupList = data + lookupListOffset; lookupCount = ttUSHORT(lookupList); + for (i=0; i= pairSetCount) return 0; + needle=glyph2; r=pairValueCount-1; l=0; + // Binary search. while (l <= r) { stbtt_uint16 secondGlyph; @@ -2284,6 +2576,7 @@ static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, in return 0; break; } + case 2: { stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); @@ -2292,12 +2585,15 @@ static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, in stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + stbtt_uint16 class1Count = ttUSHORT(table + 12); stbtt_uint16 class2Count = ttUSHORT(table + 14); stbtt_uint8 *class1Records, *class2Records; stbtt_int16 xAdvance; + if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed + class1Records = table + 16; class2Records = class1Records + 2 * (glyph1class * class2Count); xAdvance = ttSHORT(class2Records + 2 * glyph2class); @@ -2306,38 +2602,47 @@ static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, in return 0; break; } + default: return 0; // Unsupported position format } } } + return 0; } + STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) { int xAdvance = 0; + if (info->gpos) xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); else if (info->kern) xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + return xAdvance; } + STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) { if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs return 0; return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); } + STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) { stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); } + STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) { if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); if (descent) *descent = ttSHORT(info->data+info->hhea + 6); if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); } + STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) { int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); @@ -2348,6 +2653,7 @@ STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAsc if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); return 1; } + STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) { *x0 = ttSHORT(info->data + info->head + 36); @@ -2355,27 +2661,33 @@ STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *x1 = ttSHORT(info->data + info->head + 40); *y1 = ttSHORT(info->data + info->head + 42); } + STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) { int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); return (float) height / fheight; } + STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) { int unitsPerEm = ttUSHORT(info->data + info->head + 18); return pixels / unitsPerEm; } + STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) { STBTT_free(v, info->userdata); } + STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) { int i; stbtt_uint8 *data = info->data; stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + int numEntries = ttUSHORT(svg_doc_list); stbtt_uint8 *svg_docs = svg_doc_list + 2; + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) @@ -2383,12 +2695,15 @@ STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) } return 0; } + STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) { stbtt_uint8 *data = info->data; stbtt_uint8 *svg_doc; + if (info->svg == 0) return 0; + svg_doc = stbtt_FindSVGDoc(info, gl); if (svg_doc != NULL) { *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); @@ -2397,14 +2712,17 @@ STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char * return 0; } } + STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) { return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); } + ////////////////////////////////////////////////////////////////////////////// // // antialiasing software rasterizer // + STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) { int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning @@ -2422,31 +2740,38 @@ STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int g if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); } } + STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) { stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); } + STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) { stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); } + STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) { stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); } + ////////////////////////////////////////////////////////////////////////////// // // Rasterizer + typedef struct stbtt__hheap_chunk { struct stbtt__hheap_chunk *next; } stbtt__hheap_chunk; + typedef struct stbtt__hheap { struct stbtt__hheap_chunk *head; void *first_free; int num_remaining_in_head_chunk; } stbtt__hheap; + static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) { if (hh->first_free) { @@ -2467,11 +2792,13 @@ static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; } } + static void stbtt__hheap_free(stbtt__hheap *hh, void *p) { *(void **) p = hh->first_free; hh->first_free = p; } + static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) { stbtt__hheap_chunk *c = hh->head; @@ -2481,10 +2808,13 @@ static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) c = n; } } + typedef struct stbtt__edge { float x0,y0, x1,y1; int invert; } stbtt__edge; + + typedef struct stbtt__active_edge { struct stbtt__active_edge *next; @@ -2501,23 +2831,28 @@ typedef struct stbtt__active_edge #error "Unrecognized value of STBTT_RASTERIZER_VERSION" #endif } stbtt__active_edge; + #if STBTT_RASTERIZER_VERSION == 1 #define STBTT_FIXSHIFT 10 #define STBTT_FIX (1 << STBTT_FIXSHIFT) #define STBTT_FIXMASK (STBTT_FIX-1) + static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) { stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); STBTT_assert(z != NULL); if (!z) return z; + // round dx down to avoid overshooting if (dxdy < 0) z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); else z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount z->x -= off_x * STBTT_FIX; + z->ey = e->y1; z->next = 0; z->direction = e->invert ? 1 : -1; @@ -2544,6 +2879,7 @@ static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, i #else #error "Unrecognized value of STBTT_RASTERIZER_VERSION" #endif + #if STBTT_RASTERIZER_VERSION == 1 // note: this routine clips fills that extend off the edges... ideally this // wouldn't happen, but it could happen if the truetype glyph bounding boxes @@ -2552,6 +2888,7 @@ static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__ac { // non-zero winding fill int x0=0, w=0; + while (e) { if (w == 0) { // if we're currently at zero, we need to record the edge start point @@ -2562,6 +2899,7 @@ static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__ac if (w == 0) { int i = x0 >> STBTT_FIXSHIFT; int j = x1 >> STBTT_FIXSHIFT; + if (i < len && j >= 0) { if (i == j) { // x0,x1 are the same pixel, so compute combined coverage @@ -2571,19 +2909,23 @@ static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__ac scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); else i = -1; // clip + if (j < len) // add antialiasing for x1 scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); else j = len; // clip + for (++i; i < j; ++i) // fill pixels between x0 and x1 scanline[i] = scanline[i] + (stbtt_uint8) max_weight; } } } } + e = e->next; } } + static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) { stbtt__hheap hh = { 0, 0, 0 }; @@ -2592,18 +2934,22 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int max_weight = (255 / vsubsample); // weight per vertical scanline int s; // vertical subsample index unsigned char scanline_data[512], *scanline; + if (result->w > 512) scanline = (unsigned char *) STBTT_malloc(result->w, userdata); else scanline = scanline_data; + y = off_y * vsubsample; e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + while (j < result->h) { STBTT_memset(scanline, 0, result->w); for (s=0; s < vsubsample; ++s) { // find center of pixel for this scanline float scan_y = y + 0.5f; stbtt__active_edge **step = &active; + // update all active edges; // remove all active edges that terminate before the center of this scanline while (*step) { @@ -2618,6 +2964,7 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, step = &((*step)->next); // advance through list } } + // resort the list if needed for(;;) { int changed=0; @@ -2626,6 +2973,7 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, if ((*step)->x > (*step)->next->x) { stbtt__active_edge *t = *step; stbtt__active_edge *q = t->next; + t->next = q->next; q->next = t; *step = q; @@ -2635,6 +2983,7 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, } if (!changed) break; } + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline while (e->y0 <= scan_y) { if (e->y1 > scan_y) { @@ -2660,19 +3009,25 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, } ++e; } + // now process all active edges in XOR fashion if (active) stbtt__fill_active_edges(scanline, result->w, active, max_weight); + ++y; } STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); ++j; } + stbtt__hheap_cleanup(&hh, userdata); + if (scanline != scanline_data) STBTT_free(scanline, userdata); } + #elif STBTT_RASTERIZER_VERSION == 2 + // the edge passed in here does not cross the vertical line at x or the vertical line at x+1 // (i.e. it has already been clipped to those) static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) @@ -2690,6 +3045,7 @@ static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edg x1 += (x1-x0) * (e->ey - y1) / (y1-y0); y1 = e->ey; } + if (x0 == x) STBTT_assert(x1 <= x+1); else if (x0 == x+1) @@ -2700,6 +3056,7 @@ static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edg STBTT_assert(x1 >= x+1); else STBTT_assert(x1 >= x && x1 <= x+1); + if (x0 <= x && x1 <= x) scanline[x] += e->direction * (y1-y0); else if (x0 >= x+1 && x1 >= x+1) @@ -2709,27 +3066,34 @@ static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edg scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position } } + static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) { STBTT_assert(top_width >= 0); STBTT_assert(bottom_width >= 0); return (top_width + bottom_width) / 2.0f * height; } + static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) { return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); } + static float stbtt__sized_triangle_area(float height, float width) { return height * width / 2; } + static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) { float y_bottom = y_top+1; + while (e) { // brute force every pixel + // compute intersection points with top & bottom STBTT_assert(e->ey >= y_top); + if (e->fdx == 0) { float x0 = e->fx; if (x0 < len) { @@ -2748,6 +3112,7 @@ static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, float sy0,sy1; float dy = e->fdy; STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + // compute endpoints of line segment clipped to this scanline (if the // line segment starts on this scanline. x0 is the intersection of the // line with y_top, but that may be off the line segment. @@ -2765,8 +3130,10 @@ static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, x_bottom = xb; sy1 = y_bottom; } + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { // from here on, we don't have to range check x values + if ((int) x_top == (int) x_bottom) { float height; // simple case, only spans one pixel @@ -2792,12 +3159,15 @@ static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, } STBTT_assert(dy >= 0); STBTT_assert(dx >= 0); + x1 = (int) x_top; x2 = (int) x_bottom; // compute intersection with y axis at x1+1 y_crossing = y_top + dy * (x1+1 - x0); + // compute intersection with y axis at x2 y_final = y_top + dy * (x2 - x0); + // x1 x_top x2 x_bottom // y_top +------|-----+------------+------------+--------|---+------------+ // | | | | | | @@ -2814,15 +3184,20 @@ static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, // y_bottom +------------+------------+------------+------------+------------+ // // goal is to measure the area covered by '.' in each pixel + // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 // @TODO: maybe test against sy1 rather than y_bottom? if (y_crossing > y_bottom) y_crossing = y_bottom; + sign = e->direction; + // area of the rectangle covered from sy0..y_crossing area = sign * (y_crossing-sy0); + // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); + // check if final y_crossing is blown up; no test case for this if (y_final > y_bottom) { int denom = (x2 - (x1+1)); @@ -2831,6 +3206,7 @@ static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, dy = (y_final - y_crossing ) / denom; // if denom=0, y_final = y_crossing, so y_final <= y_bottom } } + // in second pixel, area covered by line segment found in first pixel // is always a rectangle 1 wide * the height of that line segment; this // is exactly what the variable 'area' stores. it also gets a contribution @@ -2840,18 +3216,22 @@ static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, // the leftmost and rightmost, a trapezoid that slides down in each pixel. // the second pixel's contribution to the third pixel will be the // rectangle 1 wide times the height change in the second pixel, which is dy. + step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, // which multiplied by 1-pixel-width is how much pixel area changes for each step in x // so the area advances by 'step' every time + for (x = x1+1; x < x2; ++x) { scanline[x] += area + step/2; // area of trapezoid is 1*step/2 area += step; } STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down STBTT_assert(sy1 > y_final-0.01f); + // area covered in the last pixel is the rectangle from all the pixels to the left, // plus the trapezoid filled by the line segment in this pixel all the way to the right edge scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f); + // the rest of the line is filled based on the total height of the line segment in this pixel scanline_fill[x2] += sign * (sy1-sy0); } @@ -2877,17 +3257,20 @@ static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, // across the x border, then the corresponding y position might not be distinct // from the other y segment, and it might ignored as an empty segment. to avoid // that, we need to explicitly produce segments based on x positions. + // rename variables to clearly-defined pairs float y0 = y_top; float x1 = (float) (x); float x2 = (float) (x+1); float x3 = xb; float y3 = y_bottom; + // x = e->x + e->dx * (y-y_top) // (y-y_top) = (x - e->x) / e->dx // y = (x - e->x) / e->dx + y_top float y1 = (x - x0) / dx + y_top; float y2 = (x+1 - x0) / dx + y_top; + if (x0 < x1 && x3 > x2) { // three segments descending down-right stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); @@ -2917,6 +3300,7 @@ static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, e = e->next; } } + // directly AA rasterize edges w/o supersampling static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) { @@ -2924,21 +3308,28 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, stbtt__active_edge *active = NULL; int y,j=0, i; float scanline_data[129], *scanline, *scanline2; + STBTT__NOTUSED(vsubsample); + if (result->w > 64) scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); else scanline = scanline_data; + scanline2 = scanline + result->w; + y = off_y; e[n].y0 = (float) (off_y + result->h) + 1; + while (j < result->h) { // find center of pixel for this scanline float scan_y_top = y + 0.0f; float scan_y_bottom = y + 1.0f; stbtt__active_edge **step = &active; + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + // update all active edges; // remove all active edges that terminate before the top of this scanline while (*step) { @@ -2952,6 +3343,7 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, step = &((*step)->next); // advance through list } } + // insert all edges that start before the bottom of this scanline while (e->y0 <= scan_y_bottom) { if (e->y0 != e->y1) { @@ -2971,9 +3363,11 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, } ++e; } + // now process all active edges if (active) stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + { float sum = 0; for (i=0; i < result->w; ++i) { @@ -2994,17 +3388,22 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, z->fx += z->fdx; // advance to position for current scanline step = &((*step)->next); // advance through list } + ++y; ++j; } + stbtt__hheap_cleanup(&hh, userdata); + if (scanline != scanline_data) STBTT_free(scanline, userdata); } #else #error "Unrecognized value of STBTT_RASTERIZER_VERSION" #endif + #define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) { int i,j; @@ -3022,12 +3421,14 @@ static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) p[j] = t; } } + static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) { /* threshold for transitioning to insertion sort */ while (n > 12) { stbtt__edge t; int c01,c12,c,m,i,j; + /* compute median of three */ m = n >> 1; c01 = STBTT__COMPARE(&p[0],&p[m]); @@ -3049,6 +3450,7 @@ static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) t = p[0]; p[0] = p[m]; p[m] = t; + /* partition loop */ i=1; j=n-1; @@ -3066,6 +3468,7 @@ static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) t = p[i]; p[i] = p[j]; p[j] = t; + ++i; --j; } @@ -3080,15 +3483,18 @@ static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) } } } + static void stbtt__sort_edges(stbtt__edge *p, int n) { stbtt__sort_edges_quicksort(p, n); stbtt__sort_edges_ins_sort(p, n); } + typedef struct { float x,y; } stbtt__point; + static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) { float y_scale_inv = invert ? -scale_y : scale_y; @@ -3102,13 +3508,16 @@ static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcou #error "Unrecognized value of STBTT_RASTERIZER_VERSION" #endif // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + // now we have to blow out the windings into explicit edge lists n = 0; for (i=0; i < windings; ++i) n += wcount[i]; + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel if (e == 0) return; n = 0; + m=0; for (i=0; i < windings; ++i) { stbtt__point *p = pts + m; @@ -3132,19 +3541,24 @@ static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcou ++n; } } + // now sort the edges by their highest point (should snap to integer, and then by x) //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); stbtt__sort_edges(e, n); + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + STBTT_free(e, userdata); } + static void stbtt__add_point(stbtt__point *points, int n, float x, float y) { if (!points) return; // during first pass, it's unallocated points[n].x = x; points[n].y = y; } + // tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) { @@ -3165,6 +3579,7 @@ static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x } return 1; } + static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) { // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough @@ -3179,8 +3594,10 @@ static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); float flatness_squared = longlen*longlen-shortlen*shortlen; + if (n > 16) // 65536 segments on one curve better be enough! return; + if (flatness_squared > objspace_flatness_squared) { float x01 = (x0+x1)/2; float y01 = (y0+y1)/2; @@ -3188,12 +3605,15 @@ static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float float y12 = (y1+y2)/2; float x23 = (x2+x3)/2; float y23 = (y2+y3)/2; + float xa = (x01+x12)/2; float ya = (y01+y12)/2; float xb = (x12+x23)/2; float yb = (y12+y23)/2; + float mx = (xa+xb)/2; float my = (ya+yb)/2; + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); } else { @@ -3201,24 +3621,31 @@ static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float *num_points = *num_points+1; } } + // returns number of contours static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) { stbtt__point *points=0; int num_points=0; + float objspace_flatness_squared = objspace_flatness * objspace_flatness; int i,n=0,start=0, pass; + // count how many "moves" there are to get the contour count for (i=0; i < num_verts; ++i) if (vertices[i].type == STBTT_vmove) ++n; + *num_contours = n; if (n == 0) return 0; + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + if (*contour_lengths == 0) { *num_contours = 0; return 0; } + // make two passes through the points so we don't need to realloc for (pass=0; pass < 2; ++pass) { float x=0,y=0; @@ -3236,6 +3663,7 @@ static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, (*contour_lengths)[n] = num_points - start; ++n; start = num_points; + x = vertices[i].x, y = vertices[i].y; stbtt__add_point(points, num_points++, x,y); break; @@ -3262,6 +3690,7 @@ static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, } (*contour_lengths)[n] = num_points - start; } + return points; error: STBTT_free(points, userdata); @@ -3270,6 +3699,7 @@ error: *num_contours = 0; return NULL; } + STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) { float scale = scale_x > scale_y ? scale_y : scale_x; @@ -3282,16 +3712,19 @@ STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, STBTT_free(windings, userdata); } } + STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) { STBTT_free(bitmap, userdata); } + STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) { int ix0,iy0,ix1,iy1; stbtt__bitmap gbm; stbtt_vertex *vertices; int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + if (scale_x == 0) scale_x = scale_y; if (scale_y == 0) { if (scale_x == 0) { @@ -3300,73 +3733,91 @@ STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info } scale_y = scale_x; } + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + // now we get the size gbm.w = (ix1 - ix0); gbm.h = (iy1 - iy0); gbm.pixels = NULL; // in case we error + if (width ) *width = gbm.w; if (height) *height = gbm.h; if (xoff ) *xoff = ix0; if (yoff ) *yoff = iy0; + if (gbm.w && gbm.h) { gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); if (gbm.pixels) { gbm.stride = gbm.w; + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); } } STBTT_free(vertices, info->userdata); return gbm.pixels; } + STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) { return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); } + STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) { int ix0,iy0; stbtt_vertex *vertices; int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); stbtt__bitmap gbm; + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); gbm.pixels = output; gbm.w = out_w; gbm.h = out_h; gbm.stride = out_stride; + if (gbm.w && gbm.h) stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + STBTT_free(vertices, info->userdata); } + STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) { stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); } + STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) { return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); } + STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) { stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); } + STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) { stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); } + STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) { return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); } + STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) { stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); } + ////////////////////////////////////////////////////////////////////////////// // // bitmap baking // // This is SUPER-CRAPPY packing to keep source code small + static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) float pixel_height, // height of font in pixels unsigned char *pixels, int pw, int ph, // bitmap to be filled in @@ -3382,7 +3833,9 @@ static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // fo STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels x=y=1; bottom_y = 1; + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + for (i=0; i < num_chars; ++i) { int advance, lsb, x0,y0,x1,y1,gw,gh; int g = stbtt_FindGlyphIndex(&f, first_char + i); @@ -3410,6 +3863,7 @@ static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // fo } return bottom_y; } + STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) { float d3d_bias = opengl_fillrule ? 0 : -0.5f; @@ -3417,22 +3871,29 @@ STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int p const stbtt_bakedchar *b = chardata + char_index; int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = round_x + d3d_bias; q->y0 = round_y + d3d_bias; q->x1 = round_x + b->x1 - b->x0 + d3d_bias; q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + q->s0 = b->x0 * ipw; q->t0 = b->y0 * iph; q->s1 = b->x1 * ipw; q->t1 = b->y1 * iph; + *xpos += b->xadvance; } + ////////////////////////////////////////////////////////////////////////////// // // rectangle packing replacement routines if you don't have stb_rect_pack.h // + #ifndef STB_RECT_PACK_VERSION + typedef int stbrp_coord; + //////////////////////////////////////////////////////////////////////////////////// // // // // @@ -3443,20 +3904,24 @@ typedef int stbrp_coord; // once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // // // //////////////////////////////////////////////////////////////////////////////////// + typedef struct { int width,height; int x,y,bottom_y; } stbrp_context; + typedef struct { unsigned char x; } stbrp_node; + struct stbrp_rect { stbrp_coord x,y; int id,w,h,was_packed; }; + static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) { con->width = pw; @@ -3467,6 +3932,7 @@ static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *no STBTT__NOTUSED(nodes); STBTT__NOTUSED(num_nodes); } + static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) { int i; @@ -3488,22 +3954,26 @@ static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rect rects[i].was_packed = 0; } #endif + ////////////////////////////////////////////////////////////////////////////// // // bitmap baking // // This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If // stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) { stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); int num_nodes = pw - padding; stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + if (context == NULL || nodes == NULL) { if (context != NULL) STBTT_free(context, alloc_context); if (nodes != NULL) STBTT_free(nodes , alloc_context); return 0; } + spc->user_allocator_context = alloc_context; spc->width = pw; spc->height = ph; @@ -3515,16 +3985,21 @@ STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, in spc->h_oversample = 1; spc->v_oversample = 1; spc->skip_missing = 0; + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + if (pixels) STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + return 1; } + STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) { STBTT_free(spc->nodes , spc->user_allocator_context); STBTT_free(spc->pack_info, spc->user_allocator_context); } + STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) { STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); @@ -3534,11 +4009,14 @@ STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h if (v_oversample <= STBTT_MAX_OVERSAMPLE) spc->v_oversample = v_oversample; } + STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) { spc->skip_missing = skip; } + #define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) { unsigned char buffer[STBTT_MAX_OVERSAMPLE]; @@ -3549,7 +4027,9 @@ static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_i int i; unsigned int total; STBTT_memset(buffer, 0, kernel_width); + total = 0; + // make kernel_width a constant in common cases so compiler can optimize out the divide switch (kernel_width) { case 2: @@ -3588,14 +4068,17 @@ static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_i } break; } + for (; i < w; ++i) { STBTT_assert(pixels[i] == 0); total -= buffer[i & STBTT__OVER_MASK]; pixels[i] = (unsigned char) (total / kernel_width); } + pixels += stride_in_bytes; } } + static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) { unsigned char buffer[STBTT_MAX_OVERSAMPLE]; @@ -3606,7 +4089,9 @@ static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_i int i; unsigned int total; STBTT_memset(buffer, 0, kernel_width); + total = 0; + // make kernel_width a constant in common cases so compiler can optimize out the divide switch (kernel_width) { case 2: @@ -3645,29 +4130,35 @@ static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_i } break; } + for (; i < h; ++i) { STBTT_assert(pixels[i*stride_in_bytes] == 0); total -= buffer[i & STBTT__OVER_MASK]; pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); } + pixels += 1; } } + static float stbtt__oversample_shift(int oversample) { if (!oversample) return 0.0f; + // The prefilter is a box filter of width "oversample", // which shifts phase by (oversample - 1)/2 pixels in // oversampled space. We want to shift in the opposite // direction to counter this. return (float)-(oversample - 1) / (2.0f * (float)oversample); } + // rects array must be big enough to accommodate all characters in the given ranges STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) { int i,j,k; int missing_glyph_added = 0; + k=0; for (i=0; i < num_ranges; ++i) { float fh = ranges[i].font_size; @@ -3694,8 +4185,10 @@ STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stb ++k; } } + return k; } + STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) { stbtt_MakeGlyphBitmapSubpixel(info, @@ -3708,20 +4201,26 @@ STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info shift_x, shift_y, glyph); + if (prefilter_x > 1) stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + if (prefilter_y > 1) stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + *sub_x = stbtt__oversample_shift(prefilter_x); *sub_y = stbtt__oversample_shift(prefilter_y); } + // rects array must be big enough to accommodate all characters in the given ranges STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) { int i,j,k, missing_glyph = -1, return_value = 1; + // save current values int old_h_over = spc->h_oversample; int old_v_over = spc->v_oversample; + k = 0; for (i=0; i < num_ranges; ++i) { float fh = ranges[i].font_size; @@ -3741,6 +4240,7 @@ STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; int glyph = stbtt_FindGlyphIndex(info, codepoint); stbrp_coord pad = (stbrp_coord) spc->padding; + // pad on left and top r->x += pad; r->y += pad; @@ -3760,14 +4260,17 @@ STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const scale * spc->v_oversample, 0,0, glyph); + if (spc->h_oversample > 1) stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, r->w, r->h, spc->stride_in_bytes, spc->h_oversample); + if (spc->v_oversample > 1) stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, r->w, r->h, spc->stride_in_bytes, spc->v_oversample); + bc->x0 = (stbtt_int16) r->x; bc->y0 = (stbtt_int16) r->y; bc->x1 = (stbtt_int16) (r->x + r->w); @@ -3777,6 +4280,7 @@ STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const bc->yoff = (float) y0 * recip_v + sub_y; bc->xoff2 = (x0 + r->w) * recip_h + sub_x; bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + if (glyph == 0) missing_glyph = j; } else if (spc->skip_missing) { @@ -3786,24 +4290,30 @@ STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const } else { return_value = 0; // if any fail, report failure } + ++k; } } + // restore original values spc->h_oversample = old_h_over; spc->v_oversample = old_v_over; + return return_value; } + STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) { stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); } + STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) { stbtt_fontinfo info; int i, j, n, return_value; // [DEAR IMGUI] removed = 1; //stbrp_context *context = (stbrp_context *) spc->pack_info; stbrp_rect *rects; + // flag all characters as NOT packed for (i=0; i < num_ranges; ++i) for (j=0; j < ranges[i].num_chars; ++j) @@ -3811,20 +4321,28 @@ STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char ranges[i].chardata_for_range[j].y0 = ranges[i].chardata_for_range[j].x1 = ranges[i].chardata_for_range[j].y1 = 0; + n = 0; for (i=0; i < num_ranges; ++i) n += ranges[i].num_chars; + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); if (rects == NULL) return 0; + info.userdata = spc->user_allocator_context; stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + stbtt_PackFontRangesPackRects(spc, rects, n); + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + STBTT_free(rects, spc->user_allocator_context); return return_value; } + STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) { @@ -3836,6 +4354,7 @@ STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char * range.font_size = font_size; return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); } + STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) { int i_ascent, i_descent, i_lineGap; @@ -3848,10 +4367,12 @@ STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int in *descent = (float) i_descent * scale; *lineGap = (float) i_lineGap * scale; } + STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) { float ipw = 1.0f / pw, iph = 1.0f / ph; const stbtt_packedchar *b = chardata + char_index; + if (align_to_integer) { float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); @@ -3865,29 +4386,37 @@ STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int q->x1 = *xpos + b->xoff2; q->y1 = *ypos + b->yoff2; } + q->s0 = b->x0 * ipw; q->t0 = b->y0 * iph; q->s1 = b->x1 * ipw; q->t1 = b->y1 * iph; + *xpos += b->xadvance; } + ////////////////////////////////////////////////////////////////////////////// // // sdf computation // + #define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) #define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) { float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + float a = q0perp - 2*q1perp + q2perp; float b = q1perp - q0perp; float c = q0perp - roperp; + float s0 = 0., s1 = 0.; int num_s = 0; + if (a != 0.0) { float discr = b*b - a*c; if (discr > 0.0) { @@ -3909,20 +4438,25 @@ static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], if (s0 >= 0.0 && s0 <= 1.0) num_s = 1; } + if (num_s == 0) return 0; else { float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; float q1d = q1[0]*rayn_x + q1[1]*rayn_y; float q2d = q2[0]*rayn_x + q2[1]*rayn_y; float rod = orig[0]*rayn_x + orig[1]*rayn_y; + float q10d = q1d - q0d; float q20d = q2d - q0d; float q0rd = q0d - rod; + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; hits[0][1] = a*s0+b; + if (num_s > 1) { hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; hits[1][1] = a*s1+b; @@ -3932,24 +4466,29 @@ static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], } } } + static int equal(float *a, float *b) { return (a[0] == b[0] && a[1] == b[1]); } + static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) { int i; float orig[2], ray[2] = { 1, 0 }; float y_frac; int winding = 0; + // make sure y never passes through a vertex of the shape y_frac = (float) STBTT_fmod(y, 1.0f); if (y_frac < 0.01f) y += 0.01f; else if (y_frac > 0.99f) y -= 0.01f; + orig[0] = x; orig[1] = y; + // test a ray from (-infinity,y) to (x,y) for (i=0; i < nverts; ++i) { if (verts[i].type == STBTT_vline) { @@ -3977,8 +4516,8 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex q2[0] = (float)x2; q2[1] = (float)y2; if (equal(q0,q1) || equal(q1,q2)) { - x0 = (int)verts[i-1].x; - y0 = (int)verts[i-1].y; + x0 = (int)verts[i-1].x; //-V1048 + y0 = (int)verts[i-1].y; //-V1048 x1 = (int)verts[i ].x; y1 = (int)verts[i ].y; if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { @@ -4000,6 +4539,7 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex } return winding; } + static float stbtt__cuberoot( float x ) { if (x<0) @@ -4007,6 +4547,7 @@ static float stbtt__cuberoot( float x ) else return (float) STBTT_pow( x,1.0f/3.0f); } + // x^3 + a*x^2 + b*x + c = 0 static int stbtt__solve_cubic(float a, float b, float c, float* r) { @@ -4031,35 +4572,45 @@ static int stbtt__solve_cubic(float a, float b, float c, float* r) r[0] = s + u * 2 * m; r[1] = s - u * (m + n); r[2] = s - u * (m - n); + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); return 3; } } + STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) { float scale_x = scale, scale_y = scale; int ix0,iy0,ix1,iy1; int w,h; unsigned char *data; + if (scale == 0) return NULL; + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + // if empty, return NULL if (ix0 == ix1 || iy0 == iy1) return NULL; + ix0 -= padding; iy0 -= padding; ix1 += padding; iy1 += padding; + w = (ix1 - ix0); h = (iy1 - iy0); + if (width ) *width = w; if (height) *height = h; if (xoff ) *xoff = ix0; if (yoff ) *yoff = iy0; + // invert for y-downwards bitmaps scale_y = -scale_y; + { int x,y,i,j; float *precompute; @@ -4067,6 +4618,7 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); data = (unsigned char *) STBTT_malloc(w * h, info->userdata); precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + for (i=0,j=num_verts-1; i < num_verts; j=i++) { if (verts[i].type == STBTT_vline) { float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; @@ -4086,6 +4638,7 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc } else precompute[i] = 0.0f; } + for (y=iy0; y < iy1; ++y) { for (x=ix0; x < ix1; ++x) { float val; @@ -4094,14 +4647,19 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc float sy = (float) y + 0.5f; float x_gspace = (sx / scale_x); float y_gspace = (sy / scale_y); + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + for (i=0; i < num_verts; ++i) { float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); if (dist2 < min_dist*min_dist) min_dist = (float) STBTT_sqrt(dist2); + // coarse culling against bbox //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) @@ -4163,6 +4721,7 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); if (dist2 < min_dist*min_dist) min_dist = (float) STBTT_sqrt(dist2); + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { t = res[0], it = 1.0f - t; px = it*it*x0 + 2*t*it*x1 + t*t*x2; @@ -4205,22 +4764,27 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc } return data; } + STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) { return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); } + STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) { STBTT_free(bitmap, userdata); } + ////////////////////////////////////////////////////////////////////////////// // // font name matching -- recommended not to use this // + // check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) { stbtt_int32 i=0; + // convert utf16 to utf8 and compare the results while converting while (len2) { stbtt_uint16 ch = s2[0]*256 + s2[1]; @@ -4255,10 +4819,12 @@ static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, s } return i; } + static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) { return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); } + // returns results in whatever encoding you request... but note that 2-byte encodings // will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) @@ -4268,6 +4834,7 @@ STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *l stbtt_uint32 offset = font->fontstart; stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); if (!nm) return NULL; + count = ttUSHORT(fc+nm+2); stringOffset = nm + ttUSHORT(fc+nm+4); for (i=0; i < count; ++i) { @@ -4280,21 +4847,25 @@ STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *l } return NULL; } + static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) { stbtt_int32 i; stbtt_int32 count = ttUSHORT(fc+nm+2); stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { stbtt_uint32 loc = nm + 6 + 12 * i; stbtt_int32 id = ttUSHORT(fc+loc+6); if (id == target_id) { // find the encoding stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + // is this a Unicode encoding? if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { stbtt_int32 slen = ttUSHORT(fc+loc+8); stbtt_int32 off = ttUSHORT(fc+loc+10); + // check if there's a prefix match stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); if (matchlen >= 0) { @@ -4317,23 +4888,28 @@ static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, } } } + // @TODO handle other encodings } } return 0; } + static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) { stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); stbtt_uint32 nm,hd; if (!stbtt__isfont(fc+offset)) return 0; + // check italics/bold/underline flags in macStyle... if (flags) { hd = stbtt__find_table(fc, offset, "head"); if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; } + nm = stbtt__find_table(fc, offset, "name"); if (!nm) return 0; + if (flags) { // if we checked the macStyle flags, then just check the family and ignore the subfamily if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; @@ -4344,8 +4920,10 @@ static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *nam if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; } + return 0; } + static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) { stbtt_int32 i; @@ -4356,40 +4934,51 @@ static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char return off; } } + #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-qual" #endif + STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, float pixel_height, unsigned char *pixels, int pw, int ph, int first_char, int num_chars, stbtt_bakedchar *chardata) { return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); } + STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) { return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); } + STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) { return stbtt_GetNumberOfFonts_internal((unsigned char *) data); } + STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) { return stbtt_InitFont_internal(info, (unsigned char *) data, offset); } + STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) { return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); } + STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) { return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); } + #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic pop #endif + #endif // STB_TRUETYPE_IMPLEMENTATION + + // FULL VERSION HISTORY // // 1.25 (2021-07-11) many fixes @@ -4452,6 +5041,7 @@ STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const // 0.2 (2009-03-11) Fix unsigned/signed char warnings // 0.1 (2009-03-09) First public release // + /* ------------------------------------------------------------------------------ This software is available under 2 licenses -- choose whichever you prefer.