From 5c9284106fcabdd15536bd9b3315c445a5d3469b Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Tue, 26 Sep 2023 17:09:12 -0400 Subject: [PATCH] renderer: Add support for imgui --- .reuse/dep5 | 4 + armoury/CMakeLists.txt | 1 + armoury/src/gearview.cpp | 3 + extern/CMakeLists.txt | 3 +- extern/imgui/CMakeLists.txt | 13 + extern/imgui/include/imconfig.h | 135 + extern/imgui/include/imgui.h | 4646 ++++++++ extern/imgui/src/imgui.cpp | 15692 ++++++++++++++++++++++++++ extern/imgui/src/imgui_demo.cpp | 8542 ++++++++++++++ extern/imgui/src/imgui_draw.cpp | 4575 ++++++++ extern/imgui/src/imgui_internal.h | 4642 ++++++++ extern/imgui/src/imgui_tables.cpp | 4178 +++++++ extern/imgui/src/imgui_widgets.cpp | 9107 +++++++++++++++ parts/mdl/mdlpart.cpp | 10 + renderer/CMakeLists.txt | 4 +- renderer/include/renderer.hpp | 7 + renderer/shaders/compile_shaders.sh | 4 +- renderer/shaders/imgui.frag | 15 + renderer/shaders/imgui.frag.spv | Bin 0 -> 740 bytes renderer/shaders/imgui.vert | 21 + renderer/shaders/imgui.vert.spv | Bin 0 -> 1528 bytes renderer/src/imguipass.cpp | 341 + renderer/src/imguipass.h | 43 + renderer/src/renderer.cpp | 19 +- 24 files changed, 51999 insertions(+), 6 deletions(-) create mode 100644 extern/imgui/CMakeLists.txt create mode 100644 extern/imgui/include/imconfig.h create mode 100644 extern/imgui/include/imgui.h create mode 100644 extern/imgui/src/imgui.cpp create mode 100644 extern/imgui/src/imgui_demo.cpp create mode 100644 extern/imgui/src/imgui_draw.cpp create mode 100644 extern/imgui/src/imgui_internal.h create mode 100644 extern/imgui/src/imgui_tables.cpp create mode 100644 extern/imgui/src/imgui_widgets.cpp create mode 100644 renderer/shaders/imgui.frag create mode 100644 renderer/shaders/imgui.frag.spv create mode 100644 renderer/shaders/imgui.vert create mode 100644 renderer/shaders/imgui.vert.spv create mode 100644 renderer/src/imguipass.cpp create mode 100644 renderer/src/imguipass.h diff --git a/.reuse/dep5 b/.reuse/dep5 index a2a6b84..808558e 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -13,4 +13,8 @@ License: MIT Files: extern/magic_enum/* Copyright: 2019 - 2023 Daniil Goncharov +License: MIT + +Files: extern/imgui/* +Copyright: 2014-2023 Omar Cornut License: MIT \ No newline at end of file diff --git a/armoury/CMakeLists.txt b/armoury/CMakeLists.txt index 9f1a243..316d0c3 100644 --- a/armoury/CMakeLists.txt +++ b/armoury/CMakeLists.txt @@ -27,6 +27,7 @@ target_link_libraries(armoury PUBLIC magic_enum physis z mdlpart + imgui NovusCommon) install(TARGETS armoury diff --git a/armoury/src/gearview.cpp b/armoury/src/gearview.cpp index e5a3171..61f1a09 100644 --- a/armoury/src/gearview.cpp +++ b/armoury/src/gearview.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "filecache.h" #include "magic_enum.hpp" @@ -21,6 +22,8 @@ GearView::GearView(GameData* data, FileCache& cache) : data(data), cache(cache) setLayout(layout); mdlPart->requestUpdate = [this] { + ImGui::Text("Hello, world!"); + if (updating) { return; } diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index cce7e5e..a5fdd5a 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -17,4 +17,5 @@ target_include_directories(physis INTERFACE ${libphysis_SOURCE_DIR}/target/publi target_link_libraries(physis INTERFACE unshield) add_subdirectory(magic_enum) -add_subdirectory(tinygltf) \ No newline at end of file +add_subdirectory(tinygltf) +add_subdirectory(imgui) \ No newline at end of file diff --git a/extern/imgui/CMakeLists.txt b/extern/imgui/CMakeLists.txt new file mode 100644 index 0000000..a26487a --- /dev/null +++ b/extern/imgui/CMakeLists.txt @@ -0,0 +1,13 @@ +add_library(imgui STATIC) +target_sources(imgui PRIVATE + include/imconfig.h + include/imgui.h + + src/imgui.cpp + src/imgui_demo.cpp + src/imgui_draw.cpp + src/imgui_internal.h + src/imgui_tables.cpp + src/imgui_widgets.cpp) +target_include_directories(imgui PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_link_libraries(imgui PUBLIC stb::stb) \ No newline at end of file diff --git a/extern/imgui/include/imconfig.h b/extern/imgui/include/imconfig.h new file mode 100644 index 0000000..933e922 --- /dev/null +++ b/extern/imgui/include/imconfig.h @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------------- +// DEAR IMGUI COMPILE-TIME OPTIONS +// Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. +// You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. +//----------------------------------------------------------------------------- +// A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it) +// B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template. +//----------------------------------------------------------------------------- +// You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp +// files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. +// 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. 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. Read "Context and Memory Allocators" section of imgui.cpp for more details. +// #define IMGUI_API __declspec( dllexport ) +// #define IMGUI_API __declspec( dllimport ) + +//---- 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 +// #define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87: disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This will be folded +// into IMGUI_DISABLE_OBSOLETE_FUNCTIONS in a few versions. + +//---- 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 +// ShowStackToolWindow() will be empty (this was called IMGUI_DISABLE_METRICS_WINDOW before 1.88). + +//---- 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) #define +// IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require +// imm32.lib/.a) #define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME). #define +// IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this +// is why this is not the default). #define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can +// implement them yourself (e.g. if you don't want to link with vsnprintf) #define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement +// ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. #define IMGUI_DISABLE_FILE_FUNCTIONS // Don't +// implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) #define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // +// Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with +// fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. #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_SSE // +// Disable use of SSE intrinsics even if available + +//---- Include imgui_user.h at the end of imgui.h as a convenience +// #define IMGUI_INCLUDE_IMGUI_USER_H + +//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) +// #define IMGUI_USE_BGRA_PACKED_COLOR + +//---- 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" +// #define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" +// #define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if IMGUI_USE_STB_SPRINTF is defined. +// #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+lunasvg library to render OpenType SVG fonts (SVGinOT) +// Requires lunasvg headers to be available in the include path + program to be linked with the lunasvg library (not provided). +// Only works in combination with IMGUI_ENABLE_FREETYPE. +// (implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement) +// #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 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); +} +*/ diff --git a/extern/imgui/include/imgui.h b/extern/imgui/include/imgui.h new file mode 100644 index 0000000..6dd9ea9 --- /dev/null +++ b/extern/imgui/include/imgui.h @@ -0,0 +1,4646 @@ +// dear imgui, v1.89.9 +// (headers) + +// Help: +// - Read FAQ at http://dearimgui.com/faq +// - Newcomers, read 'Programmer guide' in imgui.cpp for notes on how to setup Dear ImGui in your codebase. +// - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that. +// Read imgui.cpp for details, links and comments. + +// Resources: +// - FAQ http://dearimgui.com/faq +// - Homepage https://github.com/ocornut/imgui +// - Releases & changelog https://github.com/ocornut/imgui/releases +// - Gallery https://github.com/ocornut/imgui/issues/6478 (please post your screenshots/video there!) +// - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there) +// - Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started +// - Glossary https://github.com/ocornut/imgui/wiki/Glossary +// - Issues & support https://github.com/ocornut/imgui/issues +// - Tests & Automation https://github.com/ocornut/imgui_test_engine + +// Getting Started? +// - Read https://github.com/ocornut/imgui/wiki/Getting-Started +// - 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. + +// Library Version +// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') +#define IMGUI_VERSION "1.89.9" +#define IMGUI_VERSION_NUM 18990 +#define IMGUI_HAS_TABLE + +/* + +Index of this file: +// [SECTION] Header mess +// [SECTION] Forward declarations and basic types +// [SECTION] Dear ImGui end-user API functions +// [SECTION] Flags & Enumerations +// [SECTION] Helpers: Memory allocations macros, ImVector<> +// [SECTION] ImGuiStyle +// [SECTION] ImGuiIO +// [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiPayload, ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs) +// [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) +// [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, +ImDrawData) +// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont) +// [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport) +// [SECTION] Platform Dependent Interfaces (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, because we don't guarantee backward nor forward ABI compatibility (also function call overhead, as +// dear imgui is a call-heavy API) +#ifndef IMGUI_API +#define IMGUI_API +#endif +#ifndef IMGUI_IMPL_API +#define IMGUI_IMPL_API IMGUI_API +#endif + +// Helper Macros +#ifndef IM_ASSERT +#include +#define IM_ASSERT(_EXPR) assert(_EXPR) // You can override the default assert handler by editing imconfig.h +#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. +#define IM_OFFSETOF(_TYPE, _MEMBER) offsetof(_TYPE, _MEMBER) // Offset of _MEMBER within _TYPE. Standardized as offsetof() in C++11 +#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. +#if !defined(IMGUI_USE_STB_SPRINTF) && defined(__MINGW32__) && !defined(__clang__) +#define IM_FMTARGS(FMT) __attribute__((format(gnu_printf, FMT, FMT + 1))) +#define IM_FMTLIST(FMT) __attribute__((format(gnu_printf, FMT, 0))) +#elif !defined(IMGUI_USE_STB_SPRINTF) && (defined(__clang__) || defined(__GNUC__)) +#define IM_FMTARGS(FMT) __attribute__((format(printf, FMT, FMT + 1))) +#define IM_FMTLIST(FMT) __attribute__((format(printf, FMT, 0))) +#else +#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)) +#define IM_MSVC_RUNTIME_CHECKS_RESTORE __pragma(runtime_checks("", restore)) __pragma(check_stack()) __pragma(strict_gs_check(pop)) +#else +#define IM_MSVC_RUNTIME_CHECKS_OFF +#define IM_MSVC_RUNTIME_CHECKS_RESTORE +#endif + +// Warnings +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6). +#endif +#if defined(__clang__) +#pragma clang diagnostic push +#if __has_warning("-Wunknown-warning-option") +#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#pragma clang diagnostic ignored "-Wreserved-identifier" // warning: identifier '_Xxx' is reserved because it starts with '_' followed by a capital letter +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#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 +//----------------------------------------------------------------------------- + +// Forward declarations +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) +struct ImDrawData; // All draw command lists required to render the frame + pos/size coordinates to use for the projection matrix. +struct ImDrawList; // A single draw command list (generally one per window, conceptually you may see this as a dynamic "mesh" builder) +struct ImDrawListSharedData; // Data shared among multiple draw lists (typically owned by parent ImGui context, but you may create one yourself) +struct ImDrawListSplitter; // Helper to split a draw list into different layers which can be drawn into out of order, then flattened back. +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 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 ImColor; // Helper functions to create a color that can be converted to either u32 or float4 (*OBSOLETE* please avoid using) +struct ImGuiContext; // Dear ImGui context (opaque structure, unless including imgui_internal.h) +struct ImGuiIO; // Main configuration and I/O between your application and ImGui +struct ImGuiInputTextCallbackData; // Shared state of InputText() when using custom ImGuiInputTextCallback (rare/advanced use) +struct ImGuiKeyData; // Storage for ImGuiIO and IsKeyDown(), IsKeyPressed() etc functions. +struct ImGuiListClipper; // Helper to manually clip large list of items +struct ImGuiOnceUponAFrame; // Helper for running a block of code not more than once a frame +struct ImGuiPayload; // User data payload for drag and drop operations +struct ImGuiPlatformImeData; // Platform IME data for io.SetPlatformImeDataFn() function. +struct ImGuiSizeCallbackData; // Callback data when using SetNextWindowSizeConstraints() (rare/advanced use) +struct ImGuiStorage; // Helper for key->value storage +struct ImGuiStyle; // Runtime data for styling/colors +struct ImGuiTableSortSpecs; // Sorting specifications for a table (often handling sort specs for a single column, occasionally more) +struct ImGuiTableColumnSortSpecs; // Sorting specification for one column of a table +struct ImGuiTextBuffer; // Helper to hold and append into a text buffer (~string builder) +struct ImGuiTextFilter; // Helper to parse and apply text filters (e.g. "aaaaa[,bbbbb][,ccccc]") +struct ImGuiViewport; // A Platform Window (always only one in 'master' branch), in the future may represent Platform Monitor + +// 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! +// In Visual Studio IDE: CTRL+comma ("Edit.GoToAll") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. +enum ImGuiKey : int; // -> enum ImGuiKey // Enum: A key identifier (ImGuiKey_XXX or ImGuiMod_XXX value) +enum ImGuiMouseSource : int; // -> enum ImGuiMouseSource // Enum; A mouse input source identifier (Mouse, TouchScreen, Pen) +typedef int ImGuiCol; // -> enum ImGuiCol_ // Enum: A color identifier for styling +typedef int ImGuiCond; // -> enum ImGuiCond_ // Enum: A condition for many Set*() functions +typedef int ImGuiDataType; // -> enum ImGuiDataType_ // Enum: A primary data type +typedef int ImGuiDir; // -> enum ImGuiDir_ // Enum: A cardinal direction +typedef int ImGuiMouseButton; // -> enum ImGuiMouseButton_ // Enum: A mouse button identifier (0=left, 1=right, 2=middle) +typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A mouse cursor shape +typedef int ImGuiSortDirection; // -> enum ImGuiSortDirection_ // Enum: A sorting direction (ascending or descending) +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 for compatibility with old C++, 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 IDE: CTRL+comma ("Edit.GoToAll") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in 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 ImGuiBackendFlags; // -> enum ImGuiBackendFlags_ // Flags: for io.BackendFlags +typedef int ImGuiButtonFlags; // -> enum ImGuiButtonFlags_ // Flags: for InvisibleButton() +typedef int ImGuiColorEditFlags; // -> enum ImGuiColorEditFlags_ // Flags: for ColorEdit4(), ColorPicker4() etc. +typedef int ImGuiConfigFlags; // -> enum ImGuiConfigFlags_ // Flags: for io.ConfigFlags +typedef int ImGuiComboFlags; // -> enum ImGuiComboFlags_ // Flags: for BeginCombo() +typedef int ImGuiDragDropFlags; // -> enum ImGuiDragDropFlags_ // Flags: for BeginDragDropSource(), AcceptDragDropPayload() +typedef int ImGuiFocusedFlags; // -> enum ImGuiFocusedFlags_ // Flags: for IsWindowFocused() +typedef int ImGuiHoveredFlags; // -> enum ImGuiHoveredFlags_ // Flags: for IsItemHovered(), IsWindowHovered() etc. +typedef int ImGuiInputTextFlags; // -> enum ImGuiInputTextFlags_ // Flags: for InputText(), InputTextMultiline() +typedef int + ImGuiKeyChord; // -> ImGuiKey | ImGuiMod_XXX // Flags: for storage only for now: an ImGuiKey optionally OR-ed with one or more ImGuiMod_XXX values. +typedef int ImGuiPopupFlags; // -> enum ImGuiPopupFlags_ // Flags: for OpenPopup*(), BeginPopupContext*(), IsPopupOpen() +typedef int ImGuiSelectableFlags; // -> enum ImGuiSelectableFlags_ // Flags: for Selectable() +typedef int ImGuiSliderFlags; // -> enum ImGuiSliderFlags_ // Flags: for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc. +typedef int ImGuiTabBarFlags; // -> enum ImGuiTabBarFlags_ // Flags: for BeginTabBar() +typedef int ImGuiTabItemFlags; // -> enum ImGuiTabItemFlags_ // Flags: for BeginTabItem() +typedef int ImGuiTableFlags; // -> enum ImGuiTableFlags_ // Flags: For BeginTable() +typedef int ImGuiTableColumnFlags; // -> enum ImGuiTableColumnFlags_// Flags: For TableSetupColumn() +typedef int ImGuiTableRowFlags; // -> enum ImGuiTableRowFlags_ // Flags: For TableNextRow() +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() + +// 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. +#ifndef ImTextureID +typedef void *ImTextureID; // Default: store a pointer or an integer fitting in a pointer (most renderer backends are ok with that) +#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 + +// 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 +typedef unsigned char ImU8; // 8-bit unsigned integer +typedef signed short ImS16; // 16-bit signed integer +typedef unsigned short ImU16; // 16-bit unsigned integer +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 + +// 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 short ImWchar16; // A single decoded U16 character/code point. We encode them as multi bytes UTF-8 when used in strings. +typedef unsigned int ImWchar32; // A single decoded U32 character/code point. We encode them as multi bytes UTF-8 when used in strings. +#ifdef IMGUI_USE_WCHAR32 // ImWchar [configurable type: override in imconfig.h with '#define IMGUI_USE_WCHAR32' to support Unicode planes 1-16] +typedef ImWchar32 ImWchar; +#else +typedef ImWchar16 ImWchar; +#endif + +// 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. +IM_MSVC_RUNTIME_CHECKS_OFF +struct ImVec2 { + float x, y; + constexpr ImVec2() + : x(0.0f) + , y(0.0f) + { + } + constexpr ImVec2(float _x, float _y) + : x(_x) + , y(_y) + { + } + float &operator[](size_t idx) + { + IM_ASSERT(idx == 0 || idx == 1); + return ((float *)(void *)(char *)this)[idx]; + } // We very rarely use this [] operator, so the assert overhead is fine. + float operator[](size_t idx) const + { + IM_ASSERT(idx == 0 || idx == 1); + return ((const float *)(const void *)(const char *)this)[idx]; + } +#ifdef IM_VEC2_CLASS_EXTRA + 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 { + float x, y, z, w; + constexpr ImVec4() + : x(0.0f) + , y(0.0f) + , z(0.0f) + , w(0.0f) + { + } + constexpr ImVec4(float _x, float _y, float _z, float _w) + : x(_x) + , y(_y) + , z(_z) + , w(_w) + { + } +#ifdef IM_VEC4_CLASS_EXTRA + IM_VEC4_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and + // ImVec4. +#endif +}; +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 +// - Each context create its own ImFontAtlas by default. You may instance one yourself and pass it to CreateContext() to share a font atlas between contexts. +// - 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. Read "Context and Memory Allocators" section of imgui.cpp for details. +IMGUI_API ImGuiContext *CreateContext(ImFontAtlas *shared_font_atlas = NULL); +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 IO structure (mouse/keyboard/gamepad inputs, time, various configuration options/flags) +IMGUI_API ImGuiStyle &GetStyle(); // access the Style structure (colors, sizes). Always use PushStyleCol(), PushStyleVar() to modify style mid-frame! +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. + +// 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. +IMGUI_API void ShowDebugLogWindow(bool *p_open = NULL); // create Debug Log window. display a simplified log of important dear imgui events. +IMGUI_API void +ShowStackToolWindow(bool *p_open = NULL); // create Stack Tool window. hover items with mouse to query information about the source of their unique ID. +IMGUI_API void ShowAboutWindow(bool *p_open = NULL); // create About window. display Dear ImGui version, credits and build/system information. +IMGUI_API void ShowStyleEditor(ImGuiStyle *ref = NULL); // add style editor block (not a window). you can pass in a reference ImGuiStyle structure to compare + // to, revert to and save to (else it uses the default style) +IMGUI_API bool ShowStyleSelector(const char *label); // add style selector block (not a window), essentially a combo listing the default styles. +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, +// which clicking will set the boolean to false when clicked. +// - You may append multiple times to the same window during the same frame by calling Begin()/End() pairs multiple times. +// Some information such as 'flags' or 'p_open' will only be considered by the first call to Begin(). +// - Begin() return false to indicate the window is collapsed or fully clipped, so you may early out and omit submitting +// anything to the window. Always call a matching End() for each Begin() call, regardless of its return value! +// [Important: due to legacy reason, this is inconsistent with most other functions such as BeginMenu/EndMenu, +// BeginPopup/EndPopup, etc. where the EndXXX call should only be called if the corresponding BeginXXX function +// returned true. Begin and BeginChild are the only odd ones out. Will be fixed in a future update.] +// - 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. +// - For each independent axis of 'size': ==0.0f: use remaining host window size / >0.0f: fixed size / <0.0f: use remaining window size minus abs(size) / Each +// axis can use a different mode, e.g. ImVec2(0,400). +// - BeginChild() returns false to indicate the window is collapsed or fully clipped, so you may early out and omit submitting anything to the window. +// Always call a matching EndChild() for each BeginChild() call, regardless of its return value. +// [Important: due to legacy reason, this is inconsistent with most other functions such as BeginMenu/EndMenu, +// BeginPopup/EndPopup, etc. where the EndXXX call should only be called if the corresponding BeginXXX function +// returned true. Begin and BeginChild are the only odd ones out. Will be fixed in a future update.] +IMGUI_API bool BeginChild(const char *str_id, const ImVec2 &size = ImVec2(0, 0), bool border = false, ImGuiWindowFlags flags = 0); +IMGUI_API bool BeginChild(ImGuiID id, const ImVec2 &size = ImVec2(0, 0), bool border = false, ImGuiWindowFlags 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(); +IMGUI_API bool IsWindowCollapsed(); +IMGUI_API bool IsWindowFocused(ImGuiFocusedFlags flags = 0); // is current window focused? or its root/child, depending on flags. see flags for options. +IMGUI_API bool IsWindowHovered(ImGuiHoveredFlags flags = 0); // is current window hovered (and typically: not blocked by a popup/modal)? see flags for options. + // NB: If you are trying to check whether your mouse should be dispatched to imgui or to your app, + // you should use the 'io.WantCaptureMouse' boolean for that! Please read the FAQ! +IMGUI_API ImDrawList *GetWindowDrawList(); // get draw list associated to the current window, to append your own drawing primitives +IMGUI_API ImVec2 GetWindowPos(); // get current window position in screen space (note: it is unlikely you need to use this. Consider using current layout pos + // instead, GetScreenCursorPos()) +IMGUI_API ImVec2 GetWindowSize(); // get current window size (note: it is unlikely you need to use this. Consider using GetScreenCursorPos() and e.g. + // GetContentRegionAvail() instead) +IMGUI_API float GetWindowWidth(); // get current window width (shortcut for GetWindowSize().x) +IMGUI_API float GetWindowHeight(); // get current window height (shortcut for GetWindowSize().y) + +// 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. +IMGUI_API void SetNextWindowSize(const ImVec2 &size, + ImGuiCond cond = 0); // set next window size. set axis to 0.0f to force an auto-fit on this axis. call before Begin() +IMGUI_API void +SetNextWindowSizeConstraints(const ImVec2 &size_min, + const ImVec2 &size_max, + ImGuiSizeCallback custom_callback = NULL, + void *custom_callback_data = NULL); // set next window size limits. use -1,-1 on either X/Y axis to preserve the current size. + // Sizes will be rounded down. Use callback to apply non-trivial programmatic constraints. +IMGUI_API void SetNextWindowContentSize( + const ImVec2 &size); // set next window content size (~ scrollable client area, which enforce the range of scrollbars). Not including window decorations + // (title bar, menu bar, etc.) nor WindowPadding. set an axis to 0.0f to leave it automatic. call before Begin() +IMGUI_API void SetNextWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // set next window collapsed state. call before Begin() +IMGUI_API void SetNextWindowFocus(); // set next window to be focused / top-most. call before Begin() +IMGUI_API void SetNextWindowScroll(const ImVec2 &scroll); // set next window scrolling value (use < 0.0f to not affect a given axis). +IMGUI_API void SetNextWindowBgAlpha(float alpha); // set next window background color alpha. helper to easily override the Alpha component of + // ImGuiCol_WindowBg/ChildBg/PopupBg. you may also use ImGuiWindowFlags_NoBackground. +IMGUI_API void SetWindowPos(const ImVec2 &pos, ImGuiCond cond = 0); // (not recommended) set current window position - call within Begin()/End(). prefer using + // SetNextWindowPos(), as this may incur tearing and side-effects. +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. + +// Content region +// - Retrieve available space from a given point. GetContentRegionAvail() is frequently useful. +// - Those functions are bound to be redesigned (they are confusing, incomplete and the Min/Max return values are in local window coordinates which increases +// confusion) +IMGUI_API ImVec2 GetContentRegionAvail(); // == GetContentRegionMax() - GetCursorPos() +IMGUI_API ImVec2 +GetContentRegionMax(); // current content boundaries (typically window boundaries including scrolling, or current column boundaries), in windows coordinates +IMGUI_API ImVec2 GetWindowContentRegionMin(); // content boundaries min for the full window (roughly (0,0)-Scroll), in window coordinates +IMGUI_API ImVec2 GetWindowContentRegionMax(); // content boundaries max for the full window (roughly (0,0)+Size-Scroll) where Size can be overridden with + // SetNextWindowContentSize(), in window coordinates + +// 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(). +IMGUI_API float GetScrollX(); // get scrolling amount [0 .. GetScrollMaxX()] +IMGUI_API float GetScrollY(); // get scrolling amount [0 .. GetScrollMaxY()] +IMGUI_API void SetScrollX(float scroll_x); // set scrolling amount [0 .. GetScrollMaxX()] +IMGUI_API void SetScrollY(float scroll_y); // set scrolling amount [0 .. GetScrollMaxY()] +IMGUI_API float GetScrollMaxX(); // get maximum scrolling amount ~~ ContentSize.x - WindowSize.x - DecorationsSize.x +IMGUI_API float GetScrollMaxY(); // get maximum scrolling amount ~~ ContentSize.y - WindowSize.y - DecorationsSize.y +IMGUI_API void +SetScrollHereX(float center_x_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_x_ratio=0.0: left, 0.5: center, 1.0: + // right. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead. +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 +IMGUI_API void PopFont(); +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); +IMGUI_API void PushStyleVar(ImGuiStyleVar idx, float val); // modify a style float variable. always use this if you modify the style after NewFrame(). +IMGUI_API void PushStyleVar(ImGuiStyleVar idx, const ImVec2 &val); // modify a style ImVec2 variable. always use this if you modify the style after NewFrame(). +IMGUI_API void PopStyleVar(int count = 1); +IMGUI_API void +PushTabStop(bool tab_stop); // == tab stop enable. Allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets +IMGUI_API void PopTabStop(); +IMGUI_API void +PushButtonRepeat(bool repeat); // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (using io.KeyRepeatDelay/io.KeyRepeatRate + // setting). Note that you can call IsItemActive() after any Button() to tell if the button is held in the current frame. +IMGUI_API void PopButtonRepeat(); + +// 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(); +IMGUI_API void SetNextItemWidth(float item_width); // set width of the _next_ common large "item+label" widget. >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 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 while 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); // 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. + +// Cursor / Layout +// - 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. +// - You can call SameLine() between widgets to undo the last carriage return and output at the right of the preceding widget. +// - Attention! We currently have inconsistencies between window-local and absolute positions we will aim to fix with future API: +// Window-local coordinates: SameLine(), GetCursorPos(), SetCursorPos(), GetCursorStartPos(), GetContentRegionMax(), GetWindowContentRegion*(), +// PushTextWrapPos() Absolute coordinate: GetCursorScreenPos(), SetCursorScreenPos(), all ImDrawList:: 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. +IMGUI_API void NewLine(); // undo a SameLine() or force a new line when in a horizontal-layout context. +IMGUI_API void Spacing(); // add vertical spacing. +IMGUI_API void Dummy(const ImVec2 &size); // add a dummy item of given size. unlike InvisibleButton(), Dummy() won't take the mouse click or be navigable into. +IMGUI_API void Indent(float indent_w = 0.0f); // move content position toward the right, by indent_w, or style.IndentSpacing if indent_w <= 0 +IMGUI_API void Unindent(float indent_w = 0.0f); // move content position back to the left, by indent_w, or style.IndentSpacing if indent_w <= 0 +IMGUI_API void BeginGroup(); // lock horizontal starting position +IMGUI_API void EndGroup(); // unlock horizontal starting position + capture the whole group bounding box into one "item" (so you can use IsItemHovered() or + // layout primitives such as SameLine() on whole group, etc.) +IMGUI_API ImVec2 GetCursorPos(); // cursor position in window coordinates (relative to window position) +IMGUI_API float GetCursorPosX(); // (some functions are using window-relative coordinates, such as: GetCursorPos, GetCursorStartPos, GetContentRegionMax, + // GetWindowContentRegion* etc. +IMGUI_API float GetCursorPosY(); // other functions such as GetCursorScreenPos or everything in ImDrawList:: +IMGUI_API void SetCursorPos(const ImVec2 &local_pos); // are using the main, absolute coordinate system. +IMGUI_API void SetCursorPosX(float local_x); // GetWindowPos() + GetCursorPos() == GetCursorScreenPos() etc.) +IMGUI_API void SetCursorPosY(float local_y); // +IMGUI_API ImVec2 GetCursorStartPos(); // initial cursor position in window coordinates +IMGUI_API ImVec2 +GetCursorScreenPos(); // cursor position in absolute coordinates (useful to work with ImDrawList API). generally top-left == GetMainViewport()->Pos == (0,0) in + // single viewport mode, and bottom-right == GetMainViewport()->Pos+Size == io.DisplaySize in single-viewport mode. +IMGUI_API void SetCursorScreenPos(const ImVec2 &pos); // cursor position in absolute coordinates +IMGUI_API void AlignTextToFramePadding(); // vertically align upcoming text baseline to FramePadding.y so that it will align properly to regularly framed items + // (call if you have text on a line before a framed item) +IMGUI_API float GetTextLineHeight(); // ~ FontSize +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: +// - "Q: Why is my widget not reacting when I click on it?" +// - "Q: How can I have widgets with an empty label?" +// - "Q: How can I have multiple widgets with the same label?" +// - Short version: ID are hashes of the entire ID stack. If you are creating widgets in a loop you most likely +// want to push a unique identifier (e.g. object pointer, loop index) to uniquely differentiate them. +// - You can also use the "Label##foobar" syntax within widget label to distinguish them from each others. +// - In this header file we use the "label"/"name" terminology to denote a string that will be displayed + used as an ID, +// whereas "str_id" denote a string that is only used as an ID and not normally displayed. +IMGUI_API void PushID(const char *str_id); // push string into the ID stack (will hash string). +IMGUI_API void PushID(const char *str_id_begin, const char *str_id_end); // push string into the ID stack (will hash string). +IMGUI_API void PushID(const void *ptr_id); // push pointer into the ID stack (will hash pointer). +IMGUI_API void PushID(int int_id); // push integer into the ID stack (will hash integer). +IMGUI_API void PopID(); // pop from the ID stack. +IMGUI_API ImGuiID +GetID(const char *str_id); // calculate unique ID (hash of whole ID stack + given parameter). e.g. if you want to query into ImGuiStorage yourself +IMGUI_API ImGuiID GetID(const char *str_id_begin, const char *str_id_end); +IMGUI_API ImGuiID GetID(const void *ptr_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 +IMGUI_API void TextV(const char *fmt, va_list args) IM_FMTLIST(1); +IMGUI_API void TextColored(const ImVec4 &col, const char *fmt, ...) + IM_FMTARGS(2); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor(); +IMGUI_API void TextColoredV(const ImVec4 &col, const char *fmt, va_list args) IM_FMTLIST(2); +IMGUI_API void TextDisabled(const char *fmt, ...) + IM_FMTARGS(1); // shortcut for PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); Text(fmt, ...); PopStyleColor(); +IMGUI_API void TextDisabledV(const char *fmt, va_list args) IM_FMTLIST(1); +IMGUI_API void TextWrapped(const char *fmt, ...) + IM_FMTARGS(1); // shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos();. Note that this won't work on an auto-resizing window if there's no + // other widgets to extend the window width, yoy may need to set a size using SetNextWindowSize(). +IMGUI_API void TextWrappedV(const char *fmt, va_list args) IM_FMTLIST(1); +IMGUI_API void LabelText(const char *label, const char *fmt, ...) IM_FMTARGS(2); // display text+label aligned the same way as value+label widgets +IMGUI_API void LabelTextV(const char *label, const char *fmt, va_list args) IM_FMTLIST(2); +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 an 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. +IMGUI_API bool Button(const char *label, const ImVec2 &size = ImVec2(0, 0)); // button +IMGUI_API bool SmallButton(const char *label); // button with FramePadding=(0,0) to easily embed within text +IMGUI_API bool InvisibleButton(const char *str_id, + const ImVec2 &size, + ImGuiButtonFlags flags = 0); // flexible button behavior without the visuals, frequently useful to build custom behaviors using + // the public api (along with IsItemActive, IsItemHovered, etc.) +IMGUI_API bool ArrowButton(const char *str_id, ImGuiDir dir); // square button with an arrow shape +IMGUI_API bool Checkbox(const char *label, bool *v); +IMGUI_API bool CheckboxFlags(const char *label, int *flags, int flags_value); +IMGUI_API bool CheckboxFlags(const char *label, unsigned int *flags, unsigned int flags_value); +IMGUI_API bool RadioButton(const char *label, bool active); // use with e.g. if (RadioButton("one", my_value==1)) { my_value = 1; } +IMGUI_API bool RadioButton(const char *label, int *v, int v_button); // shortcut to handle the above pattern when value is an integer +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 + +// Widgets: Images +// - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples +IMGUI_API void Image(ImTextureID user_texture_id, + const ImVec2 &size, + const ImVec2 &uv0 = ImVec2(0, 0), + const ImVec2 &uv1 = ImVec2(1, 1), + const ImVec4 &tint_col = ImVec4(1, 1, 1, 1), + const ImVec4 &border_col = ImVec4(0, 0, 0, 0)); +IMGUI_API bool ImageButton(const char *str_id, + ImTextureID user_texture_id, + const ImVec2 &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. +IMGUI_API bool BeginCombo(const char *label, const char *preview_value, ImGuiComboFlags flags = 0); +IMGUI_API void EndCombo(); // only call EndCombo() if BeginCombo() returns true! +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, + bool (*items_getter)(void *data, int idx, const char **out_text), + void *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', +// the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a +// contiguous set, e.g. &myvector.x +// - 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. +// - Format string may also be set to NULL or use the default format ("%f" or "%d"). +// - Speed are per-pixel of mouse movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For gamepad/keyboard navigation, minimum +// speed is Max(v_speed, minimum_step_at_given_precision). +// - Use v_min < v_max to clamp edits to given limits. Note that CTRL+Click manual input can override those limits if ImGuiSliderFlags_AlwaysClamp is not used. +// - Use v_max = FLT_MAX / INT_MAX etc to avoid clamping to a maximum, same with v_min = -FLT_MAX / INT_MIN to avoid clamping to a minimum. +// - 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. +// - Legacy: Pre-1.78 there are DragXXX() function signatures that take a final `float power=1.0f' argument instead of the `ImGuiSliderFlags flags=0' argument. +// If you get a warning converting a float to ImGuiSliderFlags, read https://github.com/ocornut/imgui/issues/3361 +IMGUI_API bool DragFloat(const char *label, + float *v, + float v_speed = 1.0f, + float v_min = 0.0f, + float v_max = 0.0f, + const char *format = "%.3f", + ImGuiSliderFlags flags = 0); // If v_min >= v_max we have no bound +IMGUI_API bool DragFloat2(const char *label, + float v[2], + float v_speed = 1.0f, + float v_min = 0.0f, + float v_max = 0.0f, + const char *format = "%.3f", + ImGuiSliderFlags flags = 0); +IMGUI_API bool DragFloat3(const char *label, + float v[3], + float v_speed = 1.0f, + float v_min = 0.0f, + float v_max = 0.0f, + const char *format = "%.3f", + ImGuiSliderFlags flags = 0); +IMGUI_API bool DragFloat4(const char *label, + float v[4], + float v_speed = 1.0f, + float v_min = 0.0f, + float v_max = 0.0f, + const char *format = "%.3f", + ImGuiSliderFlags flags = 0); +IMGUI_API bool DragFloatRange2(const char *label, + float *v_current_min, + float *v_current_max, + float v_speed = 1.0f, + float v_min = 0.0f, + float v_max = 0.0f, + const char *format = "%.3f", + const char *format_max = NULL, + ImGuiSliderFlags flags = 0); +IMGUI_API bool DragInt(const char *label, + int *v, + float v_speed = 1.0f, + int v_min = 0, + int v_max = 0, + const char *format = "%d", + ImGuiSliderFlags flags = 0); // If v_min >= v_max we have no bound +IMGUI_API bool DragInt2(const char *label, int v[2], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char *format = "%d", ImGuiSliderFlags flags = 0); +IMGUI_API bool DragInt3(const char *label, int v[3], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char *format = "%d", ImGuiSliderFlags flags = 0); +IMGUI_API bool DragInt4(const char *label, int v[4], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char *format = "%d", ImGuiSliderFlags flags = 0); +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. +// - Format string may also be set to NULL or use the default format ("%f" or "%d"). +// - Legacy: Pre-1.78 there are SliderXXX() function signatures that take a final `float power=1.0f' argument instead of the `ImGuiSliderFlags flags=0' +// argument. +// If you get a warning converting a float to ImGuiSliderFlags, read https://github.com/ocornut/imgui/issues/3361 +IMGUI_API bool SliderFloat(const char *label, + float *v, + float v_min, + float v_max, + const char *format = "%.3f", + ImGuiSliderFlags flags = 0); // adjust format to decorate the value with a prefix or a suffix for in-slider labels or unit display. +IMGUI_API bool SliderFloat2(const char *label, float v[2], float v_min, float v_max, const char *format = "%.3f", ImGuiSliderFlags flags = 0); +IMGUI_API bool SliderFloat3(const char *label, float v[3], float v_min, float v_max, const char *format = "%.3f", ImGuiSliderFlags flags = 0); +IMGUI_API bool SliderFloat4(const char *label, float v[4], float v_min, float v_max, const char *format = "%.3f", ImGuiSliderFlags flags = 0); +IMGUI_API bool SliderAngle(const char *label, + float *v_rad, + float v_degrees_min = -360.0f, + float v_degrees_max = +360.0f, + const char *format = "%.0f deg", + ImGuiSliderFlags flags = 0); +IMGUI_API bool SliderInt(const char *label, int *v, int v_min, int v_max, const char *format = "%d", ImGuiSliderFlags flags = 0); +IMGUI_API bool SliderInt2(const char *label, int v[2], int v_min, int v_max, const char *format = "%d", ImGuiSliderFlags flags = 0); +IMGUI_API bool SliderInt3(const char *label, int v[3], int v_min, int v_max, const char *format = "%d", ImGuiSliderFlags flags = 0); +IMGUI_API bool SliderInt4(const char *label, int v[4], int v_min, int v_max, const char *format = "%d", ImGuiSliderFlags flags = 0); +IMGUI_API bool SliderScalar(const char *label, + ImGuiDataType data_type, + void *p_data, + const void *p_min, + const void *p_max, + const char *format = NULL, + ImGuiSliderFlags flags = 0); +IMGUI_API bool SliderScalarN(const char *label, + ImGuiDataType data_type, + void *p_data, + int components, + const void *p_min, + const void *p_max, + const char *format = NULL, + ImGuiSliderFlags flags = 0); +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. +IMGUI_API bool +InputText(const char *label, char *buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void *user_data = NULL); +IMGUI_API bool InputTextMultiline(const char *label, + char *buf, + size_t buf_size, + const ImVec2 &size = ImVec2(0, 0), + ImGuiInputTextFlags flags = 0, + ImGuiInputTextCallback callback = NULL, + void *user_data = NULL); +IMGUI_API bool InputTextWithHint(const char *label, + const char *hint, + char *buf, + size_t buf_size, + ImGuiInputTextFlags flags = 0, + ImGuiInputTextCallback callback = NULL, + void *user_data = NULL); +IMGUI_API bool InputFloat(const char *label, float *v, float step = 0.0f, float step_fast = 0.0f, const char *format = "%.3f", ImGuiInputTextFlags flags = 0); +IMGUI_API bool InputFloat2(const char *label, float v[2], const char *format = "%.3f", ImGuiInputTextFlags flags = 0); +IMGUI_API bool InputFloat3(const char *label, float v[3], const char *format = "%.3f", ImGuiInputTextFlags flags = 0); +IMGUI_API bool InputFloat4(const char *label, float v[4], const char *format = "%.3f", ImGuiInputTextFlags flags = 0); +IMGUI_API bool InputInt(const char *label, int *v, int step = 1, int step_fast = 100, ImGuiInputTextFlags flags = 0); +IMGUI_API bool InputInt2(const char *label, int v[2], ImGuiInputTextFlags flags = 0); +IMGUI_API bool InputInt3(const char *label, int v[3], ImGuiInputTextFlags flags = 0); +IMGUI_API bool InputInt4(const char *label, int v[4], ImGuiInputTextFlags flags = 0); +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 +IMGUI_API bool ColorEdit3(const char *label, float col[3], ImGuiColorEditFlags flags = 0); +IMGUI_API bool ColorEdit4(const char *label, float col[4], ImGuiColorEditFlags flags = 0); +IMGUI_API bool ColorPicker3(const char *label, float col[3], ImGuiColorEditFlags flags = 0); +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); +IMGUI_API bool TreeNode(const char *str_id, const char *fmt, ...) + IM_FMTARGS(2); // helper variation to easily decorelate the id from the displayed string. Read the FAQ about why and how to use ID. to align arbitrary text + // at the same level as a TreeNode() you can use Bullet(). +IMGUI_API bool TreeNode(const void *ptr_id, const char *fmt, ...) IM_FMTARGS(2); // " +IMGUI_API bool TreeNodeV(const char *str_id, const char *fmt, va_list args) IM_FMTLIST(2); +IMGUI_API bool TreeNodeV(const void *ptr_id, const char *fmt, va_list args) IM_FMTLIST(2); +IMGUI_API bool TreeNodeEx(const char *label, ImGuiTreeNodeFlags flags = 0); +IMGUI_API bool TreeNodeEx(const char *str_id, ImGuiTreeNodeFlags flags, const char *fmt, ...) IM_FMTARGS(3); +IMGUI_API bool TreeNodeEx(const void *ptr_id, ImGuiTreeNodeFlags flags, const char *fmt, ...) IM_FMTARGS(3); +IMGUI_API bool TreeNodeExV(const char *str_id, ImGuiTreeNodeFlags flags, const char *fmt, va_list args) IM_FMTLIST(3); +IMGUI_API bool TreeNodeExV(const void *ptr_id, ImGuiTreeNodeFlags flags, const char *fmt, va_list args) IM_FMTLIST(3); +IMGUI_API void +TreePush(const char *str_id); // ~ Indent()+PushId(). Already called by TreeNode() when returning true, but you can call TreePush/TreePop yourself if desired. +IMGUI_API void TreePush(const void *ptr_id); // " +IMGUI_API void TreePop(); // ~ Unindent()+PopId() +IMGUI_API float GetTreeNodeToLabelSpacing(); // horizontal distance preceding label when using TreeNode*() or Bullet() == (g.FontSize + style.FramePadding.x*2) + // for a regular unframed TreeNode +IMGUI_API bool CollapsingHeader( + const char *label, + ImGuiTreeNodeFlags flags = 0); // if returning 'true' the header is open. doesn't indent nor push on ID stack. user doesn't have to call TreePop(). +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. + +// 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. + +// Widgets: List Boxes +// - This is essentially a thin wrapper to using BeginChild/EndChild with some stylistic changes. +// - The BeginListBox()/EndListBox() api allows you to manage your contents and selection state however you want it, by creating e.g. Selectable() or any 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. +// - 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, + bool (*items_getter)(void *data, int idx, const char **out_text), + void *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. +// - Use BeginMenu() to create a menu. You can call BeginMenu() multiple time with the same identifier to append more items to it. +// - Not that MenuItem() keyboardshortcuts are displayed as a convenience but _not processed_ by Dear ImGui at the moment. +IMGUI_API bool BeginMenuBar(); // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set on parent window). +IMGUI_API void EndMenuBar(); // only call EndMenuBar() if BeginMenuBar() returns true! +IMGUI_API bool BeginMainMenuBar(); // create and append to a full screen menu-bar. +IMGUI_API void EndMainMenuBar(); // only call EndMainMenuBar() if BeginMainMenuBar() returns true! +IMGUI_API bool BeginMenu(const char *label, bool enabled = true); // create a sub-menu entry. only call EndMenu() if this returns true! +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. SetTooltip() is a shortcut for the 'if (BeginTooltip()) { Text(...); EndTooltip(); }' idiom. +IMGUI_API bool BeginTooltip(); // begin/append a tooltip window. +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_Tooltip) && BeginTooltip())' idiom. +// - SetItemTooltip() is a shortcut for the 'if (IsItemHovered(ImGuiHoveredFlags_Tooltip)) { SetTooltip(...); }' idiom. +// - Where 'ImGuiHoveredFlags_Tooltip' itself is a shortcut to use 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav' depending on active +// input type. For mouse it defaults to 'ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort'. +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 preceeding 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. +// - Their visibility state (~bool) is held internally instead of being held by the programmer as we are used to with regular Begin*() calls. +// - The 3 properties above are related: we need to retain popup visibility state in the library because popups may be closed as any time. +// - You can bypass the hovering restriction by using ImGuiHoveredFlags_AllowWhenBlockedByPopup when calling IsItemHovered() or IsWindowHovered(). +// - IMPORTANT: Popup identifiers are relative to the current ID stack, so OpenPopup and BeginPopup generally needs to be at the same level of the stack. +// This is sometimes leading to confusing mistakes. May rework this in the future. + +// Popups: begin/end functions +// - BeginPopup(): query popup state, if open start appending into the window. Call EndPopup() afterwards. ImGuiWindowFlags are forwarded to the window. +// - BeginPopupModal(): block every interaction behind the window, cannot be closed by user, add a dimming background, has a title bar. +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. +// - CloseCurrentPopup(): use inside the BeginPopup()/EndPopup() scope to close manually. +// - CloseCurrentPopup() is called by default by Selectable()/MenuItem() when activated (FIXME: need some options). +// - Use ImGuiPopupFlags_NoOpenOverExistingPopup to avoid opening a popup if there's already one at the same level. This is equivalent to e.g. testing for +// !IsAnyPopupOpen() prior to OpenPopup(). +// - Use IsWindowAppearing() after BeginPopup() to tell if a window just opened. +// - IMPORTANT: Notice that for OpenPopupOnItemClick() we exceptionally default flags to 1 (== ImGuiPopupFlags_MouseButtonRight) for backward compatibility +// with older API taking 'int mouse_button = 1' parameter +IMGUI_API void OpenPopup(const char *str_id, ImGuiPopupFlags popup_flags = 0); // call to mark popup as open (don't call every frame!). +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. +// - IMPORTANT: Notice that BeginPopupContextXXX takes ImGuiPopupFlags just like OpenPopup() and unlike BeginPopup(). For full consistency, we may add +// ImGuiWindowFlags to the BeginPopupContextXXX functions in the future. +// - IMPORTANT: Notice that we exceptionally default their flags to 1 (== ImGuiPopupFlags_MouseButtonRight) for backward compatibility with older API taking +// 'int mouse_button = 1' parameter, so if you add other flags remember to re-add the ImGuiPopupFlags_MouseButtonRight. +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. +// - See ImGuiTableFlags_ and ImGuiTableColumnFlags_ enums for a description of available flags. +// The typical call flow is: +// - 1. Call BeginTable(), early out if returning false. +// - 2. Optionally call TableSetupColumn() to submit column name/flags/defaults. +// - 3. Optionally call TableSetupScrollFreeze() to request scroll freezing of columns/rows. +// - 4. Optionally call TableHeadersRow() to submit a header row. Names are pulled from TableSetupColumn() data. +// - 5. Populate contents: +// - In most situations you can use TableNextRow() + TableSetColumnIndex(N) to start appending into a column. +// - If you are using tables as a sort of grid, where every column is holding the same type of contents, +// you may prefer using TableNextColumn() instead of TableNextRow() + TableSetColumnIndex(). +// TableNextColumn() will automatically wrap-around into the next row if needed. +// - IMPORTANT: Comparatively to the old Columns() API, we need to call TableNextColumn() for the first column! +// - Summary of possible call flow: +// -------------------------------------------------------------------------------------------------------- +// TableNextRow() -> TableSetColumnIndex(0) -> Text("Hello 0") -> TableSetColumnIndex(1) -> Text("Hello 1") // OK +// TableNextRow() -> TableNextColumn() -> Text("Hello 0") -> TableNextColumn() -> Text("Hello 1") // OK +// TableNextColumn() -> Text("Hello 0") -> TableNextColumn() -> Text("Hello 1") // OK: TableNextColumn() automatically gets +// to next row! +// TableNextRow() -> Text("Hello 0") // Not OK! Missing TableSetColumnIndex() or +// TableNextColumn()! Text will not appear! +// -------------------------------------------------------------------------------------------------------- +// - 5. Call EndTable() +IMGUI_API bool BeginTable(const char *str_id, int column, ImGuiTableFlags flags = 0, const ImVec2 &outer_size = ImVec2(0.0f, 0.0f), float inner_width = 0.0f); +IMGUI_API void EndTable(); // only call EndTable() if BeginTable() returns true! +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. +// Headers are required to perform: reordering, sorting, and opening the context menu. +// The context menu can also be made available in columns body using ImGuiTableFlags_ContextMenuInBody. +// - You may manually submit headers using TableNextRow() + TableHeader() calls, but this is only useful in +// some advanced use cases (e.g. adding custom widgets in header row). +// - Use TableSetupScrollFreeze() to lock columns/rows so they stay visible when scrolled. +IMGUI_API void TableSetupColumn(const char *label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = 0.0f, ImGuiID user_id = 0); +IMGUI_API void TableSetupScrollFreeze(int cols, int rows); // lock columns/rows so they stay visible when scrolled. +IMGUI_API void TableHeadersRow(); // submit all headers cells based on data provided to TableSetupColumn() + submit context menu +IMGUI_API void TableHeader(const char *label); // submit one header cell manually (rarely used) + +// 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 +// changed since last call, or the first time. Make sure to set 'SpecsDirty = false' after sorting, +// else you may wastefully sort your data every frame! +// - Functions args 'int column_n' treat the default value of -1 as the same as passing the current column index. +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 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 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 border = true); +IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished +IMGUI_API int GetColumnIndex(); // get current column index +IMGUI_API float GetColumnWidth(int column_index = -1); // get column width (in pixels). pass -1 to use current column +IMGUI_API void SetColumnWidth(int column_index, float width); // set column width (in pixels). pass -1 to use current column +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 +IMGUI_API void EndTabBar(); // only call EndTabBar() if BeginTabBar() returns true! +IMGUI_API bool BeginTabItem(const char *label, bool *p_open = NULL, ImGuiTabItemFlags flags = 0); // create a Tab. Returns true if the Tab is selected. +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. + +// 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) +IMGUI_API void LogToFile(int auto_open_depth = -1, const char *filename = NULL); // start logging to file +IMGUI_API void LogToClipboard(int auto_open_depth = -1); // start logging to OS clipboard +IMGUI_API void LogFinish(); // stop logging (close file, etc.) +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(). +// - If you stop calling BeginDragDropSource() the payload is preserved however it won't have a preview tooltip (we currently display a fallback "..." tooltip, +// see #1725) +// - An item can be both drag source and drop target. +IMGUI_API bool BeginDragDropSource(ImGuiDragDropFlags flags = 0); // call after submitting an item which may be dragged. when this return true, you can call + // SetDragDropPayload() + EndDragDropSource() +IMGUI_API bool +SetDragDropPayload(const char *type, + const void *data, + size_t sz, + ImGuiCond cond = 0); // type is a user defined string of maximum 32 characters. Strings starting with '_' are reserved for dear imgui + // internal types. Data is copied and held by imgui. Return true when payload has been accepted. +IMGUI_API void EndDragDropSource(); // only call EndDragDropSource() if BeginDragDropSource() returns true! +IMGUI_API bool BeginDragDropTarget(); // call after submitting an item that may receive a payload. If this returns true, you can call AcceptDragDropPayload() + + // EndDragDropTarget() +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. may return NULL. 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) +// - BeginDisabled(false) essentially does nothing useful but is provided to facilitate use of boolean expressions. If you can avoid calling +// BeginDisabled(False)/EndDisabled() best to avoid it. +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 +// - Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHereY()" when applicable to signify "this is the default item" +IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a 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. + +// 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. +IMGUI_API bool +IsItemHovered(ImGuiHoveredFlags flags = 0); // is the last item hovered? (and usable, aka not blocked by a popup, etc.). See ImGuiHoveredFlags for more options. +IMGUI_API bool IsItemActive(); // is the last item active? (e.g. button being held, text field being edited. This will continuously return true while holding + // mouse button on an item. Items that don't interact will always return false) +IMGUI_API bool IsItemFocused(); // is the last item focused for keyboard/gamepad navigation? +IMGUI_API bool IsItemClicked( + ImGuiMouseButton mouse_button = 0); // is the last item hovered and mouse clicked on? (**) == IsMouseClicked(mouse_button) && IsItemHovered()Important. + // (**) this is NOT equivalent to the behavior of e.g. Button(). Read comments in function definition. +IMGUI_API bool IsItemVisible(); // is the last item visible? (items may be out of sight because of clipping/scrolling) +IMGUI_API bool IsItemEdited(); // did the last item modify its underlying value this frame? or was pressed? This is generally the same as the "bool" return + // value of many widgets. +IMGUI_API bool IsItemActivated(); // was the last item just made active (item was previously inactive). +IMGUI_API bool IsItemDeactivated(); // was the last item just made inactive (item was previously active). Useful for Undo/Redo patterns with widgets that + // require continuous editing. +IMGUI_API bool +IsItemDeactivatedAfterEdit(); // was the last item just made inactive and made a value change when it was active? (e.g. Slider/Drag moved). Useful for Undo/Redo + // patterns with widgets that require continuous editing. Note that you may get false positives (some widgets such as + // Combo()/ListBox()/Selectable() will return true even when clicking an already selected item). +IMGUI_API bool IsItemToggledOpen(); // was the last item open state toggled? set by TreeNode(). +IMGUI_API bool IsAnyItemHovered(); // is any item hovered? +IMGUI_API bool IsAnyItemActive(); // is any item active? +IMGUI_API bool IsAnyItemFocused(); // is any item focused? +IMGUI_API ImGuiID GetItemID(); // get ID of last item (~~ often same ImGui::GetID(label) beforehand) +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(); // this draw list will be the first rendered one. Useful to quickly draw shapes/text behind dear imgui contents. +IMGUI_API ImDrawList *GetForegroundDrawList(); // this draw list will be the last 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. +IMGUI_API double GetTime(); // get global imgui time. incremented by io.DeltaTime every frame. +IMGUI_API int GetFrameCount(); // get global imgui frame count. incremented by 1 every frame. +IMGUI_API ImDrawListSharedData *GetDrawListSharedData(); // you may use this when creating your own ImDrawList instances. +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(); +IMGUI_API bool BeginChildFrame(ImGuiID id, + const ImVec2 &size, + ImGuiWindowFlags flags = 0); // helper to create a child window / scrolling region that looks like a normal widget frame +IMGUI_API void EndChildFrame(); // always call EndChildFrame() regardless of BeginChildFrame() return values (which indicates a collapsed/clipped window) + +// 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...). +// - before v1.87, we used ImGuiKey to carry native/user indices as defined by each backends. About use of those legacy ImGuiKey values: +// - without IMGUI_DISABLE_OBSOLETE_KEYIO (legacy support): you can still use your legacy native/user indices (< 512) according to how your backend/engine +// stored them in io.KeysDown[], but need to cast them to ImGuiKey. +// - with IMGUI_DISABLE_OBSOLETE_KEYIO (this is the way forward): any use of ImGuiKey will assert with key < 512. GetKeyIndex() is pass-through and +// therefore deprecated (gone if IMGUI_DISABLE_OBSOLETE_KEYIO is defined). +IMGUI_API bool IsKeyDown(ImGuiKey key); // is key being held. +IMGUI_API bool IsKeyPressed(ImGuiKey key, + bool repeat = true); // was key pressed (went from !Down to Down)? if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate +IMGUI_API bool IsKeyReleased(ImGuiKey key); // was key released (went from Down to !Down)? +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 a provided for debugging purpose and are not meant to be + // saved persistently not 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: Mouse specific +// - 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. +// - Dragging operations are only reported after mouse has moved a certain distance away from the initial clicking position (see 'lock_threshold' and +// 'io.MouseDraggingThreshold') +IMGUI_API bool IsMouseDown(ImGuiMouseButton button); // is mouse button held? +IMGUI_API bool IsMouseClicked(ImGuiMouseButton button, + bool repeat = false); // did mouse button clicked? (went from !Down to Down). Same as GetMouseClickedCount() == 1. +IMGUI_API bool IsMouseReleased(ImGuiMouseButton button); // did mouse button released? (went from Down to !Down) +IMGUI_API bool IsMouseDoubleClicked(ImGuiMouseButton button); // did mouse button double-clicked? Same as GetMouseClickedCount() == 2. (note that a double-click + // will also report IsMouseClicked() == true) +IMGUI_API int GetMouseClickedCount(ImGuiMouseButton button); // return the number of successive mouse-clicks at the time where a click happen (otherwise 0). +IMGUI_API bool IsMouseHoveringRect(const ImVec2 &r_min, + const ImVec2 &r_max, + bool clip = true); // is mouse hovering given bounding rect (in screen space). clipped by current clipping settings, but + // disregarding of other consideration of focus/window ordering/popup-block. +IMGUI_API bool IsMousePosValid(const ImVec2 *mouse_pos = NULL); // by convention we use (-FLT_MAX,-FLT_MAX) to denote that there is no mouse available +IMGUI_API bool IsAnyMouseDown(); // [WILL OBSOLETE] is any mouse button held? 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. +IMGUI_API ImVec2 GetMousePos(); // shortcut to ImGui::GetIO().MousePos provided by user, to be consistent with other calls +IMGUI_API ImVec2 GetMousePosOnOpeningCurrentPopup(); // retrieve mouse position at the time of opening popup we have BeginPopup() into (helper to avoid user + // backing that value themselves) +IMGUI_API bool IsMouseDragging(ImGuiMouseButton button, + float lock_threshold = -1.0f); // is mouse dragging? (if lock_threshold < -1.0f, uses io.MouseDraggingThreshold) +IMGUI_API ImVec2 GetMouseDragDelta(ImGuiMouseButton button = 0, + float lock_threshold = -1.0f); // return the delta from the initial clicking position while the mouse button is pressed or + // was just released. This is locked and return 0.0f until the mouse moves past a distance + // threshold at least once (if lock_threshold < -1.0f, uses io.MouseDraggingThreshold) +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. + +// 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. +// - Important: default value "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). +IMGUI_API void LoadIniSettingsFromDisk(const char *ini_filename); // call after CreateContext() and before the first call to NewFrame(). NewFrame() + // automatically calls LoadIniSettingsFromDisk(io.IniFilename). +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 +IMGUI_API void DebugTextEncoding(const char *text); +IMGUI_API bool DebugCheckVersionAndDataLayout(const char *version_str, + size_t sz_io, + size_t sz_style, + size_t sz_vec2, + size_t sz_vec4, + size_t sz_drawvert, + size_t sz_drawidx); // This is called by IMGUI_CHECKVERSION() macro. + +// 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() +// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details. +IMGUI_API void SetAllocatorFunctions(ImGuiMemAllocFunc alloc_func, ImGuiMemFreeFunc free_func, void *user_data = NULL); +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); + +} // 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_ { + ImGuiWindowFlags_None = 0, + ImGuiWindowFlags_NoTitleBar = 1 << 0, // Disable title-bar + ImGuiWindowFlags_NoResize = 1 << 1, // Disable user resizing with the lower-right grip + ImGuiWindowFlags_NoMove = 1 << 2, // Disable user moving the window + ImGuiWindowFlags_NoScrollbar = 1 << 3, // Disable scrollbars (window can still scroll with mouse or programmatically) + ImGuiWindowFlags_NoScrollWithMouse = 1 << 4, // Disable user vertically scrolling with mouse wheel. On child window, mouse wheel will be forwarded to the + // parent unless NoScrollbar is also set. + ImGuiWindowFlags_NoCollapse = + 1 << 5, // Disable user collapsing window by double-clicking on it. Also referred to as Window Menu Button (e.g. within a docking node). + ImGuiWindowFlags_AlwaysAutoResize = 1 << 6, // Resize every window to its content every frame + ImGuiWindowFlags_NoBackground = + 1 << 7, // Disable drawing background color (WindowBg, etc.) and outside border. Similar as using SetNextWindowBgAlpha(0.0f). + ImGuiWindowFlags_NoSavedSettings = 1 << 8, // Never load/save settings in .ini file + ImGuiWindowFlags_NoMouseInputs = 1 << 9, // Disable catching mouse, hovering test with pass through. + ImGuiWindowFlags_MenuBar = 1 << 10, // Has a menu-bar + ImGuiWindowFlags_HorizontalScrollbar = + 1 << 11, // Allow horizontal scrollbar to appear (off by default). You may use SetNextWindowContentSize(ImVec2(width,0.0f)); prior to calling Begin() to + // specify width. Read code in imgui_demo in the "Horizontal Scrolling" section. + ImGuiWindowFlags_NoFocusOnAppearing = 1 << 12, // Disable taking focus when transitioning from hidden to visible state + ImGuiWindowFlags_NoBringToFrontOnFocus = + 1 << 13, // Disable bringing window to front when taking focus (e.g. clicking on it or programmatically giving it focus) + ImGuiWindowFlags_AlwaysVerticalScrollbar = 1 << 14, // Always show vertical scrollbar (even if ContentSize.y < Size.y) + ImGuiWindowFlags_AlwaysHorizontalScrollbar = 1 << 15, // Always show horizontal scrollbar (even if ContentSize.x < Size.x) + ImGuiWindowFlags_AlwaysUseWindowPadding = + 1 << 16, // Ensure child windows without border uses style.WindowPadding (ignored by default for non-bordered child windows, because more convenient) + ImGuiWindowFlags_NoNavInputs = 1 << 18, // No gamepad/keyboard navigation within the window + ImGuiWindowFlags_NoNavFocus = 1 << 19, // No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB) + ImGuiWindowFlags_UnsavedDocument = 1 << 20, // Display a dot next to the title. When used in a tab/docking context, tab is selected when clicking the X + + // closure is not 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. + 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_NavFlattened = + 1 << 23, // [BETA] On child window: allow gamepad/keyboard navigation to cross over parent border to this child or between sibling child windows. + ImGuiWindowFlags_ChildWindow = 1 << 24, // Don't use! For internal use by BeginChild() + ImGuiWindowFlags_Tooltip = 1 << 25, // Don't use! For internal use by BeginTooltip() + 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() +}; + +// Flags for ImGui::InputText() +// (Those are per-item flags. There are shared flags in ImGuiIO: io.ConfigInputTextCursorBlink and io.ConfigInputTextEnterKeepActive) +enum ImGuiInputTextFlags_ { + ImGuiInputTextFlags_None = 0, + ImGuiInputTextFlags_CharsDecimal = 1 << 0, // Allow 0123456789.+-*/ + ImGuiInputTextFlags_CharsHexadecimal = 1 << 1, // Allow 0123456789ABCDEFabcdef + ImGuiInputTextFlags_CharsUppercase = 1 << 2, // Turn a..z into A..Z + ImGuiInputTextFlags_CharsNoBlank = 1 << 3, // Filter out spaces, tabs + ImGuiInputTextFlags_AutoSelectAll = 1 << 4, // Select entire text when first taking mouse focus + ImGuiInputTextFlags_EnterReturnsTrue = 1 << 5, // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider looking at + // the IsItemDeactivatedAfterEdit() function. + ImGuiInputTextFlags_CallbackCompletion = 1 << 6, // Callback on pressing TAB (for completion handling) + ImGuiInputTextFlags_CallbackHistory = 1 << 7, // Callback on pressing Up/Down arrows (for history handling) + ImGuiInputTextFlags_CallbackAlways = 1 << 8, // Callback on each iteration. User code may query cursor position, modify text buffer. + ImGuiInputTextFlags_CallbackCharFilter = + 1 << 9, // Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. + ImGuiInputTextFlags_AllowTabInput = 1 << 10, // Pressing TAB input a '\t' character into the text field + ImGuiInputTextFlags_CtrlEnterForNewLine = + 1 << 11, // In multi-line mode, unfocus with Enter, add new line with Ctrl+Enter (default is opposite: unfocus with Ctrl+Enter, add line with Enter). + ImGuiInputTextFlags_NoHorizontalScroll = 1 << 12, // Disable following the cursor horizontally + ImGuiInputTextFlags_AlwaysOverwrite = 1 << 13, // Overwrite mode + ImGuiInputTextFlags_ReadOnly = 1 << 14, // Read-only mode + ImGuiInputTextFlags_Password = 1 << 15, // Password mode, display all characters as '*' + 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(). + ImGuiInputTextFlags_CharsScientific = 1 << 17, // Allow 0123456789.+-*/eE (Scientific notation input) + ImGuiInputTextFlags_CallbackResize = + 1 << 18, // 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 << 19, // Callback on any edit (note that InputText() already returns true on edit, the callback is useful mainly to + // manipulate the underlying buffer while focus is active) + ImGuiInputTextFlags_EscapeClearsAll = + 1 << 20, // Escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) + + // Obsolete names + // ImGuiInputTextFlags_AlwaysInsertMode = ImGuiInputTextFlags_AlwaysOverwrite // [renamed in 1.82] name was not matching behavior +}; + +// Flags for ImGui::TreeNodeEx(), ImGui::CollapsingHeader*() +enum ImGuiTreeNodeFlags_ { + ImGuiTreeNodeFlags_None = 0, + ImGuiTreeNodeFlags_Selected = 1 << 0, // Draw as selected + ImGuiTreeNodeFlags_Framed = 1 << 1, // Draw frame with background (e.g. for CollapsingHeader) + ImGuiTreeNodeFlags_AllowOverlap = 1 << 2, // Hit testing to allow subsequent widgets to overlap this one + ImGuiTreeNodeFlags_NoTreePushOnOpen = 1 << 3, // Don't do a TreePush() when open (e.g. for CollapsingHeader) = no extra indent nor pushing on ID stack + ImGuiTreeNodeFlags_NoAutoOpenOnLog = + 1 << 4, // Don't automatically and temporarily open node when Logging is active (by default logging will automatically open tree nodes) + ImGuiTreeNodeFlags_DefaultOpen = 1 << 5, // Default node to be open + ImGuiTreeNodeFlags_OpenOnDoubleClick = 1 << 6, // Need double-click to open node + ImGuiTreeNodeFlags_OpenOnArrow = 1 << 7, // Only open when clicking on the arrow part. If ImGuiTreeNodeFlags_OpenOnDoubleClick is also set, single-click + // arrow or double-click all box to open. + ImGuiTreeNodeFlags_Leaf = 1 << 8, // No collapsing, no arrow (use as a convenience for leaf nodes). + ImGuiTreeNodeFlags_Bullet = 1 << 9, // Display a bullet instead of arrow. IMPORTANT: node can still be marked open/close if you don't set the _Leaf flag! + ImGuiTreeNodeFlags_FramePadding = 1 << 10, // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. + // Equivalent to calling AlignTextToFramePadding(). + ImGuiTreeNodeFlags_SpanAvailWidth = + 1 << 11, // Extend hit box to the right-most edge, even if not framed. This is not the default in order to allow adding other items on the same line. In + // the future we may refactor the hit system to be front-to-back, allowing natural overlaps and then this can become the default. + ImGuiTreeNodeFlags_SpanFullWidth = 1 << 12, // Extend hit box to the left-most and right-most edges (bypass the indented area). + ImGuiTreeNodeFlags_NavLeftJumpsBackHere = + 1 << 13, // (WIP) Nav: left direction may move to this TreeNode() from any of its child (items submitted between TreeNode and TreePop) + // ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 14, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible + ImGuiTreeNodeFlags_CollapsingHeader = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_NoAutoOpenOnLog, + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + 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, 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. +// It is therefore guaranteed to be legal to pass a mouse button index in ImGuiPopupFlags. +// - For the same reason, we exceptionally default the ImGuiPopupFlags argument of BeginPopupContextXXX functions to 1 instead of 0. +// IMPORTANT: because the default parameter is 1 (==ImGuiPopupFlags_MouseButtonRight), if you rely on the default parameter +// and want to use another flag, you need to pass in the ImGuiPopupFlags_MouseButtonRight flag explicitly. +// - Multiple buttons currently cannot be combined/or-ed in those functions (we could allow it later). +enum ImGuiPopupFlags_ { + ImGuiPopupFlags_None = 0, + ImGuiPopupFlags_MouseButtonLeft = 0, // For BeginPopupContext*(): open on Left Mouse release. Guaranteed to always be == 0 (same as ImGuiMouseButton_Left) + ImGuiPopupFlags_MouseButtonRight = + 1, // For BeginPopupContext*(): open on Right Mouse release. Guaranteed to always be == 1 (same as ImGuiMouseButton_Right) + ImGuiPopupFlags_MouseButtonMiddle = + 2, // For BeginPopupContext*(): open on Middle Mouse release. Guaranteed to always be == 2 (same as ImGuiMouseButton_Middle) + ImGuiPopupFlags_MouseButtonMask_ = 0x1F, + ImGuiPopupFlags_MouseButtonDefault_ = 1, + ImGuiPopupFlags_NoOpenOverExistingPopup = + 1 << 5, // For OpenPopup*(), BeginPopupContext*(): don't open if there's already a popup at the same level of the popup stack + ImGuiPopupFlags_NoOpenOverItems = 1 << 6, // For BeginPopupContextWindow(): don't return true when hovering items, only when hovering empty space + ImGuiPopupFlags_AnyPopupId = 1 << 7, // For IsPopupOpen(): ignore the ImGuiID parameter and test for any popup. + ImGuiPopupFlags_AnyPopupLevel = 1 << 8, // 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_ { + ImGuiSelectableFlags_None = 0, + ImGuiSelectableFlags_DontClosePopups = 1 << 0, // Clicking this doesn't close parent popup window + ImGuiSelectableFlags_SpanAllColumns = 1 << 1, // Selectable frame can span all columns (text will still fit in current column) + ImGuiSelectableFlags_AllowDoubleClick = 1 << 2, // Generate press events on double clicks too + 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 + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiSelectableFlags_AllowItemOverlap = ImGuiSelectableFlags_AllowOverlap, // Renamed in 1.89.7 +#endif +}; + +// Flags for ImGui::BeginCombo() +enum ImGuiComboFlags_ { + ImGuiComboFlags_None = 0, + ImGuiComboFlags_PopupAlignLeft = 1 << 0, // Align the popup toward the left by default + ImGuiComboFlags_HeightSmall = 1 << 1, // Max ~4 items visible. Tip: If you want your combo popup to be a specific size you can use + // SetNextWindowSizeConstraints() prior to calling BeginCombo() + ImGuiComboFlags_HeightRegular = 1 << 2, // Max ~8 items visible (default) + ImGuiComboFlags_HeightLarge = 1 << 3, // Max ~20 items visible + ImGuiComboFlags_HeightLargest = 1 << 4, // As many fitting items as possible + ImGuiComboFlags_NoArrowButton = 1 << 5, // Display on the preview box without the square arrow button + ImGuiComboFlags_NoPreview = 1 << 6, // Display only a square arrow button + ImGuiComboFlags_HeightMask_ = ImGuiComboFlags_HeightSmall | ImGuiComboFlags_HeightRegular | ImGuiComboFlags_HeightLarge | ImGuiComboFlags_HeightLargest, +}; + +// Flags for ImGui::BeginTabBar() +enum ImGuiTabBarFlags_ { + ImGuiTabBarFlags_None = 0, + ImGuiTabBarFlags_Reorderable = 1 << 0, // Allow manually dragging tabs to re-order them + New tabs are appended at the end of list + ImGuiTabBarFlags_AutoSelectNewTabs = 1 << 1, // Automatically select new tabs when they appear + ImGuiTabBarFlags_TabListPopupButton = 1 << 2, // Disable buttons to open the tab list popup + ImGuiTabBarFlags_NoCloseWithMiddleMouseButton = + 1 << 3, // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You can still repro this behavior on + // user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false. + 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_FittingPolicyResizeDown = 1 << 6, // Resize tabs when they don't fit + ImGuiTabBarFlags_FittingPolicyScroll = 1 << 7, // Add scroll buttons when tabs don't fit + ImGuiTabBarFlags_FittingPolicyMask_ = ImGuiTabBarFlags_FittingPolicyResizeDown | ImGuiTabBarFlags_FittingPolicyScroll, + ImGuiTabBarFlags_FittingPolicyDefault_ = ImGuiTabBarFlags_FittingPolicyResizeDown, +}; + +// Flags for ImGui::BeginTabItem() +enum ImGuiTabItemFlags_ { + ImGuiTabItemFlags_None = 0, + ImGuiTabItemFlags_UnsavedDocument = + 1 << 0, // Display a dot next to the title + tab is selected when clicking the X + closure is not 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. + ImGuiTabItemFlags_SetSelected = 1 << 1, // Trigger flag to programmatically make the tab selected when calling BeginTabItem() + ImGuiTabItemFlags_NoCloseWithMiddleMouseButton = 1 + << 2, // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You can still repro this behavior on user's + // side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false. + ImGuiTabItemFlags_NoPushId = 1 << 3, // Don't call PushID(tab->ID)/PopID() on BeginTabItem()/EndTabItem() + ImGuiTabItemFlags_NoTooltip = 1 << 4, // Disable tooltip for the given tab + ImGuiTabItemFlags_NoReorder = 1 << 5, // Disable reordering this tab or having another tab cross over this tab + ImGuiTabItemFlags_Leading = 1 << 6, // Enforce the tab position to the left of the tab bar (after the tab list popup button) + ImGuiTabItemFlags_Trailing = 1 << 7, // Enforce the tab position to the right of the tab bar (before the scrolling buttons) +}; + +// 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. +// - The DEFAULT sizing policies are: +// - Default to ImGuiTableFlags_SizingFixedFit if ScrollX is on, or if host window has ImGuiWindowFlags_AlwaysAutoResize. +// - Default to ImGuiTableFlags_SizingStretchSame if ScrollX is off. +// - When ScrollX is off: +// - Table defaults to ImGuiTableFlags_SizingStretchSame -> all Columns defaults to ImGuiTableColumnFlags_WidthStretch with same weight. +// - Columns sizing policy allowed: Stretch (default), Fixed/Auto. +// - Fixed Columns (if any) will generally obtain their requested width (unless the table cannot fit them all). +// - Stretch Columns will share the remaining width according to their respective weight. +// - Mixed Fixed/Stretch columns is possible but has various side-effects on resizing behaviors. +// The typical use of mixing sizing policies is: any number of LEADING Fixed columns, followed by one or two TRAILING Stretch columns. +// (this is because the visible order of columns have subtle but necessary effects on how they react to manual resizing). +// - When ScrollX is on: +// - Table defaults to ImGuiTableFlags_SizingFixedFit -> all Columns defaults to ImGuiTableColumnFlags_WidthFixed +// - Columns sizing policy allowed: Fixed/Auto mostly. +// - Fixed Columns can be enlarged as needed. Table will show a horizontal scrollbar if needed. +// - When using auto-resizing (non-resizable) fixed columns, querying the content width to use item right-alignment e.g. SetNextItemWidth(-FLT_MIN) doesn't +// make sense, would create a feedback loop. +// - Using Stretch columns OFTEN DOES NOT MAKE SENSE if ScrollX is on, UNLESS you have specified a value for 'inner_width' in BeginTable(). +// If you specify a value for 'inner_width' then effectively the scrolling space is known and Stretch or mixed Fixed/Stretch columns become meaningful +// again. +// - Read on documentation at the top of imgui_tables.cpp for details. +enum ImGuiTableFlags_ { + // Features + ImGuiTableFlags_None = 0, + ImGuiTableFlags_Resizable = 1 << 0, // Enable resizing columns. + ImGuiTableFlags_Reorderable = 1 << 1, // Enable reordering columns in header row (need calling TableSetupColumn() + TableHeadersRow() to display headers) + ImGuiTableFlags_Hideable = 1 << 2, // Enable hiding/disabling columns in context menu. + ImGuiTableFlags_Sortable = + 1 << 3, // Enable sorting. Call TableGetSortSpecs() to obtain sort specs. Also see ImGuiTableFlags_SortMulti and ImGuiTableFlags_SortTristate. + ImGuiTableFlags_NoSavedSettings = 1 << 4, // Disable persisting columns order, width and sort settings in the .ini file. + ImGuiTableFlags_ContextMenuInBody = 1 << 5, // Right-click on columns body/contents will display table context menu. By default it is available in + // TableHeadersRow(). Decorations + ImGuiTableFlags_RowBg = 1 << 6, // Set each RowBg color with ImGuiCol_TableRowBg or ImGuiCol_TableRowBgAlt (equivalent of calling TableSetBgColor with + // ImGuiTableBgFlags_RowBg0 on each row manually) + ImGuiTableFlags_BordersInnerH = 1 << 7, // Draw horizontal borders between rows. + ImGuiTableFlags_BordersOuterH = 1 << 8, // Draw horizontal borders at the top and bottom. + ImGuiTableFlags_BordersInnerV = 1 << 9, // Draw vertical borders between columns. + ImGuiTableFlags_BordersOuterV = 1 << 10, // Draw vertical borders on the left and right sides. + ImGuiTableFlags_BordersH = ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_BordersOuterH, // Draw horizontal borders. + ImGuiTableFlags_BordersV = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersOuterV, // Draw vertical borders. + ImGuiTableFlags_BordersInner = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersInnerH, // Draw inner borders. + ImGuiTableFlags_BordersOuter = ImGuiTableFlags_BordersOuterV | ImGuiTableFlags_BordersOuterH, // Draw outer borders. + ImGuiTableFlags_Borders = ImGuiTableFlags_BordersInner | ImGuiTableFlags_BordersOuter, // Draw all borders. + ImGuiTableFlags_NoBordersInBody = 1 << 11, // [ALPHA] Disable vertical borders in columns Body (borders will always appear in Headers). -> May move to style + ImGuiTableFlags_NoBordersInBodyUntilResize = 1 << 12, // [ALPHA] Disable vertical borders in columns Body until hovered for resize (borders will always + // appear in Headers). -> May move to style Sizing Policy (read above for defaults) + ImGuiTableFlags_SizingFixedFit = 1 << 13, // Columns default to _WidthFixed or _WidthAuto (if resizable or not resizable), matching contents width. + ImGuiTableFlags_SizingFixedSame = 2 << 13, // Columns default to _WidthFixed or _WidthAuto (if resizable or not resizable), matching the maximum contents + // width of all columns. Implicitly enable ImGuiTableFlags_NoKeepColumnsVisible. + ImGuiTableFlags_SizingStretchProp = 3 << 13, // Columns default to _WidthStretch with default weights proportional to each columns contents widths. + ImGuiTableFlags_SizingStretchSame = 4 << 13, // Columns default to _WidthStretch with default weights all equal, unless overridden by TableSetupColumn(). + // Sizing Extra Options + ImGuiTableFlags_NoHostExtendX = 1 << 16, // Make outer width auto-fit to columns, overriding outer_size.x value. Only available when ScrollX/ScrollY are + // disabled and Stretch columns are not used. + ImGuiTableFlags_NoHostExtendY = 1 << 17, // Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit). Only available + // when ScrollX/ScrollY are disabled. Data below the limit will be clipped and not visible. + ImGuiTableFlags_NoKeepColumnsVisible = + 1 << 18, // Disable keeping column always minimally visible when ScrollX is off and table gets too small. Not recommended if columns are resizable. + ImGuiTableFlags_PreciseWidths = + 1 << 19, // Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: 33,33,34. + // With this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth. Clipping + ImGuiTableFlags_NoClip = 1 << 20, // Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into + // other columns). Generally incompatible with TableSetupScrollFreeze(). Padding + ImGuiTableFlags_PadOuterX = 1 << 21, // Default if BordersOuterV is on. Enable outermost padding. Generally desirable if you have headers. + ImGuiTableFlags_NoPadOuterX = 1 << 22, // Default if BordersOuterV is off. Disable outermost padding. + ImGuiTableFlags_NoPadInnerX = 1 << 23, // Disable inner padding between columns (double inner padding if BordersOuterV is on, single inner padding if + // BordersOuterV is off). Scrolling + ImGuiTableFlags_ScrollX = + 1 << 24, // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Changes default sizing policy. + // Because this creates a child window, ScrollY is currently generally recommended when using ScrollX. + ImGuiTableFlags_ScrollY = 1 << 25, // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. + // Sorting + ImGuiTableFlags_SortMulti = + 1 << 26, // Hold shift when clicking headers to sort on multiple column. TableGetSortSpecs() may return specs where (SpecsCount > 1). + ImGuiTableFlags_SortTristate = 1 << 27, // Allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0). + + // [Internal] Combinations and masks + ImGuiTableFlags_SizingMask_ = + ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_SizingFixedSame | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_SizingStretchSame, +}; + +// Flags for ImGui::TableSetupColumn() +enum ImGuiTableColumnFlags_ { + // Input configuration flags + ImGuiTableColumnFlags_None = 0, + ImGuiTableColumnFlags_Disabled = 1 << 0, // Overriding/master disable flag: hide column, won't show in context menu (unlike calling TableSetColumnEnabled() + // which manipulates the user accessible state) + ImGuiTableColumnFlags_DefaultHide = 1 << 1, // Default as a hidden/disabled column. + ImGuiTableColumnFlags_DefaultSort = 1 << 2, // Default as a sorting column. + ImGuiTableColumnFlags_WidthStretch = 1 << 3, // Column will stretch. Preferable with horizontal scrolling disabled (default if table sizing policy is + // _SizingStretchSame or _SizingStretchProp). + ImGuiTableColumnFlags_WidthFixed = 1 << 4, // Column will not stretch. Preferable with horizontal scrolling enabled (default if table sizing policy is + // _SizingFixedFit and table is resizable). + ImGuiTableColumnFlags_NoResize = 1 << 5, // Disable manual resizing. + ImGuiTableColumnFlags_NoReorder = 1 << 6, // Disable manual reordering this column, this will also prevent other columns from crossing over this column. + ImGuiTableColumnFlags_NoHide = 1 << 7, // Disable ability to hide/disable this column. + ImGuiTableColumnFlags_NoClip = 1 << 8, // Disable clipping for this column (all NoClip columns will render in a same draw command). + ImGuiTableColumnFlags_NoSort = 1 << 9, // Disable ability to sort on this field (even if ImGuiTableFlags_Sortable is set on the table). + ImGuiTableColumnFlags_NoSortAscending = 1 << 10, // Disable ability to sort in the ascending direction. + ImGuiTableColumnFlags_NoSortDescending = 1 << 11, // Disable ability to sort in the descending direction. + ImGuiTableColumnFlags_NoHeaderLabel = + 1 << 12, // TableHeadersRow() will not submit label for this column. Convenient for some small columns. Name will still appear in context menu. + ImGuiTableColumnFlags_NoHeaderWidth = 1 << 13, // Disable header text width contribution to automatic column width. + ImGuiTableColumnFlags_PreferSortAscending = 1 << 14, // Make the initial sort direction Ascending when first sorting on this column (default). + ImGuiTableColumnFlags_PreferSortDescending = 1 << 15, // Make the initial sort direction Descending when first sorting on this column. + 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. + + // 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. +// - Layer 1: draw with RowBg1 color if set, otherwise draw with ColumnBg1 if set. +// - Layer 2: draw with CellBg color if set. +// The purpose of the two row/columns layers is to let you decide if a background color change should override or blend with the existing color. +// When using ImGuiTableFlags_RowBg on the table, each row has the RowBg0 color automatically set for odd/even rows. +// If you set the color of RowBg0 target, your color will override the existing RowBg0 color. +// If you set the color of RowBg1 or ColumnBg1 target, your color will blend over the RowBg0 color. +enum ImGuiTableBgTarget_ { + ImGuiTableBgTarget_None = 0, + ImGuiTableBgTarget_RowBg0 = 1, // Set row background color 0 (generally used for background, automatically set when ImGuiTableFlags_RowBg is used) + ImGuiTableBgTarget_RowBg1 = 2, // Set row background color 1 (generally used for selection marking) + ImGuiTableBgTarget_CellBg = 3, // Set cell background color (top-most color) +}; + +// Flags for ImGui::IsWindowFocused() +enum ImGuiFocusedFlags_ { + ImGuiFocusedFlags_None = 0, + ImGuiFocusedFlags_ChildWindows = 1 << 0, // Return true if any children of the window is focused + ImGuiFocusedFlags_RootWindow = 1 << 1, // Test from root window (top most parent of the current hierarchy) + ImGuiFocusedFlags_AnyWindow = 1 << 2, // Return true if any window is focused. Important: If you are trying to tell how to dispatch your low-level inputs, + // do NOT use this. Use 'io.WantCaptureMouse' instead! Please read the FAQ! + ImGuiFocusedFlags_NoPopupHierarchy = + 1 << 3, // Do not consider popup hierarchy (do not treat popup emitter as parent of popup) (when used with _ChildWindows or _RootWindow) + // 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. +enum ImGuiHoveredFlags_ { + ImGuiHoveredFlags_None = 0, // Return true if directly over the item/window, not obstructed by another window, not obstructed by an active popup or modal + // blocking inputs under them. + ImGuiHoveredFlags_ChildWindows = 1 << 0, // IsWindowHovered() only: Return true if any children of the window is hovered + ImGuiHoveredFlags_RootWindow = 1 << 1, // IsWindowHovered() only: Test from root window (top most parent of the current hierarchy) + ImGuiHoveredFlags_AnyWindow = 1 << 2, // IsWindowHovered() only: Return true if any window is hovered + ImGuiHoveredFlags_NoPopupHierarchy = 1 << 3, // IsWindowHovered() only: Do not consider popup hierarchy (do not treat popup emitter as parent of popup) + // (when used with _ChildWindows or _RootWindow) + // ImGuiHoveredFlags_DockHierarchy = 1 << 4, // IsWindowHovered() only: Consider docking hierarchy (treat dockspace host as parent of docked + // window) (when used with _ChildWindows or _RootWindow) + ImGuiHoveredFlags_AllowWhenBlockedByPopup = 1 << 5, // Return true even if a popup window is normally blocking access to this item/window + // ImGuiHoveredFlags_AllowWhenBlockedByModal = 1 << 6, // Return true even if a modal popup window is normally blocking access to this item/window. + // FIXME-TODO: Unavailable yet. + ImGuiHoveredFlags_AllowWhenBlockedByActiveItem = 1 + << 7, // Return true even if an active item is blocking access to this item/window. Useful for Drag and Drop patterns. + ImGuiHoveredFlags_AllowWhenOverlappedByItem = + 1 << 8, // IsItemHovered() only: Return true even if the item uses AllowOverlap mode and is overlapped by another hoverable item. + ImGuiHoveredFlags_AllowWhenOverlappedByWindow = + 1 << 9, // IsItemHovered() only: Return true even if the position is obstructed or overlapped by another window. + ImGuiHoveredFlags_AllowWhenDisabled = 1 << 10, // IsItemHovered() only: Return true even if the item is disabled + ImGuiHoveredFlags_NoNavOverride = 1 << 11, // IsItemHovered() only: Disable using gamepad/keyboard navigation state when active, always query mouse + 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. + // e.g. 'TooltipHoveredFlagsForMouse' defaults to 'ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort'. + // - 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. + ImGuiHoveredFlags_Stationary = 1 << 13, // Require mouse to be stationary for style.HoverStationaryDelay (~0.15 sec) _at least one time_. After this, can + // move on same item/window. Using the stationary test tends to reduces the need for a long delay. + ImGuiHoveredFlags_DelayNone = 1 << 14, // IsItemHovered() only: Return true immediately (default). As this is the default you generally ignore this. + ImGuiHoveredFlags_DelayShort = 1 << 15, // IsItemHovered() only: Return true after style.HoverDelayShort elapsed (~0.15 sec) (shared between items) + + // requires mouse to be stationary for style.HoverStationaryDelay (once per item). + 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::BeginDragDropSource(), ImGui::AcceptDragDropPayload() +enum ImGuiDragDropFlags_ { + ImGuiDragDropFlags_None = 0, + // BeginDragDropSource() flags + ImGuiDragDropFlags_SourceNoPreviewTooltip = 1 << 0, // Disable preview tooltip. By default, a successful call to BeginDragDropSource opens a tooltip so you + // can display a preview or description of the source contents. This flag disables this behavior. + ImGuiDragDropFlags_SourceNoDisableHover = + 1 << 1, // By default, when dragging we clear data so that IsItemHovered() will return false, to avoid subsequent user code submitting tooltips. This + // flag disables this behavior so you can still call IsItemHovered() on the source item. + ImGuiDragDropFlags_SourceNoHoldToOpenOthers = + 1 << 2, // Disable the behavior that allows to open tree nodes and collapsing header by holding over them while dragging a source item. + ImGuiDragDropFlags_SourceAllowNullID = + 1 << 3, // Allow items such as Text(), Image() that have no unique identifier to be used as drag source, by manufacturing a temporary identifier based + // on their window-relative position. This is extremely unusual within the dear imgui ecosystem and so we made it explicit. + ImGuiDragDropFlags_SourceExtern = 1 << 4, // External source (from outside of dear imgui), won't attempt to read current item/window info. Will always + // return true. Only one Extern source can be active simultaneously. + ImGuiDragDropFlags_SourceAutoExpirePayload = + 1 << 5, // Automatically expire the payload if the source cease to be submitted (otherwise payloads are persisting while being dragged) + // AcceptDragDropPayload() flags + ImGuiDragDropFlags_AcceptBeforeDelivery = 1 << 10, // AcceptDragDropPayload() will returns true even before the mouse button is released. You can then call + // IsDelivery() to test if the payload needs to be delivered. + 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. +}; + +// 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_ { + ImGuiDataType_S8, // signed char / char (with sensible compilers) + ImGuiDataType_U8, // unsigned char + ImGuiDataType_S16, // short + ImGuiDataType_U16, // unsigned short + ImGuiDataType_S32, // int + ImGuiDataType_U32, // unsigned int + ImGuiDataType_S64, // long long / __int64 + ImGuiDataType_U64, // unsigned long long / unsigned __int64 + ImGuiDataType_Float, // float + ImGuiDataType_Double, // double + ImGuiDataType_COUNT +}; + +// A cardinal direction +enum ImGuiDir_ { ImGuiDir_None = -1, ImGuiDir_Left = 0, ImGuiDir_Right = 1, ImGuiDir_Up = 2, ImGuiDir_Down = 3, ImGuiDir_COUNT }; + +// A sorting direction +enum ImGuiSortDirection_ { + ImGuiSortDirection_None = 0, + 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 as legacy native/opaque key values (< 1.87). +// Since >= 1.89 we increased typing (went from int to enum), some legacy code may need a cast to ImGuiKey. +// Read details about the 1.87 and 1.89 transition : https://github.com/ocornut/imgui/issues/4921 +// Note that "Keys" related to physical keys and are not the same concept as input "Characters", the later are submitted via io.AddInputCharacter(). +enum ImGuiKey : int { + // Keyboard + ImGuiKey_None = 0, + ImGuiKey_Tab = 512, // == ImGuiKey_NamedKey_BEGIN + ImGuiKey_LeftArrow, + ImGuiKey_RightArrow, + ImGuiKey_UpArrow, + ImGuiKey_DownArrow, + ImGuiKey_PageUp, + ImGuiKey_PageDown, + ImGuiKey_Home, + ImGuiKey_End, + ImGuiKey_Insert, + ImGuiKey_Delete, + ImGuiKey_Backspace, + ImGuiKey_Space, + ImGuiKey_Enter, + ImGuiKey_Escape, + ImGuiKey_LeftCtrl, + ImGuiKey_LeftShift, + ImGuiKey_LeftAlt, + ImGuiKey_LeftSuper, + 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, + ImGuiKey_A, + ImGuiKey_B, + ImGuiKey_C, + ImGuiKey_D, + ImGuiKey_E, + ImGuiKey_F, + ImGuiKey_G, + ImGuiKey_H, + ImGuiKey_I, + ImGuiKey_J, + ImGuiKey_K, + ImGuiKey_L, + ImGuiKey_M, + ImGuiKey_N, + ImGuiKey_O, + ImGuiKey_P, + ImGuiKey_Q, + ImGuiKey_R, + ImGuiKey_S, + ImGuiKey_T, + ImGuiKey_U, + ImGuiKey_V, + ImGuiKey_W, + ImGuiKey_X, + ImGuiKey_Y, + ImGuiKey_Z, + ImGuiKey_F1, + ImGuiKey_F2, + ImGuiKey_F3, + ImGuiKey_F4, + ImGuiKey_F5, + ImGuiKey_F6, + ImGuiKey_F7, + ImGuiKey_F8, + ImGuiKey_F9, + ImGuiKey_F10, + ImGuiKey_F11, + ImGuiKey_F12, + ImGuiKey_Apostrophe, // ' + ImGuiKey_Comma, // , + ImGuiKey_Minus, // - + ImGuiKey_Period, // . + ImGuiKey_Slash, // / + ImGuiKey_Semicolon, // ; + ImGuiKey_Equal, // = + ImGuiKey_LeftBracket, // [ + ImGuiKey_Backslash, // \ (this text inhibit multiline comment caused by backslash) + ImGuiKey_RightBracket, // ] + ImGuiKey_GraveAccent, // ` + ImGuiKey_CapsLock, + ImGuiKey_ScrollLock, + ImGuiKey_NumLock, + ImGuiKey_PrintScreen, + ImGuiKey_Pause, + ImGuiKey_Keypad0, + ImGuiKey_Keypad1, + ImGuiKey_Keypad2, + ImGuiKey_Keypad3, + ImGuiKey_Keypad4, + ImGuiKey_Keypad5, + ImGuiKey_Keypad6, + ImGuiKey_Keypad7, + ImGuiKey_Keypad8, + ImGuiKey_Keypad9, + ImGuiKey_KeypadDecimal, + ImGuiKey_KeypadDivide, + ImGuiKey_KeypadMultiply, + ImGuiKey_KeypadSubtract, + ImGuiKey_KeypadAdd, + ImGuiKey_KeypadEnter, + ImGuiKey_KeypadEqual, + + // Gamepad (some of those are analog values, 0.0f to 1.0f) // NAVIGATION ACTION + // (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] + + // 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, + ImGuiKey_COUNT, + + // 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. + // - 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. + // In practice: it's complicated; mods are often provided from different sources. Keyboard layout, IME, sticky keys and + // backends tend to interfere and break that equivalence. The safer decision is to relay that ambiguity down to the end-user... + ImGuiMod_None = 0, + ImGuiMod_Ctrl = 1 << 12, // Ctrl + ImGuiMod_Shift = 1 << 13, // Shift + ImGuiMod_Alt = 1 << 14, // Option/Menu + ImGuiMod_Super = 1 << 15, // Cmd/Super/Windows + ImGuiMod_Shortcut = 1 << 11, // Alias for Ctrl (non-macOS) _or_ Super (macOS). + ImGuiMod_Mask_ = 0xF800, // 5-bits + + // [Internal] Prior to 1.87 we required user to fill io.KeysDown[512] using their own native index + the io.KeyMap[] array. + // We are ditching this method but keeping a legacy path for user code doing e.g. IsKeyPressed(MY_NATIVE_KEY_CODE) + // If you need to iterate all keys (for e.g. an input mapper) you may use ImGuiKey_NamedKey_BEGIN..ImGuiKey_NamedKey_END. + ImGuiKey_NamedKey_BEGIN = 512, + ImGuiKey_NamedKey_END = ImGuiKey_COUNT, + ImGuiKey_NamedKey_COUNT = ImGuiKey_NamedKey_END - ImGuiKey_NamedKey_BEGIN, +#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO + 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_KeysData_OFFSET) index. +#else + ImGuiKey_KeysData_SIZE = ImGuiKey_COUNT, // Size of KeysData[]: hold legacy 0..512 keycodes + named keys + ImGuiKey_KeysData_OFFSET = 0, // Accesses to io.KeysData[] must use (key - ImGuiKey_KeysData_OFFSET) index. +#endif + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiKey_ModCtrl = ImGuiMod_Ctrl, + ImGuiKey_ModShift = ImGuiMod_Shift, + ImGuiKey_ModAlt = ImGuiMod_Alt, + ImGuiKey_ModSuper = ImGuiMod_Super, // Renamed in 1.89 + ImGuiKey_KeyPadEnter = ImGuiKey_KeypadEnter, // Renamed in 1.87 +#endif +}; + +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO +// OBSOLETED in 1.88 (from July 2022): ImGuiNavInput and io.NavInputs[]. +// Official backends between 1.60 and 1.86: will keep working and feed gamepad inputs as long as IMGUI_DISABLE_OBSOLETE_KEYIO is not set. +// Custom backends: feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. +enum ImGuiNavInput { + ImGuiNavInput_Activate, + ImGuiNavInput_Cancel, + ImGuiNavInput_Input, + ImGuiNavInput_Menu, + ImGuiNavInput_DpadLeft, + ImGuiNavInput_DpadRight, + ImGuiNavInput_DpadUp, + ImGuiNavInput_DpadDown, + ImGuiNavInput_LStickLeft, + ImGuiNavInput_LStickRight, + ImGuiNavInput_LStickUp, + ImGuiNavInput_LStickDown, + ImGuiNavInput_FocusPrev, + ImGuiNavInput_FocusNext, + ImGuiNavInput_TweakSlow, + ImGuiNavInput_TweakFast, + ImGuiNavInput_COUNT, +}; +#endif + +// Configuration flags stored in io.ConfigFlags. Set by user/application. +enum ImGuiConfigFlags_ { + ImGuiConfigFlags_None = 0, + ImGuiConfigFlags_NavEnableKeyboard = 1 << 0, // Master keyboard navigation enable flag. Enable full Tabbing + directional arrows + space/enter to activate. + ImGuiConfigFlags_NavEnableGamepad = 1 << 1, // Master gamepad navigation enable flag. Backend also needs to set ImGuiBackendFlags_HasGamepad. + ImGuiConfigFlags_NavEnableSetMousePos = + 1 << 2, // Instruct navigation to move the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is awkward. Will update + // io.MousePos and set io.WantSetMousePos=true. If enabled you MUST honor io.WantSetMousePos requests in your backend, otherwise ImGui will + // react as if the mouse is jumping around back and forth. + ImGuiConfigFlags_NavNoCaptureKeyboard = 1 << 3, // Instruct navigation to not set the io.WantCaptureKeyboard flag when io.NavActive is set. + ImGuiConfigFlags_NoMouse = + 1 << 4, // Instruct imgui to clear mouse position/buttons in NewFrame(). This allows ignoring the mouse information set by the backend. + 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. + + // 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. +}; + +// Backend capabilities flags stored in io.BackendFlags. Set by imgui_impl_xxx or custom backend. +enum ImGuiBackendFlags_ { + ImGuiBackendFlags_None = 0, + ImGuiBackendFlags_HasGamepad = 1 << 0, // Backend Platform supports gamepad and currently has one connected. + 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 + // ImGuiConfigFlags_NavEnableSetMousePos 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. +}; + +// Enumeration for PushStyleColor() / PopStyleColor() +enum ImGuiCol_ { + ImGuiCol_Text, + ImGuiCol_TextDisabled, + ImGuiCol_WindowBg, // Background of normal windows + ImGuiCol_ChildBg, // Background of child windows + ImGuiCol_PopupBg, // Background of popups, menus, tooltips windows + ImGuiCol_Border, + ImGuiCol_BorderShadow, + ImGuiCol_FrameBg, // Background of checkbox, radio button, plot, slider, text input + ImGuiCol_FrameBgHovered, + ImGuiCol_FrameBgActive, + ImGuiCol_TitleBg, + ImGuiCol_TitleBgActive, + ImGuiCol_TitleBgCollapsed, + ImGuiCol_MenuBarBg, + ImGuiCol_ScrollbarBg, + ImGuiCol_ScrollbarGrab, + ImGuiCol_ScrollbarGrabHovered, + ImGuiCol_ScrollbarGrabActive, + ImGuiCol_CheckMark, + ImGuiCol_SliderGrab, + ImGuiCol_SliderGrabActive, + ImGuiCol_Button, + ImGuiCol_ButtonHovered, + ImGuiCol_ButtonActive, + ImGuiCol_Header, // Header* colors are used for CollapsingHeader, TreeNode, Selectable, MenuItem + ImGuiCol_HeaderHovered, + ImGuiCol_HeaderActive, + ImGuiCol_Separator, + ImGuiCol_SeparatorHovered, + ImGuiCol_SeparatorActive, + ImGuiCol_ResizeGrip, // Resize grip in lower-right and lower-left corners of windows. + ImGuiCol_ResizeGripHovered, + ImGuiCol_ResizeGripActive, + ImGuiCol_Tab, // TabItem in a TabBar + ImGuiCol_TabHovered, + ImGuiCol_TabActive, + ImGuiCol_TabUnfocused, + ImGuiCol_TabUnfocusedActive, + ImGuiCol_PlotLines, + ImGuiCol_PlotLinesHovered, + ImGuiCol_PlotHistogram, + ImGuiCol_PlotHistogramHovered, + ImGuiCol_TableHeaderBg, // Table header background + ImGuiCol_TableBorderStrong, // Table outer and header borders (prefer using Alpha=1.0 here) + ImGuiCol_TableBorderLight, // Table inner borders (prefer using Alpha=1.0 here) + ImGuiCol_TableRowBg, // Table row background (even rows) + ImGuiCol_TableRowBgAlt, // Table row background (odd rows) + ImGuiCol_TextSelectedBg, + ImGuiCol_DragDropTarget, // Rectangle highlighting a drop target + ImGuiCol_NavHighlight, // Gamepad/keyboard: current highlighted item + 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 +}; + +// 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. +// - Tip: Use your programming IDE navigation facilities on the names in the _second column_ below to find the actual members and their description. +// In Visual Studio IDE: CTRL+comma ("Edit.GoToAll") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. +// - When changing this enum, you need to update the associated internal table GStyleVarInfo[] accordingly. This is where we link enum values to members +// offset/type. +enum ImGuiStyleVar_ { + // Enum name --------------------- // Member in ImGuiStyle structure (see ImGuiStyle for descriptions) + ImGuiStyleVar_Alpha, // float Alpha + ImGuiStyleVar_DisabledAlpha, // float DisabledAlpha + ImGuiStyleVar_WindowPadding, // ImVec2 WindowPadding + ImGuiStyleVar_WindowRounding, // float WindowRounding + ImGuiStyleVar_WindowBorderSize, // float WindowBorderSize + ImGuiStyleVar_WindowMinSize, // ImVec2 WindowMinSize + ImGuiStyleVar_WindowTitleAlign, // ImVec2 WindowTitleAlign + ImGuiStyleVar_ChildRounding, // float ChildRounding + ImGuiStyleVar_ChildBorderSize, // float ChildBorderSize + ImGuiStyleVar_PopupRounding, // float PopupRounding + ImGuiStyleVar_PopupBorderSize, // float PopupBorderSize + ImGuiStyleVar_FramePadding, // ImVec2 FramePadding + ImGuiStyleVar_FrameRounding, // float FrameRounding + ImGuiStyleVar_FrameBorderSize, // float FrameBorderSize + ImGuiStyleVar_ItemSpacing, // ImVec2 ItemSpacing + ImGuiStyleVar_ItemInnerSpacing, // ImVec2 ItemInnerSpacing + ImGuiStyleVar_IndentSpacing, // float IndentSpacing + ImGuiStyleVar_CellPadding, // ImVec2 CellPadding + ImGuiStyleVar_ScrollbarSize, // float ScrollbarSize + ImGuiStyleVar_ScrollbarRounding, // float ScrollbarRounding + ImGuiStyleVar_GrabMinSize, // float GrabMinSize + ImGuiStyleVar_GrabRounding, // float GrabRounding + ImGuiStyleVar_TabRounding, // float TabRounding + ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign + ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign + ImGuiStyleVar_SeparatorTextBorderSize, // float SeparatorTextBorderSize + ImGuiStyleVar_SeparatorTextAlign, // ImVec2 SeparatorTextAlign + ImGuiStyleVar_SeparatorTextPadding, // ImVec2 SeparatorTextPadding + ImGuiStyleVar_COUNT +}; + +// Flags for InvisibleButton() [extended in imgui_internal.h] +enum ImGuiButtonFlags_ { + ImGuiButtonFlags_None = 0, + ImGuiButtonFlags_MouseButtonLeft = 1 << 0, // React on left mouse button (default) + ImGuiButtonFlags_MouseButtonRight = 1 << 1, // React on right mouse button + ImGuiButtonFlags_MouseButtonMiddle = 1 << 2, // React on center mouse button + + // [Internal] + ImGuiButtonFlags_MouseButtonMask_ = ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight | ImGuiButtonFlags_MouseButtonMiddle, + ImGuiButtonFlags_MouseButtonDefault_ = ImGuiButtonFlags_MouseButtonLeft, +}; + +// Flags for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton() +enum ImGuiColorEditFlags_ { + ImGuiColorEditFlags_None = 0, + ImGuiColorEditFlags_NoAlpha = + 1 << 1, // // ColorEdit, ColorPicker, ColorButton: ignore Alpha component (will only read 3 components from the input pointer). + ImGuiColorEditFlags_NoPicker = 1 << 2, // // ColorEdit: disable picker when clicking on color square. + ImGuiColorEditFlags_NoOptions = 1 << 3, // // ColorEdit: disable toggling options menu when right-clicking on inputs/small preview. + ImGuiColorEditFlags_NoSmallPreview = + 1 << 4, // // ColorEdit, ColorPicker: disable color square preview next to the inputs. (e.g. to show only the inputs) + ImGuiColorEditFlags_NoInputs = + 1 << 5, // // ColorEdit, ColorPicker: disable inputs sliders/text widgets (e.g. to show only the small preview color square). + ImGuiColorEditFlags_NoTooltip = 1 << 6, // // ColorEdit, ColorPicker, ColorButton: disable tooltip when hovering the preview. + ImGuiColorEditFlags_NoLabel = + 1 << 7, // // ColorEdit, ColorPicker: disable display of inline text label (the label is still forwarded to the tooltip and picker). + 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) + + // 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_AlphaPreview = + 1 << 17, // // ColorEdit, ColorPicker, ColorButton: display preview as a transparent color over a checkerboard, instead of opaque. + ImGuiColorEditFlags_AlphaPreviewHalf = + 1 << 18, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half checkerboard, instead of opaque. + 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). + ImGuiColorEditFlags_DisplayRGB = + 1 << 20, // [Display] // ColorEdit: override _display_ type among RGB/HSV/Hex. ColorPicker: select any combination using one or more of RGB/HSV/Hex. + ImGuiColorEditFlags_DisplayHSV = 1 << 21, // [Display] // " + ImGuiColorEditFlags_DisplayHex = 1 << 22, // [Display] // " + ImGuiColorEditFlags_Uint8 = 1 << 23, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0..255. + ImGuiColorEditFlags_Float = 1 << 24, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0.0f..1.0f floats instead of + // 0..255 integers. No round-trip of value via integers. + ImGuiColorEditFlags_PickerHueBar = 1 << 25, // [Picker] // ColorPicker: bar for Hue, rectangle for Sat/Value. + 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_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 + // 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 are shared flags in ImGuiIO: io.ConfigDragClickToInputText) +enum ImGuiSliderFlags_ { + ImGuiSliderFlags_None = 0, + ImGuiSliderFlags_AlwaysClamp = + 1 << 4, // Clamp value to min/max bounds when input manually with CTRL+Click. By default CTRL+Click allows going out of bounds. + ImGuiSliderFlags_Logarithmic = 1 << 5, // Make the widget logarithmic (linear otherwise). Consider using ImGuiSliderFlags_NoRoundToFormat with this if using + // a format-string with small amount of digits. + ImGuiSliderFlags_NoRoundToFormat = + 1 << 6, // Disable rounding underlying value to match precision of the display format string (e.g. %.3f values are rounded to those 3 digits) + ImGuiSliderFlags_NoInput = 1 << 7, // Disable CTRL+Click or Enter key allowing to input text directly into the widget + 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. + + // Obsolete names + // ImGuiSliderFlags_ClampOnInput = ImGuiSliderFlags_AlwaysClamp, // [renamed in 1.79] +}; + +// 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_ { ImGuiMouseButton_Left = 0, ImGuiMouseButton_Right = 1, 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_ { + ImGuiMouseCursor_None = -1, + ImGuiMouseCursor_Arrow = 0, + ImGuiMouseCursor_TextInput, // When hovering over InputText, etc. + ImGuiMouseCursor_ResizeAll, // (Unused by Dear ImGui functions) + ImGuiMouseCursor_ResizeNS, // When hovering over a horizontal border + ImGuiMouseCursor_ResizeEW, // When hovering over a vertical border or a column + ImGuiMouseCursor_ResizeNESW, // When hovering over the bottom-left corner of a window + ImGuiMouseCursor_ResizeNWSE, // When hovering over the bottom-right corner of a window + ImGuiMouseCursor_Hand, // (Unused by Dear ImGui functions. Use for e.g. hyperlinks) + 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. +// You can submit a change of pointer type using io.AddMouseSourceEvent(). +enum ImGuiMouseSource : int { + ImGuiMouseSource_Mouse = 0, // Input is coming from an actual mouse. + ImGuiMouseSource_TouchScreen, // Input is coming from a touch screen (no hovering prior to initial press, less precise initial press aiming, dual-axis + // wheeling possible). + ImGuiMouseSource_Pen, // Input is coming from a pressure/magnetic pen (often used in conjunction with high-sampling rates). + ImGuiMouseSource_COUNT +}; + +// Enumeration for ImGui::SetWindow***(), SetNextWindow***(), 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. +enum ImGuiCond_ { + ImGuiCond_None = 0, // No condition (always set the variable), same as _Always + ImGuiCond_Always = 1 << 0, // No condition (always set the variable), same as _None + ImGuiCond_Once = 1 << 1, // Set the variable once per runtime session (only the first call will succeed) + 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] Helpers: Memory allocations macros, ImVector<> +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// 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() +#define IM_ALLOC(_SIZE) ImGui::MemAlloc(_SIZE) +#define IM_FREE(_PTR) ImGui::MemFree(_PTR) +#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). +//----------------------------------------------------------------------------- +// - You generally do NOT need to care or use this ever. But we need to make it available in imgui.h because some of our public structures are relying on it. +// - We use std-like naming convention here, which is a little unusual for this codebase. +// - Important: clear() frees memory, resize(0) keep the allocated buffer. We use resize(0) a lot to intentionally recycle allocated buffers across frames and +// amortize our costs. +// - 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 { + 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() + { + 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); + } + inline int max_size() const + { + return 0x7FFFFFFF / (int)sizeof(T); + } + 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; + } + inline const T *end() const + { + return Data + Size; + } + inline T &front() + { + IM_ASSERT(Size > 0); + return Data[0]; + } + inline const T &front() const + { + IM_ASSERT(Size > 0); + return Data[0]; + } + 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--; + } + inline void push_front(const T &v) + { + if (Size == 0) + push_back(v); + else + insert(Data, v); + } + inline T *erase(const T *it) + { + IM_ASSERT(it >= Data && it < Data + Size); + const ptrdiff_t off = it - Data; + memmove(Data + off, Data + off + 1, ((size_t)Size - (size_t)off - 1) * sizeof(T)); + Size--; + return Data + off; + } + inline T *erase(const T *it, const T *it_last) + { + IM_ASSERT(it >= Data && it < Data + Size && it_last >= it && it_last <= Data + Size); + const ptrdiff_t count = it_last - it; + const ptrdiff_t off = it - Data; + memmove(Data + off, Data + off + count, ((size_t)Size - (size_t)off - (size_t)count) * sizeof(T)); + Size -= (int)count; + return Data + off; + } + inline T *erase_unsorted(const T *it) + { + IM_ASSERT(it >= Data && it < Data + Size); + const ptrdiff_t off = it - Data; + if (it < Data + Size - 1) + memcpy(Data + off, Data + Size - 1, sizeof(T)); + Size--; + return Data + off; + } + inline T *insert(const T *it, const T &v) + { + IM_ASSERT(it >= Data && it <= Data + Size); + const ptrdiff_t off = it - Data; + if (Size == Capacity) + reserve(_grow_capacity(Size + 1)); + if (off < (int)Size) + memmove(Data + off + 1, Data + off, ((size_t)Size - (size_t)off) * sizeof(T)); + memcpy(&Data[off], &v, sizeof(v)); + Size++; + return Data + off; + } + inline bool contains(const T &v) const + { + const T *data = Data; + const T *data_end = Data + Size; + while (data < data_end) + if (*data++ == v) + return true; + return false; + } + inline T *find(const T &v) + { + T *data = Data; + const T *data_end = Data + Size; + while (data < data_end) + if (*data == v) + break; + else + ++data; + return data; + } + inline const T *find(const T &v) const + { + const T *data = Data; + const T *data_end = Data + Size; + while (data < data_end) + if (*data == v) + break; + else + ++data; + return data; + } + inline bool find_erase(const T &v) + { + const T *it = find(v); + if (it < Data + Size) { + erase(it); + return true; + } + return false; + } + inline bool find_erase_unsorted(const T &v) + { + const T *it = find(v); + if (it < Data + Size) { + erase_unsorted(it); + return true; + } + return false; + } + 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 +//----------------------------------------------------------------------------- +// You may modify the ImGui::GetStyle() main instance during initialization and before NewFrame(). +// 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 { + 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. + float WindowRounding; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and + // are not recommended. + float WindowBorderSize; // Thickness of border around windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 WindowMinSize; // Minimum window size. This is a global setting. If you want to constrain individual windows, use SetNextWindowSizeConstraints(). + ImVec2 WindowTitleAlign; // Alignment for title bar text. Defaults to (0.0f,0.5f) for left-aligned,vertically centered. + ImGuiDir WindowMenuButtonPosition; // Side of the collapsing/docking button in the title bar (None/Left/Right). Defaults to ImGuiDir_Left. + float ChildRounding; // Radius of child window corners rounding. Set to 0.0f to have rectangular windows. + float + ChildBorderSize; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + float PopupRounding; // Radius of popup window corners rounding. (Note that tooltip windows use WindowRounding) + float PopupBorderSize; // Thickness of border around popup/tooltip windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more + // CPU/GPU costly). + ImVec2 FramePadding; // Padding within a framed rectangle (used by most widgets). + float FrameRounding; // Radius of frame corners rounding. Set to 0.0f to have rectangular frame (used by most widgets). + float FrameBorderSize; // Thickness of border around frames. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + ImVec2 ItemSpacing; // Horizontal and vertical spacing between widgets/lines. + ImVec2 ItemInnerSpacing; // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label). + ImVec2 CellPadding; // Padding within a table cell. CellPadding.y may be altered between different rows. + ImVec2 TouchExtraPadding; // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort + // widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + float IndentSpacing; // Horizontal indentation when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + float ColumnsMinSpacing; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). + float ScrollbarSize; // Width of the vertical scrollbar, Height of the horizontal scrollbar. + float ScrollbarRounding; // Radius of grab corners for scrollbar. + float GrabMinSize; // Minimum width/height of a grab box for slider/scrollbar. + float GrabRounding; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + float LogSliderDeadzone; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. + 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 TabMinWidthForCloseButton; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, + // set to FLT_MAX to never show close button unless selected. + 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. + float SeparatorTextBorderSize; // Thickkness of border in SeparatorText() + ImVec2 SeparatorTextAlign; // Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). + ImVec2 SeparatorTextPadding; // Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended + // to be == FramePadding.y. + ImVec2 DisplayWindowPadding; // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to + // regular windows. + ImVec2 DisplaySafeAreaPadding; // If you cannot see the edges of your screen (e.g. on a TV) increase the safe area padding. Apply to popups/tooltips as well + // regular windows. NB: Prefer configuring your TV sets correctly! + float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. + bool AntiAliasedLines; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to + // ImDrawList). + bool AntiAliasedLinesUseTex; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT + // point/nearest filtering). Latched at the beginning of the frame (copied to ImDrawList). + 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. + 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. + float HoverDelayShort; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayShort). Usually used along with HoverStationaryDelay. + 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); +}; + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiIO +//----------------------------------------------------------------------------- +// Communicate most settings and inputs/outputs to Dear ImGui using this structure. +// Access via ImGui::GetIO(). Read 'Programmer guide' section in .cpp file for general usage. +//----------------------------------------------------------------------------- + +// [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 { + bool Down; // True for if key is down + float DownDuration; // Duration the key has been down (<0.0f: not pressed, 0.0f: just pressed, >0.0f: time held) + 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. Gamepad/keyboard 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. + 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. + + 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 // 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. + + // Miscellaneous options + 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. + bool ConfigMacOSXBehaviors; // = defined(__APPLE__) // OS X style: Text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead + // of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting + // whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl. + bool ConfigInputTrickleEventQueue; // = true // 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. + bool ConfigInputTextCursorBlink; // = true // Enable blinking cursor (optional as some users consider it to be distracting). + bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will keep item active and select contents (single-line only). + bool ConfigDragClickToInputText; // = false // [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without + // moving). Not desirable on devices without a keyboard. + bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires + // (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback. (This used to be a + // per-window ImGuiWindowFlags_ResizeFromAnySide flag) + bool ConfigWindowsMoveFromTitleBarOnly; // = false // Enable allowing to move windows only when clicking on their title bar. Does not apply to windows + // without a title bar. + 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. + float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click, in pixels. + 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 + //------------------------------------------------------------------ + + // 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. + // Consider using e.g. Win32's IsDebuggerPresent() as an additional filter (or see ImOsIsDebuggerPresent() in imgui_test_engine/imgui_te_utils.cpp for a + // Unix compatible version). + bool ConfigDebugIgnoreFocusLoss; // = false // Ignore io.AddFocusEvent(false), consequently not calling io.ClearInputKeys() in input processing. + + // Options to audit .ini data + bool ConfigDebugIniSettings; // = false // Save .ini data with extra comments (particularly helpful for Docking, but makes saving slower) + + //------------------------------------------------------------------ + // Platform Functions + // (the imgui_impl_xxxx backend files are setting those up for you) + //------------------------------------------------------------------ + + // 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 + const char *BackendRendererName; // = NULL + 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 + + // 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 *(*GetClipboardTextFn)(void *user_data); + void (*SetClipboardTextFn)(void *user_data, const char *text); + void *ClipboardUserData; + + // 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 (*SetPlatformImeDataFn)(ImGuiViewport *viewport, ImGuiPlatformImeData *data); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + void *ImeWindowHandle; // = NULL // [Obsolete] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor + // positioning. +#else + void *_UnusedPadding; // Unused field to keep data structure the same size. +#endif + + // Optional: Platform locale + ImWchar PlatformLocaleDecimalPoint; // '.' // [Experimental] Configure decimal point e.g. '.' or ',' useful for some languages (e.g. German), + // generally pulled from *localeconv()->decimal_point + + //------------------------------------------------------------------ + // 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. + IMGUI_API void AddMousePosEvent(float x, + float y); // Queue a mouse position update. Use -FLT_MAX,-FLT_MAX to signify no mouse (e.g. app not focused and not hovered) + IMGUI_API void AddMouseButtonEvent(int button, bool down); // Queue a mouse button change + IMGUI_API void AddMouseWheelEvent( + float wheel_x, + float wheel_y); // Queue a mouse wheel update. wheel_y<0: scroll down, wheel_y>0: scroll up, wheel_x<0: scroll right, wheel_x>0: scroll left. + IMGUI_API void AddMouseSourceEvent(ImGuiMouseSource source); // Queue a mouse source change (Mouse/TouchScreen/Pen) + IMGUI_API void AddFocusEvent(bool focused); // Queue a gain/loss of focus for the application (generally based on OS/platform focus of your window) + 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. + IMGUI_API void ClearInputKeys(); // Clear current keyboard/mouse/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + IMGUI_API void ClearInputCharacters(); // [Obsolete] 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). + bool WantSetMousePos; // MousePos has been altered, backend should reposition mouse on next frame. Rarely used! Set only when + // ImGuiConfigFlags_NavEnableSetMousePos flag is enabled. + bool WantSaveIniSettings; // When manual .ini load/save is active (io.IniFilename == NULL), this will be set to notify your application that you can call + // SaveIniSettingsToMemory() and save yourself. Important: clear io.WantSaveIniSettings yourself after saving! + bool NavActive; // Keyboard/Gamepad navigation is currently allowed (will handle ImGuiKey_NavXXX events) = a window is focused and it doesn't use the + // ImGuiWindowFlags_NoNavInputs flag. + bool NavVisible; // Keyboard/Gamepad navigation is visible and allowed (will handle ImGuiKey_NavXXX events). + float Framerate; // Estimate of application framerate (rolling average over 60 frames, based on io.DeltaTime), in frame per second. Solely for convenience. + // Slow applications may not want to use a moving average or may want to reset underlying buffers occasionally. + int MetricsRenderVertices; // Vertices output during last call to Render() + int MetricsRenderIndices; // Indices output during last call to Render() = number of triangles * 3 + int MetricsRenderWindows; // Number of visible windows + int MetricsActiveWindows; // Number of active windows + int MetricsActiveAllocations; // Number of active allocations, updated by MemAlloc/MemFree based on current context. May be off if you have multiple imgui + // contexts. + 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. + + // 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) +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + int KeyMap[ImGuiKey_COUNT]; // [LEGACY] Input: map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. The first + // 512 are now unused and should be kept zero. Legacy backend will write into KeyMap[] using ImGuiKey_ indices which are always + // >512. + 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. +#endif + + //------------------------------------------------------------------ + // [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) + ImVec2 MousePos; // Mouse position, in pixels. Set to ImVec2(-FLT_MAX, -FLT_MAX) if mouse is unavailable (on another screen, etc.) + bool MouseDown[5]; // Mouse buttons: 0=left, 1=right, 2=middle + extras (ImGuiMouseButton_COUNT == 5). Dear ImGui mostly uses left and right buttons. Other + // buttons allow us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API. + float MouseWheel; // Mouse wheel Vertical: 1 unit scrolls about 5 lines text. >0 scrolls Up, <0 scrolls Down. Hold SHIFT to turn vertical scroll into + // horizontal scroll. + 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). + bool KeyCtrl; // Keyboard modifier down: Control + bool KeyShift; // Keyboard modifier down: Shift + bool KeyAlt; // Keyboard modifier down: Alt + bool KeySuper; // Keyboard modifier down: Cmd/Super/Windows + + // 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. DOES NOT CONTAINS ImGuiMod_Shortcut which is pretranslated). Read-only, updated by NewFrame() + ImGuiKeyData KeysData[ImGuiKey_KeysData_SIZE]; // Key state for all known keys. 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 + double MouseClickedTime[5]; // Time of last click (used to figure out double-click) + bool MouseClicked[5]; // Mouse button went from !Down to Down (same as MouseClickedCount[x] != 0) + bool MouseDoubleClicked[5]; // Has mouse button been double-clicked? (same as MouseClickedCount[x] == 2) + ImU16 MouseClickedCount[5]; // == 0 (not clicked), == 1 (same as MouseClicked[]), == 2 (double-clicked), == 3 (triple-clicked) etc. when going from !Down to + // Down + ImU16 MouseClickedLastCount[5]; // Count successive number of clicks. Stays valid after mouse release. Reset after another click is done. + bool MouseReleased[5]; // Mouse button went from Down to !Down + bool MouseDownOwned[5]; // Track if button was clicked inside a dear imgui window or over void blocked by a popup. We don't request mouse capture from the + // application if click started outside ImGui bounds. + bool MouseDownOwnedUnlessPopupClose[5]; // Track if button was clicked inside a dear imgui window. + bool MouseWheelRequestAxisSwap; // On a non-Mac system, holding SHIFT requests WheelY to perform the equivalent of a WheelX event. On a Mac system this is + // already enforced by the system. + float MouseDownDuration[5]; // Duration the mouse button has been down (0.0f == just clicked) + float MouseDownDurationPrev[5]; // Previous time the mouse button has been down + float MouseDragMaxDistanceSqr[5]; // Squared maximum distance of how much mouse has traveled from the clicking point (used for moving thresholds) + float PenPressure; // Touch/Pen pressure (0.0f to 1.0f, should be >0.0f only when MouseDown[0] == true). Helper storage currently unused by Dear ImGui. + bool AppFocusLost; // Only modify via AddFocusEvent() + bool AppAcceptingEvents; // Only modify via SetAppAcceptingEvents() + ImS8 BackendUsingLegacyKeyArrays; // -1: unknown, 0: using AddKeyEvent(), 1: using legacy io.KeysDown[] + bool BackendUsingLegacyNavInputArray; // 0: using AddKeyAnalogEvent(), 1: writing to legacy io.NavInputs[] directly + ImWchar16 InputQueueSurrogate; // For AddInputCharacterUTF16() + ImVector InputQueueCharacters; // Queue of _characters_ input (obtained by platform backend). Fill using AddInputCharacter() helper. + + IMGUI_API ImGuiIO(); +}; + +//----------------------------------------------------------------------------- +// [SECTION] Misc data structures +//----------------------------------------------------------------------------- + +// 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) +// - ImGuiInputTextFlags_CallbackEdit: Callback on buffer edit (note that InputText() already returns true on edit, the callback is useful mainly to +// manipulate the underlying buffer while focus is active) +// - ImGuiInputTextFlags_CallbackAlways: Callback on each iteration +// - ImGuiInputTextFlags_CallbackCompletion: Callback on pressing TAB +// - ImGuiInputTextFlags_CallbackHistory: Callback on pressing Up/Down arrows +// - ImGuiInputTextFlags_CallbackCharFilter: Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in +// callback to discard. +// - ImGuiInputTextFlags_CallbackResize: Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. +struct ImGuiInputTextCallbackData { + ImGuiContext *Ctx; // Parent UI context + 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 + // - To modify the text buffer in a callback, prefer using the InsertChars() / DeleteChars() function. InsertChars() will take care of calling the resize + // callback if necessary. + // - If you know your edits are not going to resize the underlying buffer allocation, you may modify the contents of 'Buf[]' directly. You need to update + // 'BufTextLen' accordingly (0 <= BufTextLen < BufSize) and set 'BufDirty'' to true so InputText can update its internal state. + ImWchar EventChar; // Character input // Read-write // [CharFilter] Replace character with another one, or set to zero to drop. + // return 1 is equivalent to setting EventChar=0; + ImGuiKey EventKey; // Key pressed (Up/Down/TAB) // Read-only // [Completion,History] + char *Buf; // Text buffer // Read-write // [Resize] Can replace pointer / [Completion,History,Always] Only write to pointed data, + // don't replace the actual pointer! + int BufTextLen; // Text length (in bytes) // Read-write // [Resize,Completion,History,Always] Exclude zero-terminator storage. In C land: == + // strlen(some_text), in C++ land: string.length() + int BufSize; // Buffer size (in bytes) = capacity+1 // Read-only // [Resize,Completion,History,Always] Include zero-terminator storage. In C land == + // ARRAYSIZE(my_char_array), in C++ land: string.capacity()+1 + bool BufDirty; // Set if you modify Buf/BufTextLen! // Write // [Completion,History,Always] + 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(); + IMGUI_API void DeleteChars(int pos, int bytes_count); + IMGUI_API void InsertChars(int pos, const char *text, const char *text_end = NULL); + void SelectAll() + { + SelectionStart = 0; + SelectionEnd = BufTextLen; + } + 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 { + void *UserData; // Read-only. What user passed to SetNextWindowSizeConstraints(). Generally store an integer or float in here (need reinterpret_cast<>). + ImVec2 Pos; // Read-only. Window position, for reference. + 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. +}; + +// 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) + int DataFrameCount; // Data timestamp + 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; + } +}; + +// Sorting specification for one column of a table (sizeof == 12 bytes) +struct ImGuiTableColumnSortSpecs { + ImGuiID ColumnUserID; // User id of the column (if specified by a TableSetupColumn() call) + 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 : 8; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending (you can use this or SortSign, whichever is more + // convenient for your sort function) + + ImGuiTableColumnSortSpecs() + { + memset(this, 0, sizeof(*this)); + } +}; + +// 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. +// Make sure to set 'SpecsDirty = false' after sorting, else you may wastefully sort your data every frame! +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)); + } +}; + +//----------------------------------------------------------------------------- +// [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 +#define IM_UNICODE_CODEPOINT_MAX 0x10FFFF // Maximum Unicode code point supported by this build. +#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 { + ImGuiOnceUponAFrame() + { + RefFrame = -1; + } + 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 { + IMGUI_API ImGuiTextFilter(const char *default_filter = ""); + IMGUI_API bool Draw(const char *label = "Filter (inc,-exc)", float width = 0.0f); // Helper calling InputText+Build + IMGUI_API bool PassFilter(const char *text, const char *text_end = NULL) const; + 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; + } + IMGUI_API void split(char separator, ImVector *out) const; + }; + char InputBuf[256]; + ImVector Filters; + 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; + } + const char *end() const + { + return Buf.Data ? &Buf.back() : EmptyString; + } // Buf is zero-terminated, so end() will point on the zero-terminator + int size() const + { + return Buf.Size ? Buf.Size - 1 : 0; + } + bool empty() const + { + return Buf.Size <= 1; + } + void clear() + { + Buf.clear(); + } + void reserve(int capacity) + { + Buf.reserve(capacity); + } + const char *c_str() const + { + return Buf.Data ? Buf.Data : EmptyString; + } + IMGUI_API void append(const char *str, const char *str_end = NULL); + IMGUI_API void appendf(const char *fmt, ...) IM_FMTARGS(2); + IMGUI_API void appendfv(const char *fmt, va_list args) IM_FMTLIST(2); +}; + +// 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) +// This is optimized for efficient lookup (dichotomy into a contiguous buffer) and rare insertion (typically tied to user interactions aka max once a frame) +// You can use it as custom user storage for temporary values. Declare your own storage if, for example: +// - You want to manipulate the open/close state of a particular sub-tree in your interface (tree node uses Int 0/1 to store their state). +// - You want to store custom debug data easily without adding or editing structures in your code (probably not efficient, but convenient) +// Types are NOT stored, so it is up to you to make sure your Key don't collide with different types. +struct ImGuiStorage { + // [Internal] + struct ImGuiStoragePair { + ImGuiID key; + union { + int val_i; + float val_f; + void *val_p; + }; + ImGuiStoragePair(ImGuiID _key, int _val_i) + { + key = _key; + val_i = _val_i; + } + ImGuiStoragePair(ImGuiID _key, float _val_f) + { + key = _key; + val_f = _val_f; + } + ImGuiStoragePair(ImGuiID _key, void *_val_p) + { + key = _key; + val_p = _val_p; + } + }; + + 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. + void Clear() + { + Data.clear(); + } + IMGUI_API int GetInt(ImGuiID key, int default_val = 0) const; + IMGUI_API void SetInt(ImGuiID key, int val); + IMGUI_API bool GetBool(ImGuiID key, bool default_val = false) const; + IMGUI_API void SetBool(ImGuiID key, bool val); + IMGUI_API float GetFloat(ImGuiID key, float default_val = 0.0f) const; + 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) + // float* pvar = ImGui::GetFloatRef(key); ImGui::SliderFloat("var", pvar, 0, 100.0f); some_var += *pvar; + IMGUI_API int *GetIntRef(ImGuiID key, int default_val = 0); + 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); + + // Use on your own storage if you know only integer are being stored (open/close all tree nodes) + IMGUI_API void SetAllInt(int val); + + // 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(); +}; + +// 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. +// The clipper calculates the range of visible items and advance the cursor to compensate for the non-visible items we have skipped. +// (Dear ImGui already clip items based on their bounds but: it needs to first layout the item to do so, and generally +// fetching/submitting your own data incurs additional cost. Coarse clipping using ImGuiListClipper allows you to easily +// scale using lists with tens of thousands of items without a problem) +// Usage: +// ImGuiListClipper clipper; +// clipper.Begin(1000); // We have 1000 elements, evenly spaced. +// while (clipper.Step()) +// for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) +// ImGui::Text("line number %d", i); +// Generally what happens is: +// - Clipper lets you process the first element (DisplayStart = 0, DisplayEnd = 1) regardless of it being visible or not. +// - User code submit that one element. +// - Clipper can measure the height of the first element +// - Clipper calculate the actual range of elements to display based on the current clipping rectangle, position the cursor before the first visible element. +// - User code submit visible elements. +// - The clipper also handles various subtleties related to keyboard/gamepad navigation, wrapping etc. +struct ImGuiListClipper { + ImGuiContext *Ctx; // Parent UI context + int DisplayStart; // First item to display, updated by each call to Step() + 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 + 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) + // 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(); + IMGUI_API ~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. + +#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 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) +// - You can use '#define IMGUI_DEFINE_MATH_OPERATORS' to import our operators, provided as a courtesy. +#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 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); +} +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. +#ifndef IM_COL32_R_SHIFT +#ifdef IMGUI_USE_BGRA_PACKED_COLOR +#define IM_COL32_R_SHIFT 16 +#define IM_COL32_G_SHIFT 8 +#define IM_COL32_B_SHIFT 0 +#define IM_COL32_A_SHIFT 24 +#define IM_COL32_A_MASK 0xFF000000 +#else +#define IM_COL32_R_SHIFT 0 +#define IM_COL32_G_SHIFT 8 +#define IM_COL32_B_SHIFT 16 +#define IM_COL32_A_SHIFT 24 +#define IM_COL32_A_MASK 0xFF000000 +#endif +#endif +#define IM_COL32(R, G, B, A) \ + (((ImU32)(A) << IM_COL32_A_SHIFT) | ((ImU32)(B) << IM_COL32_B_SHIFT) | ((ImU32)(G) << IM_COL32_G_SHIFT) | ((ImU32)(R) << IM_COL32_R_SHIFT)) +#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. +// **None of the ImGui API are using ImColor directly but you can use it as a convenience to pass colors in either ImU32 or ImVec4 formats. Explicitly cast to +// ImU32 or ImVec4 if needed. +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) + { + } + constexpr ImColor(int r, int g, int b, int a = 255) + : Value((float)r * (1.0f / 255.0f), (float)g * (1.0f / 255.0f), (float)b * (1.0f / 255.0f), (float)a * (1.0f / 255.0f)) + { + } + 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] 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 (63) +#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: +// A) Change your GPU render state, +// B) render a complex 3D scene inside a UI element without an intermediate texture/render target, etc. +// The expected behavior from your rendering function is 'if (cmd.UserCallback != NULL) { cmd.UserCallback(parent_list, cmd); } else { RenderTriangles() }' +// If you want to override the signature of ImDrawCallback, you can simply use e.g. '#define ImDrawCallback MyDrawCallback' (in imconfig.h) + update rendering +// backend accordingly. +#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. +// It is not done by default because they are many perfectly useful way of altering render state for imgui contents (e.g. changing shader/blending settings +// before an Image call). +#define ImDrawCallback_ResetRenderState (ImDrawCallback)(-1) + +// 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). +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. + 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[]. + ImDrawCallback UserCallback; // 4-8 // If != NULL, call the function instead of rendering the vertices. clip_rect and texture_id will be set normally. + void *UserCallbackData; // 4-8 // The draw callback code can access this. + + 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; + } +}; + +// Vertex layout +#ifndef IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT +struct ImDrawVert { + ImVec2 pos; + ImVec2 uv; + ImU32 col; +}; +#else +// You can override the vertex format layout by defining IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT in imconfig.h +// The code expect ImVec2 pos (8 bytes), ImVec2 uv (8 bytes), ImU32 col (4 bytes), but you can re-order them or add other fields as needed to simplify +// integration in your engine. The type has to be described within the macro (you can either declare the struct or use a typedef). This is because ImVec2/ImU32 +// are likely not declared at the time you'd want to set your type up. 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; + 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 { + 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 + IMGUI_API void ClearFreeMemory(); + IMGUI_API void Split(ImDrawList *draw_list, int count); + 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_ { + ImDrawFlags_None = 0, + ImDrawFlags_Closed = 1 << 0, // PathStroke(), AddPolyline(): specify that shape should be closed (Important: this is always == 1 for legacy reason) + ImDrawFlags_RoundCornersTopLeft = + 1 << 4, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-left corner only (when rounding > 0.0f, we default to all corners). Was 0x01. + ImDrawFlags_RoundCornersTopRight = + 1 << 5, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-right corner only (when rounding > 0.0f, we default to all corners). Was 0x02. + ImDrawFlags_RoundCornersBottomLeft = + 1 << 6, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-left corner only (when rounding > 0.0f, we default to all corners). Was 0x04. + ImDrawFlags_RoundCornersBottomRight = + 1 << 7, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-right corner only (when rounding > 0.0f, we default to all corners). Wax 0x08. + ImDrawFlags_RoundCornersNone = + 1 << 8, // AddRect(), AddRectFilled(), PathRect(): disable rounding on all corners (when rounding > 0.0f). This is NOT zero, NOT an implicit flag! + ImDrawFlags_RoundCornersTop = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight, + ImDrawFlags_RoundCornersBottom = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, + ImDrawFlags_RoundCornersLeft = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersTopLeft, + ImDrawFlags_RoundCornersRight = ImDrawFlags_RoundCornersBottomRight | ImDrawFlags_RoundCornersTopRight, + ImDrawFlags_RoundCornersAll = + ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, + 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_ { + ImDrawListFlags_None = 0, + ImDrawListFlags_AntiAliasedLines = 1 << 0, // Enable anti-aliased lines/borders (*2 the number of triangles for 1.0f wide line or lines thin enough to be + // drawn using textures, otherwise *3 the number of triangles) + ImDrawListFlags_AntiAliasedLinesUseTex = 1 << 1, // Enable anti-aliased lines/borders using textures when possible. Require backend to render with bilinear + // filtering (NOT point/nearest filtering). + 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. +// Each dear imgui window contains its own ImDrawList. You can use ImGui::GetWindowDrawList() to +// access the current window draw list and draw custom primitives. +// You can interleave normal ImGui:: calls and adding primitives to the current draw list. +// In single viewport mode, top-left is == GetMainViewport()->Pos (generally 0,0), bottom-right is == GetMainViewport()->Pos+Size (generally io.DisplaySize). +// You are totally free to apply whatever transformation matrix to want to the data (depending on the use of the transformation you may want to apply it to +// ClipRect as well!) Important: Primitives are always added to the list and not culled (culling is done at higher-level by ImGui:: functions), if you use this +// API a lot consider coarse culling your drawn objects. +struct ImDrawList { + // This is what you have to render + ImVector CmdBuffer; // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback. + 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) + const char *_OwnerName; // Pointer to owner window's name for debugging + ImDrawVert *_VtxWritePtr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImDrawIdx *_IdxWritePtr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImVector _ClipRectStack; // [Internal] + ImVector _TextureIdStack; // [Internal] + ImVector _Path; // [Internal] current path building + 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!) + float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content + + // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData() or create and use your own ImDrawListSharedData (so you can use + // ImDrawList without ImGui) + ImDrawList(ImDrawListSharedData *shared_data) + { + memset(this, 0, sizeof(*this)); + _Data = shared_data; + } + + ~ImDrawList() + { + _ClearFreeMemory(); + } + 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(); + 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. + // - For circle primitives, use "num_segments == 0" to automatically calculate tessellation (preferred). + // In older versions (until Dear ImGui 1.77) the AddCircle functions defaulted to num_segments == 12. + // In future versions we will use textures to provide cheaper and higher-quality circles. + // Use AddNgon() and AddNgonFilled() functions if you need to guarantee a specific number of sides. + IMGUI_API void AddLine(const ImVec2 &p1, const ImVec2 &p2, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddRect(const ImVec2 &p_min, + const ImVec2 &p_max, + ImU32 col, + float rounding = 0.0f, + ImDrawFlags flags = 0, + float thickness = 1.0f); // a: upper-left, b: lower-right (== upper-left + size) + IMGUI_API void AddRectFilled(const ImVec2 &p_min, + const ImVec2 &p_max, + ImU32 col, + float rounding = 0.0f, + ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size) + IMGUI_API void + 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); + IMGUI_API void AddQuad(const ImVec2 &p1, const ImVec2 &p2, const ImVec2 &p3, const ImVec2 &p4, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddQuadFilled(const ImVec2 &p1, const ImVec2 &p2, const ImVec2 &p3, const ImVec2 &p4, ImU32 col); + IMGUI_API void AddTriangle(const ImVec2 &p1, const ImVec2 &p2, const ImVec2 &p3, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddTriangleFilled(const ImVec2 &p1, const ImVec2 &p2, const ImVec2 &p3, ImU32 col); + IMGUI_API void AddCircle(const ImVec2 ¢er, float radius, ImU32 col, int num_segments = 0, float thickness = 1.0f); + IMGUI_API void AddCircleFilled(const ImVec2 ¢er, float radius, ImU32 col, int num_segments = 0); + IMGUI_API void AddNgon(const ImVec2 ¢er, float radius, ImU32 col, int num_segments, float thickness = 1.0f); + IMGUI_API void AddNgonFilled(const ImVec2 ¢er, float radius, ImU32 col, int num_segments); + IMGUI_API void AddText(const ImVec2 &pos, ImU32 col, const char *text_begin, const char *text_end = NULL); + IMGUI_API void AddText(const 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 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 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) + + // Image primitives + // - Read FAQ to understand what ImTextureID is. + // - "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); + + // Stateful path API, add points then finish with PathFillConvex() or PathStroke() + // - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" + // anti-aliasing. + inline void PathClear() + { + _Path.Size = 0; + } + inline void PathLineTo(const ImVec2 &pos) + { + _Path.push_back(pos); + } + inline void PathLineToMergeDuplicate(const ImVec2 &pos) + { + if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) + _Path.push_back(pos); + } + inline void PathFillConvex(ImU32 col) + { + AddConvexPolyFilled(_Path.Data, _Path.Size, col); + _Path.Size = 0; + } + inline void PathStroke(ImU32 col, ImDrawFlags flags = 0, float thickness = 1.0f) + { + AddPolyline(_Path.Data, _Path.Size, col, flags, thickness); + _Path.Size = 0; + } + IMGUI_API void PathArcTo(const ImVec2 ¢er, float radius, float a_min, float a_max, int num_segments = 0); + IMGUI_API void PathArcToFast(const ImVec2 ¢er, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle + 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 + IMGUI_API void AddCallback( + ImDrawCallback callback, + void *callback_data); // Your rendering function must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. + 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) + // - This API shouldn't have been in ImDrawList in the first place! + // Prefer using your own persistent instance of ImDrawListSplitter as you can stack them. + // Using the ImDrawList::ChannelsXXXX you cannot stack a split over another. + 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. + IMGUI_API void PrimReserve(int idx_count, int vtx_count); + IMGUI_API void PrimUnreserve(int idx_count, int vtx_count); + IMGUI_API void PrimRect(const ImVec2 &a, const ImVec2 &b, ImU32 col); // Axis aligned rectangle (composed of two triangles) + IMGUI_API void PrimRectUV(const ImVec2 &a, const ImVec2 &b, const ImVec2 &uv_a, const ImVec2 &uv_b, ImU32 col); + IMGUI_API void 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); + 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 + // 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 _ResetForNewFrame(); + IMGUI_API void _ClearFreeMemory(); + IMGUI_API void _PopUnusedDrawCmd(); + IMGUI_API void _TryMergeDrawCmds(); + IMGUI_API void _OnChangedClipRect(); + IMGUI_API void _OnChangedTextureID(); + IMGUI_API void _OnChangedVtxOffset(); + IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const; + IMGUI_API void _PathArcToFastEx(const ImVec2 ¢er, float radius, int a_min_sample, int a_max_sample, int a_step); + IMGUI_API void _PathArcToN(const ImVec2 ¢er, 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 (should always be == CmdLists.size) + 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. + ImGuiViewport *OwnerViewport; // Viewport carrying the ImDrawData instance, might be of use to the renderer (generally not). + + // Functions + ImDrawData() + { + Clear(); + } + IMGUI_API void Clear(); + IMGUI_API void AddDrawList(ImDrawList *draw_list); // Helper to add an external draw list into an existing 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] Font API (ImFontConfig, ImFontGlyph, ImFontAtlasFlags, ImFontAtlas, ImFontGlyphRangesBuilder, ImFont) +//----------------------------------------------------------------------------- + +struct ImFontConfig { + 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). + int FontNo; // 0 // Index of font within TTF/OTF file + float SizePixels; // // Size in pixels for rasterizer (more or less maps to the resulting font height). + int OversampleH; // 2 // Rasterize at higher quality for sub-pixel positioning. 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; // 1 // Rasterize at higher quality for sub-pixel positioning. This is not really useful as we don't use sub-pixel positions on the + // Y axis. + bool PixelSnapH; // false // Align every glyph to pixel boundary. 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. + ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs. Only X axis is supported for now. + 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 + 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. + unsigned int FontBuilderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. + float RasterizerMultiply; // 1.0f // Brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more + // readable. + ImWchar EllipsisChar; // -1 // Explicitly specify unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will + // be used. + + // [Internal] + char Name[40]; // Name (strictly to ease debugging) + ImFont *DstFont; + + 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) +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; // Distance to next character (= data from font + ImFontConfig::GlyphExtraSpacing.x baked in) + float X0, Y0, X1, Y1; // Glyph corners + float U0, V0, U1, V1; // Texture coordinates +}; + +// 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 + inline void SetBit(size_t n) + { + int off = (int)(n >> 5); + ImU32 mask = 1u << (n & 31); + UsedChars[off] |= mask; + } // Set bit n in the array + inline void AddChar(ImWchar c) + { + SetBit(c); + } // Add character + IMGUI_API void AddText(const char *text, const char *text_end = NULL); // Add string (each character of the UTF-8 string are added) + 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 { + unsigned short Width, Height; // Input // Desired rectangle dimension + unsigned short X, Y; // Output // Packed position in Atlas + unsigned int GlyphID; // Input // For custom font glyphs only (ID < 0x110000) + 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() + { + Width = Height = 0; + X = Y = 0xFFFF; + GlyphID = 0; + GlyphAdvanceX = 0.0f; + GlyphOffset = ImVec2(0, 0); + Font = NULL; + } + bool IsPacked() const + { + return X != 0xFFFF; + } +}; + +// Flags for ImFontAtlas build +enum ImFontAtlasFlags_ { + ImFontAtlasFlags_None = 0, + ImFontAtlasFlags_NoPowerOfTwoHeight = 1 << 0, // Don't round the height to next power of two + 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) +// - 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. +// - Important: By default, AddFontFromMemoryTTF() takes ownership of the data. Even though we are not writing to it, we will free the pointer on destruction. +// You can set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed, +// - Even though many functions are suffixed with "TTF", OTF data is supported just as well. +// - This is an old API and it is currently awkward for those and various other reasons! We will address them in the future! +struct ImFontAtlas { + IMGUI_API 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_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_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 ClearTexData(); // Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. + IMGUI_API void ClearFonts(); // Clear output font data (glyphs storage, UV coordinates). + 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; + } + + //------------------------------------------- + // Glyph Ranges + //------------------------------------------- + + // 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 + IMGUI_API const ImWchar *GetGlyphRangesChineseFull(); // Default + Half-Width + Japanese Hiragana/Katakana + full set of about 21000 CJK Unified Ideographs + IMGUI_API const ImWchar *GetGlyphRangesChineseSimplifiedCommon(); // Default + Half-Width + Japanese Hiragana/Katakana + set of 2500 CJK Unified Ideographs + // for common simplified Chinese + 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 + + //------------------------------------------- + // [BETA] 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 prefered 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. + // - 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; + IMGUI_API bool GetMouseCursorTexData(ImGuiMouseCursor cursor, ImVec2 *out_offset, ImVec2 *out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]); + + //------------------------------------------- + // Members + //------------------------------------------- + + 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. + int TexGlyphPadding; // 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). + bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. + void *UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). + + // [Internal] + // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. + 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 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 ConfigData; // 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 + + // [Obsolete] + // 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 { + // 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 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) + + // Members: Hot ~28/40 bytes (for CalcTextSize + render loop) + ImVector IndexLookup; // 12-16 // out // // Sparse. Index glyphs by Unicode code-point. + ImVector Glyphs; // 12-16 // out // // All glyphs. + const ImFontGlyph *FallbackGlyph; // 4-8 // out // = FindGlyph(FontFallbackChar) + + // Members: Cold ~32/40 bytes + ImFontAtlas *ContainerAtlas; // 4-8 // out // // What we has been loaded into + const ImFontConfig *ConfigData; // 4-8 // in // // Pointer within ContainerAtlas->ConfigData + short ConfigDataCount; // 2 // in // ~ 1 // Number of ImFontConfig involved in creating this font. Bigger than 1 when merging multiple font + // sources into one ImFont. + ImWchar FallbackChar; // 2 // out // = FFFD/'?' // Character used if a glyph isn't found. + ImWchar EllipsisChar; // 2 // out // = '...'/'.'// Character used for ellipsis rendering. + short EllipsisCharCount; // 1 // out // 1 or 3 + float EllipsisWidth; // 4 // out // Width + float EllipsisCharStep; // 4 // out // Step between characters when EllipsisCount > 0 + bool DirtyLookupTables; // 1 // out // + float Scale; // 4 // in // = 1.f // Base font scale, 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] + 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) + ImU8 Used4kPagesMap[(IM_UNICODE_CODEPOINT_MAX + 1) / 4096 + / 8]; // 2 bytes if ImWchar=ImWchar16, 34 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. + + // Methods + IMGUI_API ImFont(); + IMGUI_API ~ImFont(); + IMGUI_API const ImFontGlyph *FindGlyph(ImWchar c) const; + IMGUI_API const ImFontGlyph *FindGlyphNoFallback(ImWchar c) const; + float GetCharAdvance(ImWchar c) const + { + return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; + } + bool IsLoaded() const + { + return ContainerAtlas != NULL; + } + const char *GetDebugName() const + { + return ConfigData ? ConfigData->Name : ""; + } + + // '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 ImVec2 CalcTextSizeA(float size, + float max_width, + float wrap_width, + const char *text_begin, + const char *text_end = NULL, + const char **remaining = NULL) const; // utf8 + IMGUI_API const char *CalcWordWrapPositionA(float scale, const char *text, const char *text_end, float wrap_width) const; + IMGUI_API void RenderChar(ImDrawList *draw_list, float size, const ImVec2 &pos, ImU32 col, ImWchar c) const; + 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) const; + + // [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 SetGlyphVisible(ImWchar c, bool visible); + IMGUI_API bool IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last); +}; + +//----------------------------------------------------------------------------- +// [SECTION] Viewports +//----------------------------------------------------------------------------- + +// Flags stored in ImGuiViewport::Flags, giving indications to the platform backends. +enum ImGuiViewportFlags_ { + ImGuiViewportFlags_None = 0, + ImGuiViewportFlags_IsPlatformWindow = 1 << 0, // Represent a Platform Window + ImGuiViewportFlags_IsPlatformMonitor = 1 << 1, // Represent a Platform Monitor (unused yet) + ImGuiViewportFlags_OwnedByApp = 1 << 2, // Platform Window: is created/managed by the application (rather than a dear imgui backend) +}; + +// - 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. +// - About Main Area vs Work Area: +// - Main Area = entire viewport. +// - Work Area = entire viewport minus sections used by main menu bars (for platform windows), or by task bar (for platform monitor). +// - Windows are generally trying to stay within the Work Area of their host viewport. +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 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) + + // Platform/Backend Dependent Data + void *PlatformHandleRaw; // void* to hold lower-level, platform-native window handle (under Win32 this is expected to be a HWND, unused for other platforms) + + ImGuiViewport() + { + memset(this, 0, sizeof(*this)); + } + + // 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] Platform Dependent Interfaces +//----------------------------------------------------------------------------- + +// (Optional) Support for IME (Input Method Editor) via the io.SetPlatformImeDataFn() function. +struct ImGuiPlatformImeData { + bool WantVisible; // A widget wants the IME to be visible + ImVec2 InputPos; // Position of the input cursor + float InputLineHeight; // Line height + + 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. +//----------------------------------------------------------------------------- + +namespace ImGui +{ +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO +IMGUI_API ImGuiKey GetKeyIndex(ImGuiKey key); // map ImGuiKey_* values into legacy native key index. == io.KeyMap[key] +#else +static inline ImGuiKey GetKeyIndex(ImGuiKey key) +{ + IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END + && "ImGuiKey and native_index was merged together and native_index is disabled by IMGUI_DISABLE_OBSOLETE_KEYIO. Please switch to ImGuiKey."); + return key; +} +#endif +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +namespace ImGui +{ +// 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) +{ + PushTabStop(tab_stop); +} +static inline void PopAllowKeyboardFocus() +{ + PopTabStop(); +} +// 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) +// OBSOLETED in 1.88 (from May 2022) +static inline void CaptureKeyboardFromApp(bool want_capture_keyboard = true) +{ + SetNextFrameWantCaptureKeyboard(want_capture_keyboard); +} // Renamed as name was misleading + removed default value. +static inline void CaptureMouseFromApp(bool want_capture_mouse = true) +{ + SetNextFrameWantCaptureMouse(want_capture_mouse); +} // Renamed as name was misleading + removed default value. +// OBSOLETED in 1.86 (from November 2021) +IMGUI_API void CalcListClipping(int items_count, + float items_height, + int *out_items_display_start, + int *out_items_display_end); // Calculate coarse clipping for large list of evenly sized items. Prefer using ImGuiListClipper. +// OBSOLETED in 1.85 (from August 2021) +static inline float GetWindowContentRegionWidth() +{ + return GetWindowContentRegionMax().x - GetWindowContentRegionMin().x; +} + +// Some of the older obsolete names along with their replacement (commented out so they are not reported in IDE) +//-- OBSOLETED in 1.81 (from February 2021) +// static inline bool ListBoxHeader(const char* label, const ImVec2& size = ImVec2(0, 0)) { return BeginListBox(label, size); } +// static inline bool ListBoxHeader(const char* label, int items_count, int height_in_items = -1) { float height = GetTextLineHeightWithSpacing() * +// ((height_in_items < 0 ? ImMin(items_count, 7) : height_in_items) + 0.25f) + GetStyle().FramePadding.y * 2.0f; return BeginListBox(label, ImVec2(0.0f, +// height)); } // Helper to calculate size from items_count and height_in_items static inline void ListBoxFooter() { EndListBox(); } +//-- OBSOLETED in 1.79 (from August 2020) +// static inline void OpenPopupContextItem(const char* str_id = NULL, ImGuiMouseButton mb = 1) { OpenPopupOnItemClick(str_id, mb); } // Bool return value +// removed. Use IsWindowAppearing() in BeginPopup() instead. Renamed in 1.77, renamed back in 1.79. Sorry! +//-- OBSOLETED in 1.78 (from June 2020): Old drag/sliders functions that took a 'float power > 1.0f' argument instead of ImGuiSliderFlags_Logarithmic. See +// github.com/ocornut/imgui/issues/3361 for details. IMGUI_API bool DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, +// const void* p_min, const void* p_max, const char* format, float power = 1.0f) // OBSOLETED in 1.78 +// (from June 2020) IMGUI_API bool 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, float power = 1.0f); // OBSOLETED in 1.78 (from June 2020) IMGUI_API bool +// SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power = 1.0f); // +// OBSOLETED in 1.78 (from June 2020) IMGUI_API bool SliderScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* +// p_min, const void* p_max, const char* format, float power = 1.0f); // OBSOLETED in 1.78 (from June +// 2020) static inline bool DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power = 1.0f) { return +// DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020) static inline bool +// DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power = 1.0f) { return DragScalarN(label, +// ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020) static inline bool DragFloat3(const char* label, +// float v[3], float v_speed, float v_min, float v_max, const char* format, float power = 1.0f) { return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, +// &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020) static inline bool DragFloat4(const char* label, float v[4], float v_speed, float +// v_min, float v_max, const char* format, float power = 1.0f) { return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power); } +// // OBSOLETED in 1.78 (from June 2020) static inline bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power +// = 1.0f) { return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June +// 2020) static inline bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power = 1.0f) { return +// SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020) static inline bool +// SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power = 1.0f) { return SliderScalarN(label, +// ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020) static inline bool SliderFloat4(const char* label, +// float v[4], float v_min, float v_max, const char* format, float power = 1.0f) { return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, +// &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020) +//-- OBSOLETED in 1.77 and before +// static inline bool BeginPopupContextWindow(const char* str_id, ImGuiMouseButton mb, bool over_items) { return BeginPopupContextWindow(str_id, mb | +// (over_items ? 0 : ImGuiPopupFlags_NoOpenOverItems)); } // OBSOLETED in 1.77 (from June 2020) static inline void TreeAdvanceToLabelPos() { +// SetCursorPosX(GetCursorPosX() + GetTreeNodeToLabelSpacing()); } // OBSOLETED in 1.72 (from July 2019) static inline void SetNextTreeNodeOpen(bool open, +// ImGuiCond cond = 0) { SetNextItemOpen(open, cond); } // OBSOLETED in 1.71 (from June 2019) static inline float +// GetContentRegionAvailWidth() { return GetContentRegionAvail().x; } // OBSOLETED in 1.70 (from May 2019) static inline +// ImDrawList* GetOverlayDrawList() { return GetForegroundDrawList(); } // OBSOLETED in 1.69 (from Mar 2019) static +// inline void SetScrollHere(float ratio = 0.5f) { SetScrollHereY(ratio); } // OBSOLETED in 1.66 (from Nov 2018) +// static inline bool IsItemDeactivatedAfterChange() { return IsItemDeactivatedAfterEdit(); } // OBSOLETED in 1.63 (from Aug +// 2018) +//-- OBSOLETED in 1.60 and before +// static inline bool IsAnyWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_AnyWindow); } // OBSOLETED in 1.60 (from Apr +// 2018) static inline bool IsAnyWindowHovered() { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); } // OBSOLETED in 1.60 +// (between Dec 2017 and Apr 2018) static inline void ShowTestWindow() { return ShowDemoWindow(); } // OBSOLETED in 1.53 (between Oct 2017 +// and Dec 2017) static inline bool IsRootWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootWindow); } // OBSOLETED +// in 1.53 (between Oct 2017 and Dec 2017) static inline bool IsRootWindowOrAnyChildFocused() { return +// IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); } // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017) static inline void +// SetNextWindowContentWidth(float w) { SetNextWindowContentSize(ImVec2(w, 0.0f)); } // OBSOLETED in 1.53 (between Oct 2017 and Dec +// 2017) static inline float GetItemsLineHeightWithSpacing() { return GetFrameHeightWithSpacing(); } // OBSOLETED in 1.53 +// (between Oct 2017 and Dec 2017) IMGUI_API bool Begin(char* name, bool* p_open, ImVec2 size_first_use, float bg_alpha = -1.0f, ImGuiWindowFlags flags=0); +// // OBSOLETED in 1.52 (between Aug 2017 and Oct 2017): Equivalent of using SetNextWindowSize(size, ImGuiCond_FirstUseEver) and SetNextWindowBgAlpha(). static +// inline bool IsRootWindowOrAnyChildHovered() { return IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); } // OBSOLETED in 1.52 (between Aug 2017 +// and Oct 2017) static inline void AlignFirstTextHeightToWidgets() { AlignTextToFramePadding(); } // OBSOLETED +// in 1.52 (between Aug 2017 and Oct 2017) static inline void SetNextWindowPosCenter(ImGuiCond c=0) { SetNextWindowPos(GetMainViewport()->GetCenter(), c, +// ImVec2(0.5f,0.5f)); } // OBSOLETED in 1.52 (between Aug 2017 and Oct 2017) static inline bool IsItemHoveredRect() { return +// IsItemHovered(ImGuiHoveredFlags_RectOnly); } // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017) static inline bool +// IsPosHoveringAnyWindow(const ImVec2&) { IM_ASSERT(0); return false; } // OBSOLETED in 1.51 (between Jun 2017 and Aug +// 2017): This was misleading and partly broken. You probably want to use the io.WantCaptureMouse flag instead. static inline bool IsMouseHoveringAnyWindow() +// { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); } // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017) static inline bool +// IsMouseHoveringWindow() { return IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); } +// // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017) +//-- OBSOLETED in 1.50 and before +// static inline bool CollapsingHeader(char* label, const char* str_id, bool framed = true, bool default_open = false) { return CollapsingHeader(label, +// (default_open ? (1 << 5) : 0)); } // OBSOLETED in 1.49 static inline ImFont*GetWindowFont() { return GetFont(); } // OBSOLETED in 1.48 +// static inline float GetWindowFontSize() { return GetFontSize(); } // OBSOLETED in 1.48 +// static inline void SetScrollPosHere() { SetScrollHere(); } // OBSOLETED in 1.42 +} + +// OBSOLETED in 1.82 (from Mars 2021): flags for AddRect(), AddRectFilled(), AddImageRounded(), PathRect() +typedef ImDrawFlags ImDrawCornerFlags; +enum ImDrawCornerFlags_ { + ImDrawCornerFlags_None = ImDrawFlags_RoundCornersNone, // Was == 0 prior to 1.82, this is now == ImDrawFlags_RoundCornersNone which is != 0 and not implicit + ImDrawCornerFlags_TopLeft = + ImDrawFlags_RoundCornersTopLeft, // Was == 0x01 (1 << 0) prior to 1.82. Order matches ImDrawFlags_NoRoundCorner* flag (we exploit this internally). + ImDrawCornerFlags_TopRight = ImDrawFlags_RoundCornersTopRight, // Was == 0x02 (1 << 1) prior to 1.82. + ImDrawCornerFlags_BotLeft = ImDrawFlags_RoundCornersBottomLeft, // Was == 0x04 (1 << 2) prior to 1.82. + ImDrawCornerFlags_BotRight = ImDrawFlags_RoundCornersBottomRight, // Was == 0x08 (1 << 3) prior to 1.82. + ImDrawCornerFlags_All = ImDrawFlags_RoundCornersAll, // Was == 0x0F prior to 1.82 + ImDrawCornerFlags_Top = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_TopRight, + ImDrawCornerFlags_Bot = ImDrawCornerFlags_BotLeft | ImDrawCornerFlags_BotRight, + 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", but you may + // store only 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 }; + +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +// RENAMED IMGUI_DISABLE_METRICS_WINDOW > IMGUI_DISABLE_DEBUG_TOOLS in 1.88 (from June 2022) +#if defined(IMGUI_DISABLE_METRICS_WINDOW) && !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && !defined(IMGUI_DISABLE_DEBUG_TOOLS) +#define IMGUI_DISABLE_DEBUG_TOOLS +#endif +#if defined(IMGUI_DISABLE_METRICS_WINDOW) && defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) +#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 (convenient for user to only explicitly include vanilla imgui.h) +#ifdef IMGUI_INCLUDE_IMGUI_USER_H +#include "imgui_user.h" +#endif + +#endif // #ifndef IMGUI_DISABLE diff --git a/extern/imgui/src/imgui.cpp b/extern/imgui/src/imgui.cpp new file mode 100644 index 0000000..f1d3233 --- /dev/null +++ b/extern/imgui/src/imgui.cpp @@ -0,0 +1,15692 @@ +// dear imgui, v1.89.9 +// (main code and documentation) + +// Help: +// - Read FAQ at http://dearimgui.com/faq +// - Newcomers, read 'Programmer guide' below for notes on how to setup Dear ImGui in your codebase. +// - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that. +// Read imgui.cpp for details, links and comments. + +// Resources: +// - FAQ http://dearimgui.com/faq +// - Homepage https://github.com/ocornut/imgui +// - Releases & changelog https://github.com/ocornut/imgui/releases +// - Gallery https://github.com/ocornut/imgui/issues/6478 (please post your screenshots/video there!) +// - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there) +// - Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started +// - Glossary https://github.com/ocornut/imgui/wiki/Glossary +// - Issues & support https://github.com/ocornut/imgui/issues +// - Tests & Automation https://github.com/ocornut/imgui_test_engine + +// Getting Started? +// - Read https://github.com/ocornut/imgui/wiki/Getting-Started +// - 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. + +// Developed by Omar Cornut and every direct or indirect contributors to the GitHub. +// See LICENSE.txt for copyright and licensing details (standard MIT License). +// This library is free but needs your support to sustain development and maintenance. +// Businesses: you can support continued development via B2B invoiced technical support, maintenance and sponsoring contracts. +// PLEASE reach out at contact AT dearimgui DOT com. See https://github.com/ocornut/imgui/wiki/Sponsors +// 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 + - READ FIRST + - 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 +- 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 +// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO) +// [SECTION] MISC HELPERS/UTILITIES (Geometry functions) +// [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions) +// [SECTION] MISC HELPERS/UTILITIES (File functions) +// [SECTION] MISC HELPERS/UTILITIES (ImText* functions) +// [SECTION] MISC HELPERS/UTILITIES (Color functions) +// [SECTION] ImGuiStorage +// [SECTION] ImGuiTextFilter +// [SECTION] ImGuiTextBuffer, ImGuiTextIndex +// [SECTION] ImGuiListClipper +// [SECTION] STYLING +// [SECTION] RENDER HELPERS +// [SECTION] INITIALIZATION, SHUTDOWN +// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) +// [SECTION] INPUTS +// [SECTION] ERROR CHECKING +// [SECTION] LAYOUT +// [SECTION] SCROLLING +// [SECTION] TOOLTIPS +// [SECTION] POPUPS +// [SECTION] KEYBOARD/GAMEPAD NAVIGATION +// [SECTION] DRAG AND DROP +// [SECTION] LOGGING/CAPTURING +// [SECTION] SETTINGS +// [SECTION] LOCALIZATION +// [SECTION] VIEWPORTS, PLATFORM WINDOWS +// [SECTION] PLATFORM DEPENDENT HELPERS +// [SECTION] METRICS/DEBUGGER WINDOW +// [SECTION] DEBUG LOG WINDOW +// [SECTION] OTHER DEBUG TOOLS (ITEM PICKER, 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. + - Minimize setup and maintenance. + - Minimize state storage on user side. + - 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. + - Click [X]: Close a window, available when 'bool* p_open' is passed to ImGui::Begin(). + - Click ^, Double-Click title: Collapse window. + - 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. + - CTRL+Shift+Left/Right: Select words. + - CTRL+A or Double-Click: Select All. + - CTRL+X, CTRL+C, CTRL+V: Use OS clipboard. + - CTRL+Z, CTRL+Y: Undo, Redo. + - ESCAPE: Revert text to its original value. + - On OSX, controls are automatically adjusted to match standard OSX text editing shortcuts and behaviors. + + - KEYBOARD CONTROLS + - Basic: + - Tab, SHIFT+Tab Cycle through text editable fields. + - CTRL+Tab, CTRL+Shift+Tab Cycle through windows. + - CTRL+Click Input text into a Slider or Drag widget. + - Extended features with `io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard`: + - Tab, SHIFT+Tab: Cycle through every items. + - Arrow keys Move through items using directional navigation. Tweak value. + - Arrow keys + Alt, Shift Tweak slower, tweak faster (when using arrow keys). + - Enter Activate item (prefer text input when possible). + - Space Activate item (prefer tweaking with arrows when possible). + - Escape Deactivate item, leave child window, close popup. + - Page Up, Page Down Previous page, next page. + - Home, End Scroll to top, scroll to bottom. + - Alt Toggle between scrolling layer and menu layer. + - CTRL+Tab then Ctrl+Arrows Move window. Hold SHIFT to resize instead of moving. + - Output when ImGuiConfigFlags_NavEnableKeyboard set, + - 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! + - Download controller mapping PNG/PSD at http://dearimgui.com/controls_sheets + - Backend support: backend needs to: + - Set 'io.BackendFlags |= ImGuiBackendFlags_HasGamepad' + call io.AddKeyEvent/AddKeyAnalogEvent() with ImGuiKey_Gamepad_XXX keys. + - For analog values (0.0f to 1.0f), backend is responsible to handling a dead-zone and rescaling inputs accordingly. + 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.). + - BEFORE 1.87, BACKENDS USED TO WRITE TO io.NavInputs[]. This is now obsolete. Please call io functions instead! + - 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) + in order to share your PC mouse/keyboard. + - See https://github.com/ocornut/imgui/wiki/Useful-Extensions#remoting for other remoting solutions. + - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the ImGuiConfigFlags_NavEnableSetMousePos flag. + Enabling ImGuiConfigFlags_NavEnableSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs Dear ImGui to move your mouse cursor along with navigation + movements. When enabled, the NewFrame() function may alter 'io.MousePos' and set 'io.WantSetMousePos' to notify you that it wants the mouse cursor to be moved. + When that happens your backend NEEDS to move the OS or underlying mouse cursor on the next frame. Some of the backends in examples/ do that. + (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) + - Your code creates the UI every frame of your application loop, if your code doesn't run the UI is gone! + The UI can be highly dynamic, there are no construction or destruction steps, less superfluous + data retention on your side, less state duplication, less state synchronization, fewer bugs. + - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features. + Or browse https://pthom.github.io/imgui_manual_online/manual/imgui_manual.html for interactive web version. + - The library is designed to be built from sources. Avoid pre-compiled binaries and packaged versions. See imconfig.h to configure your build. + - Dear ImGui is an implementation of the IMGUI paradigm (immediate-mode graphical user interface, a term coined by Casey Muratori). + You can learn about IMGUI principles at http://www.johno.se/book/imgui.html, http://mollyrocket.com/861 & more links in Wiki. + - Dear ImGui is a "single pass" rasterizing implementation of the IMGUI paradigm, aimed at ease of use and high-performances. + For every application frame, your UI code will be called only once. This is in contrast to e.g. Unity's implementation of an IMGUI, + where the UI code is called multiple times ("multiple passes") from a single entry point. There are pros and cons to both approaches. + - Our origin is on the top-left. In axis aligned bounding boxes, Min = top-left, Max = bottom-right. + - Please make sure you have asserts enabled (IM_ASSERT redirects to assert() by default, but can be redirected). + If you get an assert, read the messages and comments around the assert. + - This codebase aims to be highly optimized: + - A typical idle frame should never call malloc/free. + - We rely on a maximum of constant-time or O(N) algorithms. Limiting searches/scans as much as possible. + - We put particular energy in making sure performances are decent with typical "Debug" build settings as well. + Which mean we tend to avoid over-relying on "zero-cost abstraction" as they aren't zero-cost at all. + - This codebase aims to be both highly opinionated and highly flexible: + - This code works because of the things it choose to solve or not solve. + - C++: this is a pragmatic C-ish codebase: we don't use fancy C++ features, we don't include C++ headers, + and ImGui:: is a namespace. We rarely use member functions (and when we did, I am mostly regretting it now). + This is to increase compatibility, increase maintainability and facilitate use from other languages. + - C++: ImVec2/ImVec4 do not expose math operators by default, because it is expected that you use your own math types. + See FAQ "How can I use my own math types instead of ImVec2/ImVec4?" for details about setting up imconfig.h for that. + We can can optionally export math operators for ImVec2/ImVec4 using IMGUI_DEFINE_MATH_OPERATORS, which we use internally. + - C++: pay attention that ImVector<> manipulates plain-old-data and does not honor construction/destruction + (so don't use ImVector in your code or at our own risk!). + - Building: We don't use nor mandate a build system for the main library. + 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. + - About imconfig.h: + - You may modify your copy of imconfig.h, in this case don't overwrite it. + - 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. + - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes. + If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed + from the public API. If you have a problem with a missing function/symbols, search for its name in the code, there will + 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. + - Run and study the examples and demo in imgui_demo.cpp to get acquainted with the library. + - In the majority of cases you should be able to use unmodified backends files available in the backends/ folder. + - Add the Dear ImGui source files + selected backend source files to your projects or using your preferred build system. + It is recommended you build and statically link the .cpp files as part of your project and NOT as a shared library (DLL). + - You can later customize the imconfig.h file to tweak some compile-time behavior, such as integrating Dear ImGui types with your own maths types. + - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them. + - Dear ImGui never touches or knows about your GPU state. The only function that knows about GPU is the draw function that you provide. + Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render" + 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). + 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) + { + // Feed inputs to dear imgui, start new frame + ImGui_ImplDX11_NewFrame(); + ImGui_ImplWin32_NewFrame(); + ImGui::NewFrame(); + + // Any application code here + ImGui::Text("Hello, world!"); + + // Render dear imgui into screen + 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 + 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. + + // 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); + + // Application main loop + while (true) + { + // Setup low-level inputs, e.g. on Win32: calling GetKeyboardState(), or write to those fields from your Windows message handlers, etc. + // (In the examples/ app this is usually done within the ImGui_ImplXXX_NewFrame() function from one of the demo Platform Backends) + io.DeltaTime = 1.0f/60.0f; // set the time elapsed since the previous frame (in seconds) + io.DisplaySize.x = 1920.0f; // set the current display width + io.DisplaySize.y = 1280.0f; // set the current display height here + 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 + // (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::Render(); + ImDrawData* draw_data = ImGui::GetDrawData(); + MyImGuiRenderFunction(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 and example applications for details 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. + + - 2023/08/25 (1.89.9) - Clipper: Renamed IncludeRangeByIndices() (also called ForceDisplayRangeByIndices() before 1.89.6) to IncludeItemsByIndex(). Kept inline + redirection function. Sorry! + - 2023/07/12 (1.89.8) - ImDrawData: CmdLists now owned, changed from ImDrawList** to ImVector. Majority of users shouldn't be affected, but you + cannot compare to NULL nor reassign manually anymore. Instead use AddDrawList(). (#6406, #4879, #1878) + - 2023/06/28 (1.89.7) - overlapping items: obsoleted 'SetItemAllowOverlap()' (called after item) in favor of calling 'SetNextItemAllowOverlap()' (called before + item). 'SetItemAllowOverlap()' didn't and couldn't work reliably since 1.89 (2022-11-15). + - 2023/06/28 (1.89.7) - overlapping items: renamed 'ImGuiTreeNodeFlags_AllowItemOverlap' to 'ImGuiTreeNodeFlags_AllowOverlap', + 'ImGuiSelectableFlags_AllowItemOverlap' to 'ImGuiSelectableFlags_AllowOverlap'. Kept redirecting enums (will obsolete). + - 2023/06/28 (1.89.7) - overlapping items: IsItemHovered() now by default return false when querying an item using AllowOverlap mode which is being overlapped. + Use ImGuiHoveredFlags_AllowWhenOverlappedByItem to revert to old behavior. + - 2023/06/28 (1.89.7) - overlapping items: Selectable and TreeNode don't allow overlap when active so overlapping widgets won't appear as hovered. While this + fixes a common small visual issue, it also means that calling IsItemHovered() after a non-reactive elements - e.g. Text() - overlapping an active one may fail + if you don't use IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem). (#6610) + - 2023/06/20 (1.89.7) - moved io.HoverDelayShort/io.HoverDelayNormal to style.HoverDelayShort/style.HoverDelayNormal. As the fields were added in 1.89 and + expected to be left unchanged by most users, or only tweaked once during app initialization, we are exceptionally accepting the breakage. + - 2023/05/30 (1.89.6) - backends: renamed "imgui_impl_sdlrenderer.cpp" to "imgui_impl_sdlrenderer2.cpp" and "imgui_impl_sdlrenderer.h" to + "imgui_impl_sdlrenderer2.h". This is in prevision for the future release of SDL3. + - 2023/05/22 (1.89.6) - listbox: commented out obsolete/redirecting functions that were marked obsolete more than two years ago: + - ListBoxHeader() -> use BeginListBox() (note how two variants of ListBoxHeader() existed. Check commented versions in imgui.h for + reference) + - ListBoxFooter() -> use EndListBox() + - 2023/05/15 (1.89.6) - clipper: commented out obsolete redirection constructor 'ImGuiListClipper(int items_count, float items_height = -1.0f)' that was marked + obsolete in 1.79. Use default constructor + clipper.Begin(). + - 2023/05/15 (1.89.6) - clipper: renamed ImGuiListClipper::ForceDisplayRangeByIndices() to ImGuiListClipper::IncludeRangeByIndices(). + - 2023/03/14 (1.89.4) - commented out redirecting enums/functions names that were marked obsolete two years ago: + - ImGuiSliderFlags_ClampOnInput -> use ImGuiSliderFlags_AlwaysClamp + - ImGuiInputTextFlags_AlwaysInsertMode -> use ImGuiInputTextFlags_AlwaysOverwrite + - ImDrawList::AddBezierCurve() -> use ImDrawList::AddBezierCubic() + - ImDrawList::PathBezierCurveTo() -> use ImDrawList::PathBezierCubicCurveTo() + - 2023/03/09 (1.89.4) - renamed PushAllowKeyboardFocus()/PopAllowKeyboardFocus() to PushTabStop()/PopTabStop(). Kept inline redirection functions (will + obsolete). + - 2023/03/09 (1.89.4) - tooltips: Added 'bool' return value to BeginTooltip() for API consistency. Please only submit contents and call EndTooltip() if + BeginTooltip() returns true. In reality the function will _currently_ always return true, but further changes down the line may change this, best to clarify + API sooner. + - 2023/02/15 (1.89.4) - moved the optional "courtesy maths operators" implementation from imgui_internal.h in imgui.h. + Even though we encourage using your own maths types and operators by setting up IM_VEC2_CLASS_EXTRA, + it has been frequently requested by people to use our own. We had an opt-in define which was + previously fulfilled in imgui_internal.h. It is now fulfilled in imgui.h. (#6164) + - OK: #define IMGUI_DEFINE_MATH_OPERATORS / #include "imgui.h" / #include "imgui_internal.h" + - Error: #include "imgui.h" / #define IMGUI_DEFINE_MATH_OPERATORS / #include "imgui_internal.h" + - 2023/02/07 (1.89.3) - backends: renamed "imgui_impl_sdl.cpp" to "imgui_impl_sdl2.cpp" and "imgui_impl_sdl.h" to "imgui_impl_sdl2.h". (#6146) This is in + prevision for the future release of SDL3. + - 2022/10/26 (1.89) - commented out redirecting OpenPopupContextItem() which was briefly the name of OpenPopupOnItemClick() from 1.77 to 1.79. + - 2022/10/12 (1.89) - removed runtime patching of invalid "%f"/"%0.f" format strings for DragInt()/SliderInt(). This was obsoleted in 1.61 (May 2018). + See 1.61 changelog for details. + - 2022/09/26 (1.89) - renamed and merged keyboard modifiers key enums and flags into a same set. Kept inline redirection enums (will obsolete). + - ImGuiKey_ModCtrl and ImGuiModFlags_Ctrl -> ImGuiMod_Ctrl + - ImGuiKey_ModShift and ImGuiModFlags_Shift -> ImGuiMod_Shift + - ImGuiKey_ModAlt and ImGuiModFlags_Alt -> ImGuiMod_Alt + - ImGuiKey_ModSuper and ImGuiModFlags_Super -> ImGuiMod_Super + the ImGuiKey_ModXXX were introduced in 1.87 and mostly used by backends. + the ImGuiModFlags_XXX have been exposed in imgui.h but not really used by any public api only by third-party extensions. + exceptionally commenting out the older ImGuiKeyModFlags_XXX names ahead of obsolescence schedule to reduce confusion and because they + were not meant to be used anyway. + - 2022/09/20 (1.89) - ImGuiKey is now a typed enum, allowing ImGuiKey_XXX symbols to be named in debuggers. + this will require uses of legacy backend-dependent indices to be casted, e.g. + - with imgui_impl_glfw: IsKeyPressed(GLFW_KEY_A) -> IsKeyPressed((ImGuiKey)GLFW_KEY_A); + - with imgui_impl_win32: IsKeyPressed('A') -> IsKeyPressed((ImGuiKey)'A') + - etc. However if you are upgrading code you might well use the better, backend-agnostic IsKeyPressed(ImGuiKey_A) now! + - 2022/09/12 (1.89) - removed the bizarre legacy default argument for 'TreePush(const void* ptr = NULL)', always pass a pointer value explicitly. NULL/nullptr + is ok but require cast, e.g. TreePush((void*)nullptr); + - 2022/09/05 (1.89) - commented out redirecting functions/enums names that were marked obsolete in 1.77 and 1.78 (June 2020): + - DragScalar(), DragScalarN(), DragFloat(), DragFloat2(), DragFloat3(), DragFloat4(): For old signatures ending with (..., const char* + format, float power = 1.0f) -> use (..., format ImGuiSliderFlags_Logarithmic) if power != 1.0f. + - SliderScalar(), SliderScalarN(), SliderFloat(), SliderFloat2(), SliderFloat3(), SliderFloat4(): For old signatures ending with (..., + const char* format, float power = 1.0f) -> use (..., format ImGuiSliderFlags_Logarithmic) if power != 1.0f. + - BeginPopupContextWindow(const char*, ImGuiMouseButton, bool) -> use BeginPopupContextWindow(const char*, ImGuiPopupFlags) + - 2022/09/02 (1.89) - obsoleted using SetCursorPos()/SetCursorScreenPos() to extend parent window/cell boundaries. + this relates to when moving the cursor position beyond current boundaries WITHOUT submitting an item. + - previously this would make the window content size ~200x200: + Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); + - instead, please submit an item: + Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End(); + - alternative: + Begin(...) + Dummy(ImVec2(200,200)) + End(); + - content size is now only extended when submitting an item! + - with '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' this will now be detected and assert. + - without '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' this will silently be fixed until we obsolete it. + - 2022/08/03 (1.89) - changed signature of ImageButton() function. Kept redirection function (will obsolete). + - added 'const char* str_id' parameter + removed 'int frame_padding = -1' parameter. + - old signature: bool ImageButton(ImTextureID tex_id, ImVec2 size, ImVec2 uv0 = ImVec2(0,0), ImVec2 uv1 = ImVec2(1,1), int frame_padding + = -1, ImVec4 bg_col = ImVec4(0,0,0,0), ImVec4 tint_col = ImVec4(1,1,1,1)); + - used the ImTextureID value to create an ID. This was inconsistent with other functions, led to ID conflicts, and caused problems + with engines using transient ImTextureID values. + - had a FramePadding override which was inconsistent with other functions and made the already-long signature even longer. + - new signature: bool ImageButton(const char* str_id, ImTextureID tex_id, ImVec2 size, ImVec2 uv0 = ImVec2(0,0), ImVec2 uv1 = + ImVec2(1,1), ImVec4 bg_col = ImVec4(0,0,0,0), ImVec4 tint_col = ImVec4(1,1,1,1)); + - requires an explicit identifier. You may still use e.g. PushID() calls and then pass an empty identifier. + - always uses style.FramePadding for padding, to be consistent with other buttons. You may use PushStyleVar() to alter this. + - 2022/07/08 (1.89) - inputs: removed io.NavInputs[] and ImGuiNavInput enum (following 1.87 changes). + - Official backends from 1.87+ -> no issue. + - Official backends from 1.60 to 1.86 -> will build and convert gamepad inputs, unless IMGUI_DISABLE_OBSOLETE_KEYIO is + defined. Need updating! + - Custom backends not writing to io.NavInputs[] -> no issue. + - Custom backends writing to io.NavInputs[] -> will build and convert gamepad inputs, unless IMGUI_DISABLE_OBSOLETE_KEYIO is + defined. Need fixing! + - TL;DR: Backends should call io.AddKeyEvent()/io.AddKeyAnalogEvent() with ImGuiKey_GamepadXXX values instead of filling io.NavInput[]. + - 2022/06/15 (1.88) - renamed IMGUI_DISABLE_METRICS_WINDOW to IMGUI_DISABLE_DEBUG_TOOLS for correctness. kept support for old define (will obsolete). + - 2022/05/03 (1.88) - backends: osx: removed ImGui_ImplOSX_HandleEvent() from backend API in favor of backend automatically handling event capture. All + ImGui_ImplOSX_HandleEvent() calls should be removed as they are now unnecessary. + - 2022/04/05 (1.88) - inputs: renamed ImGuiKeyModFlags to ImGuiModFlags. Kept inline redirection enums (will obsolete). This was never used in public API + functions but technically present in imgui.h and ImGuiIO. + - 2022/01/20 (1.87) - inputs: reworded gamepad IO. + - Backend writing to io.NavInputs[] -> backend should call io.AddKeyEvent()/io.AddKeyAnalogEvent() with ImGuiKey_GamepadXXX + values. + - 2022/01/19 (1.87) - sliders, drags: removed support for legacy arithmetic operators (+,+-,*,/) when inputing text. This doesn't break any api/code but a + feature that used to be accessible by end-users (which seemingly no one used). + - 2022/01/17 (1.87) - inputs: reworked mouse IO. + - Backend writing to io.MousePos -> backend should call io.AddMousePosEvent() + - Backend writing to io.MouseDown[] -> backend should call io.AddMouseButtonEvent() + - Backend writing to io.MouseWheel -> backend should call io.AddMouseWheelEvent() + - Backend writing to io.MouseHoveredViewport -> backend should call io.AddMouseViewportEvent() [Docking branch w/ multi-viewports + only] note: for all calls to IO new functions, the Dear ImGui context should be bound/current. read https://github.com/ocornut/imgui/issues/4921 for details. + - 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(). Removed GetKeyIndex(), now + unecessary. 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.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. + - 2022/01/05 (1.87) - inputs: renamed ImGuiKey_KeyPadEnter to ImGuiKey_KeypadEnter to align with new symbols. Kept redirection enum. + - 2022/01/05 (1.87) - removed io.ImeSetInputScreenPosFn() in favor of more flexible io.SetPlatformImeDataFn(). Removed 'void* io.ImeWindowHandle' in favor of + writing to 'void* ImGuiViewport::PlatformHandleRaw'. + - 2022/01/01 (1.87) - commented out redirecting functions/enums names that were marked obsolete in 1.69, 1.70, 1.71, 1.72 (March-July 2019) + - ImGui::SetNextTreeNodeOpen() -> use ImGui::SetNextItemOpen() + - ImGui::GetContentRegionAvailWidth() -> use ImGui::GetContentRegionAvail().x + - ImGui::TreeAdvanceToLabelPos() -> use ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetTreeNodeToLabelSpacing()); + - ImFontAtlas::CustomRect -> use ImFontAtlasCustomRect + - ImGuiColorEditFlags_RGB/HSV/HEX -> use ImGuiColorEditFlags_DisplayRGB/HSV/Hex + - 2021/12/20 (1.86) - backends: removed obsolete Marmalade backend (imgui_impl_marmalade.cpp) + example. Find last supported version at + https://github.com/ocornut/imgui/wiki/Bindings + - 2021/11/04 (1.86) - removed CalcListClipping() function. Prefer using ImGuiListClipper which can return non-contiguous ranges. Please open an issue if you + think you really need this function. + - 2021/08/23 (1.85) - removed GetWindowContentRegionWidth() function. keep inline redirection helper. can use 'GetWindowContentRegionMax().x - + GetWindowContentRegionMin().x' instead for generally 'GetContentRegionAvail().x' is more useful. + - 2021/07/26 (1.84) - commented out redirecting functions/enums names that were marked obsolete in 1.67 and 1.69 (March 2019): + - ImGui::GetOverlayDrawList() -> use ImGui::GetForegroundDrawList() + - ImFont::GlyphRangesBuilder -> use ImFontGlyphRangesBuilder + - 2021/05/19 (1.83) - backends: obsoleted direct access to ImDrawCmd::TextureId in favor of calling ImDrawCmd::GetTexID(). + - if you are using official backends from the source tree: you have nothing to do. + - if you have copied old backend code or using your own: change access to draw_cmd->TextureId to draw_cmd->GetTexID(). + - 2021/03/12 (1.82) - upgraded ImDrawList::AddRect(), AddRectFilled(), PathRect() to use ImDrawFlags instead of ImDrawCornersFlags. + - ImDrawCornerFlags_TopLeft -> use ImDrawFlags_RoundCornersTopLeft + - ImDrawCornerFlags_BotRight -> use ImDrawFlags_RoundCornersBottomRight + - ImDrawCornerFlags_None -> use ImDrawFlags_RoundCornersNone etc. + flags now sanely defaults to 0 instead of 0x0F, consistent with all other flags in the API. + breaking: the default with rounding > 0.0f is now "round all corners" vs old implicit "round no corners": + - rounding == 0.0f + flags == 0 --> meant no rounding --> unchanged (common use) + - rounding > 0.0f + flags != 0 --> meant rounding --> unchanged (common use) + - rounding == 0.0f + flags != 0 --> meant no rounding --> unchanged (unlikely use) + - rounding > 0.0f + flags == 0 --> meant no rounding --> BREAKING (unlikely use): will now round all corners --> use + ImDrawFlags_RoundCornersNone or rounding == 0.0f. this ONLY matters for hard coded use of 0 + rounding > 0.0f. Use of named ImDrawFlags_RoundCornersNone (new) + or ImDrawCornerFlags_None (old) are ok. the old ImDrawCornersFlags used awkward default values of ~0 or 0xF (4 lower bits set) to signify "round all corners" + and we sometimes encouraged using them as shortcuts. legacy path still support use of hard coded ~0 or any value from 0x1 or 0xF. They will behave the same + with legacy paths enabled (will assert otherwise). + - 2021/03/11 (1.82) - removed redirecting functions/enums names that were marked obsolete in 1.66 (September 2018): + - ImGui::SetScrollHere() -> use ImGui::SetScrollHereY() + - 2021/03/11 (1.82) - clarified that ImDrawList::PathArcTo(), ImDrawList::PathArcToFast() won't render with radius < 0.0f. Previously it sorts of accidentally + worked but would generally lead to counter-clockwise paths and have an effect on anti-aliasing. + - 2021/03/10 (1.82) - upgraded ImDrawList::AddPolyline() and PathStroke() "bool closed" parameter to "ImDrawFlags flags". The matching ImDrawFlags_Closed value + is guaranteed to always stay == 1 in the future. + - 2021/02/22 (1.82) - (*undone in 1.84*) win32+mingw: Re-enabled IME functions by default even under MinGW. In July 2016, issue #738 had me incorrectly disable + those default functions for MinGW. MinGW users should: either link with -limm32, either set their imconfig file with '#define + IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS'. + - 2021/02/17 (1.82) - renamed rarely used style.CircleSegmentMaxError (old default = 1.60f) to style.CircleTessellationMaxError (new default = 0.30f) as the + meaning of the value changed. + - 2021/02/03 (1.81) - renamed ListBoxHeader(const char* label, ImVec2 size) to BeginListBox(). Kept inline redirection function (will obsolete). + - removed ListBoxHeader(const char* label, int items_count, int height_in_items = -1) in favor of specifying size. Kept inline redirection + function (will obsolete). + - renamed ListBoxFooter() to EndListBox(). Kept inline redirection function (will obsolete). + - 2021/01/26 (1.81) - removed ImGuiFreeType::BuildFontAtlas(). Kept inline redirection function. Prefer using '#define IMGUI_ENABLE_FREETYPE', but there's a + runtime selection path available too. The shared extra flags parameters (very rarely used) are now stored in ImFontAtlas::FontBuilderFlags. + - renamed ImFontConfig::RasterizerFlags (used by FreeType) to ImFontConfig::FontBuilderFlags. + - renamed ImGuiFreeType::XXX flags to ImGuiFreeTypeBuilderFlags_XXX for consistency with other API. + - 2020/10/12 (1.80) - removed redirecting functions/enums that were marked obsolete in 1.63 (August 2018): + - ImGui::IsItemDeactivatedAfterChange() -> use ImGui::IsItemDeactivatedAfterEdit(). + - ImGuiCol_ModalWindowDarkening -> use ImGuiCol_ModalWindowDimBg + - ImGuiInputTextCallback -> use ImGuiTextEditCallback + - ImGuiInputTextCallbackData -> use ImGuiTextEditCallbackData + - 2020/12/21 (1.80) - renamed ImDrawList::AddBezierCurve() to AddBezierCubic(), and PathBezierCurveTo() to PathBezierCubicCurveTo(). Kept inline redirection + function (will obsolete). + - 2020/12/04 (1.80) - added imgui_tables.cpp file! Manually constructed project files will need the new file added! + - 2020/11/18 (1.80) - renamed undocumented/internals ImGuiColumnsFlags_* to ImGuiOldColumnFlags_* in prevision of incoming Tables API. + - 2020/11/03 (1.80) - renamed io.ConfigWindowsMemoryCompactTimer to io.ConfigMemoryCompactTimer as the feature will apply to other data structures + - 2020/10/14 (1.80) - backends: moved all backends files (imgui_impl_XXXX.cpp, imgui_impl_XXXX.h) from examples/ to backends/. + - 2020/10/12 (1.80) - removed redirecting functions/enums that were marked obsolete in 1.60 (April 2018): + - io.RenderDrawListsFn pointer -> use ImGui::GetDrawData() value and call the render function of your backend + - ImGui::IsAnyWindowFocused() -> use ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow) + - ImGui::IsAnyWindowHovered() -> use ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) + - ImGuiStyleVar_Count_ -> use ImGuiStyleVar_COUNT + - ImGuiMouseCursor_Count_ -> use ImGuiMouseCursor_COUNT + - removed redirecting functions names that were marked obsolete in 1.61 (May 2018): + - InputFloat (... int decimal_precision ...) -> use InputFloat (... const char* format ...) with format = "%.Xf" where X is your value + for decimal_precision. + - same for InputFloat2()/InputFloat3()/InputFloat4() variants taking a `int decimal_precision` parameter. + - 2020/10/05 (1.79) - removed ImGuiListClipper: Renamed constructor parameters which created an ambiguous alternative to using the ImGuiListClipper::Begin() + function, with misleading edge cases (note: imgui_memory_editor <0.40 from imgui_club/ used this old clipper API. Update your copy if needed). + - 2020/09/25 (1.79) - renamed ImGuiSliderFlags_ClampOnInput to ImGuiSliderFlags_AlwaysClamp. Kept redirection enum (will obsolete sooner because previous name + was added recently). + - 2020/09/25 (1.79) - renamed style.TabMinWidthForUnselectedCloseButton to style.TabMinWidthForCloseButton. + - 2020/09/21 (1.79) - renamed OpenPopupContextItem() back to OpenPopupOnItemClick(), reverting the change from 1.77. For varieties of reason this is more + self-explanatory. + - 2020/09/21 (1.79) - removed return value from OpenPopupOnItemClick() - returned true on mouse release on an item - because it is inconsistent with other + popup APIs and makes others misleading. It's also and unnecessary: you can use IsWindowAppearing() after BeginPopup() for a similar result. + - 2020/09/17 (1.79) - removed ImFont::DisplayOffset in favor of ImFontConfig::GlyphOffset. DisplayOffset was applied after scaling and not very + meaningful/useful outside of being needed by the default ProggyClean font. If you scaled this value after calling AddFontDefault(), this is now done + automatically. It was also getting in the way of better font scaling, so let's get rid of it now! + - 2020/08/17 (1.78) - obsoleted use of the trailing 'float power=1.0f' parameter for DragFloat(), DragFloat2(), DragFloat3(), DragFloat4(), DragFloatRange2(), + DragScalar(), DragScalarN(), SliderFloat(), SliderFloat2(), SliderFloat3(), SliderFloat4(), SliderScalar(), SliderScalarN(), VSliderFloat() and + VSliderScalar(). replaced the 'float power=1.0f' argument with integer-based flags defaulting to 0 (as with all our flags). worked out a backward-compatibility + scheme so hopefully most C++ codebase should not be affected. in short, when calling those functions: + - if you omitted the 'power' parameter (likely!), you are not affected. + - if you set the 'power' parameter to 1.0f (same as previous default value): 1/ your compiler may warn on float>int conversion, 2/ + everything else will work. 3/ you can replace the 1.0f value with 0 to fix the warning, and be technically correct. + - if you set the 'power' parameter to >1.0f (to enable non-linear editing): 1/ your compiler may warn on float>int conversion, 2/ code + will assert at runtime, 3/ in case asserts are disabled, the code will not crash and enable the _Logarithmic flag. 4/ you can replace the >1.0f value with + ImGuiSliderFlags_Logarithmic to fix the warning/assert and get a _similar_ effect as previous uses of power >1.0f. see + https://github.com/ocornut/imgui/issues/3361 for all details. kept inline redirection functions (will obsolete) apart for: DragFloatRange2(), VSliderFloat(), + VSliderScalar(). For those three the 'float power=1.0f' version was removed directly as they were most unlikely ever used. for shared code, you can version + check at compile-time with `#if IMGUI_VERSION_NUM >= 17704`. + - obsoleted use of v_min > v_max in DragInt, DragFloat, DragScalar to lock edits (introduced in 1.73, was not demoed nor documented very), + will be replaced by a more generic ReadOnly feature. You may use the ImGuiSliderFlags_ReadOnly internal flag in the meantime. + - 2020/06/23 (1.77) - removed BeginPopupContextWindow(const char*, int mouse_button, bool also_over_items) in favor of BeginPopupContextWindow(const char*, + ImGuiPopupFlags flags) with ImGuiPopupFlags_NoOverItems. + - 2020/06/15 (1.77) - renamed OpenPopupOnItemClick() to OpenPopupContextItem(). Kept inline redirection function (will obsolete). [NOTE: THIS WAS REVERTED + IN 1.79] + - 2020/06/15 (1.77) - removed CalcItemRectClosestPoint() entry point which was made obsolete and asserting in December 2017. + - 2020/04/23 (1.77) - removed unnecessary ID (first arg) of ImFontAtlas::AddCustomRectRegular(). + - 2020/01/22 (1.75) - ImDrawList::AddCircle()/AddCircleFilled() functions don't accept negative radius any more. + - 2019/12/17 (1.75) - [undid this change in 1.76] made Columns() limited to 64 columns by asserting above that limit. While the current code technically + supports it, future code may not so we're putting the restriction ahead. + - 2019/12/13 (1.75) - [imgui_internal.h] changed ImRect() default constructor initializes all fields to 0.0f instead of (FLT_MAX,FLT_MAX,-FLT_MAX,-FLT_MAX). If + you used ImRect::Add() to create bounding boxes by adding multiple points into it, you may need to fix your initial value. + - 2019/12/08 (1.75) - removed redirecting functions/enums that were marked obsolete in 1.53 (December 2017): + - ShowTestWindow() -> use ShowDemoWindow() + - IsRootWindowFocused() -> use IsWindowFocused(ImGuiFocusedFlags_RootWindow) + - IsRootWindowOrAnyChildFocused() -> use IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) + - SetNextWindowContentWidth(w) -> use SetNextWindowContentSize(ImVec2(w, 0.0f) + - GetItemsLineHeightWithSpacing() -> use GetFrameHeightWithSpacing() + - ImGuiCol_ChildWindowBg -> use ImGuiCol_ChildBg + - ImGuiStyleVar_ChildWindowRounding -> use ImGuiStyleVar_ChildRounding + - ImGuiTreeNodeFlags_AllowOverlapMode -> use ImGuiTreeNodeFlags_AllowItemOverlap + - IMGUI_DISABLE_TEST_WINDOWS -> use IMGUI_DISABLE_DEMO_WINDOWS + - 2019/12/08 (1.75) - obsoleted calling ImDrawList::PrimReserve() with a negative count (which was vaguely documented and rarely if ever used). Instead, we + added an explicit PrimUnreserve() API. + - 2019/12/06 (1.75) - removed implicit default parameter to IsMouseDragging(int button = 0) to be consistent with other mouse functions (none of the other + functions have it). + - 2019/11/21 (1.74) - ImFontAtlas::AddCustomRectRegular() now requires an ID larger than 0x110000 (instead of 0x10000) to conform with supporting Unicode + planes 1-16 in a future update. ID below 0x110000 will now assert. + - 2019/11/19 (1.74) - renamed IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS to IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS for consistency. + - 2019/11/19 (1.74) - renamed IMGUI_DISABLE_MATH_FUNCTIONS to IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS for consistency. + - 2019/10/22 (1.74) - removed redirecting functions/enums that were marked obsolete in 1.52 (October 2017): + - Begin() [old 5 args version] -> use Begin() [3 args], use SetNextWindowSize() SetNextWindowBgAlpha() if needed + - IsRootWindowOrAnyChildHovered() -> use IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) + - AlignFirstTextHeightToWidgets() -> use AlignTextToFramePadding() + - SetNextWindowPosCenter() -> use SetNextWindowPos() with a pivot of (0.5f, 0.5f) + - ImFont::Glyph -> use ImFontGlyph + - 2019/10/14 (1.74) - inputs: Fixed a miscalculation in the keyboard/mouse "typematic" repeat delay/rate calculation, used by keys and e.g. repeating mouse + buttons as well as the GetKeyPressedAmount() function. if you were using a non-default value for io.KeyRepeatRate (previous default was 0.250), you can add + +io.KeyRepeatDelay to it to compensate for the fix. The function was triggering on: 0.0 and (delay+rate*N) where (N>=1). Fixed formula responds to (N>=0). + Effectively it made io.KeyRepeatRate behave like it was set to (io.KeyRepeatRate + io.KeyRepeatDelay). If you never altered io.KeyRepeatRate nor used + GetKeyPressedAmount() this won't affect you. + - 2019/07/15 (1.72) - removed TreeAdvanceToLabelPos() which is rarely used and only does SetCursorPosX(GetCursorPosX() + GetTreeNodeToLabelSpacing()). Kept + redirection function (will obsolete). + - 2019/07/12 (1.72) - renamed ImFontAtlas::CustomRect to ImFontAtlasCustomRect. Kept redirection typedef (will obsolete). + - 2019/06/14 (1.72) - removed redirecting functions/enums names that were marked obsolete in 1.51 (June 2017): ImGuiCol_Column*, ImGuiSetCond_*, + IsItemHoveredRect(), IsPosHoveringAnyWindow(), IsMouseHoveringAnyWindow(), IsMouseHoveringWindow(), IMGUI_ONCE_UPON_A_FRAME. Grep this log for details and new + names, or see how they were implemented until 1.71. + - 2019/06/07 (1.71) - rendering of child window outer decorations (bg color, border, scrollbars) is now performed as part of the parent window. If you have + overlapping child windows in a same parent, and relied on their relative z-order to be mapped to their submission order, this will affect + your rendering. This optimization is disabled if the parent window has no visual output, because it appears to be the most common situation leading to the + creation of overlapping child windows. Please reach out if you are affected. + - 2019/05/13 (1.71) - renamed SetNextTreeNodeOpen() to SetNextItemOpen(). Kept inline redirection function (will obsolete). + - 2019/05/11 (1.71) - changed io.AddInputCharacter(unsigned short c) signature to io.AddInputCharacter(unsigned int c). + - 2019/04/29 (1.70) - improved ImDrawList thick strokes (>1.0f) preserving correct thickness up to 90 degrees angles (e.g. rectangles). If you have custom + rendering using thick lines, they will appear thicker now. + - 2019/04/29 (1.70) - removed GetContentRegionAvailWidth(), use GetContentRegionAvail().x instead. Kept inline redirection function (will obsolete). + - 2019/03/04 (1.69) - renamed GetOverlayDrawList() to GetForegroundDrawList(). Kept redirection function (will obsolete). + - 2019/02/26 (1.69) - renamed ImGuiColorEditFlags_RGB/ImGuiColorEditFlags_HSV/ImGuiColorEditFlags_HEX to + ImGuiColorEditFlags_DisplayRGB/ImGuiColorEditFlags_DisplayHSV/ImGuiColorEditFlags_DisplayHex. Kept redirection enums (will obsolete). + - 2019/02/14 (1.68) - made it illegal/assert when io.DisplayTime == 0.0f (with an exception for the first frame). If for some reason your time step calculation + gives you a zero value, replace it with an arbitrarily small value! + - 2019/02/01 (1.68) - removed io.DisplayVisibleMin/DisplayVisibleMax (which were marked obsolete and removed from viewport/docking branch already). + - 2019/01/06 (1.67) - renamed io.InputCharacters[], marked internal as was always intended. Please don't access directly, and use AddInputCharacter() instead! + - 2019/01/06 (1.67) - renamed ImFontAtlas::GlyphRangesBuilder to ImFontGlyphRangesBuilder. Kept redirection typedef (will obsolete). + - 2018/12/20 (1.67) - made it illegal to call Begin("") with an empty string. This somehow half-worked before but had various undesirable side-effects. + - 2018/12/10 (1.67) - renamed io.ConfigResizeWindowsFromEdges to io.ConfigWindowsResizeFromEdges as we are doing a large pass on configuration flags. + - 2018/10/12 (1.66) - renamed misc/stl/imgui_stl.* to misc/cpp/imgui_stdlib.* in prevision for other C++ helper files. + - 2018/09/28 (1.66) - renamed SetScrollHere() to SetScrollHereY(). Kept redirection function (will obsolete). + - 2018/09/06 (1.65) - renamed stb_truetype.h to imstb_truetype.h, stb_textedit.h to imstb_textedit.h, and stb_rect_pack.h to imstb_rectpack.h. + If you were conveniently using the imgui copy of those STB headers in your project you will have to update your include paths. + - 2018/09/05 (1.65) - renamed io.OptCursorBlink/io.ConfigCursorBlink to io.ConfigInputTextCursorBlink. (#1427) + - 2018/08/31 (1.64) - added imgui_widgets.cpp file, extracted and moved widgets code out of imgui.cpp into imgui_widgets.cpp. Re-ordered some of the code + remaining in imgui.cpp. NONE OF THE FUNCTIONS HAVE CHANGED. THE CODE IS SEMANTICALLY 100% IDENTICAL, BUT _EVERY_ FUNCTION HAS BEEN MOVED. Because of this, any + local modifications to imgui.cpp will likely conflict when you update. Read docs/CHANGELOG.txt for suggestions. + - 2018/08/22 (1.63) - renamed IsItemDeactivatedAfterChange() to IsItemDeactivatedAfterEdit() for consistency with new IsItemEdited() API. Kept redirection + function (will obsolete soonish as IsItemDeactivatedAfterChange() is very recent). + - 2018/08/21 (1.63) - renamed ImGuiTextEditCallback to ImGuiInputTextCallback, ImGuiTextEditCallbackData to ImGuiInputTextCallbackData for consistency. Kept + redirection types (will obsolete). + - 2018/08/21 (1.63) - removed ImGuiInputTextCallbackData::ReadOnly since it is a duplication of (ImGuiInputTextCallbackData::Flags & + ImGuiInputTextFlags_ReadOnly). + - 2018/08/01 (1.63) - removed per-window ImGuiWindowFlags_ResizeFromAnySide beta flag in favor of a global io.ConfigResizeWindowsFromEdges [update 1.67 renamed + to ConfigWindowsResizeFromEdges] to enable the feature. + - 2018/08/01 (1.63) - renamed io.OptCursorBlink to io.ConfigCursorBlink [-> io.ConfigInputTextCursorBlink in 1.65], io.OptMacOSXBehaviors to + ConfigMacOSXBehaviors for consistency. + - 2018/07/22 (1.63) - changed ImGui::GetTime() return value from float to double to avoid accumulating floating point imprecisions over time. + - 2018/07/08 (1.63) - style: renamed ImGuiCol_ModalWindowDarkening to ImGuiCol_ModalWindowDimBg for consistency with other features. Kept redirection enum + (will obsolete). + - 2018/06/08 (1.62) - examples: the imgui_impl_XXX files have been split to separate platform (Win32, GLFW, SDL2, etc.) from renderer (DX11, OpenGL, Vulkan, + etc.). old backends will still work as is, however prefer using the separated backends as they will be updated to support multi-viewports. when adopting new + backends follow the main.cpp code of your preferred examples/ folder to know which functions to call. in particular, note that old backends called + ImGui::NewFrame() at the end of their ImGui_ImplXXXX_NewFrame() function. + - 2018/06/06 (1.62) - renamed GetGlyphRangesChinese() to GetGlyphRangesChineseFull() to distinguish other variants and discourage using the full set. + - 2018/06/06 (1.62) - TreeNodeEx()/TreeNodeBehavior(): the ImGuiTreeNodeFlags_CollapsingHeader helper now include the ImGuiTreeNodeFlags_NoTreePushOnOpen flag. + See Changelog for details. + - 2018/05/03 (1.61) - DragInt(): the default compile-time format string has been changed from "%.0f" to "%d", as we are not using integers internally any more. + If you used DragInt() with custom format strings, make sure you change them to use %d or an integer-compatible format. + To honor backward-compatibility, the DragInt() code will currently parse and modify format strings to replace %*f with %d, giving time to + users to upgrade their code. If you have IMGUI_DISABLE_OBSOLETE_FUNCTIONS enabled, the code will instead assert! You may run a reg-exp search on your codebase + for e.g. "DragInt.*%f" to help you find them. + - 2018/04/28 (1.61) - obsoleted InputFloat() functions taking an optional "int decimal_precision" in favor of an equivalent and more flexible "const char* + format", consistent with other functions. Kept redirection functions (will obsolete). + - 2018/04/09 (1.61) - IM_DELETE() helper function added in 1.60 doesn't clear the input _pointer_ reference, more consistent with expectation and allows + passing r-value. + - 2018/03/20 (1.60) - renamed io.WantMoveMouse to io.WantSetMousePos for consistency and ease of understanding (was added in 1.52, _not_ used by core and only + honored by some backend ahead of merging the Nav branch). + - 2018/03/12 (1.60) - removed ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered as the closing cross uses regular button colors + now. + - 2018/03/08 (1.60) - changed ImFont::DisplayOffset.y to default to 0 instead of +1. Fixed rounding of Ascent/Descent to match TrueType renderer. If you were + adding or subtracting to ImFont::DisplayOffset check if your fonts are correctly aligned vertically. + - 2018/03/03 (1.60) - renamed ImGuiStyleVar_Count_ to ImGuiStyleVar_COUNT and ImGuiMouseCursor_Count_ to ImGuiMouseCursor_COUNT for consistency with other + public enums. + - 2018/02/18 (1.60) - BeginDragDropSource(): temporarily removed the optional mouse_button=0 parameter because it is not really usable in many situations at + the moment. + - 2018/02/16 (1.60) - obsoleted the io.RenderDrawListsFn callback, you can call your graphics engine render function after ImGui::Render(). Use + ImGui::GetDrawData() to retrieve the ImDrawData* to display. + - 2018/02/07 (1.60) - reorganized context handling to be more explicit, + - YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL ImGui::DestroyContext() AT THE END. + - removed Shutdown() function, as DestroyContext() serve this purpose. + - you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts. Otherwise CreateContext() will create + its own font atlas instance. + - removed allocator parameters from CreateContext(), they are now setup with SetAllocatorFunctions(), and shared by all contexts. + - removed the default global context and font atlas instance, which were confusing for users of DLL reloading and users of multiple + contexts. + - 2018/01/31 (1.60) - moved sample TTF files from extra_fonts/ to misc/fonts/. If you loaded files directly from the imgui repo you may need to update your + paths. + - 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept redirection function (will obsolete). + - 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept redirection function (will obsolete). + - 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to ImGuiSizeCallbackData. + - 2017/12/29 (1.60) - removed CalcItemRectClosestPoint() which was weird and not really used by anyone except demo code. If you need it it's easy to replicate + on your side. + - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function (will obsolete). + - 2017/12/21 (1.53) - ImDrawList: renamed style.AntiAliasedShapes to style.AntiAliasedFill for consistency and as a way to explicitly break code that + manipulate those flag at runtime. You can now manipulate ImDrawList::Flags + - 2017/12/21 (1.53) - ImDrawList: removed 'bool anti_aliased = true' final parameter of ImDrawList::AddPolyline() and ImDrawList::AddConvexPolyFilled(). Prefer + manipulating ImDrawList::Flags if you need to toggle them during the frame. + - 2017/12/14 (1.53) - using the ImGuiWindowFlags_NoScrollWithMouse flag on a child window forwards the mouse wheel event to the parent window, unless either + ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set. + - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function (will obsolete). + - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow). Kept redirection function (will + obsolete). + - obsoleted IsRootWindowOrAnyChildFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection + function (will obsolete). + - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept redirection enum (will obsolete). + - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete). + - 2017/11/27 (1.53) - renamed ImGuiTextBuffer::append() helper to appendf(), appendv() to appendfv(). If you copied the 'Log' demo in your code, it uses + appendv() so that needs to be renamed. + - 2017/11/18 (1.53) - Style, Begin: removed ImGuiWindowFlags_ShowBorders window flag. Borders are now fully set up in the ImGuiStyle structure (see e.g. + style.FrameBorderSize, style.WindowBorderSize). Use ImGui::ShowStyleEditor() to look them up. Please note that the style system will keep evolving (hopefully + stabilizing in Q1 2018), and so custom styles will probably subtly break over time. It is recommended you use the StyleColorsClassic(), StyleColorsDark(), + StyleColorsLight() functions. + - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency. + - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg. + - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding, ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding. + - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); + - 2017/10/24 (1.52) - renamed IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS to + IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS for consistency. + - 2017/10/20 (1.52) - changed IsWindowHovered() default parameters behavior to return false if an item is active in another window (e.g. click-dragging item + from another window to this window). You can use the newly introduced IsWindowHovered() flags to requests this specific behavior if you need it. + - 2017/10/20 (1.52) - marked IsItemHoveredRect()/IsMouseHoveringWindow() as obsolete, in favor of using the newly introduced flags for IsItemHovered() and + IsWindowHovered(). See https://github.com/ocornut/imgui/issues/1382 for details. removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 + since they were merely more consistent names for the two functions we are now obsoleting. IsItemHoveredRect() --> + IsItemHovered(ImGuiHoveredFlags_RectOnly) IsMouseHoveringAnyWindow() --> IsWindowHovered(ImGuiHoveredFlags_AnyWindow) IsMouseHoveringWindow() --> + IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) [weird, old behavior] + - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead! + - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete). + - 2017/09/26 (1.52) - renamed ImFont::Glyph to ImFontGlyph. Kept redirection typedef (will obsolete). + - 2017/09/25 (1.52) - removed SetNextWindowPosCenter() because SetNextWindowPos() now has the optional pivot information to do the same and more. Kept + redirection function (will obsolete). + - 2017/08/25 (1.52) - io.MousePos needs to be set to ImVec2(-FLT_MAX,-FLT_MAX) when mouse is unavailable/missing. Previously ImVec2(-1,-1) was enough but we + now accept negative mouse coordinates. In your backend if you need to support unavailable mouse, make sure to replace "io.MousePos = ImVec2(-1,-1)" with + "io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX)". + - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use + IsItemHovered(ImGuiHoveredFlags_RectOnly)! + - 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/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). + - 2017/08/11 (1.51) - renamed ImGuiSetCond_Always to ImGuiCond_Always, ImGuiSetCond_Once to ImGuiCond_Once, ImGuiSetCond_FirstUseEver to + ImGuiCond_FirstUseEver, ImGuiSetCond_Appearing to ImGuiCond_Appearing. Kept redirection enums (will obsolete). + - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton(). + - 2017/08/08 (1.51) - removed ColorEditMode() and ImGuiColorEditMode in favor of ImGuiColorEditFlags and parameters to the various Color*() functions. The + SetColorEditOptions() allows to initialize default but the user can still change them with right-click context menu. + - changed prototype of 'ColorEdit4(const char* label, float col[4], bool show_alpha = true)' to 'ColorEdit4(const char* label, float + col[4], ImGuiColorEditFlags flags = 0)', where passing flags = 0x01 is a safe no-op (hello dodgy backward compatibility!). - check and run the demo window, + under "Color/Picker Widgets", to understand the various new options. + - changed prototype of rarely used 'ColorButton(ImVec4 col, bool small_height = false, bool outline_border = true)' to 'ColorButton(const + char* desc_id, ImVec4 col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0, 0))' + - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse + - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset. + - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity. + - 2016/11/06 (1.50) - BeginChild(const char*) now applies the stack id to the provided label, consistently with other functions as it should always have been. + It shouldn't affect you unless (extremely unlikely) you were appending multiple times to a same child from different locations of the stack id. If that's the + case, generate an id with GetID() and use it instead of passing string to BeginChild(). + - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it. + - 2016/09/25 (1.50) - style.WindowTitleAlign is now a ImVec2 (ImGuiAlign enum was removed). set to (0.5f,0.5f) for horizontal+vertical centering, (0.0f,0.0f) + for upper-left, etc. + - 2016/07/30 (1.50) - SameLine(x) with x>0.0f is now relative to left of column/group if any, and not always to left of window. This was sort of always the + intent and hopefully, breakage should be minimal. + - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) + anymore. If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you, otherwise if <1.0f you need to tweak your + custom theme to readjust for the fact that we don't draw a WindowBg background behind the title bar. This helper function will convert an old + TitleBg/TitleBgActive color into a new one with the same visual output, given the OLD color and the OLD WindowBg color: ImVec4 ConvertTitleBgCol(const ImVec4& + win_bg_col, const ImVec4& title_bg_col) { float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a; return + ImVec4((win_bg_col.x * win_bg_col.w + title_bg_col.x) * k, (win_bg_col.y * win_bg_col.w + title_bg_col.y) * k, (win_bg_col.z * win_bg_col.w + title_bg_col.z) * + k, new_a); } If this is confusing, pick the RGB value from title bar from an old screenshot and apply this as TitleBg/TitleBgActive. Or you may just create + TitleBgActive from a tweaked TitleBg color. + - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), + DestroyContext(), GetCurrentContext(), SetCurrentContext(). + - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection. + - 2016/05/01 (1.49) - obsoleted old signature of CollapsingHeader(const char* label, const char* str_id = NULL, bool display_frame = true, bool default_open = + false) as extra parameters were badly designed and rarely used. You can replace the "default_open = true" flag in new API with CollapsingHeader(label, + ImGuiTreeNodeFlags_DefaultOpen). + - 2016/04/26 (1.49) - changed ImDrawList::PushClipRect(ImVec4 rect) to ImDrawList::PushClipRect(Imvec2 min,ImVec2 max,bool + intersect_with_current_clip_rect=false). Note that higher-level ImGui::PushClipRect() is preferable because it will clip at logic/widget level, whereas + ImDrawList::PushClipRect() only affect your renderer. + - 2016/04/03 (1.48) - removed style.WindowFillAlphaDefault setting which was redundant. Bake default BG alpha inside style.Colors[ImGuiCol_WindowBg] and all + other Bg color values. (ref GitHub issue #337). + - 2016/04/03 (1.48) - renamed ImGuiCol_TooltipBg to ImGuiCol_PopupBg, used by popups/menus and tooltips. popups/menus were previously using ImGuiCol_WindowBg. + (ref github issue #337) + - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete). + - 2016/03/02 (1.48) - InputText() completion/history/always callbacks: if you modify the text buffer manually (without using DeleteChars()/InsertChars() + helper) you need to maintain the BufTextLen field. added an assert. + - 2016/01/23 (1.48) - fixed not honoring exact width passed to PushItemWidth(), previously it would add extra FramePadding.x*2 over that width. if you had + manual pixel-perfect alignment in place it might affect you. + - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis. + - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete. + - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position. + GetCursorPos()/SetCursorPos() functions now include the scrolled amount. It shouldn't affect the majority of users, but take note that + SetCursorPosX(100.0f) puts you at +100 from the starting x position which may include scrolling, not at +100 from the window left side. + GetContentRegionMax()/GetWindowContentRegionMin()/GetWindowContentRegionMax() functions allow include the scrolled amount. Typically + those were used in cases where no scrolling would happen so it may not be a problem, but watch out! + - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize + - 2015/08/05 (1.44) - split imgui.cpp into extra files: imgui_demo.cpp imgui_draw.cpp imgui_internal.h that you need to add to your project. + - 2015/07/18 (1.44) - fixed angles in ImDrawList::PathArcTo(), PathArcToFast() (introduced in 1.43) being off by an extra PI for no justifiable reason + - 2015/07/14 (1.43) - add new ImFontAtlas::AddFont() API. For the old AddFont***, moved the 'font_no' parameter of ImFontAtlas::AddFont** functions to the + ImFontConfig structure. you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text. + - 2015/07/08 (1.43) - switched rendering data to use indexed rendering. this is saving a fair amount of CPU/GPU and enables us to get anti-aliasing for a + marginal cost. this necessary change will break your rendering function! the fix should be very easy. sorry for that :( + - if you are using a vanilla copy of one of the imgui_impl_XXX.cpp provided in the example, you just need to update your copy and you can + ignore the rest. + - the signature of the io.RenderDrawListsFn handler has changed! + old: ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) + new: ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data). + parameters: 'cmd_lists' becomes 'draw_data->CmdLists', 'cmd_lists_count' becomes 'draw_data->CmdListsCount' + ImDrawList: 'commands' becomes 'CmdBuffer', 'vtx_buffer' becomes 'VtxBuffer', 'IdxBuffer' is new. + ImDrawCmd: 'vtx_count' becomes 'ElemCount', 'clip_rect' becomes 'ClipRect', 'user_callback' becomes 'UserCallback', 'texture_id' + becomes 'TextureId'. + - each ImDrawList now contains both a vertex buffer and an index buffer. For each command, render ElemCount/3 triangles using indices from + the index buffer. + - if you REALLY cannot render indexed primitives, you can call the draw_data->DeIndexAllBuffers() method to de-index the buffers. This is + slow and a waste of CPU/GPU. Prefer using indexed rendering! + - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade! + - 2015/07/10 (1.43) - changed SameLine() parameters from int to float. + - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete). + - 2015/07/02 (1.42) - renamed GetScrollPosY() to GetScrollY(). Necessary to reduce confusion along with other scrolling functions, because positions (e.g. + cursor position) are not equivalent to scrolling amount. + - 2015/06/14 (1.41) - changed ImageButton() default bg_col parameter from (0,0,0,1) (black) to (0,0,0,0) (transparent) - makes a difference when texture have + transparence + - 2015/06/14 (1.41) - changed Selectable() API from (label, selected, size) to (label, selected, flags, size). Size override should have been rarely used. + Sorry! + - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete). + - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will + obsolete). + - 2015/05/27 (1.40) - removed the third 'repeat_if_held' parameter from Button() - sorry! it was rarely used and inconsistent. Use PushButtonRepeat(true) / + PopButtonRepeat() to enable repeat on desired buttons. + - 2015/05/11 (1.40) - changed BeginPopup() API, takes a string identifier instead of a bool. ImGui needs to manage the open/closed state of popups. Call + OpenPopup() to actually set the "open" state of a popup. BeginPopup() returns true if the popup is opened. + - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same). + - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50. + - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API + - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive. + - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead. + - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline + redirection function until 1.50. + - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing + - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50. + - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing) + - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50. + - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once. + - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now. + - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior + - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing() + - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused) + - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage + functions. + - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader. + - 2015/01/11 (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels. + - old: const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); [..Upload texture to + GPU..]; + - new: unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); [..Upload texture to GPU..]; + io.Fonts->SetTexID(YourTexIdentifier); you now have more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs. It is now + recommended that you sample the font texture with bilinear interpolation. + - 2015/01/11 (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to call io.Fonts->SetTexID() + - 2015/01/11 (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix) + - 2015/01/11 (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets + - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver) + - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph) + - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility + - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered() + - 2014/10/02 (1.14) - renamed IMGUI_INCLUDE_IMGUI_USER_CPP to IMGUI_INCLUDE_IMGUI_USER_INL and imgui_user.cpp to imgui_user.inl (more IDE friendly) + - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity) + - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale() + - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn + - 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. + - Read Getting Started (https://github.com/ocornut/imgui/wiki/Getting-Started) guide. + - See demo code in imgui_demo.cpp and particularly the ImGui::ShowDemoWindow() function. + - The demo covers most features of Dear ImGui, so you can read the code and see its output. + - See documentation and comments at the top of imgui.cpp + effectively imgui.h. + - 20+ standalone example applications using e.g. OpenGL/DirectX are provided in the + examples/ folder to explain how to integrate Dear ImGui with your own engine/application. + - The Wiki (https://github.com/ocornut/imgui/wiki) has many resources and links. + - 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? + - How can I have multiple widgets with the same label? + - How can I have multiple windows with the same label? + Q: How can I display an image? What is ImTextureID, how does it work? + Q: How can I use my own math types instead of ImVec2? + 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/edit/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 "contact AT dearimgui.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. + This is among the most useful thing you can do for Dear ImGui. With increased funding, we sustain and grow work on this project. + Also see https://github.com/ocornut/imgui/wiki/Sponsors + - Businesses: you can also purchase licenses for the Dear ImGui Automation/Test Engine. + - If you are experienced with Dear ImGui and C++, look at the GitHub issues, look at the Wiki, and see how you want to help and can help! + - Disclose your usage of Dear ImGui via a dev blog post, a tweet, a screenshot, a mention somewhere etc. + 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 // intptr_t +#include // vsnprintf, sscanf, printf + +// [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_WIN32_FUNCTIONS) +#define IMGUI_DISABLE_WIN32_FUNCTIONS +#endif +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#ifndef __MINGW32__ +#include // _wfopen, OpenClipboard +#else +#include +#endif +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) // UWP doesn't have all Win32 functions +#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS +#define IMGUI_DISABLE_WIN32_DEFAULT_IME_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 +#pragma warning(disable : 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later +#pragma warning(disable : 5054) // operator '|': deprecated between enumerations of different types +#endif +#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). +#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") +#pragma clang diagnostic ignored \ + "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to + // be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored \ + "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to vsnformat(). yes, + // user passing incorrect format strings can crash the code. +#pragma clang diagnostic ignored \ + "-Wexit-time-destructors" // warning: declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to + // users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. +#pragma clang diagnostic ignored \ + "-Wglobal-constructors" // warning: declaration requires a global destructor // similar to above, not sure what the exact difference is. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#pragma clang diagnostic ignored "-Wformat-pedantic" // warning: format specifies type 'void *' but the argument has type 'xxxx *' // unreasonable, would lead + // to casting every %p arg to void*. probably enabled by -pedantic. +#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning: cast to 'void *' from smaller integer type 'int' +#pragma clang diagnostic ignored \ + "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using + // printf() is a misery with this as C++ va_arg ellipsis changes float to double. +#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#elif defined(__GNUC__) +// We disable -Wpragmas because GCC doesn't provide a has_warning equivalent and some forks/patches may not follow the warning/version association. +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*' +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false +#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 + +// Debug options +#define IMGUI_DEBUG_NAV_SCORING 0 // Display navigation scoring preview when hovering items. Display last moving direction matches when holding CTRL +#define IMGUI_DEBUG_NAV_RECTS 0 // Display the reference navigation rectangle for each window + +// 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 + +// Window resizing from edges (when io.ConfigWindowsResizeFromEdges = true and ImGuiBackendFlags_HasMouseCursors is set in io.BackendFlags by backend) +static const float WINDOWS_HOVER_PADDING = + 4.0f; // Extend outside window for hovering/resizing (maxxed with TouchPadding) and inside windows for borders. Affect FindHoveredWindow(). +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 = ImVec2(16, 10); // Multiplied by g.Style.MouseCursorScale + +//------------------------------------------------------------------------- +// [SECTION] FORWARD DECLARATIONS +//------------------------------------------------------------------------- + +static void SetCurrentWindow(ImGuiWindow *window); +static void FindHoveredWindow(); +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 IO functions +static const char *GetClipboardTextFn_DefaultImpl(void *user_data_ctx); +static void SetClipboardTextFn_DefaultImpl(void *user_data_ctx, const char *text); +static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport *viewport, ImGuiPlatformImeData *data); + +namespace ImGui +{ +// Navigation +static void NavUpdate(); +static void NavUpdateWindowing(); +static void NavUpdateWindowingOverlay(); +static void NavUpdateCancelRequest(); +static void NavUpdateCreateMoveRequest(); +static void NavUpdateCreateTabbingRequest(); +static float NavUpdatePageUpPageDown(); +static inline void NavUpdateAnyRequestFlag(); +static void NavUpdateCreateWrappingRequest(); +static void NavEndFrame(); +static bool NavScoreItem(ImGuiNavItemData *result); +static void NavApplyItemToResult(ImGuiNavItemData *result); +static void NavProcessItem(); +static void NavProcessItemForTabbingRequest(ImGuiID id, ImGuiItemFlags item_flags, ImGuiNavMoveFlags move_flags); +static ImVec2 NavCalcPreferredRefPos(); +static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow *nav_window); +static ImGuiWindow *NavRestoreLastChildNavWindow(ImGuiWindow *window); +static void NavRestoreLayer(ImGuiNavLayer layer); +static void NavRestoreHighlightAfterMove(); +static int FindWindowFocusIndex(ImGuiWindow *window); + +// Error Checking and Debug Tools +static void ErrorCheckNewFrameSanityChecks(); +static void ErrorCheckEndFrameSanityChecks(); +static void UpdateDebugToolItemPicker(); +static void UpdateDebugToolStackQueries(); + +// Inputs +static void UpdateKeyboardInputs(); +static void UpdateMouseInputs(); +static void UpdateMouseWheel(); +static void UpdateKeyRoutingTable(ImGuiKeyRoutingTable *rt); + +// Misc +static void UpdateSettings(); +static bool UpdateWindowManualResize(ImGuiWindow *window, + const ImVec2 &size_auto_fit, + int *border_held, + int resize_grip_count, + ImU32 resize_grip_col[4], + const ImRect &visibility_rect); +static void RenderWindowOuterBorders(ImGuiWindow *window); +static void 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); +static void RenderWindowTitleBarContents(ImGuiWindow *window, const ImRect &title_bar_rect, const char *name, bool *p_open); +static void RenderDimmedBackgroundBehindWindow(ImGuiWindow *window, ImU32 col); +static void RenderDimmedBackgrounds(); + +// Viewports +static void UpdateViewportsNewFrame(); + +} + +//----------------------------------------------------------------------------- +// [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(). +// - Important: Dear ImGui functions are not thread-safe because of this pointer. +// If you want thread-safety to allow N threads to access N different contexts: +// - Change this variable to use thread local storage so each thread can refer to a different context, in your imconfig.h: +// struct ImGuiContext; +// extern thread_local ImGuiContext* MyImGuiTLS; +// #define GImGui MyImGuiTLS +// And then define MyImGuiTLS in one of your cpp files. Note that thread_local is a C++11 keyword, earlier C++ uses compiler-specific keyword. +// - Future development aims to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586 +// - If you need a finite number of contexts, you may compile and use multiple instances of the ImGui code from a different namespace. +// - DLL users: read comments above. +#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. +#ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS +static void *MallocWrapper(size_t size, void *user_data) +{ + IM_UNUSED(user_data); + return malloc(size); +} +static void FreeWrapper(void *ptr, void *user_data) +{ + IM_UNUSED(user_data); + free(ptr); +} +#else +static void *MallocWrapper(size_t size, void *user_data) +{ + IM_UNUSED(user_data); + IM_UNUSED(size); + IM_ASSERT(0); + return NULL; +} +static void FreeWrapper(void *ptr, void *user_data) +{ + IM_UNUSED(user_data); + IM_UNUSED(ptr); + IM_ASSERT(0); +} +#endif +static ImGuiMemAllocFunc GImAllocatorAllocFunc = MallocWrapper; +static ImGuiMemFreeFunc GImAllocatorFreeFunc = FreeWrapper; +static void *GImAllocatorUserData = NULL; + +//----------------------------------------------------------------------------- +// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO) +//----------------------------------------------------------------------------- + +ImGuiStyle::ImGuiStyle() +{ + 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 + WindowRounding = 0.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and + // are not recommended. + WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. + WindowMinSize = ImVec2(32, 32); // Minimum window size + WindowTitleAlign = ImVec2(0.0f, 0.5f); // Alignment for title bar text + WindowMenuButtonPosition = ImGuiDir_Left; // Position of the collapsing/docking button in the title bar (left/right). Defaults to ImGuiDir_Left. + ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows + ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested. + PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows + PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested. + FramePadding = ImVec2(4, 3); // Padding within a framed rectangle (used by most widgets) + FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets). + FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested. + ItemSpacing = ImVec2(8, 4); // Horizontal and vertical spacing between widgets/lines + ItemInnerSpacing = ImVec2(4, 4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) + CellPadding = ImVec2(4, 2); // Padding within a table cell. CellPadding.y may be altered between different rows. + TouchExtraPadding = ImVec2(0, 0); // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't + // sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). + ScrollbarSize = 14.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar + ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar + GrabMinSize = 12.0f; // Minimum width/height of a grab box for slider/scrollbar + GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. + TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. + TabBorderSize = 0.0f; // Thickness of border around tabs. + TabMinWidthForCloseButton = 0.0f; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, + // set to FLT_MAX to never show close button unless selected. + 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. + SeparatorTextBorderSize = 3.0f; // Thickkness of border in SeparatorText() + SeparatorTextAlign = ImVec2(0.0f, 0.5f); // Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). + SeparatorTextPadding = ImVec2(20.0f, 3.f); // Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y + // is recommended to be == FramePadding.y. + DisplayWindowPadding = + ImVec2(19, + 19); // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. + DisplaySafeAreaPadding = + ImVec2(3, + 3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. + MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. + AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. + AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT + // point/nearest filtering). + 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; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or + // BeginItemTooltip()/SetItemTooltip() while using mouse. + HoverFlagsForTooltipNav = + ImGuiHoveredFlags_NoSharedDelay | ImGuiHoveredFlags_DelayNormal; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or + // BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. + + // 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. 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) +{ + WindowPadding = ImFloor(WindowPadding * scale_factor); + WindowRounding = ImFloor(WindowRounding * scale_factor); + WindowMinSize = ImFloor(WindowMinSize * scale_factor); + ChildRounding = ImFloor(ChildRounding * scale_factor); + PopupRounding = ImFloor(PopupRounding * scale_factor); + FramePadding = ImFloor(FramePadding * scale_factor); + FrameRounding = ImFloor(FrameRounding * scale_factor); + ItemSpacing = ImFloor(ItemSpacing * scale_factor); + ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor); + CellPadding = ImFloor(CellPadding * scale_factor); + TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor); + IndentSpacing = ImFloor(IndentSpacing * scale_factor); + ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor); + ScrollbarSize = ImFloor(ScrollbarSize * scale_factor); + ScrollbarRounding = ImFloor(ScrollbarRounding * scale_factor); + GrabMinSize = ImFloor(GrabMinSize * scale_factor); + GrabRounding = ImFloor(GrabRounding * scale_factor); + LogSliderDeadzone = ImFloor(LogSliderDeadzone * scale_factor); + TabRounding = ImFloor(TabRounding * scale_factor); + TabMinWidthForCloseButton = (TabMinWidthForCloseButton != FLT_MAX) ? ImFloor(TabMinWidthForCloseButton * scale_factor) : FLT_MAX; + SeparatorTextPadding = ImFloor(SeparatorTextPadding * scale_factor); + DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor); + DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor); + MouseCursorScale = ImFloor(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; + DisplaySize = ImVec2(-1.0f, -1.0f); + DeltaTime = 1.0f / 60.0f; + IniSavingRate = 5.0f; + 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"; +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + for (int i = 0; i < ImGuiKey_COUNT; i++) + KeyMap[i] = -1; +#endif + UserData = NULL; + + Fonts = NULL; + FontGlobalScale = 1.0f; + FontDefault = NULL; + FontAllowUserScaling = false; + DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + + MouseDoubleClickTime = 0.30f; + MouseDoubleClickMaxDist = 6.0f; + MouseDragThreshold = 6.0f; + KeyRepeatDelay = 0.275f; + KeyRepeatRate = 0.050f; + + // Miscellaneous options + MouseDrawCursor = false; +#ifdef __APPLE__ + ConfigMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag +#else + ConfigMacOSXBehaviors = false; +#endif + ConfigInputTrickleEventQueue = true; + ConfigInputTextCursorBlink = true; + ConfigInputTextEnterKeepActive = false; + ConfigDragClickToInputText = false; + ConfigWindowsResizeFromEdges = true; + ConfigWindowsMoveFromTitleBarOnly = false; + ConfigMemoryCompactTimer = 60.0f; + ConfigDebugBeginReturnValueOnce = false; + ConfigDebugBeginReturnValueLoop = false; + + // Platform Functions + // Note: Initialize() will setup default clipboard/ime handlers. + BackendPlatformName = BackendRendererName = NULL; + BackendPlatformUserData = BackendRendererUserData = BackendLanguageUserData = NULL; + PlatformLocaleDecimalPoint = '.'; + + // Input (NB: we already have memset zero the entire structure!) + MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX); + MouseSource = ImGuiMouseSource_Mouse; + for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) + MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f; + for (int i = 0; i < IM_ARRAYSIZE(KeysData); i++) { + KeysData[i].DownDuration = KeysData[i].DownDurationPrev = -1.0f; + } + AppAcceptingEvents = true; + BackendUsingLegacyKeyArrays = (ImS8)-1; + BackendUsingLegacyNavInputArray = true; // assume using legacy array until proven wrong +} + +// 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 +// FIXME: Should in theory be called "AddCharacterEvent()" to be consistent with new API +void ImGuiIO::AddInputCharacter(unsigned int c) +{ + IM_ASSERT(Ctx != NULL); + ImGuiContext &g = *Ctx; + if (c == 0 || !AppAcceptingEvents) + return; + + ImGuiInputEvent e; + e.Type = ImGuiInputEventType_Text; + e.Source = ImGuiInputSource_Keyboard; + e.EventId = g.InputEventsNextEventId++; + 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) + AddInputCharacter(IM_UNICODE_CODEPOINT_INVALID); + InputQueueSurrogate = c; + return; + } + + ImWchar cp = c; + if (InputQueueSurrogate != 0) { + if ((c & 0xFC00) != 0xDC00) // Invalid low surrogate + { + AddInputCharacter(IM_UNICODE_CODEPOINT_INVALID); + } else { +#if IM_UNICODE_CODEPOINT_MAX == 0xFFFF + cp = IM_UNICODE_CODEPOINT_INVALID; // Codepoint will not fit in ImWchar +#else + cp = (ImWchar)(((InputQueueSurrogate - 0xD800) << 10) + (c - 0xDC00) + 0x10000); +#endif + } + + InputQueueSurrogate = 0; + } + AddInputCharacter((unsigned)cp); +} + +void ImGuiIO::AddInputCharactersUTF8(const char *utf8_chars) +{ + if (!AppAcceptingEvents) + return; + while (*utf8_chars != 0) { + unsigned int c = 0; + utf8_chars += ImTextCharFromUtf8(&c, utf8_chars, NULL); + AddInputCharacter(c); + } +} + +// Clear all incoming events. +void ImGuiIO::ClearEventsQueue() +{ + IM_ASSERT(Ctx != NULL); + ImGuiContext &g = *Ctx; + g.InputEventsQueue.clear(); +} + +// Clear current keyboard/mouse/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons. +void ImGuiIO::ClearInputKeys() +{ +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + memset(KeysDown, 0, sizeof(KeysDown)); +#endif + for (int n = 0; n < IM_ARRAYSIZE(KeysData); n++) { + KeysData[n].Down = false; + KeysData[n].DownDuration = -1.0f; + KeysData[n].DownDurationPrev = -1.0f; + } + KeyCtrl = KeyShift = KeyAlt = KeySuper = false; + KeyMods = ImGuiMod_None; + MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + for (int n = 0; n < IM_ARRAYSIZE(MouseDown); n++) { + MouseDown[n] = false; + MouseDownDuration[n] = MouseDownDurationPrev[n] = -1.0f; + } + MouseWheel = MouseWheelH = 0.0f; + InputQueueCharacters.resize(0); // Behavior of old ClearInputCharacters(). +} + +// 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 +void ImGuiIO::ClearInputCharacters() +{ + InputQueueCharacters.resize(0); +} +#endif + +static ImGuiInputEvent *FindLatestInputEvent(ImGuiContext *ctx, ImGuiInputEventType type, int arg = -1) +{ + ImGuiContext &g = *ctx; + for (int n = g.InputEventsQueue.Size - 1; n >= 0; n--) { + ImGuiInputEvent *e = &g.InputEventsQueue[n]; + if (e->Type != type) + continue; + if (type == ImGuiInputEventType_Key && e->Key.Key != arg) + continue; + if (type == ImGuiInputEventType_MouseButton && e->MouseButton.Button != arg) + continue; + return e; + } + 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. +// - float analog_value: 0.0f..1.0f +// IMPORTANT: THIS FUNCTION AND OTHER "ADD" GRABS THE CONTEXT FROM OUR INSTANCE. +// WE NEED TO ENSURE THAT ALL FUNCTION CALLS ARE FULLFILLING THIS, WHICH IS WHY GetKeyData() HAS AN EXPLICIT CONTEXT. +void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) +{ + // if (e->Down) { IMGUI_DEBUG_LOG_IO("AddKeyEvent() Key='%s' %d, NativeKeycode = %d, NativeScancode = %d\n", ImGui::GetKeyName(e->Key), e->Down, + // e->NativeKeycode, e->NativeScancode); } + IM_ASSERT(Ctx != NULL); + if (key == ImGuiKey_None || !AppAcceptingEvents) + return; + ImGuiContext &g = *Ctx; + IM_ASSERT(ImGui::IsNamedKeyOrModKey( + 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. + IM_ASSERT( + key != ImGuiMod_Shortcut); // We could easily support the translation here but it seems saner to not accept it (TestEngine perform a translation itself) + + // Verify that backend isn't mixing up using new io.AddKeyEvent() api and old io.KeysDown[] + io.KeyMap[] data. +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + IM_ASSERT((BackendUsingLegacyKeyArrays == -1 || BackendUsingLegacyKeyArrays == 0) + && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!"); + if (BackendUsingLegacyKeyArrays == -1) + for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++) + IM_ASSERT(KeyMap[n] == -1 && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!"); + BackendUsingLegacyKeyArrays = 0; +#endif + if (ImGui::IsGamepadKey(key)) + BackendUsingLegacyNavInputArray = false; + + // 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); + const bool latest_key_down = latest_event ? latest_event->Key.Down : key_data->Down; + 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; + e.Source = ImGui::IsGamepadKey(key) ? ImGuiInputSource_Gamepad : ImGuiInputSource_Keyboard; + e.EventId = g.InputEventsNextEventId++; + e.Key.Key = key; + e.Key.Down = down; + 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. +void ImGuiIO::SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native_scancode, int native_legacy_index) +{ + if (key == ImGuiKey_None) + return; + IM_ASSERT(ImGui::IsNamedKey(key)); // >= 512 + IM_ASSERT(native_legacy_index == -1 || ImGui::IsLegacyKey((ImGuiKey)native_legacy_index)); // >= 0 && <= 511 + IM_UNUSED(native_keycode); // Yet unused + IM_UNUSED(native_scancode); // Yet unused + + // Build native->imgui map so old user code can still call key functions with native 0..511 values. +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + const int legacy_key = (native_legacy_index != -1) ? native_legacy_index : native_keycode; + if (!ImGui::IsLegacyKey((ImGuiKey)legacy_key)) + return; + KeyMap[legacy_key] = key; + KeyMap[key] = legacy_key; +#else + IM_UNUSED(key); + IM_UNUSED(native_legacy_index); +#endif +} + +// 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) +{ + IM_ASSERT(Ctx != NULL); + ImGuiContext &g = *Ctx; + if (!AppAcceptingEvents) + return; + + // Apply same flooring as UpdateMouseInputs() + ImVec2 pos((x > -FLT_MAX) ? ImFloorSigned(x) : x, (y > -FLT_MAX) ? ImFloorSigned(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; + e.EventId = g.InputEventsNextEventId++; + e.MousePos.PosX = pos.x; + e.MousePos.PosY = pos.y; + e.MousePos.MouseSource = g.InputEventsNextMouseSource; + g.InputEventsQueue.push_back(e); +} + +void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down) +{ + IM_ASSERT(Ctx != NULL); + ImGuiContext &g = *Ctx; + IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT); + if (!AppAcceptingEvents) + return; + + // 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; + + ImGuiInputEvent e; + e.Type = ImGuiInputEventType_MouseButton; + e.Source = ImGuiInputSource_Mouse; + e.EventId = g.InputEventsNextEventId++; + e.MouseButton.Button = mouse_button; + e.MouseButton.Down = 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; + e.EventId = g.InputEventsNextEventId++; + e.MouseWheel.WheelX = wheel_x; + e.MouseWheel.WheelY = 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) +{ + IM_ASSERT(Ctx != NULL); + ImGuiContext &g = *Ctx; + g.InputEventsNextMouseSource = source; +} + +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); +} + +//----------------------------------------------------------------------------- +// [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() + ImVec2 p_last = p1; + ImVec2 p_closest; + float p_closest_dist2 = FLT_MAX; + float t_step = 1.0f / (float)num_segments; + for (int i_step = 1; i_step <= num_segments; i_step++) { + ImVec2 p_current = ImBezierCubicCalc(p1, p2, p3, p4, t_step * i_step); + ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p); + float dist2 = ImLengthSqr(p - p_line); + if (dist2 < p_closest_dist2) { + p_closest = p_line; + p_closest_dist2 = dist2; + } + p_last = p_current; + } + 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) +{ + float dx = x4 - x1; + float dy = y4 - y1; + float d2 = ((x2 - x4) * dy - (y2 - y4) * dx); + float d3 = ((x3 - x4) * dy - (y3 - y4) * dx); + d2 = (d2 >= 0) ? d2 : -d2; + d3 = (d3 >= 0) ? d3 : -d3; + if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) { + ImVec2 p_current(x4, y4); + ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p); + float dist2 = ImLengthSqr(p - p_line); + if (dist2 < p_closest_dist2) { + p_closest = p_line; + p_closest_dist2 = dist2; + } + p_last = p_current; + } else if (level < 10) { + float x12 = (x1 + x2) * 0.5f, y12 = (y1 + y2) * 0.5f; + float x23 = (x2 + x3) * 0.5f, y23 = (y2 + y3) * 0.5f; + float x34 = (x3 + x4) * 0.5f, y34 = (y3 + y4) * 0.5f; + float x123 = (x12 + x23) * 0.5f, y123 = (y12 + y23) * 0.5f; + float x234 = (x23 + x34) * 0.5f, y234 = (y23 + y34) * 0.5f; + float x1234 = (x123 + x234) * 0.5f, y1234 = (y123 + y234) * 0.5f; + ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); + 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) +{ + IM_ASSERT(tess_tol > 0.0f); + ImVec2 p_last = p1; + ImVec2 p_closest; + float p_closest_dist2 = FLT_MAX; + 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; + ImVec2 ab_dir = b - a; + float dot = ap.x * ab_dir.x + ap.y * ab_dir.y; + if (dot < 0.0f) + return a; + float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y; + if (dot > ab_len_sqr) + 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; + bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f; + 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; + ImVec2 v1 = c - a; + ImVec2 v2 = p - a; + const float denom = v0.x * v1.y - v1.x * v0.y; + out_v = (v2.x * v1.y - v1.x * v2.y) / denom; + 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); + ImVec2 proj_bc = ImLineClosestPoint(b, c, p); + ImVec2 proj_ca = ImLineClosestPoint(c, a, p); + float dist2_ab = ImLengthSqr(p - proj_ab); + float dist2_bc = ImLengthSqr(p - proj_bc); + float dist2_ca = ImLengthSqr(p - proj_ca); + float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca)); + if (m == dist2_ab) + return proj_ab; + if (m == dist2_bc) + 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) +{ + int d; + 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) + return; + if (count > 1) + strncpy(dst, src, count - 1); + dst[count - 1] = 0; +} + +char *ImStrdup(const char *str) +{ + size_t len = strlen(str); + void *buf = IM_ALLOC(len + 1); + return (char *)memcpy(buf, (const void *)str, len + 1); +} + +char *ImStrdupcpy(char *dst, size_t *p_dst_size, const char *src) +{ + size_t dst_buf_size = p_dst_size ? *p_dst_size : strlen(dst) + 1; + size_t src_size = strlen(src) + 1; + if (dst_buf_size < src_size) { + IM_FREE(dst); + dst = (char *)IM_ALLOC(src_size); + if (p_dst_size) + *p_dst_size = src_size; + } + 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 *)memchr(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 + int n = 0; + 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 *)memchr(str, '\n', str_end - str); + return p ? p : str_end; +} + +const ImWchar *ImStrbolW(const ImWchar *buf_mid_line, const ImWchar *buf_begin) // find beginning-of-line +{ + while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n') + 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 + strlen(needle); + + const char un0 = (char)ImToUpper(*needle); + while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end)) { + if (ImToUpper(*haystack) == un0) { + const char *b = needle + 1; + for (const char *a = haystack + 1; b < needle_end; a++, b++) + if (ImToUpper(*a) != ImToUpper(*b)) + break; + if (b == needle_end) + return haystack; + } + haystack++; + } + 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) +{ + char *p = buf; + while (p[0] == ' ' || p[0] == '\t') // Leading blanks + p++; + char *p_start = p; + while (*p != 0) // Find end of string + p++; + while (p > p_start && (p[-1] == ' ' || p[-1] == '\t')) // Trailing blanks + p--; + if (p_start != buf) // Copy memory if we had leading blanks + 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 +// designed using two-passes worst case, which probably could be improved using the stbsp_vsprintfcb() function.) +#ifdef IMGUI_USE_STB_SPRINTF +#ifndef IMGUI_DISABLE_STB_SPRINTF_IMPLEMENTATION +#define STB_SPRINTF_IMPLEMENTATION +#endif +#ifdef IMGUI_STB_SPRINTF_FILENAME +#include IMGUI_STB_SPRINTF_FILENAME +#else +#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; + va_start(args, fmt); +#ifdef IMGUI_USE_STB_SPRINTF + int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args); +#else + int w = vsnprintf(buf, buf_size, fmt, args); +#endif + va_end(args); + if (buf == NULL) + return w; + if (w == -1 || w >= (int)buf_size) + w = (int)buf_size - 1; + buf[w] = 0; + return w; +} + +int ImFormatStringV(char *buf, size_t buf_size, const char *fmt, va_list args) +{ +#ifdef IMGUI_USE_STB_SPRINTF + int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args); +#else + int w = vsnprintf(buf, buf_size, fmt, args); +#endif + if (buf == NULL) + return w; + if (w == -1 || w >= (int)buf_size) + w = (int)buf_size - 1; + buf[w] = 0; + return w; +} +#endif // #ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS + +void ImFormatStringToTempBuffer(const char **out_buf, const char **out_buf_end, const char *fmt, ...) +{ + ImGuiContext &g = *GImGui; + va_list args; + va_start(args, fmt); + if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) { + const char *buf = va_arg(args, const char *); // Skip formatting when using "%s" + *out_buf = buf; + if (out_buf_end) { + *out_buf_end = buf + strlen(buf); + } + } else { + int buf_len = ImFormatStringV(g.TempBuffer.Data, g.TempBuffer.Size, fmt, args); + *out_buf = g.TempBuffer.Data; + if (out_buf_end) { + *out_buf_end = g.TempBuffer.Data + buf_len; + } + } + va_end(args); +} + +void ImFormatStringToTempBufferV(const char **out_buf, const char **out_buf_end, const char *fmt, va_list args) +{ + ImGuiContext &g = *GImGui; + if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) { + const char *buf = va_arg(args, const char *); // Skip formatting when using "%s" + *out_buf = buf; + if (out_buf_end) { + *out_buf_end = buf + strlen(buf); + } + } else { + int buf_len = ImFormatStringV(g.TempBuffer.Data, g.TempBuffer.Size, fmt, args); + *out_buf = g.TempBuffer.Data; + if (out_buf_end) { + *out_buf_end = g.TempBuffer.Data + buf_len; + } + } +} + +// 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: +// - avoid an unnecessary branch/memory tap, - keep the ImHashXXX functions usable by static constructors, - make it thread-safe. +static const ImU32 GCrc32LookupTable[256] = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, + 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, + 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, + 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, + 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, + 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, + 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, + 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, + 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, + 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, + 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, + 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, + 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, + 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, + 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, + 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, + 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D, +}; + +// 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. +ImGuiID ImHashData(const void *data_p, size_t data_size, ImGuiID seed) +{ + ImU32 crc = ~seed; + const unsigned char *data = (const unsigned char *)data_p; + const ImU32 *crc32_lut = GCrc32LookupTable; + while (data_size-- != 0) + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *data++]; + return ~crc; +} + +// 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. +// - If we reach ### in the string we discard the hash so far and reset to the seed. +// - We don't do 'current += 2; continue;' after handling ### to keep the code smaller/faster (measured ~10% diff in Debug build) +// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements. +ImGuiID ImHashStr(const char *data_p, size_t data_size, ImGuiID seed) +{ + seed = ~seed; + ImU32 crc = seed; + const unsigned char *data = (const unsigned char *)data_p; + const ImU32 *crc32_lut = GCrc32LookupTable; + if (data_size != 0) { + while (data_size-- != 0) { + unsigned char c = *data++; + if (c == '#' && data_size >= 2 && data[0] == '#' && data[1] == '#') + crc = seed; + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; + } + } else { + while (unsigned char c = *data++) { + if (c == '#' && data[0] == '#' && data[1] == '#') + crc = seed; + crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; + } + } + 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(__CYGWIN__) && !defined(__GNUC__) + // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames. + // 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); + ImVector buf; + buf.resize(filename_wsize + mode_wsize); + ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, (wchar_t *)&buf[0], filename_wsize); + ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, (wchar_t *)&buf[filename_wsize], mode_wsize); + return ::_wfopen((const wchar_t *)&buf[0], (const wchar_t *)&buf[filename_wsize]); +#else + 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. +void *ImFileLoadToMemory(const char *filename, const char *mode, size_t *out_file_size, int padding_bytes) +{ + 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) { + ImFileClose(f); + return NULL; + } + if (ImFileRead(file_data, 1, file_size, f) != file_size) { + ImFileClose(f); + IM_FREE(file_data); + return NULL; + } + 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. +int ImTextCharFromUtf8(unsigned int *out_char, const char *in_text, const char *in_text_end) +{ + static const char lengths[32] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0}; + static const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; + static const uint32_t mins[] = {0x400000, 0, 0x80, 0x800, 0x10000}; + static const int shiftc[] = {0, 18, 12, 6, 0}; + 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]; + s[0] = in_text + 0 < in_text_end ? in_text[0] : 0; + 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 + e |= ((*out_char >> 11) == 0x1b) << 7; // surrogate half? + e |= (*out_char > IM_UNICODE_CODEPOINT_MAX) << 8; // out of range? + e |= (s[1] & 0xc0) >> 2; + e |= (s[2] & 0xc0) >> 4; + 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. + // One byte is consumed in case of invalid first byte of in_text. + // All available bytes (at most `len` bytes) are consumed on incomplete/invalid second to last bytes. + // Invalid or incomplete input may consume less bytes than wanted, therefore every byte has to be inspected in s. + 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; + ImWchar *buf_end = buf + buf_size; + while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text) { + unsigned int c; + in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); + *buf_out++ = (ImWchar)c; + } + *buf_out = 0; + if (in_text_remaining) + *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; + while ((!in_text_end || in_text < in_text_end) && *in_text) { + unsigned int c; + in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); + char_count++; + } + 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) +{ + if (c < 0x80) { + buf[0] = (char)c; + return 1; + } + if (c < 0x800) { + if (buf_size < 2) + return 0; + buf[0] = (char)(0xc0 + (c >> 6)); + buf[1] = (char)(0x80 + (c & 0x3f)); + return 2; + } + if (c < 0x10000) { + if (buf_size < 3) + return 0; + buf[0] = (char)(0xe0 + (c >> 12)); + buf[1] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[2] = (char)(0x80 + ((c) & 0x3f)); + return 3; + } + if (c <= 0x10FFFF) { + if (buf_size < 4) + return 0; + buf[0] = (char)(0xf0 + (c >> 18)); + buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); + buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[3] = (char)(0x80 + ((c) & 0x3f)); + return 4; + } + // Invalid code point, the max unicode is 0x10FFFF + return 0; +} + +const char *ImTextCharToUtf8(char out_buf[5], unsigned int c) +{ + int count = ImTextCharToUtf8_inline(out_buf, 5, c); + out_buf[count] = 0; + return out_buf; +} + +// 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; + if (c < 0x800) + return 2; + if (c < 0x10000) + return 3; + 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; + const char *buf_end = out_buf + out_buf_size; + while (buf_p < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text) { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + *buf_p++ = (char)c; + else + buf_p += ImTextCharToUtf8_inline(buf_p, (int)(buf_end - buf_p - 1), c); + } + *buf_p = 0; + return (int)(buf_p - out_buf); +} + +int ImTextCountUtf8BytesFromStr(const ImWchar *in_text, const ImWchar *in_text_end) +{ + int bytes_count = 0; + while ((!in_text_end || in_text < in_text_end) && *in_text) { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + bytes_count++; + else + bytes_count += ImTextCountUtf8BytesFromChar(c); + } + return bytes_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; + int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t); + int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t); + 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; + return ImVec4(((in >> IM_COL32_R_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_G_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, + ((in >> IM_COL32_A_SHIFT) & 0xFF) * s); +} + +ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4 &in) +{ + ImU32 out; + out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT; + out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT; + 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) +{ + float K = 0.f; + if (g < b) { + ImSwap(g, b); + K = -1.f; + } + if (r < g) { + 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) +{ + if (s == 0.0f) { + // gray + 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; + case 1: + out_r = q; + out_g = v; + out_b = p; + break; + case 2: + out_r = p; + out_g = v; + out_b = t; + break; + case 3: + out_r = p; + out_g = q; + out_b = v; + break; + case 4: + out_r = t; + out_g = p; + out_b = v; + break; + 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 +static ImGuiStorage::ImGuiStoragePair *LowerBound(ImVector &data, ImGuiID key) +{ + ImGuiStorage::ImGuiStoragePair *first = data.Data; + ImGuiStorage::ImGuiStoragePair *last = data.Data + data.Size; + size_t count = (size_t)(last - first); + while (count > 0) { + size_t count2 = count >> 1; + ImGuiStorage::ImGuiStoragePair *mid = first + count2; + if (mid->key < key) { + first = ++mid; + count -= count2 + 1; + } else { + count = count2; + } + } + return first; +} + +// 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() +{ + struct StaticFunc { + static int IMGUI_CDECL PairComparerByID(const void *lhs, const void *rhs) + { + // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that. + if (((const ImGuiStoragePair *)lhs)->key > ((const ImGuiStoragePair *)rhs)->key) + return +1; + if (((const ImGuiStoragePair *)lhs)->key < ((const ImGuiStoragePair *)rhs)->key) + return -1; + return 0; + } + }; + ImQsort(Data.Data, (size_t)Data.Size, sizeof(ImGuiStoragePair), StaticFunc::PairComparerByID); +} + +int ImGuiStorage::GetInt(ImGuiID key, int default_val) const +{ + ImGuiStoragePair *it = LowerBound(const_cast &>(Data), key); + if (it == Data.end() || it->key != key) + 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 = LowerBound(const_cast &>(Data), key); + if (it == Data.end() || it->key != key) + return default_val; + return it->val_f; +} + +void *ImGuiStorage::GetVoidPtr(ImGuiID key) const +{ + ImGuiStoragePair *it = LowerBound(const_cast &>(Data), key); + if (it == Data.end() || it->key != key) + 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) +{ + ImGuiStoragePair *it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + 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 = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + it = Data.insert(it, ImGuiStoragePair(key, default_val)); + return &it->val_f; +} + +void **ImGuiStorage::GetVoidPtrRef(ImGuiID key, void *default_val) +{ + ImGuiStoragePair *it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) + 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) +{ + ImGuiStoragePair *it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) { + Data.insert(it, ImGuiStoragePair(key, val)); + return; + } + 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 = LowerBound(Data, key); + if (it == Data.end() || it->key != key) { + Data.insert(it, ImGuiStoragePair(key, val)); + return; + } + it->val_f = val; +} + +void ImGuiStorage::SetVoidPtr(ImGuiID key, void *val) +{ + ImGuiStoragePair *it = LowerBound(Data, key); + if (it == Data.end() || it->key != key) { + Data.insert(it, ImGuiStoragePair(key, val)); + return; + } + it->val_p = val; +} + +void ImGuiStorage::SetAllInt(int v) +{ + for (int i = 0; i < Data.Size; i++) + Data[i].val_i = v; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiTextFilter +//----------------------------------------------------------------------------- + +// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" +ImGuiTextFilter::ImGuiTextFilter(const char *default_filter) //-V1077 +{ + InputBuf[0] = 0; + CountGrep = 0; + if (default_filter) { + ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf)); + Build(); + } +} + +bool ImGuiTextFilter::Draw(const char *label, float width) +{ + if (width != 0.0f) + ImGui::SetNextItemWidth(width); + bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf)); + if (value_changed) + Build(); + return value_changed; +} + +void ImGuiTextFilter::ImGuiTextRange::split(char separator, ImVector *out) const +{ + out->resize(0); + const char *wb = b; + const char *we = wb; + while (we < e) { + if (*we == separator) { + out->push_back(ImGuiTextRange(wb, we)); + wb = we + 1; + } + we++; + } + if (wb != we) + out->push_back(ImGuiTextRange(wb, we)); +} + +void ImGuiTextFilter::Build() +{ + Filters.resize(0); + ImGuiTextRange input_range(InputBuf, InputBuf + strlen(InputBuf)); + input_range.split(',', &Filters); + + CountGrep = 0; + for (ImGuiTextRange &f : Filters) { + while (f.b < f.e && ImCharIsBlankA(f.b[0])) + f.b++; + while (f.e > f.b && ImCharIsBlankA(f.e[-1])) + f.e--; + if (f.empty()) + continue; + if (f.b[0] != '-') + CountGrep += 1; + } +} + +bool ImGuiTextFilter::PassFilter(const char *text, const char *text_end) const +{ + if (Filters.empty()) + return true; + + if (text == NULL) + text = ""; + + for (const ImGuiTextRange &f : Filters) { + if (f.empty()) + continue; + if (f.b[0] == '-') { + // Subtract + if (ImStristr(text, text_end, f.b + 1, f.e) != NULL) + return false; + } else { + // Grep + if (ImStristr(text, text_end, f.b, f.e) != NULL) + 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 +#if defined(__GNUC__) || defined(__clang__) +#define va_copy(dest, src) __builtin_va_copy(dest, src) +#else +#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)strlen(str); + + // Add zero-terminator the first time + const int write_off = (Buf.Size != 0) ? Buf.Size : 1; + const int needed_sz = write_off + len; + if (write_off + len >= Buf.Capacity) { + 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; + va_start(args, 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; + if (write_off + len >= Buf.Capacity) { + 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); + if (old_size == new_size) + return; + if (EndOffset == 0 || base[EndOffset - 1] == '\n') + LineOffsets.push_back(EndOffset); + const char *base_end = base + new_size; + for (const char *p = base + old_size; (p = (const char *)memchr(p, '\n', base_end - p)) != 0;) + if (++p < base_end) // Don't push a trailing offset on last \n + LineOffsets.push_back((int)(intptr_t)(p - base)); + EndOffset = ImMax(EndOffset, new_size); +} + +//----------------------------------------------------------------------------- +// [SECTION] ImGuiListClipper +// This is currently not as flexible/powerful as it should be and really confusing/spaghetti, mostly because we changed +// the API mid-way through development and support two ways to using the clipper, needs some rework (see TODO) +//----------------------------------------------------------------------------- + +// 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() +{ + ImGuiContext &g = *GImGui; + return (g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems); +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +// Legacy helper to calculate coarse clipping of large list of evenly sized items. +// This legacy API is not ideal because it assumes we will return a single contiguous rectangle. +// Prefer using ImGuiListClipper which can returns non-contiguous ranges. +void ImGui::CalcListClipping(int items_count, float items_height, int *out_items_display_start, int *out_items_display_end) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + if (g.LogEnabled) { + // If logging is active, do not perform any clipping + *out_items_display_start = 0; + *out_items_display_end = items_count; + return; + } + if (GetSkipItemForListClipping()) { + *out_items_display_start = *out_items_display_end = 0; + return; + } + + // We create the union of the ClipRect and the scoring rect which at worst should be 1 page away from ClipRect + // We don't include g.NavId's rectangle in there (unless g.NavJustMovedToId is set) because the rectangle enlargement can get costly. + ImRect rect = window->ClipRect; + if (g.NavMoveScoringItems) + rect.Add(g.NavScoringNoClipRect); + if (g.NavJustMovedToId && window->NavLastIds[0] == g.NavJustMovedToId) + rect.Add(WindowRectRelToAbs(window, window->NavRectRel[0])); // Could store and use NavJustMovedToRectRel + + const ImVec2 pos = window->DC.CursorPos; + int start = (int)((rect.Min.y - pos.y) / items_height); + int end = (int)((rect.Max.y - pos.y) / items_height); + + // When performing a navigation request, ensure we have one item extra in the direction we are moving to + // FIXME: Verify this works with tabbing + const bool is_nav_request = (g.NavMoveScoringItems && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav); + if (is_nav_request && g.NavMoveClipDir == ImGuiDir_Up) + start--; + if (is_nav_request && g.NavMoveClipDir == ImGuiDir_Down) + end++; + + start = ImClamp(start, 0, items_count); + end = ImClamp(end + 1, start, items_count); + *out_items_display_start = start; + *out_items_display_end = end; +} +#endif + +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++) { + IM_ASSERT(!ranges[i].PosToIndexConvert && !ranges[i - 1].PosToIndexConvert); + if (ranges[i - 1].Max < ranges[i].Min) + continue; + ranges[i - 1].Min = ImMin(ranges[i - 1].Min, ranges[i].Min); + ranges[i - 1].Max = ImMax(ranges[i - 1].Max, ranges[i].Max); + ranges.erase(ranges.Data + i); + 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. + // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue. + // The clipper should probably have a final step to display the last item in a regular manner, maybe with an opt-out flag for data sets which may have + // costly seek? + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + float off_y = pos_y - window->DC.CursorPos.y; + window->DC.CursorPos.y = pos_y; + window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, pos_y - g.Style.ItemSpacing.y); + window->DC.CursorPosPrevLine.y = + window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHereY() can properly function after the end of our clipper usage. + window->DC.PrevLineSize.y = + (line_height - g.Style.ItemSpacing.y); // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth + // step to let user process and display the last item in their list. + if (ImGuiOldColumns *columns = window->DC.CurrentColumns) + columns->LineMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly + if (ImGuiTable *table = g.CurrentTable) { + if (table->IsInsideRow) + 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->RowBgColorCounter += row_increase; + } +} + +static void ImGuiListClipper_SeekCursorForItem(ImGuiListClipper *clipper, int item_n) +{ + // StartPosY starts from ItemsFrozen hence the subtraction + // Perform the add and multiply with double to allow seeking through larger ranges + ImGuiListClipperData *data = (ImGuiListClipperData *)clipper->TempData; + float pos_y = (float)((double)clipper->StartPosY + data->LossynessOffset + (double)(item_n - data->ItemsFrozen) * clipper->ItemsHeight); + ImGuiListClipper_SeekCursorAndSetupPrevLine(pos_y, clipper->ItemsHeight); +} + +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()); + ImGuiListClipperData *data = &g.ClipperTempData[g.ClipperTempDataStacked - 1]; + data->Reset(this); + data->LossynessOffset = window->DC.CursorStartPosLossyness.y; + TempData = data; +} + +void ImGuiListClipper::End() +{ + if (ImGuiListClipperData *data = (ImGuiListClipperData *)TempData) { + // In theory here we should assert that we are already at the right position, but it seems saner to just seek at the end and not assert/crash the user. + ImGuiContext &g = *Ctx; + IMGUI_DEBUG_LOG_CLIPPER("Clipper: End() in '%s'\n", g.CurrentWindow->Name); + if (ItemsCount >= 0 && ItemsCount < INT_MAX && DisplayStart >= 0) + ImGuiListClipper_SeekCursorForItem(this, ItemsCount); + + // Restore temporary buffer and fix back pointers which may be invalidated when nesting + IM_ASSERT(data->ListClipper == this); + data->StepNo = data->Ranges.Size; + if (--g.ClipperTempDataStacked > 0) { + data = &g.ClipperTempData[g.ClipperTempDataStacked - 1]; + data->ListClipper->TempData = data; + } + TempData = NULL; + } + ItemsCount = -1; +} + +void ImGuiListClipper::IncludeItemsByIndex(int item_begin, int item_end) +{ + ImGuiListClipperData *data = (ImGuiListClipperData *)TempData; + IM_ASSERT(DisplayStart < 0); // Only allowed after Begin() and if there has not been a specified range yet. + IM_ASSERT(item_begin <= item_end); + if (item_begin < item_end) + data->Ranges.push_back(ImGuiListClipperRange::FromIndices(item_begin, item_end)); +} + +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) { + clipper->DisplayStart = data->ItemsFrozen; + clipper->DisplayEnd = ImMin(data->ItemsFrozen + 1, clipper->ItemsCount); + if (clipper->DisplayStart < clipper->DisplayEnd) + 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) { + clipper->StartPosY = window->DC.CursorPos.y; + if (clipper->ItemsHeight <= 0.0f) { + // Submit the first item (or range) so we can measure its height (generally the first range is 0..1) + data->Ranges.push_front(ImGuiListClipperRange::FromIndices(data->ItemsFrozen, data->ItemsFrozen + 1)); + clipper->DisplayStart = ImMax(data->Ranges[0].Min, data->ItemsFrozen); + clipper->DisplayEnd = ImMin(data->Ranges[0].Max, clipper->ItemsCount); + data->StepNo = 1; + return true; + } + 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); + if (affected_by_floating_point_precision) + clipper->ItemsHeight = window->DC.PrevLineSize.y + g.Style.ItemSpacing.y; // FIXME: Technically wouldn't allow multi-line entries. + + 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) { + if (g.LogEnabled) { + // If logging is active, do not perform any clipping + data->Ranges.push_back(ImGuiListClipperRange::FromIndices(0, clipper->ItemsCount)); + } else { + // 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.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 + 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(window->ClipRect.Min.y, window->ClipRect.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, + // which with the flooring/ceiling tend to lead to 2 items instead of one being submitted. + for (ImGuiListClipperRange &range : data->Ranges) + if (range.PosToIndexConvert) { + int m1 = (int)(((double)range.Min - window->DC.CursorPos.y - data->LossynessOffset) / clipper->ItemsHeight); + int m2 = (int)((((double)range.Max - window->DC.CursorPos.y - data->LossynessOffset) / clipper->ItemsHeight) + 0.999999f); + range.Min = ImClamp(already_submitted + m1 + range.PosToIndexOffsetMin, already_submitted, clipper->ItemsCount - 1); + range.Max = ImClamp(already_submitted + m2 + range.PosToIndexOffsetMax, range.Min + 1, clipper->ItemsCount); + range.PosToIndexConvert = false; + } + 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) { + clipper->DisplayStart = ImMax(data->Ranges[data->StepNo].Min, already_submitted); + clipper->DisplayEnd = ImMin(data->Ranges[data->StepNo].Max, clipper->ItemsCount); + if (clipper->DisplayStart > already_submitted) //-V1051 + ImGuiListClipper_SeekCursorForItem(clipper, clipper->DisplayStart); + data->StepNo++; + if (clipper->DisplayStart == clipper->DisplayEnd && data->StepNo < data->Ranges.Size) + continue; + 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) + ImGuiListClipper_SeekCursorForItem(clipper, clipper->ItemsCount); + + return false; +} + +bool ImGuiListClipper::Step() +{ + ImGuiContext &g = *Ctx; + bool need_items_height = (ItemsHeight <= 0.0f); + bool ret = ImGuiListClipper_StepInternal(this); + if (ret && (DisplayStart == DisplayEnd)) + ret = false; + if (g.CurrentTable && g.CurrentTable->IsUnfrozenRows == false) + IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): inside frozen table row.\n"); + if (need_items_height && ItemsHeight > 0.0f) + IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): computed ItemsHeight: %.2f.\n", ItemsHeight); + if (ret) { + IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): display %d to %d.\n", DisplayStart, DisplayEnd); + } else { + IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): End.\n"); + End(); + } + 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; + ImVec4 c = style.Colors[idx]; + c.w *= style.Alpha * alpha_mul; + return ColorConvertFloat4ToU32(c); +} + +ImU32 ImGui::GetColorU32(const ImVec4 &col) +{ + ImGuiStyle &style = GImGui->Style; + ImVec4 c = 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) +{ + ImGuiStyle &style = GImGui->Style; + if (style.Alpha >= 1.0f) + return col; + ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT; + a = (ImU32)(a * style.Alpha); // We don't need to clamp 0..255 because Style.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) +{ + ImGuiContext &g = *GImGui; + ImGuiColorMod backup; + backup.Col = idx; + backup.BackupValue = g.Style.Colors[idx]; + g.ColorStack.push_back(backup); + g.Style.Colors[idx] = ColorConvertU32ToFloat4(col); +} + +void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4 &col) +{ + ImGuiContext &g = *GImGui; + ImGuiColorMod backup; + backup.Col = idx; + backup.BackupValue = g.Style.Colors[idx]; + g.ColorStack.push_back(backup); + g.Style.Colors[idx] = col; +} + +void ImGui::PopStyleColor(int count) +{ + ImGuiContext &g = *GImGui; + if (g.ColorStack.Size < count) { + IM_ASSERT_USER_ERROR(g.ColorStack.Size > count, "Calling PopStyleColor() too many times: stack underflow."); + count = g.ColorStack.Size; + } + while (count > 0) { + ImGuiColorMod &backup = g.ColorStack.back(); + g.Style.Colors[backup.Col] = backup.BackupValue; + g.ColorStack.pop_back(); + count--; + } +} + +static const ImGuiDataVarInfo GStyleVarInfo[] = { + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha)}, // ImGuiStyleVar_Alpha + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, DisabledAlpha)}, // ImGuiStyleVar_DisabledAlpha + {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding)}, // ImGuiStyleVar_WindowPadding + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding)}, // ImGuiStyleVar_WindowRounding + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize)}, // ImGuiStyleVar_WindowBorderSize + {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize)}, // ImGuiStyleVar_WindowMinSize + {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowTitleAlign)}, // ImGuiStyleVar_WindowTitleAlign + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildRounding)}, // ImGuiStyleVar_ChildRounding + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildBorderSize)}, // ImGuiStyleVar_ChildBorderSize + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupRounding)}, // ImGuiStyleVar_PopupRounding + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupBorderSize)}, // ImGuiStyleVar_PopupBorderSize + {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding)}, // ImGuiStyleVar_FramePadding + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding)}, // ImGuiStyleVar_FrameRounding + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameBorderSize)}, // ImGuiStyleVar_FrameBorderSize + {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing)}, // ImGuiStyleVar_ItemSpacing + {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing)}, // ImGuiStyleVar_ItemInnerSpacing + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing)}, // ImGuiStyleVar_IndentSpacing + {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, CellPadding)}, // ImGuiStyleVar_CellPadding + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize)}, // ImGuiStyleVar_ScrollbarSize + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding)}, // ImGuiStyleVar_ScrollbarRounding + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize)}, // ImGuiStyleVar_GrabMinSize + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabRounding)}, // ImGuiStyleVar_GrabRounding + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, TabRounding)}, // ImGuiStyleVar_TabRounding + {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign)}, // ImGuiStyleVar_ButtonTextAlign + {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, SelectableTextAlign)}, // ImGuiStyleVar_SelectableTextAlign + {ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, SeparatorTextBorderSize)}, // ImGuiStyleVar_SeparatorTextBorderSize + {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, SeparatorTextAlign)}, // ImGuiStyleVar_SeparatorTextAlign + {ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, SeparatorTextPadding)}, // ImGuiStyleVar_SeparatorTextPadding +}; + +const ImGuiDataVarInfo *ImGui::GetStyleVarInfo(ImGuiStyleVar idx) +{ + IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT); + IM_STATIC_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_COUNT); + return &GStyleVarInfo[idx]; +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) +{ + ImGuiContext &g = *GImGui; + const ImGuiDataVarInfo *var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) { + float *pvar = (float *)var_info->GetVarPtr(&g.Style); + g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT_USER_ERROR(0, "Called PushStyleVar() variant with wrong type!"); +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2 &val) +{ + ImGuiContext &g = *GImGui; + const ImGuiDataVarInfo *var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2) { + ImVec2 *pvar = (ImVec2 *)var_info->GetVarPtr(&g.Style); + g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT_USER_ERROR(0, "Called PushStyleVar() variant with wrong type!"); +} + +void ImGui::PopStyleVar(int count) +{ + ImGuiContext &g = *GImGui; + if (g.StyleVarStack.Size < count) { + IM_ASSERT_USER_ERROR(g.StyleVarStack.Size > count, "Calling PopStyleVar() too many times: stack underflow."); + count = g.StyleVarStack.Size; + } + while (count > 0) { + // We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in Debug is not worth it. + ImGuiStyleMod &backup = g.StyleVarStack.back(); + const ImGuiDataVarInfo *info = GetStyleVarInfo(backup.VarIdx); + void *data = info->GetVarPtr(&g.Style); + if (info->Type == ImGuiDataType_Float && info->Count == 1) { + ((float *)data)[0] = backup.BackupFloat[0]; + } else if (info->Type == ImGuiDataType_Float && info->Count == 2) { + ((float *)data)[0] = backup.BackupFloat[0]; + ((float *)data)[1] = backup.BackupFloat[1]; + } + g.StyleVarStack.pop_back(); + count--; + } +} + +const char *ImGui::GetStyleColorName(ImGuiCol idx) +{ + // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1"; + switch (idx) { + case ImGuiCol_Text: + return "Text"; + case ImGuiCol_TextDisabled: + return "TextDisabled"; + case ImGuiCol_WindowBg: + return "WindowBg"; + case ImGuiCol_ChildBg: + return "ChildBg"; + case ImGuiCol_PopupBg: + return "PopupBg"; + case ImGuiCol_Border: + return "Border"; + case ImGuiCol_BorderShadow: + return "BorderShadow"; + case ImGuiCol_FrameBg: + return "FrameBg"; + case ImGuiCol_FrameBgHovered: + return "FrameBgHovered"; + case ImGuiCol_FrameBgActive: + return "FrameBgActive"; + case ImGuiCol_TitleBg: + return "TitleBg"; + case ImGuiCol_TitleBgActive: + return "TitleBgActive"; + case ImGuiCol_TitleBgCollapsed: + return "TitleBgCollapsed"; + case ImGuiCol_MenuBarBg: + return "MenuBarBg"; + case ImGuiCol_ScrollbarBg: + return "ScrollbarBg"; + case ImGuiCol_ScrollbarGrab: + return "ScrollbarGrab"; + case ImGuiCol_ScrollbarGrabHovered: + return "ScrollbarGrabHovered"; + case ImGuiCol_ScrollbarGrabActive: + return "ScrollbarGrabActive"; + case ImGuiCol_CheckMark: + return "CheckMark"; + case ImGuiCol_SliderGrab: + return "SliderGrab"; + case ImGuiCol_SliderGrabActive: + return "SliderGrabActive"; + case ImGuiCol_Button: + return "Button"; + case ImGuiCol_ButtonHovered: + return "ButtonHovered"; + case ImGuiCol_ButtonActive: + return "ButtonActive"; + case ImGuiCol_Header: + return "Header"; + case ImGuiCol_HeaderHovered: + return "HeaderHovered"; + case ImGuiCol_HeaderActive: + return "HeaderActive"; + case ImGuiCol_Separator: + return "Separator"; + case ImGuiCol_SeparatorHovered: + return "SeparatorHovered"; + case ImGuiCol_SeparatorActive: + return "SeparatorActive"; + case ImGuiCol_ResizeGrip: + return "ResizeGrip"; + case ImGuiCol_ResizeGripHovered: + return "ResizeGripHovered"; + case ImGuiCol_ResizeGripActive: + return "ResizeGripActive"; + case ImGuiCol_Tab: + return "Tab"; + case ImGuiCol_TabHovered: + return "TabHovered"; + case ImGuiCol_TabActive: + return "TabActive"; + case ImGuiCol_TabUnfocused: + return "TabUnfocused"; + case ImGuiCol_TabUnfocusedActive: + return "TabUnfocusedActive"; + case ImGuiCol_PlotLines: + return "PlotLines"; + case ImGuiCol_PlotLinesHovered: + return "PlotLinesHovered"; + case ImGuiCol_PlotHistogram: + return "PlotHistogram"; + case ImGuiCol_PlotHistogramHovered: + return "PlotHistogramHovered"; + case ImGuiCol_TableHeaderBg: + return "TableHeaderBg"; + case ImGuiCol_TableBorderStrong: + return "TableBorderStrong"; + case ImGuiCol_TableBorderLight: + return "TableBorderLight"; + case ImGuiCol_TableRowBg: + return "TableRowBg"; + case ImGuiCol_TableRowBgAlt: + return "TableRowBgAlt"; + case ImGuiCol_TextSelectedBg: + return "TextSelectedBg"; + case ImGuiCol_DragDropTarget: + return "DragDropTarget"; + case ImGuiCol_NavHighlight: + return "NavHighlight"; + case ImGuiCol_NavWindowingHighlight: + return "NavWindowingHighlight"; + case ImGuiCol_NavWindowingDimBg: + return "NavWindowingDimBg"; + case ImGuiCol_ModalWindowDimBg: + return "ModalWindowDimBg"; + } + 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) { + text_display_end = FindRenderedTextEnd(text, text_end); + } else { + if (!text_end) + text_end = text + strlen(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); + if (g.LogEnabled) + 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 + strlen(text); // FIXME-OPT + + if (text != text_end) { + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width); + if (g.LogEnabled) + 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. +// 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) +{ + // 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) { + ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y); + draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect); + } else { + 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 + const char *text_display_end = FindRenderedTextEnd(text, text_end); + 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 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) +{ + 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)); + // 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) { + // Hello wo... + // | | | + // min max ellipsis_max + // <-> this is generally some padding value + + const ImFont *font = draw_list->_Data->Font; + const float font_size = draw_list->_Data->FontSize; + const float font_scale = font_size / font->FontSize; + const char *text_end_ellipsis = NULL; + const float ellipsis_width = font->EllipsisWidth * 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)); + ImVec2 ellipsis_pos = ImFloor(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); + } else { + RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), 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 border, float rounding) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding); + const float border_size = g.Style.FrameBorderSize; + if (border && border_size > 0.0f) { + window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, 0, border_size); + 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; + ImGuiWindow *window = g.CurrentWindow; + const float border_size = g.Style.FrameBorderSize; + if (border_size > 0.0f) { + window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, 0, border_size); + window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size); + } +} + +void ImGui::RenderNavHighlight(const ImRect &bb, ImGuiID id, ImGuiNavHighlightFlags flags) +{ + ImGuiContext &g = *GImGui; + if (id != g.NavId) + return; + if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw)) + return; + ImGuiWindow *window = g.CurrentWindow; + if (window->DC.NavHideHighlightOneFrame) + return; + + float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding; + ImRect display_rect = bb; + display_rect.ClipWith(window->ClipRect); + if (flags & ImGuiNavHighlightFlags_TypeDefault) { + const float THICKNESS = 2.0f; + const float DISTANCE = 3.0f + THICKNESS * 0.5f; + display_rect.Expand(ImVec2(DISTANCE, DISTANCE)); + bool fully_visible = window->ClipRect.Contains(display_rect); + if (!fully_visible) + window->DrawList->PushClipRect(display_rect.Min, display_rect.Max); + window->DrawList->AddRect(display_rect.Min + ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f), + display_rect.Max - ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f), + GetColorU32(ImGuiCol_NavHighlight), + rounding, + 0, + THICKNESS); + if (!fully_visible) + window->DrawList->PopClipRect(); + } + if (flags & ImGuiNavHighlightFlags_TypeThin) { + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, 0, 1.0f); + } +} + +void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill, ImU32 col_border, ImU32 col_shadow) +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(mouse_cursor > ImGuiMouseCursor_None && mouse_cursor < ImGuiMouseCursor_COUNT); + ImFontAtlas *font_atlas = g.DrawListSharedData.Font->ContainerAtlas; + 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. + ImVec2 offset, size, uv[4]; + if (!font_atlas->GetMouseCursorTexData(mouse_cursor, &offset, &size, &uv[0], &uv[2])) + continue; + const ImVec2 pos = base_pos - offset; + const float scale = base_scale; + 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); + draw_list->PopTextureID(); + } +} + +//----------------------------------------------------------------------------- +// [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 + IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this. +#else + 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) +{ + *p_alloc_func = GImAllocatorAllocFunc; + *p_free_func = GImAllocatorFreeFunc; + *p_user_data = GImAllocatorUserData; +} + +ImGuiContext *ImGui::CreateContext(ImFontAtlas *shared_font_atlas) +{ + ImGuiContext *prev_ctx = GetCurrentContext(); + ImGuiContext *ctx = IM_NEW(ImGuiContext)(shared_font_atlas); + SetCurrentContext(ctx); + Initialize(); + if (prev_ctx != NULL) + SetCurrentContext(prev_ctx); // Restore previous context if any, else keep new one. + return ctx; +} + +void ImGui::DestroyContext(ImGuiContext *ctx) +{ + ImGuiContext *prev_ctx = GetCurrentContext(); + if (ctx == NULL) //-V1051 + ctx = prev_ctx; + SetCurrentContext(ctx); + Shutdown(); + SetCurrentContext((prev_ctx != ctx) ? prev_ctx : NULL); + IM_DELETE(ctx); +} + +// IMPORTANT: ###xxx suffixes must be same in ALL languages +static const ImGuiLocEntry GLocalizationEntriesEnUS[] = { + {ImGuiLocKey_VersionStr, "Dear ImGui " IMGUI_VERSION " (" IM_STRINGIFY(IMGUI_VERSION_NUM) ")"}, + {ImGuiLocKey_TableSizeOne, "Size column to fit###SizeOne"}, + {ImGuiLocKey_TableSizeAllFit, "Size all columns to fit###SizeAll"}, + {ImGuiLocKey_TableSizeAllDefault, "Size all columns to default###SizeAll"}, + {ImGuiLocKey_TableResetOrder, "Reset order###ResetOrder"}, + {ImGuiLocKey_WindowingMainMenuBar, "(Main menu bar)"}, + {ImGuiLocKey_WindowingPopup, "(Popup)"}, + {ImGuiLocKey_WindowingUntitled, "(Untitled)"}, +}; + +void ImGui::Initialize() +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(!g.Initialized && !g.SettingsLoaded); + + // Add .ini handle for ImGuiWindow and ImGuiTable types + { + ImGuiSettingsHandler ini_handler; + ini_handler.TypeName = "Window"; + ini_handler.TypeHash = ImHashStr("Window"); + ini_handler.ClearAllFn = WindowSettingsHandler_ClearAll; + ini_handler.ReadOpenFn = WindowSettingsHandler_ReadOpen; + ini_handler.ReadLineFn = WindowSettingsHandler_ReadLine; + ini_handler.ApplyAllFn = WindowSettingsHandler_ApplyAll; + ini_handler.WriteAllFn = WindowSettingsHandler_WriteAll; + AddSettingsHandler(&ini_handler); + } + TableSettingsAddSettingsHandler(); + + // Setup default localization table + LocalizeRegisterEntries(GLocalizationEntriesEnUS, IM_ARRAYSIZE(GLocalizationEntriesEnUS)); + + // Setup default platform clipboard/IME handlers. + g.IO.GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations + g.IO.SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; + g.IO.ClipboardUserData = (void *)&g; // Default implementation use the ImGuiContext as user data (ideally those would be arguments to the function) + g.IO.SetPlatformImeDataFn = SetPlatformImeDataFn_DefaultImpl; + + // Create default viewport + ImGuiViewportP *viewport = IM_NEW(ImGuiViewportP)(); + g.Viewports.push_back(viewport); + g.TempBuffer.resize(1024 * 3 + 1, 0); + +#ifdef IMGUI_HAS_DOCK +#endif + + g.Initialized = true; +} + +// This function is merely here to free heap allocations. +void ImGui::Shutdown() +{ + // 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) + ImGuiContext &g = *GImGui; + if (g.IO.Fonts && g.FontAtlasOwnedByContext) { + g.IO.Fonts->Locked = false; + IM_DELETE(g.IO.Fonts); + } + 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); + + CallContextHooks(&g, ImGuiContextHookType_Shutdown); + + // Clear everything else + g.Windows.clear_delete(); + g.WindowsFocusOrder.clear(); + g.WindowsTempSortBuffer.clear(); + g.CurrentWindow = NULL; + g.CurrentWindowStack.clear(); + g.WindowsById.Clear(); + g.NavWindow = NULL; + g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL; + g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL; + g.MovingWindow = NULL; + + g.KeysRoutingTable.Clear(); + + g.ColorStack.clear(); + g.StyleVarStack.clear(); + g.FontStack.clear(); + g.OpenPopupStack.clear(); + g.BeginPopupStack.clear(); + g.NavTreeNodeStack.clear(); + + 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.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 + if (g.LogFile != stdout) +#endif + ImFileClose(g.LogFile); + g.LogFile = NULL; + } + 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) +{ + ImGuiContext &g = *ctx; + IM_ASSERT(hook->Callback != NULL && hook->HookId == 0 && hook->Type != ImGuiContextHookType_PendingRemoval_); + g.Hooks.push_back(*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) +{ + ImGuiContext &g = *ctx; + IM_ASSERT(hook_id != 0); + for (ImGuiContextHook &hook : g.Hooks) + 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) +{ + ImGuiContext &g = *ctx; + for (ImGuiContextHook &hook : g.Hooks) + 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) +{ + memset(this, 0, sizeof(*this)); + Ctx = ctx; + Name = ImStrdup(name); + NameBufLen = (int)strlen(name) + 1; + ID = ImHashStr(name); + IDStack.push_back(ID); + MoveId = GetID("#MOVE"); + ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); + ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); + AutoFitFramesX = AutoFitFramesY = -1; + AutoPosLastDirection = ImGuiDir_None; + SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = 0; + SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); + LastFrameActive = -1; + LastTimeActive = -1.0f; + FontWindowScale = 1.0f; + SettingsOffset = -1; + DrawList = &DrawListInst; + DrawList->_Data = &Ctx->DrawListSharedData; + DrawList->_OwnerName = Name; + NavPreferredScoringPosRel[0] = NavPreferredScoringPosRel[1] = ImVec2(FLT_MAX, FLT_MAX); +} + +ImGuiWindow::~ImGuiWindow() +{ + IM_ASSERT(DrawList == &DrawListInst); + IM_DELETE(Name); + ColumnsStorage.clear_destruct(); +} + +ImGuiID ImGuiWindow::GetID(const char *str, const char *str_end) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed); + ImGuiContext &g = *Ctx; + if (g.DebugHookIdInfo == id) + ImGui::DebugHookIdInfo(id, ImGuiDataType_String, str, str_end); + return id; +} + +ImGuiID ImGuiWindow::GetID(const void *ptr) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHashData(&ptr, sizeof(void *), seed); + ImGuiContext &g = *Ctx; + if (g.DebugHookIdInfo == id) + ImGui::DebugHookIdInfo(id, ImGuiDataType_Pointer, ptr, NULL); + return id; +} + +ImGuiID ImGuiWindow::GetID(int n) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHashData(&n, sizeof(n), seed); + ImGuiContext &g = *Ctx; + if (g.DebugHookIdInfo == id) + ImGui::DebugHookIdInfo(id, ImGuiDataType_S32, (void *)(intptr_t)n, NULL); + return id; +} + +// This is only used in rare/specific situations to manufacture an ID out of nowhere. +ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect &r_abs) +{ + ImGuiID seed = IDStack.back(); + ImRect r_rel = ImGui::WindowRectAbsToRel(this, r_abs); + ImGuiID id = ImHashData(&r_rel, sizeof(r_rel), seed); + return id; +} + +static void SetCurrentWindow(ImGuiWindow *window) +{ + ImGuiContext &g = *GImGui; + g.CurrentWindow = window; + g.CurrentTable = window && window->DC.CurrentTableIdx != -1 ? g.Tables.GetByIndex(window->DC.CurrentTableIdx) : NULL; + if (window) { + g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); + ImGui::NavUpdateCurrentWindowIsScrollPushableX(); + } +} + +void ImGui::GcCompactTransientMiscBuffers() +{ + ImGuiContext &g = *GImGui; + g.ItemFlagsStack.clear(); + g.GroupStack.clear(); + TableGcCompactSettings(); +} + +// 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) +// This should have no noticeable visual effect. When the window reappear however, expect new allocation/buffer growth/copy cost. +void ImGui::GcCompactTransientWindowBuffers(ImGuiWindow *window) +{ + window->MemoryCompacted = true; + window->MemoryDrawListIdxCapacity = window->DrawList->IdxBuffer.Capacity; + window->MemoryDrawListVtxCapacity = window->DrawList->VtxBuffer.Capacity; + window->IDStack.clear(); + window->DrawList->_ClearFreeMemory(); + window->DC.ChildWindows.clear(); + 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. + // The other buffers tends to amortize much faster. + window->MemoryCompacted = false; + window->DrawList->IdxBuffer.reserve(window->MemoryDrawListIdxCapacity); + 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; + } + + // 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); + } + + // Set active id + g.ActiveIdIsJustActivated = (g.ActiveId != id); + if (g.ActiveIdIsJustActivated) { + IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() old:0x%08X (window \"%s\") -> new:0x%08X (window \"%s\")\n", + g.ActiveId, + g.ActiveIdWindow ? g.ActiveIdWindow->Name : "", + id, + window ? window->Name : ""); + g.ActiveIdTimer = 0.0f; + g.ActiveIdHasBeenPressedBefore = false; + g.ActiveIdHasBeenEditedBefore = false; + g.ActiveIdMouseButton = -1; + if (id != 0) { + g.LastActiveId = id; + g.LastActiveIdTimer = 0.0f; + } + } + g.ActiveId = id; + g.ActiveIdAllowOverlap = false; + g.ActiveIdNoClearOnFocusLoss = false; + g.ActiveIdWindow = window; + g.ActiveIdHasBeenEditedThisFrame = false; + 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; +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + g.ActiveIdUsingNavInputMask = 0x00; +#endif +} + +void ImGui::ClearActiveID() +{ + SetActiveID(0, NULL); // g.ActiveId = 0; +} + +void ImGui::SetHoveredID(ImGuiID id) +{ + ImGuiContext &g = *GImGui; + g.HoveredId = id; + g.HoveredIdAllowOverlap = false; + 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; +} + +// This is called by 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) +{ + ImGuiContext &g = *GImGui; + if (g.ActiveId == id) + g.ActiveIdIsAlive = id; + if (g.ActiveIdPreviousFrame == id) + g.ActiveIdPreviousFrameIsAlive = true; +} + +void ImGui::MarkItemEdited(ImGuiID id) +{ + // This marking is solely to be able to provide info for IsItemDeactivatedAfterEdit(). + // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need to fill the data. + ImGuiContext &g = *GImGui; + if (g.LockMarkEdited > 0) + return; + if (g.ActiveId == id || g.ActiveId == 0) { + g.ActiveIdHasBeenEditedThisFrame = true; + g.ActiveIdHasBeenEditedBefore = 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); + + // 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) + // FIXME-OPT: This could be cached/stored within the window. + ImGuiContext &g = *GImGui; + if (g.NavWindow) + if (ImGuiWindow *focused_root_window = g.NavWindow->RootWindow) + if (focused_root_window->WasActive && focused_root_window != window->RootWindow) { + // For the purpose of those flags we differentiate "standard popup" from "modal popup" + // NB: The 'else' is important because Modal windows are also Popups. + bool want_inhibit = false; + if (focused_root_window->Flags & ImGuiWindowFlags_Modal) + 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; + } + return true; +} + +static inline float CalcDelayFromHoveredFlags(ImGuiHoveredFlags flags) +{ + ImGuiContext &g = *GImGui; + if (flags & ImGuiHoveredFlags_DelayShort) + return g.Style.HoverDelayShort; + if (flags & ImGuiHoveredFlags_DelayNormal) + return g.Style.HoverDelayNormal; + return 0.0f; +} + +// 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 +bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + IM_ASSERT((flags & ~ImGuiHoveredFlags_AllowedMaskForIsItemHovered) == 0 && "Invalid flags for IsItemHovered()!"); + + if (g.NavDisableMouseHover && !g.NavDisableHighlight && !(flags & ImGuiHoveredFlags_NoNavOverride)) { + if ((g.LastItemData.InFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) + return false; + if (!IsItemFocused()) + return false; + + if (flags & ImGuiHoveredFlags_ForTooltip) + flags |= g.Style.HoverFlagsForTooltipNav; + } else { + // Test for bounding box overlap, as updated as ItemAdd() + ImGuiItemStatusFlags status_flags = g.LastItemData.StatusFlags; + if (!(status_flags & ImGuiItemStatusFlags_HoveredRect)) + return false; + + if (flags & ImGuiHoveredFlags_ForTooltip) + flags |= g.Style.HoverFlagsForTooltipMouse; + + IM_ASSERT((flags & (ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_NoPopupHierarchy)) + == 0); // Flags not supported by this function + + // 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) + // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable + // to use IsItemHovered() after EndChild() itself. Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was + // the test that has been running for a long while. + 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 && g.ActiveId != window->MoveId) + 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.InFlags & ImGuiItemFlags_NoWindowHoverableCheck)) + return false; + + // Test if the item is disabled + if ((g.LastItemData.InFlags & 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 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.InFlags & 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); + if (delay > 0.0f || (flags & ImGuiHoveredFlags_Stationary)) { + ImGuiID hover_delay_id = (g.LastItemData.ID != 0) ? g.LastItemData.ID : window->GetIDFromRectangle(g.LastItemData.Rect); + 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(). +// (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: +// - Commonly: if your ItemHoverable() call comes after an ItemAdd() call: pass 'item_flags = g.LastItemData.InFlags'. +// - Rare: otherwise you may pass 'item_flags = 0' (ImGuiItemFlags_None) unless you want to benefit from special behavior handled by ItemHoverable. +bool ImGui::ItemHoverable(const ImRect &bb, ImGuiID id, ImGuiItemFlags item_flags) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + 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) + return false; + + // Done with rectangle culling so we can perform heavier checks now. + if (!(item_flags & ImGuiItemFlags_NoWindowHoverableCheck) && !IsWindowContentHoverable(window, ImGuiHoveredFlags_None)) { + g.HoveredIdDisabled = 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) { + // 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) { + g.HoveredIdAllowOverlap = true; + if (g.HoveredIdPreviousFrame != id) + return false; + } + } + + // When disabled we'll return false but still set HoveredId + if (item_flags & ImGuiItemFlags_Disabled) { + // Release active id if turning disabled + if (g.ActiveId == id && id != 0) + ClearActiveID(); + g.HoveredIdDisabled = true; + return false; + } + + if (id != 0) { + // [DEBUG] Item Picker tool! + // We perform the check here because SetHoveredID() is not frequently called (1~ time a frame), making + // the cost of this tool near-zero. We can get slightly better call-stack and support picking non-hovered + // items if we performed the test in ItemAdd(), but that would incur a small runtime cost. + if (g.DebugItemPickerActive && g.HoveredIdPreviousFrame == id) + GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255)); + if (g.DebugItemPickerBreakId == id) + IM_DEBUG_BREAK(); + } + + if (g.NavDisableMouseHover) + return false; + + return true; +} + +// FIXME: This is inlined/duplicated in ItemAdd() +bool ImGui::IsClippedEx(const ImRect &bb, ImGuiID id) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + if (!bb.Overlaps(window->ClipRect)) + if (id == 0 || (id != g.ActiveId && id != g.NavId)) + if (!g.LogEnabled) + 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 in_flags, ImGuiItemStatusFlags item_flags, const ImRect &item_rect) +{ + ImGuiContext &g = *GImGui; + g.LastItemData.ID = item_id; + g.LastItemData.InFlags = in_flags; + g.LastItemData.StatusFlags = item_flags; + g.LastItemData.Rect = g.LastItemData.NavRect = item_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) { + // We could decide to setup a default wrapping max point for auto-resizing windows, + // or have auto-wrap (with unspecified wrapping pos) behave as a ContentSize extending function? + // if (window->Hidden && (window->Flags & ImGuiWindowFlags_AlwaysAutoResize)) + // wrap_pos_x = ImMax(window->WorkRect.Min.x + g.FontSize * 10.0f, window->WorkRect.Max.x); + // else + wrap_pos_x = window->WorkRect.Max.x; + } else if (wrap_pos_x > 0.0f) { + 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) +{ + if (ImGuiContext *ctx = GImGui) + ctx->IO.MetricsActiveAllocations++; + return (*GImAllocatorAllocFunc)(size, GImAllocatorUserData); +} + +// IM_FREE() == ImGui::MemFree() +void ImGui::MemFree(void *ptr) +{ + if (ptr) + if (ImGuiContext *ctx = GImGui) + ctx->IO.MetricsActiveAllocations--; + return (*GImAllocatorFreeFunc)(ptr, GImAllocatorUserData); +} + +const char *ImGui::GetClipboardText() +{ + ImGuiContext &g = *GImGui; + return g.IO.GetClipboardTextFn ? g.IO.GetClipboardTextFn(g.IO.ClipboardUserData) : ""; +} + +void ImGui::SetClipboardText(const char *text) +{ + ImGuiContext &g = *GImGui; + if (g.IO.SetClipboardTextFn) + g.IO.SetClipboardTextFn(g.IO.ClipboardUserData, 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; +} + +// Pass this to your backend rendering function! Valid after Render() and until the next call to NewFrame() +ImDrawData *ImGui::GetDrawData() +{ + ImGuiContext &g = *GImGui; + 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 + ImGuiContext &g = *GImGui; + IM_ASSERT(drawlist_no < IM_ARRAYSIZE(viewport->BgFgDrawLists)); + ImDrawList *draw_list = viewport->BgFgDrawLists[drawlist_no]; + if (draw_list == NULL) { + draw_list = IM_NEW(ImDrawList)(&g.DrawListSharedData); + 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->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false); + viewport->BgFgDrawListsLastFrame[drawlist_no] = g.FrameCount; + } + return draw_list; +} + +ImDrawList *ImGui::GetBackgroundDrawList(ImGuiViewport *viewport) +{ + return GetViewportBgFgDrawList((ImGuiViewportP *)viewport, 0, "##Background"); +} + +ImDrawList *ImGui::GetBackgroundDrawList() +{ + ImGuiContext &g = *GImGui; + return GetBackgroundDrawList(g.Viewports[0]); +} + +ImDrawList *ImGui::GetForegroundDrawList(ImGuiViewport *viewport) +{ + return GetViewportBgFgDrawList((ImGuiViewportP *)viewport, 1, "##Foreground"); +} + +ImDrawList *ImGui::GetForegroundDrawList() +{ + ImGuiContext &g = *GImGui; + return GetForegroundDrawList(g.Viewports[0]); +} + +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. + // We _also_ call this when clicking in a window empty space when io.ConfigWindowsMoveFromTitleBarOnly is set, but clear g.MovingWindow afterward. + // This is because we want ActiveId to be set even when the window is not permitted to move. + ImGuiContext &g = *GImGui; + FocusWindow(window); + SetActiveID(window->MoveId, window); + g.NavDisableHighlight = true; + g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindow->Pos; + g.ActiveIdNoClearOnFocusLoss = true; + SetActiveIdUsingAllKeyboardKeys(); + + bool can_move_window = true; + if ((window->Flags & ImGuiWindowFlags_NoMove) || (window->RootWindow->Flags & ImGuiWindowFlags_NoMove)) + can_move_window = false; + if (can_move_window) + g.MovingWindow = window; +} + +// 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 synched with g.ActiveId == g.MovingWindow->MoveId. +// This is currently enforced by the fact that BeginDragDropSource() is setting all g.ActiveIdUsingXXXX flags to inhibit navigation inputs, +// but if we should more thoroughly test cases where g.ActiveId or g.MovingWindow gets changed and not the other. +void ImGui::UpdateMouseMovingWindowNewFrame() +{ + ImGuiContext &g = *GImGui; + if (g.MovingWindow != NULL) { + // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window). + // We track it to preserve Focus and so that generally ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency. + KeepAliveID(g.ActiveId); + IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow); + ImGuiWindow *moving_window = g.MovingWindow->RootWindow; + if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos)) { + ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset; + SetWindowPos(moving_window, pos, ImGuiCond_Always); + FocusWindow(g.MovingWindow); + } else { + g.MovingWindow = NULL; + ClearActiveID(); + } + } else { + // When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent hovering others. + if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId) { + KeepAliveID(g.ActiveId); + if (!g.IO.MouseDown[0]) + ClearActiveID(); + } + } +} + +// Initiate moving window when clicking on empty space or title bar. +// Handle left-click and right-click focus. +void ImGui::UpdateMouseMovingWindowEndFrame() +{ + ImGuiContext &g = *GImGui; + if (g.ActiveId != 0 || g.HoveredId != 0) + 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) + if (g.IO.MouseClicked[0]) { + // Handle the edge case of a popup being closed while clicking in its empty space. + // 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 + + // Cancel moving if clicked outside of title bar + if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(root_window->Flags & ImGuiWindowFlags_NoTitleBar)) + 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 (note that we know HoveredId == 0 already) + if (g.HoveredIdDisabled) + g.MovingWindow = NULL; + } else if (root_window == NULL && g.NavWindow != NULL) { + // Clicking on void disable focus + 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) + if (g.IO.MouseClicked[1]) { + // Find the top-most window between HoveredWindow and the top-most Modal Window. + // This is where we can trim the popup stack. + ImGuiWindow *modal = GetTopMostPopupModal(); + bool hovered_window_above_modal = g.HoveredWindow && (modal == NULL || IsWindowAbove(g.HoveredWindow, modal)); + ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal, true); + } +} + +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() +{ + ImGuiContext &g = *GImGui; + ImGuiIO &io = g.IO; + g.WindowsHoverPadding = ImMax(g.Style.TouchExtraPadding, ImVec2(WINDOWS_HOVER_PADDING, WINDOWS_HOVER_PADDING)); + + // 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; + FindHoveredWindow(); + + // Modal windows prevents mouse from hovering behind them. + ImGuiWindow *modal_window = GetTopMostPopupModal(); + if (modal_window && g.HoveredWindow && !IsWindowWithinBeginStackOf(g.HoveredWindow->RootWindow, modal_window)) + clear_hovered_windows = true; + + // Disabled mouse? + 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); + const bool has_open_modal = (modal_window != NULL); + int mouse_earliest_down = -1; + bool mouse_any_down = false; + for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) { + if (io.MouseClicked[i]) { + io.MouseDownOwned[i] = (g.HoveredWindow != NULL) || has_open_popup; + io.MouseDownOwnedUnlessPopupClose[i] = (g.HoveredWindow != NULL) || has_open_modal; + } + mouse_any_down |= io.MouseDown[i]; + if (io.MouseDown[i]) + if (mouse_earliest_down == -1 || io.MouseClickedTime[i] < io.MouseClickedTime[mouse_earliest_down]) + mouse_earliest_down = i; + } + 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) { + io.WantCaptureMouse = io.WantCaptureMouseUnlessPopupClose = (g.WantCaptureMouseNextFrame != 0); + } else { + 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) + if (g.WantCaptureKeyboardNextFrame != -1) + io.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0); + else + io.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL); + if (io.NavActive && (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && !(io.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard)) + io.WantCaptureKeyboard = true; + + // 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; +} + +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 + ErrorCheckNewFrameSanityChecks(); + + // 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(); + + // Setup current font and draw list shared data + g.IO.Fonts->Locked = true; + SetCurrentFont(GetDefaultFont()); + IM_ASSERT(g.Font->IsLoaded()); + ImRect virtual_space(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); + for (ImGuiViewportP *viewport : g.Viewports) + virtual_space.Add(viewport->GetMainRect()); + g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4(); + g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; + g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError); + g.DrawListSharedData.InitialFlags = ImDrawListFlags_None; + if (g.Style.AntiAliasedLines) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines; + if (g.Style.AntiAliasedLinesUseTex && !(g.Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedLines)) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex; + if (g.Style.AntiAliasedFill) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill; + if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset; + + // Mark rendering data as invalid to prevent user who may have a handle on it to use it. + for (ImGuiViewportP *viewport : g.Viewports) + 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); + + // Update HoveredId data + if (!g.HoveredIdPreviousFrame) + g.HoveredIdTimer = 0.0f; + if (!g.HoveredIdPreviousFrame || (g.HoveredId && g.ActiveId == g.HoveredId)) + g.HoveredIdNotActiveTimer = 0.0f; + if (g.HoveredId) + g.HoveredIdTimer += g.IO.DeltaTime; + if (g.HoveredId && g.ActiveId != g.HoveredId) + g.HoveredIdNotActiveTimer += g.IO.DeltaTime; + g.HoveredIdPreviousFrame = g.HoveredId; + g.HoveredId = 0; + g.HoveredIdAllowOverlap = false; + g.HoveredIdDisabled = 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. + if (g.ActiveId != 0 && g.ActiveIdIsAlive != g.ActiveId && g.ActiveIdPreviousFrame == g.ActiveId) { + 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; + g.LastActiveIdTimer += g.IO.DeltaTime; + g.ActiveIdPreviousFrame = g.ActiveId; + g.ActiveIdPreviousFrameWindow = g.ActiveIdWindow; + g.ActiveIdPreviousFrameHasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore; + g.ActiveIdIsAlive = 0; + g.ActiveIdHasBeenEditedThisFrame = false; + g.ActiveIdPreviousFrameIsAlive = false; + g.ActiveIdIsJustActivated = false; + if (g.TempInputId != 0 && g.ActiveId != g.TempInputId) + g.TempInputId = 0; + if (g.ActiveId == 0) { + g.ActiveIdUsingNavDirMask = 0x00; + g.ActiveIdUsingAllKeyboardKeys = false; +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + g.ActiveIdUsingNavInputMask = 0x00; +#endif + } + +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + if (g.ActiveId == 0) + g.ActiveIdUsingNavInputMask = 0; + else if (g.ActiveIdUsingNavInputMask != 0) { + // If your custom widget code used: { g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel); } + // Since IMGUI_VERSION_NUM >= 18804 it should be: { SetKeyOwner(ImGuiKey_Escape, g.ActiveId); SetKeyOwner(ImGuiKey_NavGamepadCancel, g.ActiveId); } + if (g.ActiveIdUsingNavInputMask & (1 << ImGuiNavInput_Cancel)) + SetKeyOwner(ImGuiKey_Escape, g.ActiveId); + if (g.ActiveIdUsingNavInputMask & ~(1 << ImGuiNavInput_Cancel)) + IM_ASSERT(0); // Other values unsupported + } +#endif + + // 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. + if (g.HoverItemDelayId != 0 && g.MouseStationaryTimer >= g.Style.HoverStationaryDelay) + g.HoverItemUnlockedStationaryId = g.HoverItemDelayId; + else if (g.HoverItemDelayId == 0) + g.HoverItemUnlockedStationaryId = 0; + if (g.HoveredWindow != NULL && g.MouseStationaryTimer >= g.Style.HoverStationaryDelay) + 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) { + g.HoverItemDelayTimer += g.IO.DeltaTime; + g.HoverItemDelayClearTimer = 0.0f; + g.HoverItemDelayId = 0; + } else if (g.HoverItemDelayTimer > 0.0f) { + // This gives a little bit of leeway before clearing the hover timer, allowing mouse to cross gaps + // We could expose 0.25f as style.HoverClearDelay but I am not sure of the logic yet, this is particularly subtle. + g.HoverItemDelayClearTimer += g.IO.DeltaTime; + 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; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + g.DragDropWithinSource = false; + g.DragDropWithinTarget = false; + g.DragDropHoldJustPressedId = 0; + + // 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 gamepad/keyboard navigation + NavUpdate(); + + // Update mouse input state + UpdateMouseInputs(); + + // Find hovered window + // (needs to be before UpdateMouseMovingWindowNewFrame so we fill g.HoveredWindowUnderMovingWindow on the mouse release frame) + UpdateHoveredWindowAndCaptureFlags(); + + // 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; + + // Mouse wheel scrolling, scale + UpdateMouseWheel(); + + // 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; + for (ImGuiWindow *window : g.Windows) { + window->WasActive = window->Active; + window->Active = false; + 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); + } + + // 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) + TableGcCompactTransientBuffers(g.Tables.GetByIndex(i)); + for (ImGuiTableTempData &table_temp_data : g.TablesTempData) + if (table_temp_data.LastTimeActive >= 0.0f && table_temp_data.LastTimeActive < memory_compact_start_time) + TableGcCompactTransientBuffers(&table_temp_data); + 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); + g.BeginPopupStack.resize(0); + g.ItemFlagsStack.resize(0); + g.ItemFlagsStack.push_back(ImGuiItemFlags_None); + g.GroupStack.resize(0); + + // [DEBUG] Update debug features + UpdateDebugToolItemPicker(); + UpdateDebugToolStackQueries(); + if (g.DebugLocateFrames > 0 && --g.DebugLocateFrames == 0) + g.DebugLocateId = 0; + if (g.DebugLogClipperAutoDisableFrames > 0 && --g.DebugLogClipperAutoDisableFrames == 0) { + DebugLog("(Auto-disabled ImGuiDebugLogFlags_EventClipper to avoid spamming)\n"); + g.DebugLogFlags &= ~ImGuiDebugLogFlags_EventClipper; + } + + // 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. + g.WithinFrameScopeWithImplicitWindow = true; + SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver); + Begin("Debug##Default"); + IM_ASSERT(g.CurrentWindow->IsFallbackWindow == true); + + // [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. + if (g.IO.ConfigDebugBeginReturnValueLoop) + g.DebugBeginReturnValueCullDepth = + (g.DebugBeginReturnValueCullDepth == -1) ? 0 : ((g.DebugBeginReturnValueCullDepth + ((g.FrameCount % 4) == 0 ? 1 : 0)) % 10); + else + g.DebugBeginReturnValueCullDepth = -1; + + 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) +{ + const ImGuiWindow *const a = *(const ImGuiWindow *const *)lhs; + const ImGuiWindow *const b = *(const ImGuiWindow *const *)rhs; + if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup)) + return d; + if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip)) + return d; + return (a->BeginOrderWithinParent - b->BeginOrderWithinParent); +} + +static void AddWindowToSortBuffer(ImVector *out_sorted_windows, ImGuiWindow *window) +{ + out_sorted_windows->push_back(window); + if (window->Active) { + int count = window->DC.ChildWindows.Size; + ImQsort(window->DC.ChildWindows.Data, (size_t)count, sizeof(ImGuiWindow *), ChildWindowComparer); + for (int i = 0; i < count; i++) { + ImGuiWindow *child = window->DC.ChildWindows[i]; + if (child->Active) + AddWindowToSortBuffer(out_sorted_windows, child); + } + } +} + +static void AddWindowToDrawData(ImGuiWindow *window, int layer) +{ + ImGuiContext &g = *GImGui; + ImGuiViewportP *viewport = g.Viewports[0]; + g.IO.MetricsRenderWindows++; + if (window->DrawList->_Splitter._Count > 1) + window->DrawList->ChannelsMerge(); // Merge if user forgot to merge back. Also required in Docking branch for ImGuiWindowFlags_DockNodeHost windows. + ImGui::AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[layer], window->DrawList); + for (ImGuiWindow *child : window->DC.ChildWindows) + 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; + int full_size = n; + for (int i = 1; i < IM_ARRAYSIZE(builder->Layers); i++) + full_size += builder->Layers[i]->Size; + builder->Layers[0]->resize(full_size); + for (int layer_n = 1; layer_n < IM_ARRAYSIZE(builder->Layers); layer_n++) { + ImVector *layer = builder->Layers[layer_n]; + if (layer->empty()) + continue; + memcpy(builder->Layers[0]->Data + n, layer->Data, layer->Size * sizeof(ImDrawList *)); + n += layer->Size; + layer->resize(0); + } +} + +static void InitViewportDrawData(ImGuiViewportP *viewport) +{ + ImGuiIO &io = ImGui::GetIO(); + ImDrawData *draw_data = &viewport->DrawDataP; + + 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); + + draw_data->Valid = true; + draw_data->CmdListsCount = 0; + draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0; + draw_data->DisplayPos = viewport->Pos; + draw_data->DisplaySize = viewport->Size; + draw_data->FramebufferScale = io.DisplayFramebufferScale; + draw_data->OwnerViewport = viewport; +} + +// 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. +void ImGui::PushClipRect(const ImVec2 &clip_rect_min, const ImVec2 &clip_rect_max, bool intersect_with_current_clip_rect) +{ + ImGuiWindow *window = GetCurrentWindow(); + 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 void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow *window, ImU32 col) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + ImGuiViewportP *viewport = (ImGuiViewportP *)GetMainViewport(); + ImRect viewport_rect = viewport->GetMainRect(); + + // Draw behind window by moving the draw command at the FRONT of the draw list + { + // We've already called AddWindowToDrawData() which called DrawList->ChannelsMerge() on DockNodeHost windows, + // and draw list have been trimmed already, hence the explicit recreation of a draw command if missing. + // FIXME: This is creating complication, might be simpler if we could inject a drawlist in drawdata at a given position and not attempt to manipulate + // ImDrawCmd order. + ImDrawList *draw_list = window->RootWindow->DrawList; + if (draw_list->CmdBuffer.Size == 0) + draw_list->AddDrawCmd(); + draw_list->PushClipRect(viewport_rect.Min - ImVec2(1, 1), + viewport_rect.Max + ImVec2(1, 1), + false); // FIXME: Need to stricty ensure ImDrawCmd are not merged (ElemCount==6 checks below will verify that) + draw_list->AddRectFilled(viewport_rect.Min, viewport_rect.Max, col); + ImDrawCmd cmd = draw_list->CmdBuffer.back(); + IM_ASSERT(cmd.ElemCount == 6); + draw_list->CmdBuffer.pop_back(); + draw_list->CmdBuffer.push_front(cmd); + 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(); + } +} + +ImGuiWindow *ImGui::FindBottomMostVisibleWindowWithinBeginStack(ImGuiWindow *parent_window) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *bottom_most_visible_window = parent_window; + for (int i = FindWindowDisplayIndex(parent_window); i >= 0; i--) { + ImGuiWindow *window = g.Windows[i]; + if (window->Flags & ImGuiWindowFlags_ChildWindow) + continue; + if (!IsWindowWithinBeginStackOf(window, parent_window)) + break; + if (IsWindowActiveAndVisible(window) && GetWindowDisplayLayer(window) <= GetWindowDisplayLayer(parent_window)) + bottom_most_visible_window = window; + } + return bottom_most_visible_window; +} + +static void ImGui::RenderDimmedBackgrounds() +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *modal_window = GetTopMostAndVisiblePopupModal(); + if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f) + return; + const bool dim_bg_for_modal = (modal_window != NULL); + const bool dim_bg_for_window_list = (g.NavWindowingTargetAnim != NULL && g.NavWindowingTargetAnim->Active); + if (!dim_bg_for_modal && !dim_bg_for_window_list) + return; + + if (dim_bg_for_modal) { + // Draw dimming behind modal or a begin stack child, whichever comes first in draw order. + ImGuiWindow *dim_behind_window = FindBottomMostVisibleWindowWithinBeginStack(modal_window); + RenderDimmedBackgroundBehindWindow(dim_behind_window, GetColorU32(ImGuiCol_ModalWindowDimBg, g.DimBgRatio)); + } else if (dim_bg_for_window_list) { + // Draw dimming behind CTRL+Tab target window + RenderDimmedBackgroundBehindWindow(g.NavWindowingTargetAnim, GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio)); + + // Draw border around CTRL+Tab target window + ImGuiWindow *window = g.NavWindowingTargetAnim; + ImGuiViewport *viewport = GetMainViewport(); + float distance = g.FontSize; + ImRect bb = window->Rect(); + bb.Expand(distance); + if (bb.GetWidth() >= viewport->Size.x && bb.GetHeight() >= viewport->Size.y) + bb.Expand(-distance - 1.0f); // If a window fits the entire viewport, adjust its highlight inward + 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->PopClipRect(); + } +} + +// 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()?"); + + CallContextHooks(&g, ImGuiContextHookType_EndFramePre); + + ErrorCheckEndFrameSanityChecks(); + + // 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.IO.SetPlatformImeDataFn && memcmp(ime_data, &g.PlatformImeDataPrev, sizeof(ImGuiPlatformImeData)) != 0) { + IMGUI_DEBUG_LOG_IO("[io] Calling io.SetPlatformImeDataFn(): WantVisible: %d, InputPos (%.2f,%.2f)\n", + ime_data->WantVisible, + ime_data->InputPos.x, + ime_data->InputPos.y); + ImGuiViewport *viewport = GetMainViewport(); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (viewport->PlatformHandleRaw == NULL && g.IO.ImeWindowHandle != NULL) { + viewport->PlatformHandleRaw = g.IO.ImeWindowHandle; + g.IO.SetPlatformImeDataFn(viewport, ime_data); + viewport->PlatformHandleRaw = NULL; + } else +#endif + { + g.IO.SetPlatformImeDataFn(viewport, ime_data); + } + } + + // 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(); + + // Drag and Drop: Elapse payload (if delivered, or if source stops being submitted) + if (g.DragDropActive) { + bool is_delivered = g.DragDropPayload.Delivery; + bool is_elapsed = (g.DragDropPayload.DataFrameCount + 1 < g.FrameCount) + && ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceAutoExpirePayload) || !IsMouseDown(g.DragDropMouseButton)); + if (is_delivered || is_elapsed) + ClearDragDrop(); + } + + // Drag and Drop: Fallback for source tooltip. This is not ideal but better than nothing. + if (g.DragDropActive && g.DragDropSourceFrameCount < g.FrameCount && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) { + g.DragDropWithinSource = true; + SetTooltip("..."); + g.DragDropWithinSource = false; + } + + // End frame + g.WithinFrameScope = false; + g.FrameCountEnded = g.FrameCount; + + // Initiate moving window + handle left-click and right-click focus + UpdateMouseMovingWindowEndFrame(); + + // 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); + g.WindowsTempSortBuffer.reserve(g.Windows.Size); + for (ImGuiWindow *window : g.Windows) { + if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it + 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; + + // Unlock font atlas + g.IO.Fonts->Locked = false; + + // Clear Input data for next frame + 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: +// 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); + + // Draw modal/window whitening backgrounds + RenderDimmedBackgrounds(); + + // Add background ImDrawList (for each active viewport) + for (ImGuiViewportP *viewport : g.Viewports) { + InitViewportDrawData(viewport); + if (viewport->BgFgDrawLists[0] != NULL) + AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0], GetBackgroundDrawList(viewport)); + } + + // 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->RootWindow : NULL; + windows_to_render_top_most[1] = (g.NavWindowingTarget ? g.NavWindowingListWindow : NULL); + for (ImGuiWindow *window : g.Windows) { + IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'" + if (IsWindowActiveAndVisible(window) && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0 && window != windows_to_render_top_most[0] + && window != windows_to_render_top_most[1]) + AddRootWindowToDrawData(window); + } + 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; + } + + 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_FLOOR(text_size.x + 0.99999f); + + return text_size; +} + +// Find window given position, search front-to-back +// FIXME: Note that we have an inconsequential lag here: OuterRectClipped is updated in Begin(), so windows moved programmatically +// with SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is +// called, aka before the next Begin(). Moving window isn't affected. +static void FindHoveredWindow() +{ + ImGuiContext &g = *GImGui; + + ImGuiWindow *hovered_window = NULL; + ImGuiWindow *hovered_window_ignoring_moving_window = NULL; + if (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoMouseInputs)) + hovered_window = g.MovingWindow; + + ImVec2 padding_regular = g.Style.TouchExtraPadding; + ImVec2 padding_for_resize = g.IO.ConfigWindowsResizeFromEdges ? g.WindowsHoverPadding : padding_regular; + for (int i = g.Windows.Size - 1; i >= 0; i--) { + ImGuiWindow *window = g.Windows[i]; + IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer. + if (!window->Active || window->Hidden) + continue; + if (window->Flags & ImGuiWindowFlags_NoMouseInputs) + continue; + + // Using the clipped AABB, a child window will typically be clipped by its parent (not always) + ImRect bb(window->OuterRectClipped); + if (window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) + bb.Expand(padding_regular); + else + bb.Expand(padding_for_resize); + if (!bb.Contains(g.IO.MousePos)) + 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) { + ImVec2 hole_pos(window->Pos.x + (float)window->HitTestHoleOffset.x, window->Pos.y + (float)window->HitTestHoleOffset.y); + ImVec2 hole_size((float)window->HitTestHoleSize.x, (float)window->HitTestHoleSize.y); + if (ImRect(hole_pos, hole_pos + hole_size).Contains(g.IO.MousePos)) + continue; + } + + if (hovered_window == NULL) + hovered_window = window; + IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer. + if (hovered_window_ignoring_moving_window == NULL && (!g.MovingWindow || window->RootWindow != g.MovingWindow->RootWindow)) + hovered_window_ignoring_moving_window = window; + if (hovered_window && hovered_window_ignoring_moving_window) + break; + } + + g.HoveredWindow = hovered_window; + g.HoveredWindowUnderMovingWindow = hovered_window_ignoring_moving_window; +} + +bool ImGui::IsItemActive() +{ + ImGuiContext &g = *GImGui; + if (g.ActiveId) + return g.ActiveId == g.LastItemData.ID; + return false; +} + +bool ImGui::IsItemActivated() +{ + ImGuiContext &g = *GImGui; + if (g.ActiveId) + if (g.ActiveId == g.LastItemData.ID && g.ActiveIdPreviousFrame != g.LastItemData.ID) + return true; + return false; +} + +bool ImGui::IsItemDeactivated() +{ + ImGuiContext &g = *GImGui; + if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDeactivated) + return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Deactivated) != 0; + return (g.ActiveIdPreviousFrame == g.LastItemData.ID && g.ActiveIdPreviousFrame != 0 && g.ActiveId != g.LastItemData.ID); +} + +bool ImGui::IsItemDeactivatedAfterEdit() +{ + ImGuiContext &g = *GImGui; + return IsItemDeactivated() && (g.ActiveIdPreviousFrameHasBeenEditedBefore || (g.ActiveId == 0 && g.ActiveIdHasBeenEditedBefore)); +} + +// == GetItemID() == GetFocusID() +bool ImGui::IsItemFocused() +{ + ImGuiContext &g = *GImGui; + if (g.NavId != g.LastItemData.ID || g.NavId == 0) + 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; +} + +bool ImGui::IsItemToggledSelection() +{ + ImGuiContext &g = *GImGui; + return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledSelection) ? true : false; +} + +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.NavDisableHighlight; +} + +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. +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. +void ImGui::SetItemAllowOverlap() +{ + ImGuiContext &g = *GImGui; + ImGuiID id = g.LastItemData.ID; + if (g.HoveredId == id) + g.HoveredIdAllowOverlap = true; + if (g.ActiveId == id) // Before we made this obsolete, most calls to SetItemAllowOverlap() used to avoid this path by testing g.ActiveId != id. + g.ActiveIdAllowOverlap = true; +} +#endif + +// FIXME: It might be undesirable that this will likely disable KeyOwner-aware shortcuts systems. Consider a more fine-tuned version for the two users of this +// function. +void ImGui::SetActiveIdUsingAllKeyboardKeys() +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(g.ActiveId != 0); + g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_COUNT) - 1; + 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(); +} + +bool ImGui::BeginChildEx(const char *name, ImGuiID id, const ImVec2 &size_arg, bool border, ImGuiWindowFlags flags) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *parent_window = g.CurrentWindow; + + flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_ChildWindow; + flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag + + // Size + const ImVec2 content_avail = GetContentRegionAvail(); + ImVec2 size = ImFloor(size_arg); + const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00); + if (size.x <= 0.0f) + size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too many issues) + if (size.y <= 0.0f) + size.y = ImMax(content_avail.y + size.y, 4.0f); + SetNextWindowSize(size); + + // 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. + const char *temp_window_name; + if (name) + 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); + + const float backup_border_size = g.Style.ChildBorderSize; + if (!border) + g.Style.ChildBorderSize = 0.0f; + bool ret = Begin(temp_window_name, NULL, flags); + g.Style.ChildBorderSize = backup_border_size; + + ImGuiWindow *child_window = g.CurrentWindow; + child_window->ChildId = id; + child_window->AutoFitChildAxises = (ImS8)auto_fit_axises; + + // 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 navigatable items or (B) it can be scrolled. + const ImGuiID temp_id_for_activation = ImHashStr("##Child", 0, id); + if (g.ActiveId == temp_id_for_activation) + ClearActiveID(); + if (g.NavActivateId == id && !(flags & ImGuiWindowFlags_NavFlattened) + && (child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavWindowHasScrollY)) { + FocusWindow(child_window); + NavInitWindow(child_window, false); + SetActiveID(temp_id_for_activation, child_window); // Steal ActiveId with another arbitrary id so that key-press won't activate child item + g.ActiveIdSource = g.NavInputSource; + } + return ret; +} + +bool ImGui::BeginChild(const char *str_id, const ImVec2 &size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + ImGuiWindow *window = GetCurrentWindow(); + return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags); +} + +bool ImGui::BeginChild(ImGuiID id, const ImVec2 &size_arg, bool border, ImGuiWindowFlags extra_flags) +{ + IM_ASSERT(id != 0); + return BeginChildEx(NULL, id, size_arg, border, extra_flags); +} + +void ImGui::EndChild() +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + + IM_ASSERT(g.WithinEndChild == false); + IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() calls + + g.WithinEndChild = true; + if (window->BeginCount > 1) { + End(); + } else { + ImVec2 sz = window->Size; + if (window->AutoFitChildAxises & (1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f + sz.x = ImMax(4.0f, sz.x); + if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y)) + sz.y = ImMax(4.0f, sz.y); + End(); + + ImGuiWindow *parent_window = g.CurrentWindow; + ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz); + ItemSize(sz); + if ((window->DC.NavLayersActiveMask != 0 || window->DC.NavWindowHasScrollY) && !(window->Flags & ImGuiWindowFlags_NavFlattened)) { + ItemAdd(bb, window->ChildId); + RenderNavHighlight(bb, 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 (window->DC.NavLayersActiveMask == 0 && window == g.NavWindow) + RenderNavHighlight(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId, ImGuiNavHighlightFlags_TypeThin); + } else { + // Not navigable into + ItemAdd(bb, 0); + + // But when flattened we directly reach items, adjust active layer mask accordingly + if (window->Flags & ImGuiWindowFlags_NavFlattened) + parent_window->DC.NavLayersActiveMaskNext |= window->DC.NavLayersActiveMaskNext; + } + if (g.HoveredWindow == window) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + } + g.WithinEndChild = false; + g.LogLinePosY = -FLT_MAX; // To enforce a carriage return +} + +// Helper to create a child window / scrolling region that looks like a normal widget frame. +bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2 &size, ImGuiWindowFlags extra_flags) +{ + ImGuiContext &g = *GImGui; + const ImGuiStyle &style = g.Style; + PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]); + PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding); + PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize); + PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); + bool ret = BeginChild(id, size, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags); + PopStyleVar(3); + PopStyleColor(); + return ret; +} + +void ImGui::EndChildFrame() +{ + EndChild(); +} + +static void SetWindowConditionAllowFlags(ImGuiWindow *window, ImGuiCond flags, bool enabled) +{ + window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags); + window->SetWindowSizeAllowFlags = enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags); + window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~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) +{ + window->Pos = ImFloor(ImVec2(settings->Pos.x, settings->Pos.y)); + if (settings->Size.x > 0 && settings->Size.y > 0) + window->Size = window->SizeFull = ImFloor(ImVec2(settings->Size.x, settings->Size.y)); + window->Collapsed = settings->Collapsed; +} + +static void 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) { + IM_ASSERT(!g.WindowsFocusOrder.contains(window)); + g.WindowsFocusOrder.push_back(window); + window->FocusOrder = (short)(g.WindowsFocusOrder.Size - 1); + } else if (!just_created && child_flag_changed && new_is_explicit_child) { + IM_ASSERT(g.WindowsFocusOrder[window->FocusOrder] == window); + for (int n = window->FocusOrder + 1; n < g.WindowsFocusOrder.Size; n++) + g.WindowsFocusOrder[n]->FocusOrder--; + g.WindowsFocusOrder.erase(g.WindowsFocusOrder.Data + window->FocusOrder); + window->FocusOrder = -1; + } + window->IsExplicitChild = new_is_explicit_child; +} + +static void InitOrLoadWindowSettings(ImGuiWindow *window, ImGuiWindowSettings *settings) +{ + // Initial window state with e.g. default/arbitrary window position + // Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. + const ImGuiViewport *main_viewport = ImGui::GetMainViewport(); + window->Pos = main_viewport->Pos + ImVec2(60, 60); + window->SetWindowPosAllowFlags = window->SetWindowSizeAllowFlags = window->SetWindowCollapsedAllowFlags = + 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; + window->AutoFitOnlyGrows = false; + } else { + if (window->Size.x <= 0.0f) + window->AutoFitFramesX = 2; + if (window->Size.y <= 0.0f) + window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); + } +} + +static ImGuiWindow *CreateNewWindow(const char *name, ImGuiWindowFlags flags) +{ + // Create window the first time + // IMGUI_DEBUG_LOG("CreateNewWindow '%s', flags = 0x%08X\n", name, flags); + ImGuiContext &g = *GImGui; + 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 ImVec2 CalcWindowSizeAfterConstraint(ImGuiWindow *window, const ImVec2 &size_desired) +{ + ImGuiContext &g = *GImGui; + ImVec2 new_size = size_desired; + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint) { + // Using -1,-1 on either X/Y axis to preserve the current size. + ImRect cr = g.NextWindowData.SizeConstraintRect; + new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x; + new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y; + if (g.NextWindowData.SizeCallback) { + ImGuiSizeCallbackData data; + data.UserData = g.NextWindowData.SizeCallbackUserData; + data.Pos = window->Pos; + data.CurrentSize = window->SizeFull; + data.DesiredSize = new_size; + g.NextWindowData.SizeCallback(&data); + new_size = data.DesiredSize; + } + new_size.x = IM_FLOOR(new_size.x); + new_size.y = IM_FLOOR(new_size.y); + } + + // Minimum size + if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) { + ImGuiWindow *window_for_height = window; + new_size = ImMax(new_size, g.Style.WindowMinSize); + const float minimum_height = window_for_height->TitleBarHeight() + window_for_height->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f); + new_size.y = ImMax(new_size.y, minimum_height); // Reduce artifacts with very small windows + } + return new_size; +} + +static void CalcWindowContentSizes(ImGuiWindow *window, ImVec2 *content_size_current, ImVec2 *content_size_ideal) +{ + bool preserve_old_content_sizes = false; + if (window->Collapsed && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) + preserve_old_content_sizes = true; + else if (window->Hidden && window->HiddenFramesCannotSkipItems == 0 && window->HiddenFramesCanSkipItems > 0) + preserve_old_content_sizes = true; + if (preserve_old_content_sizes) { + *content_size_current = window->ContentSize; + *content_size_ideal = window->ContentSizeIdeal; + return; + } + + content_size_current->x = + (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : IM_FLOOR(window->DC.CursorMaxPos.x - window->DC.CursorStartPos.x); + content_size_current->y = + (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : IM_FLOOR(window->DC.CursorMaxPos.y - window->DC.CursorStartPos.y); + content_size_ideal->x = (window->ContentSizeExplicit.x != 0.0f) + ? window->ContentSizeExplicit.x + : IM_FLOOR(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_FLOOR(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; + ImGuiStyle &style = g.Style; + const float decoration_w_without_scrollbars = window->DecoOuterSizeX1 + window->DecoOuterSizeX2 - window->ScrollbarSizes.x; + 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); + if (window->Flags & ImGuiWindowFlags_Tooltip) { + // Tooltip always resize + return size_desired; + } else { + // Maximum window size is determined by the viewport size or monitor size + const bool is_popup = (window->Flags & ImGuiWindowFlags_Popup) != 0; + const bool is_menu = (window->Flags & ImGuiWindowFlags_ChildMenu) != 0; + ImVec2 size_min = style.WindowMinSize; + if (is_popup || is_menu) // Popups and menus bypass style.WindowMinSize by default, but we give then a non-zero minimum size to facilitate understanding + // problematic cases (e.g. empty popups) + size_min = ImMin(size_min, ImVec2(4.0f, 4.0f)); + + ImVec2 avail_size = ImGui::GetMainViewport()->WorkSize; + ImVec2 size_auto_fit = ImClamp(size_desired, size_min, ImMax(size_min, avail_size - style.DisplaySafeAreaPadding * 2.0f)); + + // 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); + bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x - size_pad.x - decoration_w_without_scrollbars < size_contents.x + && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) + || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar); + bool will_have_scrollbar_y = (size_auto_fit_after_constraint.y - size_pad.y - decoration_h_without_scrollbars < size_contents.y + && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) + || (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar); + if (will_have_scrollbar_x) + size_auto_fit.y += style.ScrollbarSize; + if (will_have_scrollbar_y) + size_auto_fit.x += style.ScrollbarSize; + return size_auto_fit; + } +} + +ImVec2 ImGui::CalcWindowNextAutoFitSize(ImGuiWindow *window) +{ + ImVec2 size_contents_current; + ImVec2 size_contents_ideal; + CalcWindowContentSizes(window, &size_contents_current, &size_contents_ideal); + ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, size_contents_ideal); + ImVec2 size_final = CalcWindowSizeAfterConstraint(window, size_auto_fit); + return size_final; +} + +static ImGuiCol GetWindowBgColorIdx(ImGuiWindow *window) +{ + if (window->Flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) + return ImGuiCol_PopupBg; + if (window->Flags & ImGuiWindowFlags_ChildWindow) + 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 + ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right + ImVec2 size_expected = pos_max - pos_min; + ImVec2 size_constrained = CalcWindowSizeAfterConstraint(window, size_expected); + *out_pos = pos_min; + if (corner_norm.x == 0.0f) + out_pos->x -= (size_constrained.x - size_expected.x); + if (corner_norm.y == 0.0f) + out_pos->y -= (size_constrained.y - size_expected.y); + *out_size = size_constrained; +} + +// Data for resizing from corner +struct ImGuiResizeGripDef { + ImVec2 CornerPosN; + ImVec2 InnerDir; + int AngleMin12, AngleMax12; +}; +static const ImGuiResizeGripDef resize_grip_def[4] = { + {ImVec2(1, 1), ImVec2(-1, -1), 0, 3}, // Lower-right + {ImVec2(0, 1), ImVec2(+1, -1), 3, 6}, // Lower-left + {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 { + ImVec2 InnerDir; + ImVec2 SegmentN1, SegmentN2; + float OuterAngle; +}; +static const ImGuiResizeBorderDef resize_border_def[4] = { + {ImVec2(+1, 0), ImVec2(0, 1), ImVec2(0, 0), IM_PI * 1.00f}, // Left + {ImVec2(-1, 0), ImVec2(1, 0), ImVec2(1, 1), IM_PI * 0.00f}, // Right + {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(); + if (thickness == 0.0f) + rect.Max -= ImVec2(1, 1); + if (border_n == ImGuiDir_Left) { + return ImRect(rect.Min.x - thickness, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding); + } + if (border_n == ImGuiDir_Right) { + return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x + thickness, rect.Max.y - perp_padding); + } + if (border_n == ImGuiDir_Up) { + return ImRect(rect.Min.x + perp_padding, rect.Min.y - thickness, rect.Max.x - perp_padding, rect.Min.y + thickness); + } + if (border_n == ImGuiDir_Down) { + return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y + thickness); + } + IM_ASSERT(0); + return ImRect(); +} + +// 0..3: corners (Lower-right, Lower-left, Unused, Unused) +ImGuiID ImGui::GetWindowResizeCornerID(ImGuiWindow *window, int n) +{ + IM_ASSERT(n >= 0 && n < 4); + ImGuiID id = window->ID; + id = ImHashStr("#RESIZE", 0, id); + id = ImHashData(&n, sizeof(int), id); + return id; +} + +// Borders (Left, Right, Up, Down) +ImGuiID ImGui::GetWindowResizeBorderID(ImGuiWindow *window, ImGuiDir dir) +{ + IM_ASSERT(dir >= 0 && dir < 4); + int n = (int)dir + 4; + ImGuiID id = window->ID; + id = ImHashStr("#RESIZE", 0, id); + 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 bool ImGui::UpdateWindowManualResize(ImGuiWindow *window, + const ImVec2 &size_auto_fit, + 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) + return false; + if (window->WasActive == false) // Early out to avoid running this code for e.g. a hidden implicit/fallback Debug window. + return false; + + bool ret_auto_fit = false; + const int resize_border_count = g.IO.ConfigWindowsResizeFromEdges ? 4 : 0; + const float grip_draw_size = IM_FLOOR(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f)); + const float grip_hover_inner_size = IM_FLOOR(grip_draw_size * 0.75f); + const float grip_hover_outer_size = g.IO.ConfigWindowsResizeFromEdges ? WINDOWS_HOVER_PADDING : 0.0f; + + 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); + + // 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); + if (resize_rect.Min.x > resize_rect.Max.x) + ImSwap(resize_rect.Min.x, resize_rect.Max.x); + if (resize_rect.Min.y > resize_rect.Max.y) + ImSwap(resize_rect.Min.y, resize_rect.Max.y); + ImGuiID resize_grip_id = window->GetID(resize_grip_n); // == GetWindowResizeCornerID() + ItemAdd(resize_rect, resize_grip_id, NULL, ImGuiItemFlags_NoNav); + ButtonBehavior(resize_rect, resize_grip_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus); + // GetForegroundDrawList(window)->AddRect(resize_rect.Min, resize_rect.Max, IM_COL32(255, 255, 0, 255)); + if (hovered || held) + g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; + + if (held && g.IO.MouseClickedCount[0] == 2 && resize_grip_n == 0) { + // Manual auto-fit when double-clicking + size_target = CalcWindowSizeAfterConstraint(window, size_auto_fit); + ret_auto_fit = true; + ClearActiveID(); + } else if (held) { + // Resize from any of the four corners + // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position + ImVec2 clamp_min = ImVec2(def.CornerPosN.x == 1.0f ? clamp_rect.Min.x : -FLT_MAX, + (def.CornerPosN.y == 1.0f || (def.CornerPosN.y == 0.0f && window_move_from_title_bar)) ? clamp_rect.Min.y : -FLT_MAX); + ImVec2 clamp_max = ImVec2(def.CornerPosN.x == 0.0f ? clamp_rect.Max.x : +FLT_MAX, def.CornerPosN.y == 0.0f ? clamp_rect.Max.y : +FLT_MAX); + ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + + ImLerp(def.InnerDir * grip_hover_outer_size, + def.InnerDir * -grip_hover_inner_size, + def.CornerPosN); // Corner of the window corresponding to our corner grip + 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); + } + for (int border_n = 0; border_n < resize_border_count; border_n++) { + 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, WINDOWS_HOVER_PADDING); + ImGuiID border_id = window->GetID(border_n + 4); // == GetWindowResizeBorderID() + ItemAdd(border_rect, border_id, NULL, ImGuiItemFlags_NoNav); + ButtonBehavior(border_rect, border_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus); + // GetForegroundDrawLists(window)->AddRect(border_rect.Min, border_rect.Max, IM_COL32(255, 255, 0, 255)); + if ((hovered && g.HoveredIdTimer > WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER) || held) { + g.MouseCursor = (axis == ImGuiAxis_X) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS; + if (held) + *border_held = border_n; + } + if (held) { + 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); + ImVec2 border_target = window->Pos; + border_target[axis] = g.IO.MousePos[axis] - g.ActiveIdClickOffset[axis] + WINDOWS_HOVER_PADDING; + border_target = ImClamp(border_target, clamp_min, clamp_max); + CalcResizePosSizeFromAnyCorner(window, border_target, ImMin(def.SegmentN1, def.SegmentN2), &pos_target, &size_target); + } + } + 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. + if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindow == window) { + ImVec2 nav_resize_dir; + if (g.NavInputSource == ImGuiInputSource_Keyboard && g.IO.KeyShift) + nav_resize_dir = GetKeyMagnitude2d(ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow); + if (g.NavInputSource == ImGuiInputSource_Gamepad) + nav_resize_dir = GetKeyMagnitude2d(ImGuiKey_GamepadDpadLeft, ImGuiKey_GamepadDpadRight, ImGuiKey_GamepadDpadUp, ImGuiKey_GamepadDpadDown); + if (nav_resize_dir.x != 0.0f || nav_resize_dir.y != 0.0f) { + const float NAV_RESIZE_SPEED = 600.0f; + const float resize_step = NAV_RESIZE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y); + g.NavWindowingAccumDeltaSize += nav_resize_dir * resize_step; + g.NavWindowingAccumDeltaSize = + ImMax(g.NavWindowingAccumDeltaSize, + clamp_rect.Min - window->Pos - window->Size); // We need Pos+Size >= clmap_rect.Min, so Size >= clmap_rect.Min - Pos, so size_delta >= + // clmap_rect.Min - window->Pos - window->Size + g.NavWindowingToggleLayer = false; + g.NavDisableMouseHover = true; + resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive); + ImVec2 accum_floored = ImFloor(g.NavWindowingAccumDeltaSize); + if (accum_floored.x != 0.0f || accum_floored.y != 0.0f) { + // FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints properly, right now a constraint will make us + // stuck. + size_target = CalcWindowSizeAfterConstraint(window, window->SizeFull + accum_floored); + g.NavWindowingAccumDeltaSize -= accum_floored; + } + } + } + + // Apply back modified position/size to window + if (size_target.x != FLT_MAX) { + window->SizeFull = size_target; + MarkIniSettingsDirty(window); + } + if (pos_target.x != FLT_MAX) { + window->Pos = ImFloor(pos_target); + MarkIniSettingsDirty(window); + } + + window->Size = window->SizeFull; + return ret_auto_fit; +} + +static inline void ClampWindowPos(ImGuiWindow *window, const ImRect &visibility_rect) +{ + ImGuiContext &g = *GImGui; + ImVec2 size_for_clamping = window->Size; + if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) + size_for_clamping.y = window->TitleBarHeight(); + window->Pos = ImClamp(window->Pos, visibility_rect.Min - size_for_clamping, visibility_rect.Max); +} + +static void ImGui::RenderWindowOuterBorders(ImGuiWindow *window) +{ + ImGuiContext &g = *GImGui; + float rounding = window->WindowRounding; + float border_size = window->WindowBorderSize; + if (border_size > 0.0f && !(window->Flags & ImGuiWindowFlags_NoBackground)) + window->DrawList->AddRect(window->Pos, window->Pos + window->Size, GetColorU32(ImGuiCol_Border), rounding, 0, border_size); + + int border_held = window->ResizeBorderHeld; + if (border_held != -1) { + const ImGuiResizeBorderDef &def = resize_border_def[border_held]; + ImRect border_r = GetResizeBorderRect(window, border_held, rounding, 0.0f); + window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN1) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, + rounding, + def.OuterAngle - IM_PI * 0.25f, + def.OuterAngle); + 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(GetColorU32(ImGuiCol_SeparatorActive), 0, ImMax(2.0f, border_size)); // Thicker than usual + } + if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) { + float y = window->Pos.y + window->TitleBarHeight() - 1; + window->DrawList->AddLine(ImVec2(window->Pos.x + border_size, y), + ImVec2(window->Pos.x + window->Size.x - border_size, y), + GetColorU32(ImGuiCol_Border), + 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) +{ + 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; + + // 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; + const float window_border_size = window->WindowBorderSize; + if (window->Collapsed) { + // Title bar only + const float backup_border_size = style.FrameBorderSize; + g.Style.FrameBorderSize = window->WindowBorderSize; + ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed); + RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding); + g.Style.FrameBorderSize = backup_border_size; + } else { + // Window background + if (!(flags & ImGuiWindowFlags_NoBackground)) { + ImU32 bg_col = GetColorU32(GetWindowBgColorIdx(window)); + bool override_alpha = false; + float alpha = 1.0f; + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasBgAlpha) { + alpha = g.NextWindowData.BgAlphaVal; + override_alpha = true; + } + if (override_alpha) + bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT); + window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), + window->Pos + window->Size, + bg_col, + window_rounding, + (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom); + } + + // Title bar + if (!(flags & ImGuiWindowFlags_NoTitleBar)) { + ImU32 title_bar_col = GetColorU32(title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); + window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawFlags_RoundCornersTop); + } + + // Menu bar + if (flags & ImGuiWindowFlags_MenuBar) { + ImRect menu_bar_rect = window->MenuBarRect(); + menu_bar_rect.ClipWith( + window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. + window->DrawList->AddRectFilled(menu_bar_rect.Min + ImVec2(window_border_size, 0), + menu_bar_rect.Max - ImVec2(window_border_size, 0), + GetColorU32(ImGuiCol_MenuBarBg), + (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, + ImDrawFlags_RoundCornersTop); + if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) + window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + } + + // 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)) { + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) { + const ImU32 col = resize_grip_col[resize_grip_n]; + if ((col & IM_COL32_A_MASK) == 0) + continue; + const ImGuiResizeGripDef &grip = resize_grip_def[resize_grip_n]; + const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPosN); + window->DrawList->PathLineTo( + corner + + grip.InnerDir + * ((resize_grip_n & 1) ? ImVec2(window_border_size, resize_grip_draw_size) : ImVec2(resize_grip_draw_size, window_border_size))); + window->DrawList->PathLineTo( + corner + + grip.InnerDir + * ((resize_grip_n & 1) ? ImVec2(resize_grip_draw_size, window_border_size) : ImVec2(window_border_size, resize_grip_draw_size))); + window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), + corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), + window_rounding, + grip.AngleMin12, + grip.AngleMax12); + window->DrawList->PathFillConvex(col); + } + } + + // Borders + if (handle_borders_and_resize_grips) + RenderWindowOuterBorders(window); + } +} + +// Render title text, collapse button, close button +void ImGui::RenderWindowTitleBarContents(ImGuiWindow *window, const ImRect &title_bar_rect, const char *name, bool *p_open) +{ + 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; + float pad_r = style.FramePadding.x; + float button_sz = g.FontSize; + ImVec2 close_button_pos; + ImVec2 collapse_button_pos; + if (has_close_button) { + close_button_pos = ImVec2(title_bar_rect.Max.x - pad_r - button_sz, title_bar_rect.Min.y + style.FramePadding.y); + pad_r += button_sz + style.ItemInnerSpacing.x; + } + if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Right) { + collapse_button_pos = ImVec2(title_bar_rect.Max.x - pad_r - button_sz, title_bar_rect.Min.y + style.FramePadding.y); + pad_r += button_sz + style.ItemInnerSpacing.x; + } + if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Left) { + 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)) + window->WantCollapseToggle = true; // Defer actual collapsing to next frame as we are too far in the Begin() function + + // Close button + if (has_close_button) + if (CloseButton(window->GetID("#CLOSE"), close_button_pos)) + *p_open = false; + + 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) + pad_l += g.Style.ItemInnerSpacing.x; + if (pad_r > style.FramePadding.x) + pad_r += g.Style.ItemInnerSpacing.x; + if (style.WindowTitleAlign.x > 0.0f && style.WindowTitleAlign.x < 1.0f) { + float centerness = ImSaturate(1.0f - ImFabs(style.WindowTitleAlign.x - 0.5f) * 2.0f); // 0.0f on either edges, 1.0f on center + float pad_extend = ImMin(ImMax(pad_l, pad_r), title_bar_rect.GetWidth() - pad_l - pad_r - text_size.x); + 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) { + ImVec2 marker_pos; + marker_pos.x = ImClamp(layout_r.Min.x + (layout_r.GetWidth() - text_size.x) * style.WindowTitleAlign.x + text_size.x, layout_r.Min.x, layout_r.Max.x); + marker_pos.y = (layout_r.Min.y + layout_r.Max.y) * 0.5f; + if (marker_pos.x > layout_r.Min.x) { + RenderBullet(window->DrawList, marker_pos, GetColorU32(ImGuiCol_Text)); + clip_r.Max.x = ImMin(clip_r.Max.x, marker_pos.x - (int)(marker_size_x * 0.5f)); + } + } + // if (g.IO.KeyShift) window->DrawList->AddRect(layout_r.Min, layout_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG] + // 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; + window->RootWindow = window->RootWindowPopupTree = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window; + if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) + window->RootWindow = parent_window->RootWindow; + if (parent_window && (flags & ImGuiWindowFlags_Popup)) + window->RootWindowPopupTree = parent_window->RootWindowPopupTree; + if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) + window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight; + while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened) { + IM_ASSERT(window->RootWindowForNav->ParentWindow != NULL); + window->RootWindowForNav = window->RootWindowForNav->ParentWindow; + } +} + +// 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. +// - WindowA // FindBlockingModal() returns Modal1 +// - WindowB // .. returns Modal1 +// - Modal1 // .. returns Modal2 +// - WindowC // .. returns Modal2 +// - WindowD // .. returns Modal2 +// - Modal2 // .. returns Modal2 +// - WindowE // .. returns NULL +// Notes: +// - FindBlockingModal(NULL) == NULL is generally equivalent to GetTopMostPopupModal() == NULL. +// Only difference is here we check for ->Active/WasActive but it may be unecessary. +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) { + ImGuiWindow *popup_window = popup_data.Window; + if (popup_window == NULL || !(popup_window->Flags & ImGuiWindowFlags_Modal)) + continue; + if (!popup_window->Active && !popup_window->WasActive) // Check WasActive, because this code may run before popup renders on current frame, also check + // Active to handle newly created windows. + continue; + if (window == NULL) // FindBlockingModal(NULL) test for if FocusWindow(NULL) is naturally possible via a mouse click. + return popup_window; + if (IsWindowWithinBeginStackOf(window, popup_window)) // Window may be over modal + continue; + return popup_window; // Place window right below first block modal + } + return NULL; +} + +// 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. +// - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file). +// You can use the "##" or "###" markers to use the same label with different id, or same id with different label. See documentation at the top of this file. +// - Return false when window is collapsed, so you can early out in your code. You always need to call ImGui::End() even if false is returned. +// - Passing 'bool* p_open' displays a Close button on the upper-right corner of the window, the pointed value will be set to false when the button is pressed. +bool ImGui::Begin(const char *name, bool *p_open, ImGuiWindowFlags flags) +{ + ImGuiContext &g = *GImGui; + const ImGuiStyle &style = g.Style; + 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); + + // Automatically disable manual moving/resizing when NoInputs is set + if ((flags & ImGuiWindowFlags_NoInputs) == ImGuiWindowFlags_NoInputs) + flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; + + if (flags & ImGuiWindowFlags_NavFlattened) + IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow); + + 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 + 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) { + ImGuiPopupData &popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size]; + 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); + } + window->Appearing = window_just_activated_by_user; + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); + + // Update Flags, LastFrameActive, BeginOrderXXX fields + if (first_begin_of_the_frame) { + UpdateWindowInFocusOrderList(window, window_just_created, flags); + window->Flags = (ImGuiWindowFlags)flags; + window->LastFrameActive = current_frame; + window->LastTimeActive = (float)g.Time; + window->BeginOrderWithinParent = 0; + window->BeginOrderWithinContext = (short)(g.WindowsActiveCount++); + } else { + flags = window->Flags; + } + + // 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 = 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 + // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow() + g.CurrentWindow = window; + ImGuiWindowStackData window_stack_data; + window_stack_data.Window = window; + window_stack_data.ParentLastItemDataBackup = g.LastItemData; + window_stack_data.StackSizesOnBegin.SetToContextState(&g); + g.CurrentWindowStack.push_back(window_stack_data); + if (flags & ImGuiWindowFlags_ChildMenu) + g.BeginMenuCount++; + + // 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; + } + + // Add to focus scope stack + PushFocusScope(window->ID); + window->NavRootFocusScopeId = g.CurrentFocusScopeId; + g.CurrentWindow = NULL; + + // Add to popup stack + if (flags & ImGuiWindowFlags_Popup) { + ImGuiPopupData &popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size]; + popup_ref.Window = window; + popup_ref.ParentNavLayer = parent_window_in_stack->DC.NavLayerCurrent; + 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; + bool window_size_x_set_by_api = false, window_size_y_set_by_api = false; + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) { + window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0; + if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f) { + // May be processed on the next frame if this is our first frame and we are measuring size + // FIXME: Look into removing the branch so everything can go through this same code path for consistency. + window->SetWindowPosVal = g.NextWindowData.PosVal; + window->SetWindowPosPivot = g.NextWindowData.PosPivotVal; + window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); + } else { + SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond); + } + } + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) { + window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f); + window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f); + SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond); + } + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasScroll) { + if (g.NextWindowData.ScrollVal.x >= 0.0f) { + window->ScrollTarget.x = g.NextWindowData.ScrollVal.x; + window->ScrollTargetCenterRatio.x = 0.0f; + } + if (g.NextWindowData.ScrollVal.y >= 0.0f) { + window->ScrollTarget.y = g.NextWindowData.ScrollVal.y; + window->ScrollTargetCenterRatio.y = 0.0f; + } + } + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasContentSize) + window->ContentSizeExplicit = g.NextWindowData.ContentSizeVal; + else if (first_begin_of_the_frame) + window->ContentSizeExplicit = ImVec2(0.0f, 0.0f); + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasCollapsed) + SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond); + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasFocus) + FocusWindow(window); + if (window->Appearing) + SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false); + + // When reusing window again multiple times a frame, just append content (don't need to setup again) + if (first_begin_of_the_frame) { + // Initialize + const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) + && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345) + const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFramesCannotSkipItems > 0); + window->Active = true; + window->HasCloseButton = (p_open != NULL); + window->ClipRect = ImVec4(-FLT_MAX, -FLT_MAX, +FLT_MAX, +FLT_MAX); + window->IDStack.resize(1); + window->DrawList->_ResetForNewFrame(); + window->DC.CurrentTableIdx = -1; + + // 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; + if (g.NavWindowingListWindow != NULL && (window->Flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using CTRL+TAB + window_title_visible_elsewhere = true; + if (window_title_visible_elsewhere && !window_just_created && strcmp(name, window->Name) != 0) { + size_t buf_len = (size_t)window->NameBufLen; + 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); + if (window->HiddenFramesCanSkipItems > 0) + window->HiddenFramesCanSkipItems--; + if (window->HiddenFramesCannotSkipItems > 0) + 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) { + window->HiddenFramesCannotSkipItems = 1; + if (flags & ImGuiWindowFlags_AlwaysAutoResize) { + if (!window_size_x_set_by_api) + window->Size.x = window->SizeFull.x = 0.f; + if (!window_size_y_set_by_api) + window->Size.y = window->SizeFull.y = 0.f; + window->ContentSize = window->ContentSizeIdeal = ImVec2(0.f, 0.f); + } + } + + // SELECT VIEWPORT + // FIXME-VIEWPORT: In the docking/viewport branch, this is the point where we select the current viewport (which may affect the style) + + ImGuiViewportP *viewport = (ImGuiViewportP *)(void *)GetMainViewport(); + SetWindowViewport(window, viewport); + SetCurrentWindow(window); + + // LOCK BORDER SIZE AND PADDING FOR THE FRAME (so that altering them doesn't cause inconsistencies) + + if (flags & ImGuiWindowFlags_ChildWindow) + window->WindowBorderSize = style.ChildBorderSize; + else + window->WindowBorderSize = ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal)) + ? style.PopupBorderSize + : style.WindowBorderSize; + window->WindowPadding = style.WindowPadding; + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) + && 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; + + bool use_current_size_for_scrollbar_x = window_just_created; + bool use_current_size_for_scrollbar_y = window_just_created; + + // 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)) { + // We don't use a regular button+id to test for double-click on title bar (mostly due to legacy reason, could be fixed), so verify that we don't + // have items over the title bar. + ImRect title_bar_rect = window->TitleBarRect(); + if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) + && g.IO.MouseClickedCount[0] == 2) + window->WantCollapseToggle = true; + if (window->WantCollapseToggle) { + window->Collapsed = !window->Collapsed; + if (!window->Collapsed) + use_current_size_for_scrollbar_y = true; + MarkIniSettingsDirty(window); + } + } else { + window->Collapsed = false; + } + window->WantCollapseToggle = false; + + // SIZE + + // Outer Decoration Sizes + // (we need to clear ScrollbarSize immediatly as CalcWindowAutoFitSize() needs it and can be called from other locations). + const ImVec2 scrollbar_sizes_from_last_frame = window->ScrollbarSizes; + window->DecoOuterSizeX1 = 0.0f; + window->DecoOuterSizeX2 = 0.0f; + 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) { + // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc. + if (!window_size_x_set_by_api) { + window->SizeFull.x = size_auto_fit.x; + use_current_size_for_scrollbar_x = true; + } + if (!window_size_y_set_by_api) { + window->SizeFull.y = size_auto_fit.y; + use_current_size_for_scrollbar_y = true; + } + } else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) { + // Auto-fit may only grow window during the first few frames + // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when + // collapsed. + if (!window_size_x_set_by_api && window->AutoFitFramesX > 0) { + window->SizeFull.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; + use_current_size_for_scrollbar_x = true; + } + if (!window_size_y_set_by_api && window->AutoFitFramesY > 0) { + window->SizeFull.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; + use_current_size_for_scrollbar_y = true; + } + 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) { + window->AutoPosLastDirection = ImGuiDir_None; + 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) { + IM_ASSERT(parent_window && parent_window->Active); + window->BeginOrderWithinParent = (short)parent_window->DC.ChildWindows.Size; + parent_window->DC.ChildWindows.push_back(window); + 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) + else if ((flags & ImGuiWindowFlags_ChildMenu) != 0) + window->Pos = FindBestWindowPosForPopup(window); + else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize) + window->Pos = FindBestWindowPosForPopup(window); + else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip) + window->Pos = FindBestWindowPosForPopup(window); + + // 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(viewport->GetMainRect()); + ImRect viewport_work_rect(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. + if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow)) + if (viewport_rect.GetWidth() > 0.0f && viewport_rect.GetHeight() > 0.0f) + ClampWindowPos(window, visibility_rect); + window->Pos = ImFloor(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. + 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)) { + if (flags & ImGuiWindowFlags_Popup) + want_focus = true; + else if ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) == 0) + want_focus = true; + } + + // [Test Engine] Register whole window in the item system (before submitting further decorations) +#ifdef IMGUI_ENABLE_TEST_ENGINE + if (g.TestEngineHookItems) { + IM_ASSERT(window->IDStack.Size == 1); + window->IDStack.Size = 0; // As window->IDStack[0] == window->ID here, make sure TestEngine doesn't erroneously see window as parent of itself. + IMGUI_TEST_ENGINE_ITEM_ADD(window->ID, window->Rect(), NULL); + IMGUI_TEST_ENGINE_ITEM_INFO(window->ID, window->Name, (g.HoveredWindow == window) ? ImGuiItemStatusFlags_HoveredRect : 0); + window->IDStack.Size = 1; + } +#endif + + // Handle manual resize: Resize Grips, Borders, Gamepad + int border_held = -1; + ImU32 resize_grip_col[4] = {}; + const int resize_grip_count = g.IO.ConfigWindowsResizeFromEdges ? 2 : 1; // Allow resize from lower-left if we have the mouse cursor feedback for it. + const float resize_grip_draw_size = IM_FLOOR(ImMax(g.FontSize * 1.10f, window->WindowRounding + 1.0f + g.FontSize * 0.2f)); + if (!window->Collapsed) + if (UpdateWindowManualResize(window, size_auto_fit, &border_held, resize_grip_count, &resize_grip_col[0], visibility_rect)) + use_current_size_for_scrollbar_x = use_current_size_for_scrollbar_y = true; + window->ResizeBorderHeld = (signed char)border_held; + + // SCROLLBAR VISIBILITY + + // Update scrollbar visibility (based on the Size that was effective during last frame or the auto-resized Size). + if (!window->Collapsed) { + // When reading the current size we need to read it after size constraints have been applied. + // Intentionally use previous frame values for InnerRect and ScrollbarSizes. + // And when we use window->DecorationUp here it doesn't have ScrollbarSizes.y applied yet. + ImVec2 avail_size_from_current_frame = ImVec2(window->SizeFull.x, window->SizeFull.y - (window->DecoOuterSizeY1 + window->DecoOuterSizeY2)); + ImVec2 avail_size_from_last_frame = window->InnerRect.GetSize() + scrollbar_sizes_from_last_frame; + ImVec2 needed_size_from_last_frame = window_just_created ? ImVec2(0, 0) : window->ContentSize + window->WindowPadding * 2.0f; + float size_x_for_scrollbars = use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x; + float size_y_for_scrollbars = use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y; + // 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)); + if (window->ScrollbarX && !window->ScrollbarY) + window->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars) && !(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) + // - Begin() initial clipping rect for drawing window background and borders. + // - Begin() clipping whole child + const ImRect host_rect = + ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip) ? parent_window->ClipRect : viewport_rect; + const ImRect outer_rect = window->Rect(); + const ImRect title_bar_rect = window->TitleBarRect(); + window->OuterRectClipped = outer_rect; + window->OuterRectClipped.ClipWith(host_rect); + + // Inner rectangle + // Not affected by window border size. Used by: + // - InnerClipRect + // - ScrollToRectEx() + // - NavUpdatePageUpPageDown() + // - Scrollbar() + window->InnerRect.Min.x = window->Pos.x + window->DecoOuterSizeX1; + 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. + // Will extend a little bit outside the normal work region. + // This is to allow e.g. Selectable or CollapsingHeader or some separators to cover that space. + // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. + // Note that if our window is collapsed we will end up with an inverted (~null) clipping rectangle which is the correct behavior. + // 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); + window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + ImMax(ImFloor(window->WindowPadding.x * 0.5f), window->WindowBorderSize)); + window->InnerClipRect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y + top_border_size); + window->InnerClipRect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - ImMax(ImFloor(window->WindowPadding.x * 0.5f), window->WindowBorderSize)); + window->InnerClipRect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y - window->WindowBorderSize); + 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 = ImFloor(window->Size.x * 0.65f); + else + window->ItemWidthDefault = ImFloor(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); + 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) + { + bool render_decorations_in_parent = false; + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip) { + // - We test overlap with the previous child window only (testing all would end up being O(log N) not a good investment here) + // - We disable this when the parent window has zero vertices, which is a common pattern leading to laying out multiple overlapping childs + ImGuiWindow *previous_child = + parent_window->DC.ChildWindows.Size >= 2 ? parent_window->DC.ChildWindows[parent_window->DC.ChildWindows.Size - 2] : NULL; + bool previous_child_overlapping = previous_child ? previous_child->Rect().Overlaps(window->Rect()) : false; + bool parent_is_empty = (parent_window->DrawList->VtxBuffer.Size == 0); + if (window->DrawList->CmdBuffer.back().ElemCount == 0 && !parent_is_empty && !previous_child_overlapping) + render_decorations_in_parent = true; + } + 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); + const bool handle_borders_and_resize_grips = true; // This exists to facilitate merge with 'docking' branch. + 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 + // - TreeNode(), CollapsingHeader() for right-most edge + // - BeginTabBar() for right-most edge + const bool allow_scrollbar_x = !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar); + const bool allow_scrollbar_y = !(flags & ImGuiWindowFlags_NoScrollbar); + const float work_rect_size_x = (window->ContentSizeExplicit.x != 0.0f + ? window->ContentSizeExplicit.x + : ImMax(allow_scrollbar_x ? window->ContentSize.x : 0.0f, + window->Size.x - window->WindowPadding.x * 2.0f - (window->DecoOuterSizeX1 + window->DecoOuterSizeX2))); + const float work_rect_size_y = (window->ContentSizeExplicit.y != 0.0f + ? window->ContentSizeExplicit.y + : ImMax(allow_scrollbar_y ? window->ContentSize.y : 0.0f, + window->Size.y - window->WindowPadding.y * 2.0f - (window->DecoOuterSizeY1 + window->DecoOuterSizeY2))); + window->WorkRect.Min.x = ImFloor(window->InnerRect.Min.x - window->Scroll.x + ImMax(window->WindowPadding.x, window->WindowBorderSize)); + window->WorkRect.Min.y = ImFloor(window->InnerRect.Min.y - window->Scroll.y + ImMax(window->WindowPadding.y, window->WindowBorderSize)); + 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. + // Used by: + // - Mouse wheel scrolling + many other things + window->ContentRegionRect.Min.x = window->Pos.x - window->Scroll.x + window->WindowPadding.x + window->DecoOuterSizeX1; + 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; + double start_pos_highp_y = (double)window->Pos.y + window->WindowPadding.y - (double)window->Scroll.y + window->DecoOuterSizeY1; + window->DC.CursorStartPos = ImVec2((float)start_pos_highp_x, (float)start_pos_highp_y); + window->DC.CursorStartPosLossyness = + ImVec2((float)(start_pos_highp_x - window->DC.CursorStartPos.x), (float)(start_pos_highp_y - window->DC.CursorStartPos.y)); + window->DC.CursorPos = window->DC.CursorStartPos; + window->DC.CursorPosPrevLine = window->DC.CursorPos; + window->DC.CursorMaxPos = window->DC.CursorStartPos; + window->DC.IdealMaxPos = window->DC.CursorStartPos; + 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.TreeJumpToParentOnPopMask = 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; + + window->DC.ItemWidth = window->ItemWidthDefault; + window->DC.TextWrapPos = -1.0f; // disabled + window->DC.ItemWidthStack.resize(0); + window->DC.TextWrapPosStack.resize(0); + + if (window->AutoFitFramesX > 0) + window->AutoFitFramesX--; + if (window->AutoFitFramesY > 0) + window->AutoFitFramesY--; + + // 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. + // - Position window behind the modal that is not a begin-parent of this window. + if (want_focus) + 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 + + // Title bar + if (!(flags & ImGuiWindowFlags_NoTitleBar)) + 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); + + // Clear hit test shape every frame + window->HitTestHoleSize.x = window->HitTestHoleSize.y = 0; + + // Pressing CTRL+C while holding on a window copy its content to the clipboard + // This works but 1. doesn't handle multiple Begin/End pairs, 2. recursing into another Begin/End pair - so we need to work that out and add better + // logging scope. Maybe we can support CTRL+C on every element? + /* + //if (g.NavWindow == window && g.ActiveId == 0) + if (g.ActiveId == window->MoveId) + if (g.IO.KeyCtrl && IsKeyPressed(ImGuiKey_C)) + LogToClipboard(); + */ + + // 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. + SetLastItemData(window->MoveId, + g.CurrentItemFlags, + IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, + 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)) + IMGUI_TEST_ENGINE_ITEM_ADD(g.LastItemData.ID, g.LastItemData.Rect, &g.LastItemData); +#endif + } else { + // Append + SetCurrentWindow(window); + } + + 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++; + g.NextWindowData.ClearFlags(); + + // Update visibility + if (first_begin_of_the_frame) { + if (flags & ImGuiWindowFlags_ChildWindow) { + // Child window can be out of sight and have "negative" clip windows. + // Mark them as collapsed so commands are skipped earlier (we can't manually collapse them because they have no title bar). + IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0); + if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 + && window->AutoFitFramesY <= 0) // FIXME: Doesn't make sense for ChildWindow?? + { + const bool nav_request = + (flags & ImGuiWindowFlags_NavFlattened) && (g.NavAnyRequest && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav); + if (!g.LogEnabled && !nav_request) + if (window->OuterRectClipped.Min.x >= window->OuterRectClipped.Max.x || window->OuterRectClipped.Min.y >= window->OuterRectClipped.Max.y) + 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->Collapsed || 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; + } + + // [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) + if (!window->IsFallbackWindow + && ((g.IO.ConfigDebugBeginReturnValueOnce && window_just_created) + || (g.IO.ConfigDebugBeginReturnValueLoop && g.DebugBeginReturnValueCullDepth == g.CurrentWindowStack.Size))) { + if (window->AutoFitFramesX > 0) { + window->AutoFitFramesX++; + } + if (window->AutoFitFramesY > 0) { + window->AutoFitFramesY++; + } + return false; + } + + 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) { + IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size > 1, "Calling End() too many times!"); + return; + } + IM_ASSERT(g.CurrentWindowStack.Size > 0); + + // Error checking: verify that user doesn't directly call End() on a child window. + if (window->Flags & ImGuiWindowFlags_ChildWindow) + IM_ASSERT_USER_ERROR(g.WithinEndChild, "Must call EndChild() and not End()!"); + + // Close anything that is open + if (window->DC.CurrentColumns) + EndColumns(); + PopClipRect(); // Inner window clip rectangle + PopFocusScope(); + + // Stop logging + if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging + LogFinish(); + + if (window->DC.IsSetPos) + ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); + + // Pop from window stack + g.LastItemData = g.CurrentWindowStack.back().ParentLastItemDataBackup; + if (window->Flags & ImGuiWindowFlags_ChildMenu) + g.BeginMenuCount--; + if (window->Flags & ImGuiWindowFlags_Popup) + g.BeginPopupStack.pop_back(); + g.CurrentWindowStack.back().StackSizesOnBegin.CompareWithContextState(&g); + g.CurrentWindowStack.pop_back(); + SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window); +} + +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++) { + g.WindowsFocusOrder[n] = g.WindowsFocusOrder[n + 1]; + g.WindowsFocusOrder[n]->FocusOrder--; + IM_ASSERT(g.WindowsFocusOrder[n]->FocusOrder == n); + } + g.WindowsFocusOrder[new_order] = window; + window->FocusOrder = (short)new_order; +} + +void ImGui::BringWindowToDisplayFront(ImGuiWindow *window) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *current_front_window = g.Windows.back(); + if (current_front_window == window || current_front_window->RootWindow == window) // Cheap early out (could be better) + return; + for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the top-most window + if (g.Windows[i] == window) { + memmove(&g.Windows[i], &g.Windows[i + 1], (size_t)(g.Windows.Size - i - 1) * sizeof(ImGuiWindow *)); + g.Windows[g.Windows.Size - 1] = window; + break; + } +} + +void ImGui::BringWindowToDisplayBack(ImGuiWindow *window) +{ + ImGuiContext &g = *GImGui; + if (g.Windows[0] == window) + return; + for (int i = 0; i < g.Windows.Size; i++) + if (g.Windows[i] == window) { + memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow *)); + g.Windows[0] = window; + break; + } +} + +void ImGui::BringWindowToDisplayBehind(ImGuiWindow *window, ImGuiWindow *behind_window) +{ + IM_ASSERT(window != NULL && behind_window != NULL); + ImGuiContext &g = *GImGui; + window = window->RootWindow; + behind_window = behind_window->RootWindow; + int pos_wnd = FindWindowDisplayIndex(window); + int pos_beh = FindWindowDisplayIndex(behind_window); + if (pos_wnd < pos_beh) { + size_t copy_bytes = (pos_beh - pos_wnd - 1) * sizeof(ImGuiWindow *); + memmove(&g.Windows.Data[pos_wnd], &g.Windows.Data[pos_wnd + 1], copy_bytes); + g.Windows[pos_beh - 1] = window; + } else { + size_t copy_bytes = (pos_wnd - pos_beh) * sizeof(ImGuiWindow *); + memmove(&g.Windows.Data[pos_beh + 1], &g.Windows.Data[pos_beh], copy_bytes); + 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)) { + IMGUI_DEBUG_LOG_FOCUS("[focus] FocusWindow(\"%s\", UnlessBelowModal): prevented by \"%s\".\n", + window ? window->Name : "", + blocking_modal->Name); + if (window && window == window->RootWindow && (window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0) + BringWindowToDisplayBehind(window, blocking_modal); // Still bring to right below modal. + 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) { + SetNavWindow(window); + if (window && g.NavDisableMouseHover) + g.NavMousePosDirty = true; + g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId + g.NavLayer = ImGuiNavLayer_Main; + g.NavFocusScopeId = window ? window->NavRootFocusScopeId : 0; + g.NavIdIsAlive = false; + + // Close popups if any + ClosePopupsOverWindow(window, false); + } + + // Move the root window to the top of the pile + IM_ASSERT(window == NULL || window->RootWindow != NULL); + ImGuiWindow *focus_front_window = window ? window->RootWindow : NULL; // NB: In docking branch this is window->RootWindowDockStop + ImGuiWindow *display_front_window = window ? window->RootWindow : NULL; + + // 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) + if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != focus_front_window) + if (!g.ActiveIdNoClearOnFocusLoss) + ClearActiveID(); + + // Passing NULL allow to disable keyboard focus + if (!window) + return; + + // Bring to front + BringWindowToFocusFront(focus_front_window); + if (((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; + IM_UNUSED(filter_viewport); // Unused in master branch. + int start_idx = g.WindowsFocusOrder.Size - 1; + if (under_this_window != NULL) { + // Aim at root window behind us, if we are in a child window that's our own root (see #4640) + int offset = -1; + while (under_this_window->Flags & ImGuiWindowFlags_ChildWindow) { + under_this_window = under_this_window->ParentWindow; + offset = 0; + } + start_idx = FindWindowFocusIndex(under_this_window) + offset; + } + for (int i = start_idx; i >= 0; i--) { + // We may later decide to test for different NoXXXInputs based on the active navigation input (mouse vs nav) but that may feel more confusing to the + // user. + ImGuiWindow *window = g.WindowsFocusOrder[i]; + IM_ASSERT(window == window->RootWindow); + if (window == ignore_window || !window->WasActive) + continue; + if ((window->Flags & (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) + != (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) { + FocusWindow(window, flags); + return; + } + } + FocusWindow(NULL, flags); +} + +// 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; + + 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; +} + +void ImGui::PushFont(ImFont *font) +{ + ImGuiContext &g = *GImGui; + if (!font) + font = GetDefaultFont(); + SetCurrentFont(font); + g.FontStack.push_back(font); + g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID); +} + +void ImGui::PopFont() +{ + ImGuiContext &g = *GImGui; + g.CurrentWindow->DrawList->PopTextureID(); + g.FontStack.pop_back(); + SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back()); +} + +void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) +{ + ImGuiContext &g = *GImGui; + ImGuiItemFlags item_flags = g.CurrentItemFlags; + IM_ASSERT(item_flags == g.ItemFlagsStack.back()); + if (enabled) + item_flags |= option; + else + item_flags &= ~option; + g.CurrentItemFlags = item_flags; + g.ItemFlagsStack.push_back(item_flags); +} + +void ImGui::PopItemFlag() +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(g.ItemFlagsStack.Size > 1); // Too many calls to PopItemFlag() - we always leave a 0 at the bottom of the stack. + 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. +// - Feedback welcome at https://github.com/ocornut/imgui/issues/211 +// - BeginDisabled(false) essentially does nothing useful but is provided to facilitate use of boolean expressions. If you can avoid calling +// BeginDisabled(False)/EndDisabled() best to avoid it. +// - Optimized shortcuts instead of PushStyleVar() + PushItemFlag() +void ImGui::BeginDisabled(bool disabled) +{ + ImGuiContext &g = *GImGui; + bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; + if (!was_disabled && disabled) { + g.DisabledAlphaBackup = g.Style.Alpha; + g.Style.Alpha *= g.Style.DisabledAlpha; // PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * g.Style.DisabledAlpha); + } + if (was_disabled || disabled) + g.CurrentItemFlags |= ImGuiItemFlags_Disabled; + g.ItemFlagsStack.push_back(g.CurrentItemFlags); + g.DisabledStackSize++; +} + +void ImGui::EndDisabled() +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(g.DisabledStackSize > 0); + g.DisabledStackSize--; + bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; + // PopItemFlag(); + g.ItemFlagsStack.pop_back(); + g.CurrentItemFlags = g.ItemFlagsStack.back(); + if (was_disabled && (g.CurrentItemFlags & ImGuiItemFlags_Disabled) == 0) + g.Style.Alpha = g.DisabledAlphaBackup; // PopStyleVar(); +} + +void ImGui::PushTabStop(bool tab_stop) +{ + PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); +} + +void ImGui::PopTabStop() +{ + PopItemFlag(); +} + +void ImGui::PushButtonRepeat(bool repeat) +{ + PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); +} + +void ImGui::PopButtonRepeat() +{ + PopItemFlag(); +} + +void ImGui::PushTextWrapPos(float wrap_pos_x) +{ + ImGuiWindow *window = GetCurrentWindow(); + window->DC.TextWrapPosStack.push_back(window->DC.TextWrapPos); + window->DC.TextWrapPos = wrap_pos_x; +} + +void ImGui::PopTextWrapPos() +{ + ImGuiWindow *window = GetCurrentWindow(); + window->DC.TextWrapPos = window->DC.TextWrapPosStack.back(); + window->DC.TextWrapPosStack.pop_back(); +} + +static ImGuiWindow *GetCombinedRootWindow(ImGuiWindow *window, bool popup_hierarchy) +{ + ImGuiWindow *last_window = NULL; + while (last_window != window) { + last_window = window; + window = window->RootWindow; + if (popup_hierarchy) + window = window->RootWindowPopupTree; + } + return window; +} + +bool ImGui::IsWindowChildOf(ImGuiWindow *window, ImGuiWindow *potential_parent, bool popup_hierarchy) +{ + ImGuiWindow *window_root = GetCombinedRootWindow(window, popup_hierarchy); + if (window_root == potential_parent) + return true; + while (window != NULL) { + if (window == potential_parent) + return true; + if (window == window_root) // end of chain + return false; + window = window->ParentWindow; + } + return false; +} + +bool ImGui::IsWindowWithinBeginStackOf(ImGuiWindow *window, ImGuiWindow *potential_parent) +{ + if (window->RootWindow == potential_parent) + return true; + while (window != NULL) { + if (window == potential_parent) + return true; + window = window->ParentWindowInBeginStack; + } + 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]; + if (candidate_window == potential_above) + return true; + if (candidate_window == potential_below) + return false; + } + return false; +} + +bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) +{ + IM_ASSERT((flags & ~ImGuiHoveredFlags_AllowedMaskForIsWindowHovered) == 0 && "Invalid flags for IsWindowHovered()!"); + + ImGuiContext &g = *GImGui; + 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() + const bool popup_hierarchy = (flags & ImGuiHoveredFlags_NoPopupHierarchy) == 0; + if (flags & ImGuiHoveredFlags_RootWindow) + cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy); + + bool result; + if (flags & ImGuiHoveredFlags_ChildWindows) + result = IsWindowChildOf(ref_window, cur_window, popup_hierarchy); + else + result = (ref_window == cur_window); + 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 fullfill the possibility that multiple IsWindowHovered() with varying flag could return true + // for different windows of the hierarchy. Possibly need a Hash(Current+Flags) ==> (Timer) cache. + // We can implement this for _Stationary because the data is linked to HoveredWindow rather than CurrentWindow. + if (flags & ImGuiHoveredFlags_ForTooltip) + flags |= g.Style.HoverFlagsForTooltipMouse; + if ((flags & ImGuiHoveredFlags_Stationary) != 0 && g.HoverWindowUnlockedStationaryId != ref_window->ID) + return false; + + return true; +} + +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; + if (flags & ImGuiHoveredFlags_RootWindow) + cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy); + + if (flags & ImGuiHoveredFlags_ChildWindows) + return IsWindowChildOf(ref_window, cur_window, popup_hierarchy); + else + return (ref_window == cur_window); +} + +// 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. +bool ImGui::IsWindowNavFocusable(ImGuiWindow *window) +{ + return window->WasActive && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus); +} + +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 = ImFloor(pos); + ImVec2 offset = window->Pos - old_pos; + if (offset.x == 0.0f && offset.y == 0.0f) + return; + MarkIniSettingsDirty(window); + window->DC.CursorPos += + offset; // As we happen to move the window while it is being appended to (which is a bad idea - will smear) let's at least offset the cursor + window->DC.CursorMaxPos += + offset; // And more importantly we need to offset CursorMaxPos/CursorStartPos this so ContentSize calculation doesn't get affected. + 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); + + // Set + ImVec2 old_size = window->SizeFull; + window->AutoFitFramesX = (size.x <= 0.0f) ? 2 : 0; + window->AutoFitFramesY = (size.y <= 0.0f) ? 2 : 0; + if (size.x <= 0.0f) + window->AutoFitOnlyGrows = false; + else + window->SizeFull.x = IM_FLOOR(size.x); + if (size.y <= 0.0f) + window->AutoFitOnlyGrows = false; + else + window->SizeFull.y = IM_FLOOR(size.y); + 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; +} + +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::SetWindowHiddendAndSkipItemsForCurrentFrame(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::SetWindowFocus() +{ + FocusWindow(GImGui->CurrentWindow); +} + +void ImGui::SetWindowFocus(const char *name) +{ + if (name) { + if (ImGuiWindow *window = FindWindowByName(name)) + FocusWindow(window); + } else { + FocusWindow(NULL); + } +} + +void ImGui::SetNextWindowPos(const ImVec2 &pos, ImGuiCond cond, const ImVec2 &pivot) +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasPos; + g.NextWindowData.PosVal = pos; + g.NextWindowData.PosPivotVal = pivot; + g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowSize(const ImVec2 &size, ImGuiCond cond) +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSize; + g.NextWindowData.SizeVal = size; + g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowSizeConstraints(const ImVec2 &size_min, const ImVec2 &size_max, ImGuiSizeCallback custom_callback, void *custom_callback_user_data) +{ + ImGuiContext &g = *GImGui; + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSizeConstraint; + g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max); + 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) +{ + ImGuiContext &g = *GImGui; + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasContentSize; + g.NextWindowData.ContentSizeVal = ImFloor(size); +} + +void ImGui::SetNextWindowScroll(const ImVec2 &scroll) +{ + ImGuiContext &g = *GImGui; + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasScroll; + g.NextWindowData.ScrollVal = scroll; +} + +void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond) +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasCollapsed; + g.NextWindowData.CollapsedVal = collapsed; + g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always; +} + +void ImGui::SetNextWindowFocus() +{ + ImGuiContext &g = *GImGui; + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasFocus; +} + +void ImGui::SetNextWindowBgAlpha(float alpha) +{ + ImGuiContext &g = *GImGui; + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasBgAlpha; + g.NextWindowData.BgAlphaVal = alpha; +} + +ImDrawList *ImGui::GetWindowDrawList() +{ + ImGuiWindow *window = GetCurrentWindow(); + return window->DrawList; +} + +ImFont *ImGui::GetFont() +{ + return GImGui->Font; +} + +float ImGui::GetFontSize() +{ + return GImGui->FontSize; +} + +ImVec2 ImGui::GetFontTexUvWhitePixel() +{ + return GImGui->DrawListSharedData.TexUvWhitePixel; +} + +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(); +} + +void ImGui::PushFocusScope(ImGuiID id) +{ + ImGuiContext &g = *GImGui; + g.FocusScopeStack.push_back(id); + g.CurrentFocusScopeId = id; +} + +void ImGui::PopFocusScope() +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(g.FocusScopeStack.Size > 0); // Too many PopFocusScope() ? + g.FocusScopeStack.pop_back(); + g.CurrentFocusScopeId = g.FocusScopeStack.Size ? g.FocusScopeStack.back() : 0; +} + +// Focus = move navigation cursor, set scrolling, set focus window. +void ImGui::FocusItem() +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + IMGUI_DEBUG_LOG_FOCUS("FocusItem(0x%08x) in window \"%s\"\n", g.LastItemData.ID, window->Name); + if (g.DragDropActive || g.MovingWindow != NULL) // FIXME: Opt-in flags for this? + { + IMGUI_DEBUG_LOG_FOCUS("FocusItem() ignored while DragDropActive!\n"); + return; + } + + ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_FocusApi | 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) +{ + ImGuiContext &g = *GImGui; + 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 + // is also automatically dropped in the event g.ActiveId is stolen. + if (g.DragDropActive || g.MovingWindow != NULL) { + IMGUI_DEBUG_LOG_FOCUS("SetKeyboardFocusHere() ignored while DragDropActive!\n"); + return; + } + + SetNavWindow(window); + + ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_Activate | ImGuiNavMoveFlags_FocusApi; + 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. + if (offset == -1) { + NavMoveRequestResolveWithLastItem(&g.NavMoveResultLocal); + } else { + g.NavTabbingDir = 1; + g.NavTabbingCounter = offset + 1; + } +} + +void ImGui::SetItemDefaultFocus() +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + if (!window->Appearing) + 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; +} + +void ImGui::PushID(const char *str_id) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + 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; + ImGuiWindow *window = g.CurrentWindow; + 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; + ImGuiWindow *window = g.CurrentWindow; + ImGuiID id = window->GetID(ptr_id); + window->IDStack.push_back(id); +} + +void ImGui::PushID(int int_id) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + 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) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + if (g.DebugHookIdInfo == id) + DebugHookIdInfo(id, ImGuiDataType_ID, NULL, NULL); + window->IDStack.push_back(id); +} + +// Helper to avoid a common series of PushOverrideID -> GetID() -> PopID() call +// (note that when using this pattern, TestEngine's "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) +ImGuiID ImGui::GetIDWithSeed(const char *str, const char *str_end, ImGuiID seed) +{ + ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed); + ImGuiContext &g = *GImGui; + if (g.DebugHookIdInfo == id) + DebugHookIdInfo(id, ImGuiDataType_String, str, str_end); + return id; +} + +ImGuiID ImGui::GetIDWithSeed(int n, ImGuiID seed) +{ + ImGuiID id = ImHashData(&n, sizeof(n), seed); + ImGuiContext &g = *GImGui; + if (g.DebugHookIdInfo == id) + DebugHookIdInfo(id, ImGuiDataType_S32, (void *)(intptr_t)n, NULL); + return id; +} + +void ImGui::PopID() +{ + ImGuiWindow *window = GImGui->CurrentWindow; + IM_ASSERT(window->IDStack.Size > 1); // Too many PopID(), or could be popping in a wrong/different window? + 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); +} + +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] INPUTS +//----------------------------------------------------------------------------- +// - GetKeyData() [Internal] +// - GetKeyIndex() [Internal] +// - GetKeyName() +// - GetKeyChordName() [Internal] +// - CalcTypematicRepeatAmount() [Internal] +// - GetTypematicRepeatRate() [Internal] +// - GetKeyPressedAmount() [Internal] +// - GetKeyMagnitude2d() [Internal] +//----------------------------------------------------------------------------- +// - UpdateKeyRoutingTable() [Internal] +// - GetRoutingIdFromOwnerId() [Internal] +// - GetShortcutRoutingData() [Internal] +// - CalcRoutingScore() [Internal] +// - SetShortcutRouting() [Internal] +// - TestShortcutRouting() [Internal] +//----------------------------------------------------------------------------- +// - IsKeyDown() +// - IsKeyPressed() +// - IsKeyReleased() +//----------------------------------------------------------------------------- +// - IsMouseDown() +// - IsMouseClicked() +// - IsMouseReleased() +// - IsMouseDoubleClicked() +// - GetMouseClickedCount() +// - IsMouseHoveringRect() [Internal] +// - IsMouseDragPastThreshold() [Internal] +// - IsMouseDragging() +// - GetMousePos() +// - GetMousePosOnOpeningCurrentPopup() +// - IsMousePosValid() +// - IsAnyMouseDown() +// - GetMouseDragDelta() +// - ResetMouseDragDelta() +// - GetMouseCursor() +// - SetMouseCursor() +//----------------------------------------------------------------------------- +// - UpdateAliasKey() +// - GetMergedModsFromKeys() +// - UpdateKeyboardInputs() +// - UpdateMouseInputs() +//----------------------------------------------------------------------------- +// - LockWheelingWindow [Internal] +// - FindBestWheelingWindow [Internal] +// - UpdateMouseWheel() [Internal] +//----------------------------------------------------------------------------- +// - SetNextFrameWantCaptureKeyboard() +// - SetNextFrameWantCaptureMouse() +//----------------------------------------------------------------------------- +// - GetInputSourceName() [Internal] +// - DebugPrintInputEvent() [Internal] +// - UpdateInputEvents() [Internal] +//----------------------------------------------------------------------------- +// - GetKeyOwner() [Internal] +// - TestKeyOwner() [Internal] +// - SetKeyOwner() [Internal] +// - SetItemKeyOwner() [Internal] +// - Shortcut() [Internal] +//----------------------------------------------------------------------------- + +ImGuiKeyData *ImGui::GetKeyData(ImGuiContext *ctx, ImGuiKey key) +{ + ImGuiContext &g = *ctx; + + // Special storage location for mods + if (key & ImGuiMod_Mask_) + key = ConvertSingleModFlagToKey(ctx, key); + +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + IM_ASSERT(key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_NamedKey_END); + if (IsLegacyKey(key) && g.IO.KeyMap[key] != -1) + key = (ImGuiKey)g.IO.KeyMap[key]; // Remap native->imgui or imgui->native +#else + IM_ASSERT(IsNamedKey(key) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend & user code."); +#endif + return &g.IO.KeysData[key - ImGuiKey_KeysData_OFFSET]; +} + +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO +ImGuiKey ImGui::GetKeyIndex(ImGuiKey key) +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(IsNamedKey(key)); + const ImGuiKeyData *key_data = GetKeyData(key); + return (ImGuiKey)(key_data - g.IO.KeysData); +} +#endif + +// Those names a provided for debugging purpose and are not meant to be saved persistently not compared. +static const char *const GKeyNames[] = { + "Tab", + "LeftArrow", + "RightArrow", + "UpArrow", + "DownArrow", + "PageUp", + "PageDown", + "Home", + "End", + "Insert", + "Delete", + "Backspace", + "Space", + "Enter", + "Escape", + "LeftCtrl", + "LeftShift", + "LeftAlt", + "LeftSuper", + "RightCtrl", + "RightShift", + "RightAlt", + "RightSuper", + "Menu", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "F10", + "F11", + "F12", + "Apostrophe", + "Comma", + "Minus", + "Period", + "Slash", + "Semicolon", + "Equal", + "LeftBracket", + "Backslash", + "RightBracket", + "GraveAccent", + "CapsLock", + "ScrollLock", + "NumLock", + "PrintScreen", + "Pause", + "Keypad0", + "Keypad1", + "Keypad2", + "Keypad3", + "Keypad4", + "Keypad5", + "Keypad6", + "Keypad7", + "Keypad8", + "Keypad9", + "KeypadDecimal", + "KeypadDivide", + "KeypadMultiply", + "KeypadSubtract", + "KeypadAdd", + "KeypadEnter", + "KeypadEqual", + "GamepadStart", + "GamepadBack", + "GamepadFaceLeft", + "GamepadFaceRight", + "GamepadFaceUp", + "GamepadFaceDown", + "GamepadDpadLeft", + "GamepadDpadRight", + "GamepadDpadUp", + "GamepadDpadDown", + "GamepadL1", + "GamepadR1", + "GamepadL2", + "GamepadR2", + "GamepadL3", + "GamepadR3", + "GamepadLStickLeft", + "GamepadLStickRight", + "GamepadLStickUp", + "GamepadLStickDown", + "GamepadRStickLeft", + "GamepadRStickRight", + "GamepadRStickUp", + "GamepadRStickDown", + "MouseLeft", + "MouseRight", + "MouseMiddle", + "MouseX1", + "MouseX2", + "MouseWheelX", + "MouseWheelY", + "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) +{ + ImGuiContext &g = *GImGui; +#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO + IM_ASSERT((IsNamedKeyOrModKey(key) || key == ImGuiKey_None) + && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend and user code."); +#else + if (IsLegacyKey(key)) { + if (g.IO.KeyMap[key] == -1) + return "N/A"; + IM_ASSERT(IsNamedKey((ImGuiKey)g.IO.KeyMap[key])); + key = (ImGuiKey)g.IO.KeyMap[key]; + } +#endif + if (key == ImGuiKey_None) + return "None"; + if (key & ImGuiMod_Mask_) + key = ConvertSingleModFlagToKey(&g, key); + if (!IsNamedKey(key)) + return "Unknown"; + + return GKeyNames[key - ImGuiKey_NamedKey_BEGIN]; +} + +// ImGuiMod_Shortcut is translated to either Ctrl or Super. +void ImGui::GetKeyChordName(ImGuiKeyChord key_chord, char *out_buf, int out_buf_size) +{ + ImGuiContext &g = *GImGui; + if (key_chord & ImGuiMod_Shortcut) + key_chord = ConvertShortcutMod(key_chord); + ImFormatString(out_buf, + (size_t)out_buf_size, + "%s%s%s%s%s", + (key_chord & ImGuiMod_Ctrl) ? "Ctrl+" : "", + (key_chord & ImGuiMod_Shift) ? "Shift+" : "", + (key_chord & ImGuiMod_Alt) ? "Alt+" : "", + (key_chord & ImGuiMod_Super) ? (g.IO.ConfigMacOSXBehaviors ? "Cmd+" : "Super+") : "", + GetKeyName((ImGuiKey)(key_chord & ~ImGuiMod_Mask_))); +} + +// t0 = previous time (e.g.: g.Time - g.IO.DeltaTime) +// t1 = current time (e.g.: g.Time) +// An event is triggered at: +// t = 0.0f t = repeat_delay, t = repeat_delay + repeat_rate*N +int ImGui::CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, float repeat_rate) +{ + if (t1 == 0.0f) + return 1; + if (t0 >= t1) + return 0; + if (repeat_rate <= 0.0f) + return (t0 < repeat_delay) && (t1 >= repeat_delay); + const int count_t0 = (t0 < repeat_delay) ? -1 : (int)((t0 - repeat_delay) / repeat_rate); + const int count_t1 = (t1 < repeat_delay) ? -1 : (int)((t1 - repeat_delay) / repeat_rate); + const int count = count_t1 - count_t0; + return count; +} + +void ImGui::GetTypematicRepeatRate(ImGuiInputFlags flags, float *repeat_delay, float *repeat_rate) +{ + ImGuiContext &g = *GImGui; + switch (flags & ImGuiInputFlags_RepeatRateMask_) { + case ImGuiInputFlags_RepeatRateNavMove: + *repeat_delay = g.IO.KeyRepeatDelay * 0.72f; + *repeat_rate = g.IO.KeyRepeatRate * 0.80f; + return; + case ImGuiInputFlags_RepeatRateNavTweak: + *repeat_delay = g.IO.KeyRepeatDelay * 0.72f; + *repeat_rate = g.IO.KeyRepeatRate * 0.30f; + return; + 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) +{ + ImGuiContext &g = *GImGui; + const ImGuiKeyData *key_data = GetKeyData(key); + if (!key_data->Down) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish + // work on key ownership) + return 0; + 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) +{ + return ImVec2(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 +// See 'Metrics->Key Owners & Shortcut Routing' to visualize the result of that operation. +static void ImGui::UpdateKeyRoutingTable(ImGuiKeyRoutingTable *rt) +{ + ImGuiContext &g = *GImGui; + rt->EntriesNext.resize(0); + for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { + const int new_routing_start_idx = rt->EntriesNext.Size; + ImGuiKeyRoutingData *routing_entry; + for (int old_routing_idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; old_routing_idx != -1; old_routing_idx = routing_entry->NextEntryIndex) { + routing_entry = &rt->Entries[old_routing_idx]; + routing_entry->RoutingCurr = routing_entry->RoutingNext; // Update entry + routing_entry->RoutingNext = ImGuiKeyOwner_None; + routing_entry->RoutingNextScore = 255; + if (routing_entry->RoutingCurr == ImGuiKeyOwner_None) + 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) + if (routing_entry->Mods == g.IO.KeyMods) { + ImGuiKeyOwnerData *owner_data = GetKeyOwnerData(&g, key); + if (owner_data->OwnerCurr == ImGuiKeyOwner_None) + owner_data->OwnerCurr = routing_entry->RoutingCurr; + } + } + + // 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++) + rt->EntriesNext[n].NextEntryIndex = (ImGuiKeyRoutingIndex)((n + 1 < rt->EntriesNext.Size) ? n + 1 : -1); + } + 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_None && owner_id != ImGuiKeyOwner_Any) ? owner_id : g.CurrentFocusScopeId; +} + +ImGuiKeyRoutingData *ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord) +{ + // Majority of shortcuts will be Key + any number of Mods + // We accept _Single_ mod with ImGuiKey_None. + // - Shortcut(ImGuiKey_S | ImGuiMod_Ctrl); // Legal + // - Shortcut(ImGuiKey_S | ImGuiMod_Ctrl | ImGuiMod_Shift); // Legal + // - Shortcut(ImGuiMod_Ctrl); // Legal + // - Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift); // Not legal + ImGuiContext &g = *GImGui; + ImGuiKeyRoutingTable *rt = &g.KeysRoutingTable; + ImGuiKeyRoutingData *routing_data; + if (key_chord & ImGuiMod_Shortcut) + key_chord = ConvertShortcutMod(key_chord); + ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); + ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); + if (key == ImGuiKey_None) + key = ConvertSingleModFlagToKey(&g, 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) { + routing_data = &rt->Entries[idx]; + 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()); + routing_data = &rt->Entries[routing_data_idx]; + routing_data->Mods = (ImU16)mods; + routing_data->NextEntryIndex = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; // Setup linked list + rt->Index[key - ImGuiKey_NamedKey_BEGIN] = routing_data_idx; + return routing_data; +} + +// Current score encoding (lower is highest priority): +// - 0: ImGuiInputFlags_RouteGlobalHigh +// - 1: ImGuiInputFlags_RouteFocused (if item active) +// - 2: ImGuiInputFlags_RouteGlobal +// - 3+: ImGuiInputFlags_RouteFocused (if window in focus-stack) +// - 254: ImGuiInputFlags_RouteGlobalLow +// - 255: never route +// 'flags' should include an explicit routing policy +static int CalcRoutingScore(ImGuiWindow *location, ImGuiID owner_id, ImGuiInputFlags flags) +{ + if (flags & ImGuiInputFlags_RouteFocused) { + ImGuiContext &g = *GImGui; + ImGuiWindow *focused = g.NavWindow; + + // ActiveID gets top priority + // (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) + // - When Window/ChildB is focused -> Window scores 4, Window/ChildB scores 3 (best) + // Assuming only WindowA is submitting a routing request, + // - When Window/ChildB is focused -> Window scores 4 (best), Window/ChildB doesn't have a score. + if (focused != NULL && focused->RootWindow == location->RootWindow) + for (int next_score = 3; focused != NULL; next_score++) { + if (focused == location) { + IM_ASSERT(next_score < 255); + return next_score; + } + focused = (focused->RootWindow != focused) ? focused->ParentWindow : NULL; // FIXME: This could be later abstracted as a focus path + } + return 255; + } + + // ImGuiInputFlags_RouteGlobalHigh is default, so calls without flags are not conditional + if (flags & ImGuiInputFlags_RouteGlobal) + return 2; + if (flags & ImGuiInputFlags_RouteGlobalLow) + return 254; + return 0; +} + +// 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. +// (Conceptually this does a "Submit for next frame" + "Test for current frame". +// As such, it could be called TrySetXXX or SubmitXXX, or the Submit and Test operations should be separate.) +// - Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its focus scope by default) +// - Using 'owner_id == ImGuiKeyOwner_None': allows disabling/locking a shortcut. +bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags) +{ + ImGuiContext &g = *GImGui; + if ((flags & ImGuiInputFlags_RouteMask_) == 0) + flags |= ImGuiInputFlags_RouteGlobalHigh; // IMPORTANT: This is the default for SetShortcutRouting() but NOT Shortcut() + else + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteMask_)); // Check that only 1 routing flag is used + + if (flags & ImGuiInputFlags_RouteUnlessBgFocused) + if (g.NavWindow == NULL) + return false; + if (flags & ImGuiInputFlags_RouteAlways) + return true; + + const int score = CalcRoutingScore(g.CurrentWindow, owner_id, flags); + 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); + const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id); + // const bool set_route = (flags & ImGuiInputFlags_ServeLast) ? (score <= routing_data->RoutingNextScore) : (score < routing_data->RoutingNextScore); + if (score < routing_data->RoutingNextScore) { + routing_data->RoutingNext = routing_id; + routing_data->RoutingNextScore = (ImU8)score; + } + + // Return routing state for CURRENT frame + return routing_data->RoutingCurr == routing_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) +{ + const ImGuiID routing_id = GetRoutingIdFromOwnerId(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); + if (!key_data->Down) + return false; + if (!TestKeyOwner(key, owner_id)) + return false; + return true; +} + +bool ImGui::IsKeyPressed(ImGuiKey key, bool repeat) +{ + return IsKeyPressed(key, ImGuiKeyOwner_Any, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None); +} + +// Important: unless legacy IsKeyPressed(ImGuiKey, bool repeat=true) which DEFAULT to repeat, this requires EXPLICIT repeat. +bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags) +{ + const ImGuiKeyData *key_data = GetKeyData(key); + if (!key_data->Down) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish + // work on key ownership) + return false; + const float t = key_data->DownDuration; + if (t < 0.0f) + return false; + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsKeyPressed) == 0); // Passing flags not supported by this function! + + bool pressed = (t == 0.0f); + if (!pressed && ((flags & ImGuiInputFlags_Repeat) != 0)) { + float repeat_delay, repeat_rate; + GetTypematicRepeatRate(flags, &repeat_delay, &repeat_rate); + pressed = (t > repeat_delay) && GetKeyPressedAmount(key, repeat_delay, repeat_rate) > 0; + } + if (!pressed) + return false; + if (!TestKeyOwner(key, 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); + if (key_data->DownDurationPrev < 0.0f || key_data->Down) + return false; + if (!TestKeyOwner(key, 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, ImGuiKeyOwner_Any, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None); +} + +bool ImGui::IsMouseClicked(ImGuiMouseButton button, ImGuiID owner_id, ImGuiInputFlags flags) +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (!g.IO.MouseDown[button]) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we + // finish work on key ownership) + return false; + const float t = g.IO.MouseDownDuration[button]; + if (t < 0.0f) + return false; + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsKeyPressed) == 0); // Passing flags not supported by this function! + + 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) +} + +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); +} + +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); + + // Expand for touch input + const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding); + if (!rect_for_touch.Contains(g.IO.MousePos)) + 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) +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (lock_threshold < 0.0f) + 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; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (!g.IO.MouseDown[button]) + return false; + return IsMouseDragPastThreshold(button, lock_threshold); +} + +ImVec2 ImGui::GetMousePos() +{ + ImGuiContext &g = *GImGui; + return g.IO.MousePos; +} + +// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed! +ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup() +{ + ImGuiContext &g = *GImGui; + if (g.BeginPopupStack.Size > 0) + 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) +{ + // The assert is only to silence a false-positive in XCode Static Analysis. + // Because GImGui is not dereferenced in every code path, the static analyzer assume that it may be NULL (which it doesn't for other functions). + IM_ASSERT(GImGui != NULL); + const float MOUSE_INVALID = -256000.0f; + 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() +{ + ImGuiContext &g = *GImGui; + for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++) + if (g.IO.MouseDown[n]) + 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. +ImVec2 ImGui::GetMouseDragDelta(ImGuiMouseButton button, float lock_threshold) +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + if (lock_threshold < 0.0f) + lock_threshold = g.IO.MouseDragThreshold; + if (g.IO.MouseDown[button] || g.IO.MouseReleased[button]) + if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold) + if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MouseClickedPos[button])) + return g.IO.MousePos - g.IO.MouseClickedPos[button]; + return ImVec2(0.0f, 0.0f); +} + +void ImGui::ResetMouseDragDelta(ImGuiMouseButton button) +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + // 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(). +// If you use software rendering by setting io.MouseDrawCursor then Dear ImGui will render those for you +ImGuiMouseCursor ImGui::GetMouseCursor() +{ + ImGuiContext &g = *GImGui; + return g.MouseCursor; +} + +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)); + ImGuiKeyData *key_data = ImGui::GetKeyData(key); + key_data->Down = v; + key_data->AnalogValue = analog_value; +} + +// [Internal] Do not use directly +static ImGuiKeyChord GetMergedModsFromKeys() +{ + ImGuiKeyChord mods = 0; + if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) { + mods |= ImGuiMod_Ctrl; + } + if (ImGui::IsKeyDown(ImGuiMod_Shift)) { + mods |= ImGuiMod_Shift; + } + if (ImGui::IsKeyDown(ImGuiMod_Alt)) { + mods |= ImGuiMod_Alt; + } + if (ImGui::IsKeyDown(ImGuiMod_Super)) { + mods |= ImGuiMod_Super; + } + return mods; +} + +static void ImGui::UpdateKeyboardInputs() +{ + ImGuiContext &g = *GImGui; + ImGuiIO &io = g.IO; + + // Import legacy keys or verify they are not used +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + if (io.BackendUsingLegacyKeyArrays == 0) { + // Backend used new io.AddKeyEvent() API: Good! Verify that old arrays are never written to externally. + for (int n = 0; n < ImGuiKey_LegacyNativeKey_END; n++) + IM_ASSERT((io.KeysDown[n] == false || IsKeyDown((ImGuiKey)n)) + && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!"); + } else { + if (g.FrameCount == 0) + for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++) + IM_ASSERT(g.IO.KeyMap[n] == -1 && "Backend is not allowed to write to io.KeyMap[0..511]!"); + + // Build reverse KeyMap (Named -> Legacy) + for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++) + if (io.KeyMap[n] != -1) { + IM_ASSERT(IsLegacyKey((ImGuiKey)io.KeyMap[n])); + io.KeyMap[io.KeyMap[n]] = n; + } + + // Import legacy keys into new ones + for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++) + if (io.KeysDown[n] || io.BackendUsingLegacyKeyArrays == 1) { + const ImGuiKey key = (ImGuiKey)(io.KeyMap[n] != -1 ? io.KeyMap[n] : n); + IM_ASSERT(io.KeyMap[n] == -1 || IsNamedKey(key)); + io.KeysData[key].Down = io.KeysDown[n]; + if (key != n) + io.KeysDown[key] = io.KeysDown[n]; // Allow legacy code using io.KeysDown[GetKeyIndex()] with old backends + io.BackendUsingLegacyKeyArrays = 1; + } + if (io.BackendUsingLegacyKeyArrays == 1) { + GetKeyData(ImGuiMod_Ctrl)->Down = io.KeyCtrl; + GetKeyData(ImGuiMod_Shift)->Down = io.KeyShift; + GetKeyData(ImGuiMod_Alt)->Down = io.KeyAlt; + GetKeyData(ImGuiMod_Super)->Down = io.KeySuper; + } + } + +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; + if (io.BackendUsingLegacyNavInputArray && nav_gamepad_active) { +#define MAP_LEGACY_NAV_INPUT_TO_KEY1(_KEY, _NAV1) \ + do { \ + io.KeysData[_KEY].Down = (io.NavInputs[_NAV1] > 0.0f); \ + io.KeysData[_KEY].AnalogValue = io.NavInputs[_NAV1]; \ + } while (0) +#define MAP_LEGACY_NAV_INPUT_TO_KEY2(_KEY, _NAV1, _NAV2) \ + do { \ + io.KeysData[_KEY].Down = (io.NavInputs[_NAV1] > 0.0f) || (io.NavInputs[_NAV2] > 0.0f); \ + io.KeysData[_KEY].AnalogValue = ImMax(io.NavInputs[_NAV1], io.NavInputs[_NAV2]); \ + } while (0) + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceDown, ImGuiNavInput_Activate); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceRight, ImGuiNavInput_Cancel); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceLeft, ImGuiNavInput_Menu); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceUp, ImGuiNavInput_Input); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadLeft, ImGuiNavInput_DpadLeft); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadRight, ImGuiNavInput_DpadRight); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadUp, ImGuiNavInput_DpadUp); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadDown, ImGuiNavInput_DpadDown); + MAP_LEGACY_NAV_INPUT_TO_KEY2(ImGuiKey_GamepadL1, ImGuiNavInput_FocusPrev, ImGuiNavInput_TweakSlow); + MAP_LEGACY_NAV_INPUT_TO_KEY2(ImGuiKey_GamepadR1, ImGuiNavInput_FocusNext, ImGuiNavInput_TweakFast); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickLeft, ImGuiNavInput_LStickLeft); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickRight, ImGuiNavInput_LStickRight); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickUp, ImGuiNavInput_LStickUp); + MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickDown, ImGuiNavInput_LStickDown); +#undef NAV_MAP_KEY + } +#endif +#endif + + // 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.KeyXXX 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. So with legacy backends the 4 values will do a unnecessary back-and-forth but it makes the code simpler and future facing. + io.KeyMods = GetMergedModsFromKeys(); + io.KeyCtrl = (io.KeyMods & ImGuiMod_Ctrl) != 0; + io.KeyShift = (io.KeyMods & ImGuiMod_Shift) != 0; + io.KeyAlt = (io.KeyMods & ImGuiMod_Alt) != 0; + io.KeySuper = (io.KeyMods & ImGuiMod_Super) != 0; + + // Clear gamepad data if disabled + if ((io.BackendFlags & ImGuiBackendFlags_HasGamepad) == 0) + for (int i = ImGuiKey_Gamepad_BEGIN; i < ImGuiKey_Gamepad_END; i++) { + io.KeysData[i - ImGuiKey_KeysData_OFFSET].Down = false; + io.KeysData[i - ImGuiKey_KeysData_OFFSET].AnalogValue = 0.0f; + } + + // Update keys + for (int i = 0; i < ImGuiKey_KeysData_SIZE; i++) { + ImGuiKeyData *key_data = &io.KeysData[i]; + key_data->DownDurationPrev = key_data->DownDuration; + key_data->DownDuration = key_data->Down ? (key_data->DownDuration < 0.0f ? 0.0f : key_data->DownDuration + io.DeltaTime) : -1.0f; + } + + // 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)) { + ImGuiKeyData *key_data = &io.KeysData[key - ImGuiKey_KeysData_OFFSET]; + ImGuiKeyOwnerData *owner_data = &g.KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; + owner_data->OwnerCurr = owner_data->OwnerNext; + if (!key_data->Down) // Important: ownership is released on the frame after a release. Ensure a 'MouseDown -> CloseWindow -> MouseUp' chain doesn't lead + // to someone else seeing the MouseUp. + owner_data->OwnerNext = ImGuiKeyOwner_None; + owner_data->LockThisFrame = owner_data->LockUntilRelease = + owner_data->LockUntilRelease && key_data->Down; // Clear LockUntilRelease when key is not Down anymore + } + + 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 = ImFloorSigned(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 gamepad/keyboard. 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.NavDisableMouseHover = false; + + io.MousePosPrev = io.MousePos; + for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) { + io.MouseClicked[i] = io.MouseDown[i] && io.MouseDownDuration[i] < 0.0f; + io.MouseClickedCount[i] = 0; // Will be filled below + io.MouseReleased[i] = !io.MouseDown[i] && io.MouseDownDuration[i] >= 0.0f; + io.MouseDownDurationPrev[i] = io.MouseDownDuration[i]; + io.MouseDownDuration[i] = io.MouseDown[i] ? (io.MouseDownDuration[i] < 0.0f ? 0.0f : io.MouseDownDuration[i] + io.DeltaTime) : -1.0f; + if (io.MouseClicked[i]) { + bool is_repeated_click = false; + if ((float)(g.Time - io.MouseClickedTime[i]) < io.MouseDoubleClickTime) { + ImVec2 delta_from_click_pos = IsMousePosValid(&io.MousePos) ? (io.MousePos - io.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f); + if (ImLengthSqr(delta_from_click_pos) < io.MouseDoubleClickMaxDist * io.MouseDoubleClickMaxDist) + is_repeated_click = true; + } + if (is_repeated_click) + io.MouseClickedLastCount[i]++; + else + io.MouseClickedLastCount[i] = 1; + io.MouseClickedTime[i] = g.Time; + io.MouseClickedPos[i] = io.MousePos; + io.MouseClickedCount[i] = io.MouseClickedLastCount[i]; + io.MouseDragMaxDistanceSqr[i] = 0.0f; + } else if (io.MouseDown[i]) { + // Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold + float delta_sqr_click_pos = IsMousePosValid(&io.MousePos) ? ImLengthSqr(io.MousePos - io.MouseClickedPos[i]) : 0.0f; + io.MouseDragMaxDistanceSqr[i] = ImMax(io.MouseDragMaxDistanceSqr[i], delta_sqr_click_pos); + } + + // 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 gamepad/keyboard navigation + if (io.MouseClicked[i]) + g.NavDisableMouseHover = false; + } +} + +static void LockWheelingWindow(ImGuiWindow *window, float wheel_amount) +{ + ImGuiContext &g = *GImGui; + if (window) + g.WheelingWindowReleaseTimer = + ImMin(g.WheelingWindowReleaseTimer + ImAbs(wheel_amount) * WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER, WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER); + else + g.WheelingWindowReleaseTimer = 0.0f; + if (g.WheelingWindow == window) + return; + IMGUI_DEBUG_LOG_IO("[io] LockWheelingWindow() \"%s\"\n", window ? window->Name : "NULL"); + g.WheelingWindow = window; + g.WheelingWindowRefMousePos = g.IO.MousePos; + if (window == NULL) { + g.WheelingWindowStartFrame = -1; + 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 + ImGuiContext &g = *GImGui; + ImGuiWindow *windows[2] = {NULL, NULL}; + for (int axis = 0; axis < 2; axis++) + if (wheel[axis] != 0.0f) + for (ImGuiWindow *window = windows[axis] = g.HoveredWindow; window->Flags & ImGuiWindowFlags_ChildWindow; + window = windows[axis] = window->ParentWindow) { + // Bubble up into parent window if: + // - a child window doesn't allow any scrolling. + // - a child window has the ImGuiWindowFlags_NoScrollWithMouse flag. + //// - a child window doesn't need scrolling because it is already at the edge for the direction we are going in (FIXME-WIP) + const bool has_scrolling = (window->ScrollMax[axis] != 0.0f); + const bool inputs_disabled = (window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs); + // const bool scrolling_past_limits = (wheel_v < 0.0f) ? (window->Scroll[axis] <= 0.0f) : (window->Scroll[axis] >= window->ScrollMax[axis]); + if (has_scrolling && !inputs_disabled) // && !scrolling_past_limits) + break; // select this window + } + 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. + if (g.WheelingWindowStartFrame == -1) + g.WheelingWindowStartFrame = g.FrameCount; + if ((g.WheelingWindowStartFrame == g.FrameCount && wheel.x != 0.0f && wheel.y != 0.0f) || (g.WheelingAxisAvg.x == g.WheelingAxisAvg.y)) { + g.WheelingWindowWheelRemainder = wheel; + return NULL; + } + return (g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? windows[0] : windows[1]; +} + +// Called by NewFrame() +void ImGui::UpdateMouseWheel() +{ + // Reset the locked window if we move the mouse or after the timer elapses. + // FIXME: Ideally we could refactor to have one timer for "changing window w/ same axis" and a shorter timer for "changing window or axis w/ other axis" + // (#3795) + ImGuiContext &g = *GImGui; + if (g.WheelingWindow != NULL) { + g.WheelingWindowReleaseTimer -= g.IO.DeltaTime; + if (IsMousePosValid() && ImLengthSqr(g.IO.MousePos - g.WheelingWindowRefMousePos) > g.IO.MouseDragThreshold * g.IO.MouseDragThreshold) + g.WheelingWindowReleaseTimer = 0.0f; + if (g.WheelingWindowReleaseTimer <= 0.0f) + LockWheelingWindow(NULL, 0.0f); + } + + ImVec2 wheel; + wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, ImGuiKeyOwner_None) ? g.IO.MouseWheelH : 0.0f; + wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, ImGuiKeyOwner_None) ? 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) { + LockWheelingWindow(mouse_window, wheel.y); + ImGuiWindow *window = mouse_window; + const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f); + const float scale = new_font_scale / window->FontWindowScale; + window->FontWindowScale = new_font_scale; + if (window == window->RootWindow) { + const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size; + SetWindowPos(window, window->Pos + offset, 0); + window->Size = ImFloor(window->Size * scale); + window->SizeFull = ImFloor(window->SizeFull * scale); + } + return; + } + 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 axises + // 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 axises are being moved. + if (ImGuiWindow *window = (g.WheelingWindow ? g.WheelingWindow : FindBestWheelingWindow(wheel))) + if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)) { + bool do_scroll[2] = {wheel.x != 0.0f && window->ScrollMax.x != 0.0f, wheel.y != 0.0f && window->ScrollMax.y != 0.0f}; + if (do_scroll[ImGuiAxis_X] && do_scroll[ImGuiAxis_Y]) + do_scroll[(g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? ImGuiAxis_Y : ImGuiAxis_X] = false; + if (do_scroll[ImGuiAxis_X]) { + LockWheelingWindow(window, wheel.x); + float max_step = window->InnerRect.GetWidth() * 0.67f; + float scroll_step = ImFloor(ImMin(2 * window->CalcFontSize(), max_step)); + SetScrollX(window, window->Scroll.x - wheel.x * scroll_step); + } + if (do_scroll[ImGuiAxis_Y]) { + LockWheelingWindow(window, wheel.y); + float max_step = window->InnerRect.GetHeight() * 0.67f; + float scroll_step = ImFloor(ImMin(5 * window->CalcFontSize(), max_step)); + SetScrollY(window, window->Scroll.y - wheel.y * scroll_step); + } + } +} + +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", "Clipboard"}; + IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT && source >= 0 && source < ImGuiInputSource_COUNT); + 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); + return mouse_source_names[source]; +} +static void DebugPrintInputEvent(const char *prefix, const ImGuiInputEvent *e) +{ + ImGuiContext &g = *GImGui; + if (e->Type == ImGuiInputEventType_MousePos) { + if (e->MousePos.PosX == -FLT_MAX && e->MousePos.PosY == -FLT_MAX) + IMGUI_DEBUG_LOG_IO("[io] %s: MousePos (-FLT_MAX, -FLT_MAX)\n", prefix); + else + IMGUI_DEBUG_LOG_IO("[io] %s: MousePos (%.1f, %.1f) (%s)\n", + prefix, + e->MousePos.PosX, + e->MousePos.PosY, + GetMouseSourceName(e->MousePos.MouseSource)); + return; + } + if (e->Type == ImGuiInputEventType_MouseButton) { + IMGUI_DEBUG_LOG_IO("[io] %s: MouseButton %d %s (%s)\n", + prefix, + e->MouseButton.Button, + e->MouseButton.Down ? "Down" : "Up", + GetMouseSourceName(e->MouseButton.MouseSource)); + return; + } + if (e->Type == ImGuiInputEventType_MouseWheel) { + IMGUI_DEBUG_LOG_IO("[io] %s: MouseWheel (%.3f, %.3f) (%s)\n", + prefix, + e->MouseWheel.WheelX, + e->MouseWheel.WheelY, + GetMouseSourceName(e->MouseWheel.MouseSource)); + return; + } + if (e->Type == ImGuiInputEventType_Key) { + IMGUI_DEBUG_LOG_IO("[io] %s: Key \"%s\" %s\n", prefix, ImGui::GetKeyName(e->Key.Key), e->Key.Down ? "Down" : "Up"); + return; + } + if (e->Type == ImGuiInputEventType_Text) { + IMGUI_DEBUG_LOG_IO("[io] %s: Text: %c (U+%08X)\n", prefix, e->Text.Char, e->Text.Char); + return; + } + 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) +// - trickle_fast_inputs = true : process as many events as possible (successive down/up/down/up will be trickled over several frames so nothing is lost) (new +// feature in 1.87) +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_keys_and_text = (trickle_fast_inputs && g.WantTextInputNextFrame == 1); + + bool mouse_moved = false, mouse_wheeled = false, key_changed = 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++) { + ImGuiInputEvent *e = &g.InputEventsQueue[event_n]; + if (e->Type == ImGuiInputEventType_MousePos) { + // Trickling Rule: Stop processing queued events if we already handled a mouse button change + ImVec2 event_pos(e->MousePos.PosX, e->MousePos.PosY); + if (trickle_fast_inputs && (mouse_button_changed != 0 || mouse_wheeled || key_changed || text_inputted)) + break; + io.MousePos = event_pos; + io.MouseSource = e->MousePos.MouseSource; + mouse_moved = true; + } else if (e->Type == ImGuiInputEventType_MouseButton) { + // Trickling Rule: Stop processing queued events if we got multiple action on the same button + const ImGuiMouseButton button = e->MouseButton.Button; + IM_ASSERT(button >= 0 && button < ImGuiMouseButton_COUNT); + if (trickle_fast_inputs && ((mouse_button_changed & (1 << button)) || mouse_wheeled)) + break; + if (trickle_fast_inputs && e->MouseButton.MouseSource == ImGuiMouseSource_TouchScreen && mouse_moved) // #2702: TouchScreen have no initial hover. + break; + io.MouseDown[button] = e->MouseButton.Down; + io.MouseSource = e->MouseButton.MouseSource; + mouse_button_changed |= (1 << button); + } else if (e->Type == ImGuiInputEventType_MouseWheel) { + // Trickling Rule: Stop processing queued events if we got multiple action on the event + if (trickle_fast_inputs && (mouse_moved || mouse_button_changed != 0)) + break; + io.MouseWheelH += e->MouseWheel.WheelX; + io.MouseWheel += e->MouseWheel.WheelY; + io.MouseSource = e->MouseWheel.MouseSource; + mouse_wheeled = true; + } else if (e->Type == ImGuiInputEventType_Key) { + // Trickling Rule: Stop processing queued events if we got multiple action on the same button + ImGuiKey key = e->Key.Key; + IM_ASSERT(key != ImGuiKey_None); + ImGuiKeyData *key_data = GetKeyData(key); + 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) || text_inputted || mouse_button_changed != 0)) + break; + key_data->Down = e->Key.Down; + key_data->AnalogValue = e->Key.AnalogValue; + key_changed = true; + key_changed_mask.SetBit(key_data_index); + + // Allow legacy code using io.KeysDown[GetKeyIndex()] with new backends +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + io.KeysDown[key_data_index] = key_data->Down; + if (io.KeyMap[key_data_index] != -1) + io.KeysDown[io.KeyMap[key_data_index]] = key_data->Down; +#endif + } else if (e->Type == ImGuiInputEventType_Text) { + // Trickling Rule: Stop processing queued events if keys/mouse have been interacted with + if (trickle_fast_inputs && ((key_changed && trickle_interleaved_keys_and_text) || mouse_button_changed != 0 || mouse_moved || mouse_wheeled)) + break; + unsigned int c = e->Text.Char; + io.InputQueueCharacters.push_back(c <= IM_UNICODE_CODEPOINT_MAX ? (ImWchar)c : IM_UNICODE_CODEPOINT_INVALID); + if (trickle_interleaved_keys_and_text) + text_inputted = true; + } else if (e->Type == ImGuiInputEventType_Focus) { + // We intentionally overwrite this and process in NewFrame(), in order to give a chance + // to multi-viewports backends to queue AddFocusEvent(false) + AddFocusEvent(true) in same frame. + const bool focus_lost = !e->AppFocused.Focused; + io.AppFocusLost = focus_lost; + } else { + 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 + // (e.g. custom backend may want to clear additional data, custom widgets may want to react with a "canceling" event). + if (g.IO.AppFocusLost) + g.IO.ClearInputKeys(); +} + +ImGuiID ImGui::GetKeyOwner(ImGuiKey key) +{ + if (!IsNamedKeyOrModKey(key)) + return ImGuiKeyOwner_None; + + 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_None; + + return owner_id; +} + +// TestKeyOwner(..., ID) : (owner == None || owner == ID) +// TestKeyOwner(..., None) : (owner == None) +// TestKeyOwner(..., Any) : no owner test +// All paths are also testing for key not being locked, for the rare cases that key have been locked with using ImGuiInputFlags_LockXXX flags. +bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id) +{ + if (!IsNamedKeyOrModKey(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. + if (owner_data->OwnerCurr != owner_id) { + if (owner_data->LockThisFrame) + return false; + if (owner_data->OwnerCurr != ImGuiKeyOwner_None) + 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 +// - SetKeyOwner(..., Any, !Lock) : illegal (assert) +// - SetKeyOwner(..., Any or None, Lock) : set lock +void ImGui::SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags) +{ + IM_ASSERT(IsNamedKeyOrModKey(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! + + ImGuiContext &g = *GImGui; + 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) +{ + if (key_chord & ImGuiMod_Ctrl) { + SetKeyOwner(ImGuiMod_Ctrl, owner_id, flags); + } + if (key_chord & ImGuiMod_Shift) { + SetKeyOwner(ImGuiMod_Shift, owner_id, flags); + } + if (key_chord & ImGuiMod_Alt) { + SetKeyOwner(ImGuiMod_Alt, owner_id, flags); + } + if (key_chord & ImGuiMod_Super) { + SetKeyOwner(ImGuiMod_Super, owner_id, flags); + } + if (key_chord & ImGuiMod_Shortcut) { + SetKeyOwner(ImGuiMod_Shortcut, 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()); +// Extensive uses of that (e.g. many calls for a single item) may want to manually perform the tests once and then call SetKeyOwner() multiple times. +// More advanced usage scenarios may want to call SetKeyOwner() manually based on different condition. +// Worth noting is that only one item can be hovered and only one item can be active, therefore this usage pattern doesn't need to bother with routing and +// priority. +void ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags) +{ + ImGuiContext &g = *GImGui; + ImGuiID id = g.LastItemData.ID; + if (id == 0 || (g.HoveredId != id && g.ActiveId != id)) + return; + if ((flags & ImGuiInputFlags_CondMask_) == 0) + flags |= ImGuiInputFlags_CondDefault_; + if ((g.HoveredId == id && (flags & ImGuiInputFlags_CondHovered)) || (g.ActiveId == id && (flags & ImGuiInputFlags_CondActive))) { + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetItemKeyOwner) == 0); // Passing flags not supported by this function! + SetKeyOwner(key, id, flags & ~ImGuiInputFlags_CondMask_); + } +} + +bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags) +{ + ImGuiContext &g = *GImGui; + + // 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_RouteMask_) == 0) + flags |= ImGuiInputFlags_RouteFocused; + if (!SetShortcutRouting(key_chord, owner_id, flags)) + return false; + + if (key_chord & ImGuiMod_Shortcut) + key_chord = ConvertShortcutMod(key_chord); + 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) + key = ConvertSingleModFlagToKey(&g, mods); + + if (!IsKeyPressed(key, owner_id, (flags & (ImGuiInputFlags_Repeat | (ImGuiInputFlags)ImGuiInputFlags_RepeatRateMask_)))) + return false; + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByShortcut) == 0); // Passing flags not supported by this function! + + return true; +} + +//----------------------------------------------------------------------------- +// [SECTION] ERROR CHECKING +//----------------------------------------------------------------------------- + +// Helper function to verify ABI compatibility between caller code and compiled version of Dear ImGui. +// Verify that the type sizes are matching between the calling file's compilation unit and imgui.cpp's compilation unit +// If this triggers you have an issue: +// - Most commonly: mismatched headers and compiled code version. +// - Or: mismatched configuration #define, compilation settings, packing pragma etc. +// The configuration settings mentioned in imconfig.h must be set for all compilation units involved with Dear ImGui, +// which is way it is required you put them in your imconfig file (and not just before including imgui.h). +// Otherwise it is possible that different compilation units would see different structure layout +bool ImGui::DebugCheckVersionAndDataLayout(const char *version, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_vert, size_t sz_idx) +{ + bool error = false; + if (strcmp(version, IMGUI_VERSION) != 0) { + error = true; + IM_ASSERT(strcmp(version, IMGUI_VERSION) == 0 && "Mismatched version string!"); + } + if (sz_io != sizeof(ImGuiIO)) { + error = true; + IM_ASSERT(sz_io == sizeof(ImGuiIO) && "Mismatched struct layout!"); + } + if (sz_style != sizeof(ImGuiStyle)) { + error = true; + IM_ASSERT(sz_style == sizeof(ImGuiStyle) && "Mismatched struct layout!"); + } + if (sz_vec2 != sizeof(ImVec2)) { + error = true; + IM_ASSERT(sz_vec2 == sizeof(ImVec2) && "Mismatched struct layout!"); + } + if (sz_vec4 != sizeof(ImVec4)) { + error = true; + IM_ASSERT(sz_vec4 == sizeof(ImVec4) && "Mismatched struct layout!"); + } + if (sz_vert != sizeof(ImDrawVert)) { + error = true; + IM_ASSERT(sz_vert == sizeof(ImDrawVert) && "Mismatched struct layout!"); + } + 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] +// Previously this would make the window content size ~200x200: +// Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); // NOT OK +// 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. +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 +} + +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. + // This is a common C/C++ idiom to allow multiple statements macros to be used in control flow blocks.) + // #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 + IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting."); + 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); +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_COUNT; n++) + IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < ImGuiKey_LegacyNativeKey_END + && "io.KeyMap[] contains an out of bound value (need to be 0..511, or -1 for unmapped key)"); + + // Check: required key mapping (we intentionally do NOT check all keys to not pressure user into setting up everything, but Space is required and was only + // added in 1.60 WIP) + if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && g.IO.BackendUsingLegacyKeyArrays == 1) + IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation."); +#endif + + // Check: the io.ConfigWindowsResizeFromEdges option requires backend to honor mouse cursor changes and set the ImGuiBackendFlags_HasMouseCursors flag + // accordingly. + if (g.IO.ConfigWindowsResizeFromEdges && !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseCursors)) + g.IO.ConfigWindowsResizeFromEdges = false; +} + +static void ImGui::ErrorCheckEndFrameSanityChecks() +{ + ImGuiContext &g = *GImGui; + + // Verify that io.KeyXXX fields haven't been tampered with. Key mods should not be modified between NewFrame() and EndFrame() + // One possible reason leading to this assert is that your backends update inputs _AFTER_ NewFrame(). + // It is known that when some modal native windows called mid-frame takes focus away, some backends such as GLFW will + // send key release events mid-frame. This would normally trigger this assertion and lead to sheared inputs. + // We silently accommodate for this case by ignoring the case where all io.KeyXXX modifiers were released (aka key_mod_flags == 0), + // while still correctly asserting on mid-frame key press events. + const ImGuiKeyChord key_mods = GetMergedModsFromKeys(); + 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); + + // [EXPERIMENTAL] Recover from errors: You may call this yourself before EndFrame(). + // ErrorCheckEndFrameRecover(); + + // Report when there is a mismatch of Begin/BeginChild vs End/EndChild calls. Important: Remember that the Begin/BeginChild API requires you + // to always call End/EndChild even if Begin/BeginChild returns false! (this is unfortunately inconsistent with most other Begin* API). + if (g.CurrentWindowStack.Size != 1) { + if (g.CurrentWindowStack.Size > 1) { + ImGuiWindow *window = g.CurrentWindowStack.back().Window; // <-- This window was not Ended! + IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?"); + IM_UNUSED(window); + while (g.CurrentWindowStack.Size > 1) + End(); + } else { + IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?"); + } + } + + IM_ASSERT_USER_ERROR(g.GroupStack.Size == 0, "Missing EndGroup call!"); +} + +// Experimental recovery from incorrect usage of BeginXXX/EndXXX/PushXXX/PopXXX calls. +// Must be called during or before EndFrame(). +// This is generally flawed as we are not necessarily End/Popping things in the right order. +// FIXME: Can't recover from inside BeginTabItem/EndTabItem yet. +// FIXME: Can't recover from interleaved BeginTabBar/Begin +void ImGui::ErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback, void *user_data) +{ + // PVS-Studio V1044 is "Loop break conditions do not depend on the number of iterations" + ImGuiContext &g = *GImGui; + while (g.CurrentWindowStack.Size > 0) //-V1044 + { + ErrorCheckEndWindowRecover(log_callback, user_data); + ImGuiWindow *window = g.CurrentWindow; + if (g.CurrentWindowStack.Size == 1) { + IM_ASSERT(window->IsFallbackWindow); + break; + } + if (window->Flags & ImGuiWindowFlags_ChildWindow) { + if (log_callback) + log_callback(user_data, "Recovered from missing EndChild() for '%s'", window->Name); + EndChild(); + } else { + if (log_callback) + log_callback(user_data, "Recovered from missing End() for '%s'", window->Name); + End(); + } + } +} + +// Must be called before End()/EndChild() +void ImGui::ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, void *user_data) +{ + ImGuiContext &g = *GImGui; + while (g.CurrentTable && (g.CurrentTable->OuterWindow == g.CurrentWindow || g.CurrentTable->InnerWindow == g.CurrentWindow)) { + if (log_callback) + log_callback(user_data, "Recovered from missing EndTable() in '%s'", g.CurrentTable->OuterWindow->Name); + EndTable(); + } + + ImGuiWindow *window = g.CurrentWindow; + ImGuiStackSizes *stack_sizes = &g.CurrentWindowStack.back().StackSizesOnBegin; + IM_ASSERT(window != NULL); + while (g.CurrentTabBar != NULL) //-V1044 + { + if (log_callback) + log_callback(user_data, "Recovered from missing EndTabBar() in '%s'", window->Name); + EndTabBar(); + } + while (window->DC.TreeDepth > 0) { + if (log_callback) + log_callback(user_data, "Recovered from missing TreePop() in '%s'", window->Name); + TreePop(); + } + while (g.GroupStack.Size > stack_sizes->SizeOfGroupStack) //-V1044 + { + if (log_callback) + log_callback(user_data, "Recovered from missing EndGroup() in '%s'", window->Name); + EndGroup(); + } + while (window->IDStack.Size > 1) { + if (log_callback) + log_callback(user_data, "Recovered from missing PopID() in '%s'", window->Name); + PopID(); + } + while (g.DisabledStackSize > stack_sizes->SizeOfDisabledStack) //-V1044 + { + if (log_callback) + log_callback(user_data, "Recovered from missing EndDisabled() in '%s'", window->Name); + EndDisabled(); + } + while (g.ColorStack.Size > stack_sizes->SizeOfColorStack) { + if (log_callback) + log_callback(user_data, "Recovered from missing PopStyleColor() in '%s' for ImGuiCol_%s", window->Name, GetStyleColorName(g.ColorStack.back().Col)); + PopStyleColor(); + } + while (g.ItemFlagsStack.Size > stack_sizes->SizeOfItemFlagsStack) //-V1044 + { + if (log_callback) + log_callback(user_data, "Recovered from missing PopItemFlag() in '%s'", window->Name); + PopItemFlag(); + } + while (g.StyleVarStack.Size > stack_sizes->SizeOfStyleVarStack) //-V1044 + { + if (log_callback) + log_callback(user_data, "Recovered from missing PopStyleVar() in '%s'", window->Name); + PopStyleVar(); + } + while (g.FontStack.Size > stack_sizes->SizeOfFontStack) //-V1044 + { + if (log_callback) + log_callback(user_data, "Recovered from missing PopFont() in '%s'", window->Name); + PopFont(); + } + while (g.FocusScopeStack.Size > stack_sizes->SizeOfFocusScopeStack + 1) //-V1044 + { + if (log_callback) + log_callback(user_data, "Recovered from missing PopFocusScope() in '%s'", window->Name); + PopFocusScope(); + } +} + +// Save current stack sizes for later compare +void ImGuiStackSizes::SetToContextState(ImGuiContext *ctx) +{ + ImGuiContext &g = *ctx; + ImGuiWindow *window = g.CurrentWindow; + SizeOfIDStack = (short)window->IDStack.Size; + SizeOfColorStack = (short)g.ColorStack.Size; + SizeOfStyleVarStack = (short)g.StyleVarStack.Size; + SizeOfFontStack = (short)g.FontStack.Size; + SizeOfFocusScopeStack = (short)g.FocusScopeStack.Size; + SizeOfGroupStack = (short)g.GroupStack.Size; + SizeOfItemFlagsStack = (short)g.ItemFlagsStack.Size; + SizeOfBeginPopupStack = (short)g.BeginPopupStack.Size; + SizeOfDisabledStack = (short)g.DisabledStackSize; +} + +// Compare to detect usage errors +void ImGuiStackSizes::CompareWithContextState(ImGuiContext *ctx) +{ + ImGuiContext &g = *ctx; + ImGuiWindow *window = g.CurrentWindow; + IM_UNUSED(window); + + // Window stacks + // NOT checking: DC.ItemWidth, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin) + IM_ASSERT(SizeOfIDStack == window->IDStack.Size && "PushID/PopID or TreeNode/TreePop Mismatch!"); + + // Global stacks + // For color, style and font stacks there is an incentive to use Push/Begin/Pop/.../End patterns, so we relax our checks a little to allow them. + IM_ASSERT(SizeOfGroupStack == g.GroupStack.Size && "BeginGroup/EndGroup Mismatch!"); + IM_ASSERT(SizeOfBeginPopupStack == g.BeginPopupStack.Size && "BeginPopup/EndPopup or BeginMenu/EndMenu Mismatch!"); + IM_ASSERT(SizeOfDisabledStack == g.DisabledStackSize && "BeginDisabled/EndDisabled Mismatch!"); + IM_ASSERT(SizeOfItemFlagsStack >= g.ItemFlagsStack.Size && "PushItemFlag/PopItemFlag Mismatch!"); + IM_ASSERT(SizeOfColorStack >= g.ColorStack.Size && "PushStyleColor/PopStyleColor Mismatch!"); + IM_ASSERT(SizeOfStyleVarStack >= g.StyleVarStack.Size && "PushStyleVar/PopStyleVar Mismatch!"); + IM_ASSERT(SizeOfFontStack >= g.FontStack.Size && "PushFont/PopFont Mismatch!"); + IM_ASSERT(SizeOfFocusScopeStack == g.FocusScopeStack.Size && "PushFocusScope/PopFocusScope Mismatch!"); +} + +//----------------------------------------------------------------------------- +// [SECTION] LAYOUT +//----------------------------------------------------------------------------- +// - ItemSize() +// - ItemAdd() +// - SameLine() +// - GetCursorScreenPos() +// - SetCursorScreenPos() +// - GetCursorPos(), GetCursorPosX(), GetCursorPosY() +// - SetCursorPos(), SetCursorPosX(), SetCursorPosY() +// - GetCursorStartPos() +// - Indent() +// - Unindent() +// - SetNextItemWidth() +// - PushItemWidth() +// - PushMultiItemsWidths() +// - PopItemWidth() +// - CalcItemWidth() +// - CalcItemSize() +// - GetTextLineHeight() +// - GetTextLineHeightWithSpacing() +// - GetFrameHeight() +// - GetFrameHeightWithSpacing() +// - GetContentRegionMax() +// - GetContentRegionMaxAbs() [Internal] +// - GetContentRegionAvail(), +// - GetWindowContentRegionMin(), GetWindowContentRegionMax() +// - BeginGroup() +// - 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. +void ImGui::ItemSize(const ImVec2 &size, float text_baseline_y) +{ + ImGuiContext &g = *GImGui; + 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; + window->DC.CursorPosPrevLine.y = line_y1; + window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); // Next line + window->DC.CursorPos.y = IM_FLOOR(line_y1 + line_height + g.Style.ItemSpacing.y); // Next line + 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(); +} + +// 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. +bool ImGui::ItemAdd(const ImRect &bb, ImGuiID id, const ImRect *nav_bb_arg, ImGuiItemFlags extra_flags) +{ + 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; + g.LastItemData.Rect = bb; + g.LastItemData.NavRect = nav_bb_arg ? *nav_bb_arg : bb; + g.LastItemData.InFlags = g.CurrentItemFlags | g.NextItemData.ItemFlags | extra_flags; + g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None; + + // Directional navigation processing + if (id != 0) { + KeepAliveID(id); + + // Runs prior to clipping early-out + // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget + // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests + // unfortunately, but it is still limited to one window. It may not scale very well for windows with ten of + // thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a frame. + // We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't be able + // to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick). + // We intentionally don't check if g.NavWindow != NULL because g.NavAnyRequest should only be set when it is non null. + // If we crash on a NULL g.NavWindow we need to fix the bug elsewhere. + if (!(g.LastItemData.InFlags & ImGuiItemFlags_NoNav)) { + window->DC.NavLayersActiveMaskNext |= (1 << window->DC.NavLayerCurrent); + if (g.NavId == id || g.NavAnyRequest) + if (g.NavWindow->RootWindowForNav == window->RootWindowForNav) + if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened)) + NavProcessItem(); + } + + // [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!"); + } + g.NextItemData.Flags = 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 + // (FIXME: This is a modified copy of IsClippedEx() so we can reuse the is_rect_visible value) + // const bool is_clipped = IsClippedEx(bb, id); + // if (is_clipped) + // return false; + const bool is_rect_visible = bb.Overlaps(window->ClipRect); + if (!is_rect_visible) + if (id == 0 || (id != g.ActiveId && id != g.ActiveIdPreviousFrame && id != g.NavId)) + if (!g.LogEnabled) + return false; + + // [DEBUG] +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + if (id != 0 && id == g.DebugLocateId) + DebugLocateItemResolveWithLastItem(); +#endif + // if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] + // if ((g.LastItemData.InFlags & ImGuiItemFlags_NoNav) == 0) + // window->DrawList->AddRect(g.LastItemData.NavRect.Min, g.LastItemData.NavRect.Max, IM_COL32(255,255,0,255)); // [DEBUG] + + // 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; + if (IsMouseHoveringRect(bb.Min, bb.Max)) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; + return true; +} + +// 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) +// spacing_w < 0 : use default spacing if offset_from_start_x == 0, no spacing if offset_from_start_x != 0 +// spacing_w >= 0 : enforce spacing amount +void ImGui::SameLine(float offset_from_start_x, float spacing_w) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + if (window->SkipItems) + return; + + if (offset_from_start_x != 0.0f) { + if (spacing_w < 0.0f) + spacing_w = 0.0f; + window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + offset_from_start_x + spacing_w + window->DC.GroupOffset.x + window->DC.ColumnsOffset.x; + window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; + } else { + if (spacing_w < 0.0f) + spacing_w = g.Style.ItemSpacing.x; + window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w; + window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; + } + window->DC.CurrLineSize = window->DC.PrevLineSize; + 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(); + window->DC.CursorPos = 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() +{ + 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(); + window->DC.CursorPos = window->Pos - window->Scroll + local_pos; + // window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); + window->DC.IsSetPos = true; +} + +void ImGui::SetCursorPosX(float x) +{ + ImGuiWindow *window = GetCurrentWindow(); + window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + 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(); + window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + 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; + ImGuiWindow *window = GetCurrentWindow(); + 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; + ImGuiWindow *window = GetCurrentWindow(); + 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) +{ + ImGuiContext &g = *GImGui; + g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasWidth; + g.NextItemData.Width = item_width; +} + +// FIXME: Remove the == 0.0f behavior? +void ImGui::PushItemWidth(float item_width) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); // Backup current width + window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width); + g.NextItemData.Flags &= ~ImGuiNextItemDataFlags_HasWidth; +} + +void ImGui::PushMultiItemsWidths(int components, float w_full) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + const ImGuiStyle &style = g.Style; + const float w_item_one = ImMax(1.0f, IM_FLOOR((w_full - (style.ItemInnerSpacing.x) * (components - 1)) / (float)components)); + const float w_item_last = ImMax(1.0f, IM_FLOOR(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components - 1))); + window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); // Backup current width + window->DC.ItemWidthStack.push_back(w_item_last); + for (int i = 0; i < components - 2; i++) + window->DC.ItemWidthStack.push_back(w_item_one); + window->DC.ItemWidth = (components == 1) ? w_item_last : w_item_one; + g.NextItemData.Flags &= ~ImGuiNextItemDataFlags_HasWidth; +} + +void ImGui::PopItemWidth() +{ + ImGuiWindow *window = GetCurrentWindow(); + 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() +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + float w; + if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasWidth) + w = g.NextItemData.Width; + else + w = window->DC.ItemWidth; + if (w < 0.0f) { + float region_max_x = GetContentRegionMaxAbs().x; + w = ImMax(1.0f, region_max_x - window->DC.CursorPos.x + w); + } + w = IM_FLOOR(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. +// The 4.0f here may be changed to match CalcItemWidth() and/or BeginChild() (right now we have a mismatch which is harmless but undesirable) +ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_w, float default_h) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + + ImVec2 region_max; + if (size.x < 0.0f || size.y < 0.0f) + region_max = GetContentRegionMaxAbs(); + + if (size.x == 0.0f) + size.x = default_w; + else if (size.x < 0.0f) + size.x = ImMax(4.0f, region_max.x - window->DC.CursorPos.x + size.x); + + if (size.y == 0.0f) + size.y = default_h; + else if (size.y < 0.0f) + size.y = ImMax(4.0f, region_max.y - window->DC.CursorPos.y + size.y); + + 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; +} + +// FIXME: All the Contents Region function are messy or misleading. WE WILL AIM TO OBSOLETE ALL OF THEM WITH A NEW "WORK RECT" API. Thanks for your patience! + +// FIXME: This is in window space (not screen space!). +ImVec2 ImGui::GetContentRegionMax() +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + ImVec2 mx = (window->DC.CurrentColumns || g.CurrentTable) ? window->WorkRect.Max : window->ContentRegionRect.Max; + return mx - window->Pos; +} + +// [Internal] Absolute coordinate. Saner. This is not exposed until we finishing refactoring work rect features. +ImVec2 ImGui::GetContentRegionMaxAbs() +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + ImVec2 mx = (window->DC.CurrentColumns || g.CurrentTable) ? window->WorkRect.Max : window->ContentRegionRect.Max; + return mx; +} + +ImVec2 ImGui::GetContentRegionAvail() +{ + ImGuiWindow *window = GImGui->CurrentWindow; + return GetContentRegionMaxAbs() - window->DC.CursorPos; +} + +// In window space (not screen space!) +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; +} + +// 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? +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; + group_data.BackupCursorPos = window->DC.CursorPos; + group_data.BackupCursorMaxPos = window->DC.CursorMaxPos; + group_data.BackupIndent = window->DC.Indent; + group_data.BackupGroupOffset = window->DC.GroupOffset; + group_data.BackupCurrLineSize = window->DC.CurrLineSize; + group_data.BackupCurrLineTextBaseOffset = window->DC.CurrLineTextBaseOffset; + group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive; + group_data.BackupHoveredIdIsAlive = g.HoveredId != 0; + group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive; + 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; + window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); + 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(); + + ImRect group_bb(group_data.BackupCursorPos, ImMax(window->DC.CursorMaxPos, group_data.BackupCursorPos)); + + window->DC.CursorPos = group_data.BackupCursorPos; + window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos); + window->DC.Indent = group_data.BackupIndent; + window->DC.GroupOffset = group_data.BackupGroupOffset; + window->DC.CurrLineSize = group_data.BackupCurrLineSize; + window->DC.CurrLineTextBaseOffset = group_data.BackupCurrLineTextBaseOffset; + 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. (The two tests not the same because + // ActiveIdIsAlive is an ID itself, in order to be able to handle ActiveId being overwritten during the frame.) + const bool group_contains_curr_active_id = (group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId; + const bool group_contains_prev_active_id = (group_data.BackupActiveIdPreviousFrameIsAlive == false) && (g.ActiveIdPreviousFrameIsAlive == true); + if (group_contains_curr_active_id) + g.LastItemData.ID = g.ActiveId; + else if (group_contains_prev_active_id) + g.LastItemData.ID = g.ActiveIdPreviousFrame; + 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_prev_active_id && g.ActiveId != g.ActiveIdPreviousFrame) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Deactivated; + + g.GroupStack.pop_back(); + // 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? +// Note that the effect for this won't be visible on X axis with default Style settings as WindowPadding.x == ItemSpacing.x by default. +static float CalcScrollEdgeSnap(float target, float snap_min, float snap_max, float snap_threshold, float center_ratio) +{ + if (target <= snap_min + snap_threshold) + return ImLerp(snap_min, target, center_ratio); + if (target >= snap_max - snap_threshold) + return ImLerp(target, snap_max, center_ratio); + return target; +} + +static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow *window) +{ + ImVec2 scroll = window->Scroll; + ImVec2 decoration_size(window->DecoOuterSizeX1 + window->DecoInnerSizeX1 + window->DecoOuterSizeX2, + window->DecoOuterSizeY1 + window->DecoInnerSizeY1 + window->DecoOuterSizeY2); + for (int axis = 0; axis < 2; axis++) { + if (window->ScrollTarget[axis] < FLT_MAX) { + float center_ratio = window->ScrollTargetCenterRatio[axis]; + float scroll_target = window->ScrollTarget[axis]; + if (window->ScrollTargetEdgeSnapDist[axis] > 0.0f) { + float snap_min = 0.0f; + float snap_max = window->ScrollMax[axis] + window->SizeFull[axis] - decoration_size[axis]; + scroll_target = CalcScrollEdgeSnap(scroll_target, snap_min, snap_max, window->ScrollTargetEdgeSnapDist[axis], center_ratio); + } + scroll[axis] = scroll_target - center_ratio * (window->SizeFull[axis] - decoration_size[axis]); + } + scroll[axis] = IM_FLOOR(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) +{ + ImGuiContext &g = *GImGui; + ImRect scroll_rect(window->InnerRect.Min - ImVec2(1, 1), window->InnerRect.Max + ImVec2(1, 1)); + scroll_rect.Min.x = ImMin(scroll_rect.Min.x + window->DecoInnerSizeX1, scroll_rect.Max.x); + 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) + SetScrollFromPosX(window, item_rect.Min.x - g.Style.ItemSpacing.x - window->Pos.x, 0.0f); + else if (item_rect.Max.x >= scroll_rect.Max.x) + SetScrollFromPosX(window, item_rect.Max.x + g.Style.ItemSpacing.x - window->Pos.x, 1.0f); + } else if (((flags & ImGuiScrollFlags_KeepVisibleCenterX) && !fully_visible_x) || (flags & ImGuiScrollFlags_AlwaysCenterX)) { + if (can_be_fully_visible_x) + SetScrollFromPosX(window, ImFloor((item_rect.Min.x + item_rect.Max.x) * 0.5f) - window->Pos.x, 0.5f); + 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) + SetScrollFromPosY(window, item_rect.Min.y - g.Style.ItemSpacing.y - window->Pos.y, 0.0f); + else if (item_rect.Max.y >= scroll_rect.Max.y) + SetScrollFromPosY(window, item_rect.Max.y + g.Style.ItemSpacing.y - window->Pos.y, 1.0f); + } else if (((flags & ImGuiScrollFlags_KeepVisibleCenterY) && !fully_visible_y) || (flags & ImGuiScrollFlags_AlwaysCenterY)) { + if (can_be_fully_visible_y) + SetScrollFromPosY(window, ImFloor((item_rect.Min.y + item_rect.Max.y) * 0.5f) - window->Pos.y, 0.5f); + 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)) { + // FIXME-SCROLL: May be an option? + if ((in_flags & (ImGuiScrollFlags_AlwaysCenterX | ImGuiScrollFlags_KeepVisibleCenterX)) != 0) + in_flags = (in_flags & ~ImGuiScrollFlags_MaskX_) | ImGuiScrollFlags_KeepVisibleEdgeX; + if ((in_flags & (ImGuiScrollFlags_AlwaysCenterY | ImGuiScrollFlags_KeepVisibleCenterY)) != 0) + 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) +// - So local_x/local_y are 0.0f for a position at the upper-left corner of a window, +// and generally local_x/local_y are >(padding+decoration) && <(size-padding-decoration) when in the visible area. +// - They mostly exist because of legacy API. +// Following the rules above, when trying to work with scrolling code, consider that: +// - SetScrollFromPosY(0.0f) == SetScrollY(0.0f + scroll.y) == has no effect! +// - SetScrollFromPosY(-scroll.y) == SetScrollY(-scroll.y + scroll.y) == SetScrollY(0.0f) == reset scroll. Of course writing SetScrollY(0.0f) directly then +// makes more sense +// We store a target position so centering and clamping can occur on the next frame when we are guaranteed to have a known window size +void ImGui::SetScrollFromPosX(ImGuiWindow *window, float local_x, float center_x_ratio) +{ + IM_ASSERT(center_x_ratio >= 0.0f && center_x_ratio <= 1.0f); + window->ScrollTarget.x = + IM_FLOOR(local_x - window->DecoOuterSizeX1 - window->DecoInnerSizeX1 + window->Scroll.x); // Convert local position to scroll offset + 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); + window->ScrollTarget.y = + IM_FLOOR(local_y - window->DecoOuterSizeY1 - window->DecoInnerSizeY1 + window->Scroll.y); // Convert local position to scroll offset + 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) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + 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) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + 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; + + if (g.DragDropWithinSource || g.DragDropWithinTarget) { + // Drag and Drop tooltips are positioning differently than other tooltips: + // - 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). + // ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding; + ImVec2 tooltip_pos = g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET * g.Style.MouseCursorScale; + SetNextWindowPos(tooltip_pos); + 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; + } + + char window_name[16]; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount); + if (tooltip_flags & ImGuiTooltipFlags_OverridePrevious) + if (ImGuiWindow *window = FindWindowByName(window_name)) + if (window->Active) { + // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one. + SetWindowHiddendAndSkipItemsForCurrentFrame(window); + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount); + } + ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove + | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize; + Begin(window_name, NULL, flags | extra_window_flags); + // 2023-03-09: Added bool return value to the API, but currently always returning true. + // If this ever returns false we need to update BeginDragDropSource() accordingly. + // if (!ret) + // End(); + // 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; + va_start(args, fmt); + SetTooltipV(fmt, args); + va_end(args); +} + +void ImGui::SetTooltipV(const char *fmt, va_list args) +{ + if (!BeginTooltipEx(ImGuiTooltipFlags_OverridePrevious, ImGuiWindowFlags_None)) + return; + 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, ...) +{ + va_list args; + va_start(args, fmt); + if (IsItemHovered(ImGuiHoveredFlags_ForTooltip)) + 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) +{ + ImGuiContext &g = *GImGui; + if (popup_flags & ImGuiPopupFlags_AnyPopupId) { + // Return true if any popup is open at the current BeginPopup() level of the popup stack + // This may be used to e.g. test for another popups already opened to handle popups priorities at the same level. + IM_ASSERT(id == 0); + if (popup_flags & ImGuiPopupFlags_AnyPopupLevel) + return g.OpenPopupStack.Size > 0; + else + return g.OpenPopupStack.Size > g.BeginPopupStack.Size; + } else { + if (popup_flags & ImGuiPopupFlags_AnyPopupLevel) { + // Return true if the popup is open anywhere in the popup stack + for (int n = 0; n < g.OpenPopupStack.Size; n++) + if (g.OpenPopupStack[n].PopupId == id) + return true; + return false; + } else { + // Return true if the popup is open at the current BeginPopup() level of the popup stack (this is the most-common query) + return g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].PopupId == id; + } + } +} + +bool ImGui::IsPopupOpen(const char *str_id, ImGuiPopupFlags popup_flags) +{ + ImGuiContext &g = *GImGui; + ImGuiID id = (popup_flags & ImGuiPopupFlags_AnyPopupId) ? 0 : g.CurrentWindow->GetID(str_id); + if ((popup_flags & ImGuiPopupFlags_AnyPopupLevel) && id != 0) + 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() +{ + ImGuiContext &g = *GImGui; + for (int n = g.OpenPopupStack.Size - 1; n >= 0; n--) + if (ImGuiWindow *popup = g.OpenPopupStack.Data[n].Window) + if (popup->Flags & ImGuiWindowFlags_Modal) + return popup; + return NULL; +} + +// See Demo->Stacked Modal to confirm what this is for. +ImGuiWindow *ImGui::GetTopMostAndVisiblePopupModal() +{ + ImGuiContext &g = *GImGui; + for (int n = g.OpenPopupStack.Size - 1; n >= 0; n--) + if (ImGuiWindow *popup = g.OpenPopupStack.Data[n].Window) + if ((popup->Flags & ImGuiWindowFlags_Modal) && IsWindowActiveAndVisible(popup)) + return popup; + return NULL; +} + +void ImGui::OpenPopup(const char *str_id, ImGuiPopupFlags popup_flags) +{ + ImGuiContext &g = *GImGui; + ImGuiID id = g.CurrentWindow->GetID(str_id); + 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). +// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL) +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; + popup_ref.BackupNavWindow = g.NavWindow; // When popup closes focus may be restored to NavWindow (depend on window type). + popup_ref.OpenFrameCount = g.FrameCount; + 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) { + g.OpenPopupStack.push_back(popup_ref); + } else { + // Gently handle the user mistakenly calling OpenPopup() every frame. It is a programming mistake! However, if we were to run the regular code path, the + // ui would become completely unusable because the popup will always be in hidden-while-calculating-size state _while_ claiming focus. Which would be a + // very confusing situation for the programmer. Instead, we silently allow the popup to proceed, it will keep reappearing and the programming error will + // be more obvious to understand. + if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1) { + g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount; + } else { + // Close child popups if any, then flag popup for open/reopen + ClosePopupToLevel(current_stack_size, false); + 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) +{ + ImGuiContext &g = *GImGui; + if (g.OpenPopupStack.Size == 0) + return; + + // Don't close our own child popup windows. + int popup_count_to_keep = 0; + if (ref_window) { + // Find the highest popup which is a descendant of the reference window (generally reference window = NavWindow) + for (; popup_count_to_keep < g.OpenPopupStack.Size; popup_count_to_keep++) { + ImGuiPopupData &popup = g.OpenPopupStack[popup_count_to_keep]; + if (!popup.Window) + continue; + IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0); + if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow) + continue; + + // Trim the stack unless the popup is a direct parent of the reference window (the reference window is often the NavWindow) + // - With this stack of window, clicking/focusing Popup1 will close Popup2 and Popup3: + // Window -> Popup1 -> Popup2 -> Popup3 + // - Each popups may contain child windows, which is why we compare ->RootWindow! + // Window -> Popup1 -> Popup1_Child -> Popup2 -> Popup2_Child + bool ref_window_is_descendent_of_popup = false; + for (int n = popup_count_to_keep; n < g.OpenPopupStack.Size; n++) + if (ImGuiWindow *popup_window = g.OpenPopupStack[n].Window) + if (IsWindowWithinBeginStackOf(ref_window, popup_window)) { + ref_window_is_descendent_of_popup = true; + break; + } + if (!ref_window_is_descendent_of_popup) + break; + } + } + 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 + { + IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupsOverWindow(\"%s\")\n", ref_window ? ref_window->Name : ""); + 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--) { + ImGuiWindow *window = g.OpenPopupStack[popup_count_to_keep - 1].Window; + if (!window || (window->Flags & ImGuiWindowFlags_Modal)) + break; + } + 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; + IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupToLevel(%d), restore_focus_to_window_under_popup=%d\n", remaining, restore_focus_to_window_under_popup); + IM_ASSERT(remaining >= 0 && remaining < g.OpenPopupStack.Size); + + // Trim open popup stack + ImGuiWindow *popup_window = g.OpenPopupStack[remaining].Window; + ImGuiWindow *popup_backup_nav_window = g.OpenPopupStack[remaining].BackupNavWindow; + g.OpenPopupStack.resize(remaining); + + if (restore_focus_to_window_under_popup) { + ImGuiWindow *focus_window = (popup_window && popup_window->Flags & ImGuiWindowFlags_ChildMenu) ? popup_window->ParentWindow : popup_backup_nav_window; + if (focus_window && !focus_window->WasActive && popup_window) + FocusTopMostWindowUnderOne(popup_window, NULL, NULL, ImGuiFocusRequestFlags_RestoreFocusedChild); // Fallback + else + FocusWindow(focus_window, (g.NavLayer == ImGuiNavLayer_Main) ? ImGuiFocusRequestFlags_RestoreFocusedChild : ImGuiFocusRequestFlags_None); + } +} + +// Close the popup we have begin-ed into. +void ImGui::CloseCurrentPopup() +{ + ImGuiContext &g = *GImGui; + 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) { + ImGuiWindow *popup_window = g.OpenPopupStack[popup_idx].Window; + ImGuiWindow *parent_popup_window = g.OpenPopupStack[popup_idx - 1].Window; + bool close_parent = false; + if (popup_window && (popup_window->Flags & ImGuiWindowFlags_ChildMenu)) + if (parent_popup_window && !(parent_popup_window->Flags & ImGuiWindowFlags_MenuBar)) + close_parent = true; + if (!close_parent) + break; + popup_idx--; + } + 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 which BeginPopupEx()! +bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags flags) +{ + ImGuiContext &g = *GImGui; + if (!IsPopupOpen(id, ImGuiPopupFlags_None)) { + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values + return false; + } + + char name[20]; + if (flags & ImGuiWindowFlags_ChildMenu) + ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.BeginMenuCount); // Recycle windows based on depth + else + ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame + + flags |= ImGuiWindowFlags_Popup; + bool is_open = Begin(name, NULL, flags); + if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) + EndPopup(); + + return is_open; +} + +bool ImGui::BeginPopup(const char *str_id, ImGuiWindowFlags flags) +{ + ImGuiContext &g = *GImGui; + if (g.OpenPopupStack.Size <= g.BeginPopupStack.Size) // Early out for performance + { + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values + return false; + } + flags |= ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings; + 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) so the actual value of *p_open is meaningless here. +bool ImGui::BeginPopupModal(const char *name, bool *p_open, ImGuiWindowFlags flags) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + const ImGuiID id = window->GetID(name); + if (!IsPopupOpen(id, ImGuiPopupFlags_None)) { + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values + 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. + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) == 0) { + const ImGuiViewport *viewport = GetMainViewport(); + SetNextWindowPos(viewport->GetCenter(), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f)); + } + + flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse; + 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) + { + EndPopup(); + if (is_open) + ClosePopupToLevel(g.BeginPopupStack.Size, true); + return false; + } + 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); + + // 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 + IM_ASSERT(g.WithinEndChild == false); + if (window->Flags & ImGuiWindowFlags_ChildWindow) + g.WithinEndChild = true; + End(); + g.WithinEndChild = false; +} + +// 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) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); + if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) { + ImGuiID id = str_id ? window->GetID(str_id) + : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + 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. +// - This is useful when using using BeginPopupContextItem() on an item which doesn't have an identifier, e.g. a Text() call. +// - This is useful when multiple code locations may want to manipulate/open the same popup, given an explicit id. +// - You may want to handle the whole on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters). +// This is essentially the same as: +// id = str_id ? GetID(str_id) : GetItemID(); +// OpenPopupOnItemClick(str_id, ImGuiPopupFlags_MouseButtonRight); +// return BeginPopup(id); +// Which is essentially the same as: +// id = str_id ? GetID(str_id) : GetItemID(); +// if (IsItemHovered() && IsMouseReleased(ImGuiMouseButton_Right)) +// OpenPopup(id); +// return BeginPopup(id); +// The main difference being that this is tweaked to avoid computing the ID twice. +bool ImGui::BeginPopupContextItem(const char *str_id, ImGuiPopupFlags popup_flags) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + if (window->SkipItems) + return false; + ImGuiID id = str_id ? window->GetID(str_id) + : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); + if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + 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; + ImGuiWindow *window = g.CurrentWindow; + if (!str_id) + str_id = "window_context"; + ImGuiID id = window->GetID(str_id); + int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); + if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered()) + 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; + ImGuiWindow *window = g.CurrentWindow; + if (!str_id) + str_id = "void_context"; + ImGuiID id = window->GetID(str_id); + int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_); + if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) + if (GetTopMostPopupModal() == NULL) + 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 +// information are available, it may represent the entire platform monitor from the frame of reference of the current viewport. +// this allows us to have tooltips/popups displayed out of the parent viewport.) +ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2 &ref_pos, + const ImVec2 &size, + ImGuiDir *last_dir, + const ImRect &r_outer, + const ImRect &r_avoid, + ImGuiPopupPositionPolicy policy) +{ + 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) { + const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = {ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up}; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++) { + const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + if (n != -1 && dir == *last_dir) // Already tried this direction? + continue; + ImVec2 pos; + if (dir == ImGuiDir_Down) + pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default) + if (dir == ImGuiDir_Right) + pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right + if (dir == ImGuiDir_Left) + pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left + if (dir == ImGuiDir_Up) + pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left + if (!r_outer.Contains(ImRect(pos, pos + size))) + continue; + *last_dir = dir; + 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) { + const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = {ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left}; + for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++) { + 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) +{ + ImGuiContext &g = *GImGui; + IM_UNUSED(window); + ImRect r_screen = ((ImGuiViewportP *)(void *)GetMainViewport())->GetMainRect(); + ImVec2 padding = g.Style.DisplaySafeAreaPadding; + 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) { + // Child menus typically request _any_ position within the parent menu item, and then we move the new menu outside the parent bounds. + // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu. + IM_ASSERT(g.CurrentWindow == window); + ImGuiWindow *parent_window = g.CurrentWindowStack[g.CurrentWindowStack.Size - 2].Window; + float horizontal_overlap = + g.Style.ItemInnerSpacing + .x; // We want some overlap to convey the relative depth of each menu (currently the amount of overlap is hard-coded to style.ItemSpacing.x). + ImRect r_avoid; + if (parent_window->DC.MenuBarAppending) + r_avoid = ImRect(-FLT_MAX, + parent_window->ClipRect.Min.y, + FLT_MAX, + parent_window->ClipRect.Max.y); // Avoid parent menu-bar. If we wanted multi-line menu-bar, we may instead want to have the calling + // window setup e.g. a NextWindowData.PosConstraintAvoidRect field + else + r_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, + -FLT_MAX, + parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x, + FLT_MAX); + return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Default); + } + if (window->Flags & ImGuiWindowFlags_Popup) { + return FindBestWindowPosForPopupEx(window->Pos, + window->Size, + &window->AutoPosLastDirection, + r_outer, + ImRect(window->Pos, window->Pos), + ImGuiPopupPositionPolicy_Default); // Ideally we'd disable r_avoid here + } + if (window->Flags & ImGuiWindowFlags_Tooltip) { + // Position tooltip (always follows mouse + clamp within outer boundaries) + // Note that drag and drop tooltips are NOT using this path: BeginTooltipEx() manually sets their position. + // In theory we could handle both cases in same location, but requires a bit of shuffling as drag and drop tooltips are calling SetWindowPos() leading + // to 'window_pos_set_by_api' being set in Begin() + IM_ASSERT(g.CurrentWindow == window); + const float scale = g.Style.MouseCursorScale; + const ImVec2 ref_pos = NavCalcPreferredRefPos(); + const ImVec2 tooltip_pos = ref_pos + TOOLTIP_DEFAULT_OFFSET * scale; + ImRect r_avoid; + if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos)) + r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8); + 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] 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::SetNavWindow(ImGuiWindow *window) +{ + ImGuiContext &g = *GImGui; + if (g.NavWindow != window) { + IMGUI_DEBUG_LOG_FOCUS("[focus] SetNavWindow(\"%s\")\n", window ? window->Name : ""); + g.NavWindow = window; + } + g.NavInitRequest = g.NavMoveSubmitted = g.NavMoveScoringItems = false; + NavUpdateAnyRequestFlag(); +} + +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; + IM_ASSERT(g.NavWindow != NULL); + IM_ASSERT(nav_layer == ImGuiNavLayer_Main || nav_layer == ImGuiNavLayer_Menu); + g.NavId = id; + g.NavLayer = nav_layer; + g.NavFocusScopeId = 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; + g.NavId = id; + g.NavLayer = nav_layer; + g.NavFocusScopeId = g.CurrentFocusScopeId; + 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.NavDisableMouseHover = true; + else + g.NavDisableHighlight = true; + + // 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) + return cand_max - curr_min; + if (curr_max < cand_min) + return cand_min - curr_max; + return 0.0f; +} + +// Scoring function for gamepad/keyboard directional navigation. Based on https://gist.github.com/rygorous/6981057 +static bool ImGui::NavScoreItem(ImGuiNavItemData *result) +{ + 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 + 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) { + IM_ASSERT((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened); + if (!window->ClipRect.Overlaps(cand)) + 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); + float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), + ImLerp(cand.Min.y, cand.Max.y, 0.8f), + ImLerp(curr.Min.y, curr.Max.y, 0.2f), + ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items + 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; + if (dbx != 0.0f || dby != 0.0f) { + // For non-overlapping boxes, use distance between boxes + dax = dbx; + day = dby; + dist_axial = dist_box; + quadrant = ImGetDirQuadrantFromDelta(dbx, dby); + } else if (dcx != 0.0f || dcy != 0.0f) { + // For overlapping boxes with different centers, use distance between centers + dax = dcx; + day = dcy; + dist_axial = dist_center; + quadrant = ImGetDirQuadrantFromDelta(dcx, dcy); + } else { + // 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]; + if (g.IO.KeyCtrl) // Hold CTRL to preview score in matching quadrant. CTRL+Arrow to rotate. + { + if (quadrant == move_dir) { + ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center); + ImDrawList *draw_list = GetForegroundDrawList(window); + draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 80)); + draw_list->AddRectFilled(cand.Min, cand.Min + CalcTextSize(buf), IM_COL32(255, 0, 0, 200)); + draw_list->AddText(cand.Min, IM_COL32(255, 255, 255, 255), buf); + } + } + const bool debug_hovering = IsMouseHoveringRect(cand.Min, cand.Max); + const bool debug_tty = (g.IO.KeyCtrl && IsKeyPressed(ImGuiKey_Space)); + if (debug_hovering || debug_tty) { + ImFormatString(buf, + IM_ARRAYSIZE(buf), + "d-box (%7.3f,%7.3f) -> %7.3f\nd-center (%7.3f,%7.3f) -> %7.3f\nd-axial (%7.3f,%7.3f) -> %7.3f\nnav %c, quadrant %c", + dbx, + dby, + dist_box, + dcx, + dcy, + dist_center, + dax, + day, + dist_axial, + "-WENS"[move_dir + 1], + "-WENS"[quadrant + 1]); + if (debug_hovering) { + ImDrawList *draw_list = GetForegroundDrawList(window); + draw_list->AddRect(curr.Min, curr.Max, IM_COL32(255, 200, 0, 100)); + draw_list->AddRect(cand.Min, cand.Max, IM_COL32(255, 255, 0, 200)); + draw_list->AddRectFilled(cand.Max - ImVec2(4, 4), cand.Max + CalcTextSize(buf) + ImVec2(4, 4), IM_COL32(40, 0, 0, 200)); + draw_list->AddText(cand.Max, ~0U, buf); + } + 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) { + // Does it beat the current best candidate? + if (dist_box < result->DistBox) { + result->DistBox = dist_box; + result->DistCenter = dist_center; + return true; + } + if (dist_box == result->DistBox) { + // Try using distance between center points to break ties + if (dist_center < result->DistCenter) { + result->DistCenter = dist_center; + new_best = true; + } else if (dist_center == result->DistCenter) { + // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving + // "later" items (with higher index) to the right/downwards by an infinitesimal amount since we the current "best" button already (so it must + // have a lower index), this is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order of appearance + // along the x axis. + if (((move_dir == ImGuiDir_Up || move_dir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance + new_best = true; + } + } + } + + // 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. 2017/09/29: FIXME: This now currently only enabled inside menu bars, ideally we'd disable it everywhere. Menus in particular need to catch failure. + // For general navigation it feels awkward. Disabling it may lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider + // offering this as an option? + if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match + if (g.NavLayer == ImGuiNavLayer_Menu && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) + if ((move_dir == ImGuiDir_Left && dax < 0.0f) || (move_dir == ImGuiDir_Right && dax > 0.0f) || (move_dir == ImGuiDir_Up && day < 0.0f) + || (move_dir == ImGuiDir_Down && day > 0.0f)) { + result->DistAxial = dist_axial; + new_best = true; + } + + return new_best; +} + +static void ImGui::NavApplyItemToResult(ImGuiNavItemData *result) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + result->Window = window; + result->ID = g.LastItemData.ID; + result->FocusScopeId = g.CurrentFocusScopeId; + result->InFlags = g.LastItemData.InFlags; + result->RectRel = WindowRectAbsToRel(window, g.LastItemData.NavRect); +} + +// 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() +{ + ImGuiContext &g = *GImGui; + 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. +static void ImGui::NavProcessItem() +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + const ImGuiID id = g.LastItemData.ID; + const ImGuiItemFlags item_flags = g.LastItemData.InFlags; + + // When inside a container that isn't scrollable with Left<>Right, clip NavRect accordingly (#2221) + 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); + } + const ImRect nav_bb = g.LastItemData.NavRect; + + // Process Init Request + if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent && (item_flags & ImGuiItemFlags_Disabled) == 0) { + // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback + const bool candidate_for_nav_default_focus = (item_flags & ImGuiItemFlags_NoNavDefaultFocus) == 0; + if (candidate_for_nav_default_focus || g.NavInitResult.ID == 0) { + NavApplyItemToResult(&g.NavInitResult); + } + if (candidate_for_nav_default_focus) { + g.NavInitRequest = false; // Found a match, clear request + 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) { + const bool is_tabbing = (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) != 0; + if (is_tabbing) { + NavProcessItemForTabbingRequest(id, item_flags, g.NavMoveFlags); + } else if (g.NavId != id || (g.NavMoveFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) { + ImGuiNavItemData *result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; + if (NavScoreItem(result)) + 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)) + NavApplyItemToResult(&g.NavMoveResultLocalVisible); + } + } + + // Update information for currently focused/navigated item + if (g.NavId == id) { + if (g.NavWindow != window) + SetNavWindow(window); // Always refresh g.NavWindow, because some operations such as FocusItem() may not have a window. + g.NavLayer = window->DC.NavLayerCurrent; + g.NavFocusScopeId = g.CurrentFocusScopeId; + g.NavIdIsAlive = true; + 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. +// - Case 2: tab forward: on ref id set counter, on counter elapse store result +// - Case 3: tab forward wrap: set result to first eligible item (preemptively), on ref id set counter, on next frame if counter hasn't elapsed store result. +// // FIXME-TABBING: Could be done as a next-frame forwarded request +// - Case 4: tab backward: store all results, on ref id pick prev, stop storing +// - Case 5: tab backward wrap: store all results, on ref id if no result keep storing until last // FIXME-TABBING: Could be done as next-frame forwarded +// requested +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) + 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. + bool can_stop; + if (move_flags & ImGuiNavMoveFlags_FocusApi) + 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) { + // Tab Forward or SetKeyboardFocusHere() with >= 0 + if (can_stop && g.NavTabbingResultFirst.ID == 0) + NavApplyItemToResult(&g.NavTabbingResultFirst); + if (can_stop && g.NavTabbingCounter > 0 && --g.NavTabbingCounter == 0) + NavMoveRequestResolveWithLastItem(result); + else if (g.NavId == id) + g.NavTabbingCounter = 1; + } else if (g.NavTabbingDir == -1) { + // Tab Backward + if (g.NavId == id) { + if (result->ID) { + g.NavMoveScoringItems = false; + NavUpdateAnyRequestFlag(); + } + } else if (can_stop) { + // Keep applying until reaching NavId + NavApplyItemToResult(result); + } + } else if (g.NavTabbingDir == 0) { + if (can_stop && g.NavId == id) + NavMoveRequestResolveWithLastItem(result); + if (can_stop && g.NavTabbingResultFirst.ID == 0) // Tab init + 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); + + if (move_flags & ImGuiNavMoveFlags_IsTabbing) + move_flags |= ImGuiNavMoveFlags_AllowCurrentNavId; + + g.NavMoveSubmitted = g.NavMoveScoringItems = true; + g.NavMoveDir = move_dir; + g.NavMoveDirForDebug = move_dir; + g.NavMoveClipDir = clip_dir; + g.NavMoveFlags = move_flags; + g.NavMoveScrollFlags = scroll_flags; + g.NavMoveForwardToNextFrame = false; + g.NavMoveKeyMods = g.IO.KeyMods; + g.NavMoveResultLocal.Clear(); + g.NavMoveResultLocalVisible.Clear(); + g.NavMoveResultOther.Clear(); + g.NavTabbingCounter = 0; + g.NavTabbingResultFirst.Clear(); + NavUpdateAnyRequestFlag(); +} + +void ImGui::NavMoveRequestResolveWithLastItem(ImGuiNavItemData *result) +{ + ImGuiContext &g = *GImGui; + g.NavMoveScoringItems = false; // Ensure request doesn't need more processing + NavApplyItemToResult(result); + NavUpdateAnyRequestFlag(); +} + +// Called by TreePop() to implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere +void ImGui::NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData *result, ImGuiNavTreeNodeData *tree_node_data) +{ + ImGuiContext &g = *GImGui; + g.NavMoveScoringItems = false; + g.LastItemData.ID = tree_node_data->ID; + g.LastItemData.InFlags = tree_node_data->InFlags; + g.LastItemData.NavRect = tree_node_data->NavRect; + NavApplyItemToResult(result); // Result this instead of implementing a NavApplyPastTreeNodeToResult() + 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) +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(g.NavMoveForwardToNextFrame == false); + NavMoveRequestCancel(); + g.NavMoveForwardToNextFrame = true; + g.NavMoveDir = move_dir; + g.NavMoveClipDir = clip_dir; + 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) +{ + ImGuiWindow *parent = nav_window; + while (parent && parent->RootWindow != parent && (parent->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) + parent = parent->ParentWindow; + 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) +{ + if (window->NavLastChildNavWindow && window->NavLastChildNavWindow->WasActive) + return window->NavLastChildNavWindow; + return window; +} + +void ImGui::NavRestoreLayer(ImGuiNavLayer layer) +{ + ImGuiContext &g = *GImGui; + if (layer == ImGuiNavLayer_Main) { + ImGuiWindow *prev_nav_window = g.NavWindow; + g.NavWindow = NavRestoreLastChildNavWindow(g.NavWindow); // FIXME-NAV: Should clear ongoing nav requests? + if (prev_nav_window) + IMGUI_DEBUG_LOG_FOCUS("[focus] NavRestoreLayer: from \"%s\" to SetNavWindow(\"%s\")\n", prev_nav_window->Name, g.NavWindow->Name); + } + ImGuiWindow *window = g.NavWindow; + if (window->NavLastIds[layer] != 0) { + SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); + } else { + g.NavLayer = layer; + NavInitWindow(window, true); + } +} + +void ImGui::NavRestoreHighlightAfterMove() +{ + ImGuiContext &g = *GImGui; + g.NavDisableHighlight = false; + g.NavDisableMouseHover = g.NavMousePosDirty = true; +} + +static inline void ImGui::NavUpdateAnyRequestFlag() +{ + ImGuiContext &g = *GImGui; + g.NavAnyRequest = g.NavMoveScoringItems || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL); + 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) +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(window == g.NavWindow); + + if (window->Flags & ImGuiWindowFlags_NoNavInputs) { + g.NavId = 0; + g.NavFocusScopeId = 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; + IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from NavInitWindow(), init_for_nav=%d, window=\"%s\", layer=%d\n", init_for_nav, window->Name, g.NavLayer); + if (init_for_nav) { + SetNavID(0, g.NavLayer, window->NavRootFocusScopeId, ImRect()); + g.NavInitRequest = true; + g.NavInitRequestFromMove = false; + g.NavInitResult.ID = 0; + NavUpdateAnyRequestFlag(); + } else { + g.NavId = window->NavLastIds[0]; + g.NavFocusScopeId = window->NavRootFocusScopeId; + } +} + +static ImVec2 ImGui::NavCalcPreferredRefPos() +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.NavWindow; + if (g.NavDisableHighlight || !g.NavDisableMouseHover || !window) { + // Mouse (we need a fallback in case the mouse becomes invalid after being used) + // The +1.0f offset when stored by OpenPopupEx() allows reopening this or another popup (same or another mouse button) while not moving the mouse, it is + // pretty standard. In theory we could move that +1.0f offset in OpenPopupEx() + ImVec2 p = IsMousePosValid(&g.IO.MousePos) ? g.IO.MousePos : g.MouseLastValidPos; + return ImVec2(p.x + 1.0f, p.y); + } else { + // When navigation is active and mouse is disabled, pick a position around the bottom left of the currently navigated item + // Take account of upcoming scrolling (maybe set mouse pos should be done in EndFrame?) + ImRect rect_rel = WindowRectRelToAbs(window, window->NavRectRel[g.NavLayer]); + if (window->LastFrameActive != g.FrameCount && (window->ScrollTarget.x != FLT_MAX || window->ScrollTarget.y != FLT_MAX)) { + ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window); + rect_rel.Translate(window->Scroll - next_scroll); + } + ImVec2 pos = ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x * 4, rect_rel.GetWidth()), + rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight())); + ImGuiViewport *viewport = GetMainViewport(); + return ImFloor(ImClamp(pos, viewport->Pos, viewport->Pos + viewport->Size)); // ImFloor() 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) { + key_less = (axis == ImGuiAxis_X) ? ImGuiKey_GamepadDpadLeft : ImGuiKey_GamepadDpadUp; + key_more = (axis == ImGuiAxis_X) ? ImGuiKey_GamepadDpadRight : ImGuiKey_GamepadDpadDown; + } else { + key_less = (axis == ImGuiAxis_X) ? ImGuiKey_LeftArrow : ImGuiKey_UpArrow; + key_more = (axis == ImGuiAxis_X) ? ImGuiKey_RightArrow : ImGuiKey_DownArrow; + } + float amount = (float)GetKeyPressedAmount(key_more, repeat_delay, repeat_rate) - (float)GetKeyPressedAmount(key_less, repeat_delay, repeat_rate); + if (amount != 0.0f && IsKeyDown(key_less) && IsKeyDown(key_more)) // Cancel when opposite directions are held, regardless of repeat phase + 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; + const ImGuiKey nav_gamepad_keys_to_change_source[] = {ImGuiKey_GamepadFaceRight, + ImGuiKey_GamepadFaceLeft, + ImGuiKey_GamepadFaceUp, + ImGuiKey_GamepadFaceDown, + ImGuiKey_GamepadDpadRight, + ImGuiKey_GamepadDpadLeft, + ImGuiKey_GamepadDpadUp, + ImGuiKey_GamepadDpadDown}; + if (nav_gamepad_active) + for (ImGuiKey key : nav_gamepad_keys_to_change_source) + if (IsKeyDown(key)) + g.NavInputSource = ImGuiInputSource_Gamepad; + const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; + const ImGuiKey nav_keyboard_keys_to_change_source[] = + {ImGuiKey_Space, ImGuiKey_Enter, ImGuiKey_Escape, ImGuiKey_RightArrow, ImGuiKey_LeftArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow}; + if (nav_keyboard_active) + 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; + if (g.NavInitResult.ID != 0) + NavInitRequestApplyResult(); + g.NavInitRequest = false; + g.NavInitRequestFromMove = false; + g.NavInitResult.ID = 0; + + // Process navigation move request + if (g.NavMoveSubmitted) + NavMoveRequestApplyResult(); + g.NavTabbingCounter = 0; + g.NavMoveSubmitted = g.NavMoveScoringItems = false; + + // 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) + if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow) + 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.NavDisableHighlight) || (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; + if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) { + const bool activate_down = (nav_keyboard_active && IsKeyDown(ImGuiKey_Space)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate)); + const bool activate_pressed = activate_down + && ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Space, false)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, false))); + const bool input_down = (nav_keyboard_active && IsKeyDown(ImGuiKey_Enter)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadInput)); + const bool input_pressed = input_down + && ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Enter, false)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadInput, false))); + if (g.ActiveId == 0 && activate_pressed) { + g.NavActivateId = g.NavId; + g.NavActivateFlags = ImGuiActivateFlags_PreferTweak; + } + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && input_pressed) { + g.NavActivateId = g.NavId; + g.NavActivateFlags = ImGuiActivateFlags_PreferInput; + } + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_down || input_down)) + g.NavActivateDownId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_pressed || input_pressed)) + g.NavActivatePressedId = g.NavId; + } + if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + g.NavDisableHighlight = true; + if (g.NavActivateId != 0) + IM_ASSERT(g.NavActivateDownId == g.NavActivateId); + + // Process programmatic activation request + // FIXME-NAV: Those should eventually be queued (unlike focus they don't cancel each others) + if (g.NavNextActivateId != 0) { + g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavNextActivateId; + 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) { + // *Fallback* manual-scroll with Nav directional keys when window has no navigable item + ImGuiWindow *window = g.NavWindow; + const float scroll_speed = + IM_ROUND(window->CalcFontSize() * 100 * io.DeltaTime); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. + const ImGuiDir move_dir = g.NavMoveDir; + if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavWindowHasScrollY && move_dir != ImGuiDir_None) { + if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right) + SetScrollX(window, ImFloor(window->Scroll.x + ((move_dir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed)); + if (move_dir == ImGuiDir_Up || move_dir == ImGuiDir_Down) + SetScrollY(window, ImFloor(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) { + const ImVec2 scroll_dir = + GetKeyMagnitude2d(ImGuiKey_GamepadLStickLeft, ImGuiKey_GamepadLStickRight, ImGuiKey_GamepadLStickUp, ImGuiKey_GamepadLStickDown); + const float tweak_factor = IsKeyDown(ImGuiKey_NavGamepadTweakSlow) ? 1.0f / 10.0f : IsKeyDown(ImGuiKey_NavGamepadTweakFast) ? 10.0f : 1.0f; + if (scroll_dir.x != 0.0f && window->ScrollbarX) + SetScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed * tweak_factor)); + if (scroll_dir.y != 0.0f) + SetScrollY(window, ImFloor(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.NavDisableHighlight = true; + g.NavDisableMouseHover = 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.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos)) { + io.MousePos = io.MousePosPrev = NavCalcPreferredRefPos(); + io.WantSetMousePos = true; + // IMGUI_DEBUG_LOG_IO("SetMousePos: (%.1f,%.1f)\n", io.MousePos.x, io.MousePos.y); + } + + // [DEBUG] + g.NavScoringDebugCount = 0; +#if IMGUI_DEBUG_NAV_RECTS + if (ImGuiWindow *debug_window = g.NavWindow) { + ImDrawList *draw_list = GetForegroundDrawList(debug_window); + int layer = g.NavLayer; /* for (int layer = 0; layer < 2; layer++)*/ + { + ImRect r = WindowRectRelToAbs(debug_window, debug_window->NavRectRel[layer]); + draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 200, 0, 255)); + } + // if (1) { ImU32 col = (!debug_window->Hidden) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredRefPos(); char buf[32]; + // ImFormatString(buf, 32, "%d", g.NavLayer); draw_list->AddCircleFilled(p, 3.0f, col); draw_list->AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); } + } +#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) { + g.NavJustMovedToId = result->ID; + g.NavJustMovedToFocusScopeId = result->FocusScopeId; + g.NavJustMovedToKeyMods = 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); + SetNavID(result->ID, g.NavLayer, result->FocusScopeId, result->RectRel); + g.NavIdIsAlive = true; // Mark as alive from previous frame as we got a result + if (g.NavInitRequestFromMove) + NavRestoreHighlightAfterMove(); +} + +// 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. + if ((move_flags & ImGuiNavMoveFlags_Forwarded) == 0) { + if (preferred_pos_rel.x == FLT_MAX) + preferred_pos_rel.x = ImMin(r.Min.x + 1.0f, r.Max.x) - rel_to_abs_offset.x; + 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; + ImGuiIO &io = g.IO; + 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) (preserve most state, which were already set by the NavMoveRequestForward() function) + IM_ASSERT(g.NavMoveDir != ImGuiDir_None && g.NavMoveClipDir != ImGuiDir_None); + IM_ASSERT(g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded); + IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequestForward %d\n", g.NavMoveDir); + } else { + // Initiate directional inputs request + g.NavMoveDir = ImGuiDir_None; + g.NavMoveFlags = ImGuiNavMoveFlags_None; + g.NavMoveScrollFlags = ImGuiScrollFlags_None; + if (window && !g.NavWindowingTarget && !(window->Flags & ImGuiWindowFlags_NoNavInputs)) { + const ImGuiInputFlags repeat_mode = ImGuiInputFlags_Repeat | (ImGuiInputFlags)ImGuiInputFlags_RepeatRateNavMove; + if (!IsActiveIdUsingNavDir(ImGuiDir_Left) + && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadLeft, ImGuiKeyOwner_None, repeat_mode)) + || (nav_keyboard_active && IsKeyPressed(ImGuiKey_LeftArrow, ImGuiKeyOwner_None, repeat_mode)))) { + g.NavMoveDir = ImGuiDir_Left; + } + if (!IsActiveIdUsingNavDir(ImGuiDir_Right) + && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadRight, ImGuiKeyOwner_None, repeat_mode)) + || (nav_keyboard_active && IsKeyPressed(ImGuiKey_RightArrow, ImGuiKeyOwner_None, repeat_mode)))) { + g.NavMoveDir = ImGuiDir_Right; + } + if (!IsActiveIdUsingNavDir(ImGuiDir_Up) + && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadUp, ImGuiKeyOwner_None, repeat_mode)) + || (nav_keyboard_active && IsKeyPressed(ImGuiKey_UpArrow, ImGuiKeyOwner_None, repeat_mode)))) { + g.NavMoveDir = ImGuiDir_Up; + } + if (!IsActiveIdUsingNavDir(ImGuiDir_Down) + && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadDown, ImGuiKeyOwner_None, repeat_mode)) + || (nav_keyboard_active && IsKeyPressed(ImGuiKey_DownArrow, ImGuiKeyOwner_None, repeat_mode)))) { + g.NavMoveDir = ImGuiDir_Down; + } + } + 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; + if (window && g.NavMoveDir == ImGuiDir_None && nav_keyboard_active) + scoring_rect_offset_y = NavUpdatePageUpPageDown(); + if (scoring_rect_offset_y != 0.0f) { + 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)) + // g.NavMoveDirForDebug = (ImGuiDir)((g.NavMoveDirForDebug + 1) & 3); + if (io.KeyCtrl) { + if (g.NavMoveDir == ImGuiDir_None) + g.NavMoveDir = g.NavMoveDirForDebug; + g.NavMoveClipDir = g.NavMoveDir; + 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) { + IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from move, window \"%s\", layer=%d\n", window ? window->Name : "", g.NavLayer); + g.NavInitRequest = g.NavInitRequestFromMove = true; + g.NavInitResult.ID = 0; + g.NavDisableHighlight = false; + } + + // 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). + if (g.NavMoveSubmitted && g.NavInputSource == ImGuiInputSource_Gamepad && g.NavLayer == ImGuiNavLayer_Main + && window != NULL) // && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded)) + { + 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"); + float pad_x = ImMin(inner_rect_rel.GetWidth(), window->CalcFontSize() * 0.5f); + float pad_y = ImMin(inner_rect_rel.GetHeight(), + window->CalcFontSize() * 0.5f); // Terrible approximation for the intent of starting navigation from first fully visible item + inner_rect_rel.Min.x = clamp_x ? (inner_rect_rel.Min.x + pad_x) : -FLT_MAX; + inner_rect_rel.Max.x = clamp_x ? (inner_rect_rel.Max.x - pad_x) : +FLT_MAX; + inner_rect_rel.Min.y = clamp_y ? (inner_rect_rel.Min.y + pad_y) : -FLT_MAX; + inner_rect_rel.Max.y = clamp_y ? (inner_rect_rel.Max.y - pad_y) : +FLT_MAX; + window->NavRectRel[g.NavLayer].ClipWithFull(inner_rect_rel); + g.NavId = 0; + } + } + + // 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) { + ImRect nav_rect_rel = !window->NavRectRel[g.NavLayer].IsInverted() ? window->NavRectRel[g.NavLayer] : ImRect(0, 0, 0, 0); + scoring_rect = WindowRectRelToAbs(window, nav_rect_rel); + scoring_rect.TranslateY(scoring_rect_offset_y); + if (g.NavMoveSubmitted) + NavBiasScoringRect(scoring_rect, window->RootWindowForNav->NavPreferredScoringPosRel[g.NavLayer], g.NavMoveDir, g.NavMoveFlags); + IM_ASSERT( + !scoring_rect.IsInverted()); // Ensure we have a non-inverted bounding box here will allow us to remove extraneous ImFabs() calls in NavScoreItem(). + // GetForegroundDrawList()->AddRect(scoring_rect.Min, scoring_rect.Max, IM_COL32(255,200,0,255)); // [DEBUG] + // 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); +} + +void ImGui::NavUpdateCreateTabbingRequest() +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.NavWindow; + IM_ASSERT(g.NavMoveDir == ImGuiDir_None); + if (window == NULL || g.NavWindowingTarget != NULL || (window->Flags & ImGuiWindowFlags_NoNavInputs)) + return; + + const bool tab_pressed = IsKeyPressed(ImGuiKey_Tab, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat) && !g.IO.KeyCtrl && !g.IO.KeyAlt; + if (!tab_pressed) + return; + + // Initiate tabbing request + // (this is ALWAYS ENABLED, regardless of ImGuiConfigFlags_NavEnableKeyboard flag!) + // Initially this was designed to use counters and modulo arithmetic, but that could not work with unsubmitted items (list clipper). Instead we use a + // strategy close to other move requests. See NavProcessItemForTabbingRequest() for a description of the various forward/backward tabbing cases with and + // without wrapping. + const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; + if (nav_keyboard_active) + g.NavTabbingDir = g.IO.KeyShift ? -1 : (g.NavDisableHighlight == true && g.ActiveId == 0) ? 0 : +1; + else + g.NavTabbingDir = g.IO.KeyShift ? -1 : (g.ActiveId == 0) ? 0 : +1; + ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_Activate; + ImGuiScrollFlags scroll_flags = window->Appearing ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY + : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY; + ImGuiDir clip_dir = (g.NavTabbingDir < 0) ? ImGuiDir_Up : ImGuiDir_Down; + 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() +{ + ImGuiContext &g = *GImGui; +#if IMGUI_DEBUG_NAV_SCORING + 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) { + if (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) + g.NavMoveFlags |= ImGuiNavMoveFlags_NoSetNavHighlight; + if (g.NavId != 0 && (g.NavMoveFlags & ImGuiNavMoveFlags_NoSetNavHighlight) == 0) + NavRestoreHighlightAfterMove(); + NavClearPreferredPosForAxis(axis); // On a failed move, clear preferred pos for this axis. + 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? + float scroll_target = (g.NavMoveDir == ImGuiDir_Up) ? result->Window->ScrollMax.y : 0.0f; + 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; + } + if (g.ActiveId != result->ID) + 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) { + g.NavJustMovedToId = result->ID; + g.NavJustMovedToFocusScopeId = result->FocusScopeId; + g.NavJustMovedToKeyMods = g.NavMoveKeyMods; + } + + // 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); + + // 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) { + 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->InFlags & ImGuiItemFlags_Inputable) == 0) + g.NavMoveFlags &= ~ImGuiNavMoveFlags_Activate; + + // Activate + if (g.NavMoveFlags & ImGuiNavMoveFlags_Activate) { + g.NavNextActivateId = result->ID; + g.NavNextActivateFlags = ImGuiActivateFlags_None; + g.NavMoveFlags |= ImGuiNavMoveFlags_NoSetNavHighlight; + if (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) + g.NavNextActivateFlags |= ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_TryToPreserveState; + } + + // Enable nav highlight + if ((g.NavMoveFlags & ImGuiNavMoveFlags_NoSetNavHighlight) == 0) + NavRestoreHighlightAfterMove(); +} + +// Process 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. +// - either to move most/all of those tests to the epilogue/end functions of the scope they are dealing with (e.g. exit child window in EndChild()) or in +// EndFrame(), to allow an earlier intercept +static void ImGui::NavUpdateCancelRequest() +{ + ImGuiContext &g = *GImGui; + const bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; + const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; + if (!(nav_keyboard_active && IsKeyPressed(ImGuiKey_Escape, ImGuiKeyOwner_None)) + && !(nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, ImGuiKeyOwner_None))) + return; + + IMGUI_DEBUG_LOG_NAV("[nav] NavUpdateCancelRequest()\n"); + if (g.ActiveId != 0) { + ClearActiveID(); + } else if (g.NavLayer != ImGuiNavLayer_Main) { + // Leave the "menu" layer + NavRestoreLayer(ImGuiNavLayer_Main); + NavRestoreHighlightAfterMove(); + } else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow) { + // Exit child window + ImGuiWindow *child_window = g.NavWindow; + ImGuiWindow *parent_window = g.NavWindow->ParentWindow; + IM_ASSERT(child_window->ChildId != 0); + ImRect child_rect = child_window->Rect(); + FocusWindow(parent_window); + SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, WindowRectAbsToRel(parent_window, child_rect)); + NavRestoreHighlightAfterMove(); + } else if (g.OpenPopupStack.Size > 0 && g.OpenPopupStack.back().Window != NULL && !(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) { + // Close open popup/menu + ClosePopupToLevel(g.OpenPopupStack.Size - 1, true); + } else { + // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were + if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) + g.NavWindow->NavLastIds[0] = 0; + g.NavId = 0; + } +} + +// 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 +// FIXME-NAV: how to get Home/End to aim at the beginning/end of a 2D grid? +static float ImGui::NavUpdatePageUpPageDown() +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.NavWindow; + if ((window->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL) + return 0.0f; + + const bool page_up_held = IsKeyDown(ImGuiKey_PageUp, ImGuiKeyOwner_None); + const bool page_down_held = IsKeyDown(ImGuiKey_PageDown, ImGuiKeyOwner_None); + const bool home_pressed = IsKeyPressed(ImGuiKey_Home, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat); + const bool end_pressed = IsKeyPressed(ImGuiKey_End, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat); + 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) { + // Fallback manual-scroll when window has no navigable item + if (IsKeyPressed(ImGuiKey_PageUp, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat)) + SetScrollY(window, window->Scroll.y - window->InnerRect.GetHeight()); + else if (IsKeyPressed(ImGuiKey_PageDown, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat)) + SetScrollY(window, window->Scroll.y + window->InnerRect.GetHeight()); + else if (home_pressed) + SetScrollY(window, 0.0f); + else if (end_pressed) + SetScrollY(window, window->ScrollMax.y); + } else { + ImRect &nav_rect_rel = window->NavRectRel[g.NavLayer]; + const float page_offset_y = ImMax(0.0f, window->InnerRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight()); + float nav_scoring_rect_offset_y = 0.0f; + if (IsKeyPressed(ImGuiKey_PageUp, true)) { + nav_scoring_rect_offset_y = -page_offset_y; + g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset up, we request the down direction (so we can always land on the last item) + g.NavMoveClipDir = ImGuiDir_Up; + g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet | ImGuiNavMoveFlags_IsPageMove; + } else if (IsKeyPressed(ImGuiKey_PageDown, true)) { + nav_scoring_rect_offset_y = +page_offset_y; + g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset down, we request the up direction (so we can always land on the last item) + g.NavMoveClipDir = ImGuiDir_Down; + g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet | ImGuiNavMoveFlags_IsPageMove; + } else if (home_pressed) { + // FIXME-NAV: handling of Home/End is assuming that the top/bottom most item will be visible with Scroll.y == 0/ScrollMax.y + // Scrolling will be handled via the ImGuiNavMoveFlags_ScrollToEdgeY flag, we don't scroll immediately to avoid scrolling happening before nav + // result. Preserve current horizontal position if we have any. + nav_rect_rel.Min.y = nav_rect_rel.Max.y = 0.0f; + if (nav_rect_rel.IsInverted()) + nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f; + g.NavMoveDir = ImGuiDir_Down; + g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdgeY; + // FIXME-NAV: MoveClipDir left to _None, intentional? + } else if (end_pressed) { + nav_rect_rel.Min.y = nav_rect_rel.Max.y = window->ContentSize.y; + if (nav_rect_rel.IsInverted()) + nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f; + g.NavMoveDir = ImGuiDir_Up; + g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdgeY; + // FIXME-NAV: MoveClipDir left to _None, intentional? + } + return nav_scoring_rect_offset_y; + } + 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))) { + bb_rel.Min.x = bb_rel.Max.x = window->ContentSize.x + window->WindowPadding.x; + if (move_flags & ImGuiNavMoveFlags_WrapX) { + bb_rel.TranslateY(-bb_rel.GetHeight()); // Previous row + clip_dir = ImGuiDir_Up; + } + do_forward = true; + } + if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) { + bb_rel.Min.x = bb_rel.Max.x = -window->WindowPadding.x; + if (move_flags & ImGuiNavMoveFlags_WrapX) { + bb_rel.TranslateY(+bb_rel.GetHeight()); // Next row + clip_dir = ImGuiDir_Down; + } + do_forward = true; + } + if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) { + bb_rel.Min.y = bb_rel.Max.y = window->ContentSize.y + window->WindowPadding.y; + if (move_flags & ImGuiNavMoveFlags_WrapY) { + bb_rel.TranslateX(-bb_rel.GetWidth()); // Previous column + clip_dir = ImGuiDir_Left; + } + do_forward = true; + } + if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) { + bb_rel.Min.y = bb_rel.Max.y = -window->WindowPadding.y; + if (move_flags & ImGuiNavMoveFlags_WrapY) { + bb_rel.TranslateX(+bb_rel.GetWidth()); // Next column + clip_dir = ImGuiDir_Right; + } + do_forward = true; + } + if (!do_forward) + return; + window->NavRectRel[g.NavLayer] = bb_rel; + NavClearPreferredPosForAxis(ImGuiAxis_X); + NavClearPreferredPosForAxis(ImGuiAxis_Y); + NavMoveRequestForward(g.NavMoveDir, clip_dir, move_flags, g.NavMoveScrollFlags); +} + +static int ImGui::FindWindowFocusIndex(ImGuiWindow *window) +{ + ImGuiContext &g = *GImGui; + IM_UNUSED(g); + int order = window->FocusOrder; + IM_ASSERT(window->RootWindow == window); // No child window (not testing _ChildWindow because of docking) + IM_ASSERT(g.WindowsFocusOrder[order] == window); + return order; +} + +static ImGuiWindow *FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N) +{ + ImGuiContext &g = *GImGui; + for (int i = i_start; i >= 0 && i < g.WindowsFocusOrder.Size && i != i_stop; i += dir) + if (ImGui::IsWindowNavFocusable(g.WindowsFocusOrder[i])) + return g.WindowsFocusOrder[i]; + return NULL; +} + +static void NavUpdateWindowingHighlightWindow(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) + window_target = FindWindowNavFocusable((focus_change_dir < 0) ? (g.WindowsFocusOrder.Size - 1) : 0, i_current, focus_change_dir); + if (window_target) // Don't reset windowing target if there's a single window in the list + { + g.NavWindowingTarget = g.NavWindowingTargetAnim = window_target; + g.NavWindowingAccumDeltaPos = g.NavWindowingAccumDeltaSize = ImVec2(0.0f, 0.0f); + } + g.NavWindowingToggleLayer = false; +} + +// 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) +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) { + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - io.DeltaTime * 10.0f, 0.0f); + if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f) + g.NavWindowingTargetAnim = NULL; + } + + // Start CTRL+Tab or Square+L/R window selection + const ImGuiID owner_id = ImHashStr("###NavUpdateWindowing"); + 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; + const bool keyboard_next_window = + allow_windowing && g.ConfigNavWindowingKeyNext && Shortcut(g.ConfigNavWindowingKeyNext, owner_id, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways); + const bool keyboard_prev_window = + allow_windowing && g.ConfigNavWindowingKeyPrev && Shortcut(g.ConfigNavWindowingKeyPrev, owner_id, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways); + const bool start_windowing_with_gamepad = + allow_windowing && nav_gamepad_active && !g.NavWindowingTarget && IsKeyPressed(ImGuiKey_NavGamepadMenu, 0, ImGuiInputFlags_None); + const bool start_windowing_with_keyboard = + allow_windowing && !g.NavWindowingTarget && (keyboard_next_window || keyboard_prev_window); // Note: enabled even without NavEnableKeyboard! + 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; + 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; + + // Register ownership of our mods. Using ImGuiInputFlags_RouteGlobalHigh 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) { + // 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) { + NavUpdateWindowingHighlightWindow(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)) { + g.NavWindowingToggleLayer &= + (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore. + if (g.NavWindowingToggleLayer && g.NavWindow) + apply_toggle_layer = true; + else if (!g.NavWindowingToggleLayer) + apply_focus_window = g.NavWindowingTarget; + g.NavWindowingTarget = NULL; + } + } + + // Keyboard: Focus + if (g.NavWindowingTarget && g.NavInputSource == 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.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f + if (keyboard_next_window || keyboard_prev_window) + NavUpdateWindowingHighlightWindow(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 + // - Testing that only Alt is tested prevents Alt+Shift or AltGR from toggling menu layer. + // - AltGR is normally Alt+Ctrl but we can't reliably detect it (not all backends/systems/layout emit it as Alt+Ctrl). But even on keyboards without AltGR + // we don't want Alt+Ctrl to open menu anyway. + if (nav_keyboard_active && IsKeyPressed(ImGuiMod_Alt, ImGuiKeyOwner_None)) { + g.NavWindowingToggleLayer = true; + g.NavInputSource = ImGuiInputSource_Keyboard; + } + if (g.NavWindowingToggleLayer && g.NavInputSource == 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) + // We cancel toggling nav layer if an owner has claimed the key. + if (io.InputQueueCharacters.Size > 0 || io.KeyCtrl || io.KeyShift || io.KeySuper || TestKeyOwner(ImGuiMod_Alt, ImGuiKeyOwner_None) == false) + g.NavWindowingToggleLayer = false; + + // Apply layer toggle on 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(ImGuiMod_Alt) && g.NavWindowingToggleLayer) + if (g.ActiveId == 0 || g.ActiveIdAllowOverlap) + if (IsMousePosValid(&io.MousePos) == IsMousePosValid(&io.MousePosPrev)) + apply_toggle_layer = true; + if (!IsKeyDown(ImGuiMod_Alt)) + g.NavWindowingToggleLayer = false; + } + + // Move window + if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove)) { + ImVec2 nav_move_dir; + if (g.NavInputSource == ImGuiInputSource_Keyboard && !io.KeyShift) + nav_move_dir = GetKeyMagnitude2d(ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow); + if (g.NavInputSource == ImGuiInputSource_Gamepad) + nav_move_dir = GetKeyMagnitude2d(ImGuiKey_GamepadLStickLeft, ImGuiKey_GamepadLStickRight, ImGuiKey_GamepadLStickUp, ImGuiKey_GamepadLStickDown); + if (nav_move_dir.x != 0.0f || nav_move_dir.y != 0.0f) { + const float NAV_MOVE_SPEED = 800.0f; + const float move_step = NAV_MOVE_SPEED * io.DeltaTime * ImMin(io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); + g.NavWindowingAccumDeltaPos += nav_move_dir * move_step; + g.NavDisableMouseHover = true; + ImVec2 accum_floored = ImFloor(g.NavWindowingAccumDeltaPos); + if (accum_floored.x != 0.0f || accum_floored.y != 0.0f) { + ImGuiWindow *moving_window = g.NavWindowingTarget->RootWindow; + SetWindowPos(moving_window, moving_window->Pos + accum_floored, ImGuiCond_Always); + g.NavWindowingAccumDeltaPos -= accum_floored; + } + } + } + + // Apply final focus + if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow)) { + ClearActiveID(); + NavRestoreHighlightAfterMove(); + ClosePopupsOverWindow(apply_focus_window, false); + FocusWindow(apply_focus_window, ImGuiFocusRequestFlags_RestoreFocusedChild); + 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 + // the target window as already been previewed once. + // FIXME-NAV: This should be done in NavInit.. or in FocusWindow... However in both of those cases, + // we won't have a guarantee that windows has been visible before and therefore NavLayersActiveMask* + // won't be valid. + if (apply_focus_window->DC.NavLayersActiveMaskNext == (1 << ImGuiNavLayer_Menu)) + g.NavLayer = ImGuiNavLayer_Menu; + } + if (apply_focus_window) + g.NavWindowingTarget = NULL; + + // 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 && (new_nav_window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 0 + && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 + && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) + new_nav_window = new_nav_window->ParentWindow; + if (new_nav_window != g.NavWindow) { + ImGuiWindow *old_nav_window = g.NavWindow; + 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) { + // Reinitialize navigation when entering menu bar with the Alt key (FIXME: could be a properly of the layer?) + if (new_nav_layer == ImGuiNavLayer_Menu) + g.NavWindow->NavLastIds[new_nav_layer] = 0; + NavRestoreLayer(new_nav_layer); + NavRestoreHighlightAfterMove(); + } + } +} + +// Window has already passed the IsWindowNavFocusable() +static const char *GetFallbackWindowNameForWindowingList(ImGuiWindow *window) +{ + if (window->Flags & ImGuiWindowFlags_Popup) + return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingPopup); + if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0) + return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingMainMenuBar); + 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("###NavWindowingList"); + const ImGuiViewport *viewport = GetMainViewport(); + SetNextWindowSizeConstraints(ImVec2(viewport->Size.x * 0.20f, viewport->Size.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX)); + SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f); + Begin("###NavWindowingList", + NULL, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs + | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); + for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--) { + ImGuiWindow *window = g.WindowsFocusOrder[n]; + IM_ASSERT(window != NULL); // Fix static analyzers + if (!IsWindowNavFocusable(window)) + continue; + const char *label = window->Name; + if (label == FindRenderedTextEnd(label)) + label = GetFallbackWindowNameForWindowingList(window); + Selectable(label, g.NavWindowingTarget == window); + } + End(); + PopStyleVar(); +} + +//----------------------------------------------------------------------------- +// [SECTION] DRAG AND DROP +//----------------------------------------------------------------------------- + +bool ImGui::IsDragDropActive() +{ + ImGuiContext &g = *GImGui; + return g.DragDropActive; +} + +void ImGui::ClearDragDrop() +{ + ImGuiContext &g = *GImGui; + g.DragDropActive = false; + g.DragDropPayload.Clear(); + g.DragDropAcceptFlags = ImGuiDragDropFlags_None; + g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + g.DragDropAcceptFrameCount = -1; + + g.DragDropPayloadBufHeap.clear(); + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); +} + +// 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). +// - Therefore if you want to use this with a mouse button other than left mouse button, it is up to the item itself to activate with another button. +// - We then pull and use the mouse button that was used to activate the item and use it to carry on the drag. +// If the item has no identifier: +// - Currently always assume left mouse button. +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; + if (!(flags & ImGuiDragDropFlags_SourceExtern)) { + source_id = g.LastItemData.ID; + if (source_id != 0) { + // Common path: items with ID + if (g.ActiveId != source_id) + return false; + if (g.ActiveIdMouseButton != -1) + mouse_button = g.ActiveIdMouseButton; + if (g.IO.MouseDown[mouse_button] == false || window->SkipItems) + return false; + g.ActiveIdAllowOverlap = false; + } else { + // Uncommon path: items without ID + if (g.IO.MouseDown[mouse_button] == false || window->SkipItems) + 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)) { + 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. + // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive. + // Rely on keeping other window->LastItemXXX fields intact. + source_id = g.LastItemData.ID = window->GetIDFromRectangle(g.LastItemData.Rect); + KeepAliveID(source_id); + bool is_hovered = ItemHoverable(g.LastItemData.Rect, source_id, g.LastItemData.InFlags); + if (is_hovered && g.IO.MouseClicked[mouse_button]) { + SetActiveID(source_id, window); + FocusWindow(window); + } + if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker. + g.ActiveIdAllowOverlap = is_hovered; + } + if (g.ActiveId != source_id) + 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(); + } else { + window = NULL; + source_id = ImHashStr("#SourceExtern"); + source_drag_active = true; + } + + if (source_drag_active) { + if (!g.DragDropActive) { + IM_ASSERT(source_id != 0); + ClearDragDrop(); + ImGuiPayload &payload = g.DragDropPayload; + payload.SourceId = source_id; + payload.SourceParentId = source_parent_id; + g.DragDropActive = true; + g.DragDropSourceFlags = flags; + g.DragDropMouseButton = mouse_button; + if (payload.SourceId == g.ActiveId) + g.ActiveIdNoClearOnFocusLoss = true; + } + 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) + // We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller may be emitting contents. + bool ret = BeginTooltip(); + IM_ASSERT(ret); // FIXME-NEWBEGIN: If this ever becomes false, we need to Begin("##Hidden", NULL, ImGuiWindowFlags_NoSavedSettings) + + // SetWindowHiddendAndSkipItemsForCurrentFrame(). + IM_UNUSED(ret); + + if (g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip)) + SetWindowHiddendAndSkipItemsForCurrentFrame(g.CurrentWindow); + } + + if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern)) + g.LastItemData.StatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; + + return true; + } + return false; +} + +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) +{ + ImGuiContext &g = *GImGui; + ImGuiPayload &payload = g.DragDropPayload; + if (cond == 0) + cond = ImGuiCond_Always; + + IM_ASSERT(type != NULL); + IM_ASSERT(strlen(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 + ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType)); + g.DragDropPayloadBufHeap.resize(0); + if (data_size > sizeof(g.DragDropPayloadBufLocal)) { + // Store in heap + g.DragDropPayloadBufHeap.resize((int)data_size); + payload.Data = g.DragDropPayloadBufHeap.Data; + memcpy(payload.Data, data, data_size); + } else if (data_size > 0) { + // Store locally + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); + payload.Data = g.DragDropPayloadBufLocal; + memcpy(payload.Data, data, data_size); + } else { + payload.Data = NULL; + } + 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->RootWindow != hovered_window->RootWindow) + return false; + IM_ASSERT(id != 0); + if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId)) + return false; + if (window->SkipItems) + return false; + + IM_ASSERT(g.DragDropWithinTarget == false); + g.DragDropTargetRect = bb; + g.DragDropTargetId = id; + g.DragDropWithinTarget = true; + return true; +} + +// We don't use BeginDragDropTargetCustom() and duplicate its code because: +// 1) we use LastItemRectHoveredRect 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. Also note how the +// HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case) +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->RootWindow != hovered_window->RootWindow || 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) { + id = window->GetIDFromRectangle(display_rect); + KeepAliveID(id); + } + if (g.DragDropPayload.SourceId == id) + return false; + + IM_ASSERT(g.DragDropWithinTarget == false); + g.DragDropTargetRect = display_rect; + g.DragDropTargetId = id; + 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; + ImGuiWindow *window = g.CurrentWindow; + ImGuiPayload &payload = g.DragDropPayload; + IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ? + 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); + ImRect r = g.DragDropTargetRect; + 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) + window->DrawList->AddRect(r.Min - ImVec2(3.5f, 3.5f), r.Max + ImVec2(3.5f, 3.5f), GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f); + + g.DragDropAcceptFrameCount = g.FrameCount; + 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; + + // IMGUI_DEBUG_LOG("AcceptDragDropPayload(): %08X: return payload\n", g.DragDropTargetId); + return &payload; +} + +// FIXME-DRAGDROP: Settle on a proper default visuals for drop target. +void ImGui::RenderDragDropTargetRect(const ImRect &bb) +{ + GetWindowDrawList()->AddRect(bb.Min - ImVec2(3.5f, 3.5f), bb.Max + ImVec2(3.5f, 3.5f), GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f); +} + +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) +{ + if (g.LogFile) { + g.LogBuffer.Buf.resize(0); + g.LogBuffer.appendfv(fmt, args); + ImFileWrite(g.LogBuffer.c_str(), sizeof(char), (ImU64)g.LogBuffer.size(), g.LogFile); + } else { + 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. +void ImGui::LogRenderedText(const ImVec2 *ref_pos, const char *text, const char *text_end) +{ + 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; + if (log_new_line) { + LogText(IM_NEWLINE); + g.LogLineFirstItem = true; + } + + if (prefix) + LogRenderedText(ref_pos, prefix, prefix + strlen(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 (;;) { + // Split the string. Each new line (after a '\n') is followed by indentation corresponding to the current depth of our log entry. + // We don't add a trailing \n yet to allow a subsequent item on the same line to be captured. + const char *line_start = text_remaining; + const char *line_end = ImStreolRange(line_start, text_end); + const bool is_last_line = (line_end == text_end); + if (line_start != line_end || !is_last_line) { + const int line_length = (int)(line_end - line_start); + const int indentation = g.LogLineFirstItem ? tree_depth * 4 : 1; + LogText("%*s%.*s", indentation, "", line_length, line_start); + g.LogLineFirstItem = false; + if (*line_end == '\n') { + LogText(IM_NEWLINE); + g.LogLineFirstItem = true; + } + } + if (is_last_line) + break; + text_remaining = line_end + 1; + } + + if (suffix) + LogRenderedText(ref_pos, suffix, suffix + strlen(suffix)); +} + +// Start logging/capturing text output +void ImGui::LogBegin(ImGuiLogType type, int auto_open_depth) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + IM_ASSERT(g.LogEnabled == false); + IM_ASSERT(g.LogFile == NULL); + IM_ASSERT(g.LogBuffer.empty()); + g.LogEnabled = true; + g.LogType = type; + g.LogNextPrefix = g.LogNextSuffix = NULL; + g.LogDepthRef = window->DC.TreeDepth; + g.LogDepthToExpand = ((auto_open_depth >= 0) ? auto_open_depth : g.LogDepthToExpandDefault); + 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) +{ + ImGuiContext &g = *GImGui; + g.LogNextPrefix = prefix; + g.LogNextSuffix = suffix; +} + +void ImGui::LogToTTY(int auto_open_depth) +{ + ImGuiContext &g = *GImGui; + if (g.LogEnabled) + return; + IM_UNUSED(auto_open_depth); +#ifndef IMGUI_DISABLE_TTY_FUNCTIONS + LogBegin(ImGuiLogType_TTY, 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. + if (!filename) + filename = g.IO.LogFilename; + if (!filename || !filename[0]) + return; + ImFileHandle f = ImFileOpen(filename, "ab"); + if (!f) { + IM_ASSERT(0); + return; + } + + LogBegin(ImGuiLogType_File, auto_open_depth); + g.LogFile = f; +} + +// Start logging/capturing text output to clipboard +void ImGui::LogToClipboard(int auto_open_depth) +{ + ImGuiContext &g = *GImGui; + if (g.LogEnabled) + return; + LogBegin(ImGuiLogType_Clipboard, auto_open_depth); +} + +void ImGui::LogToBuffer(int auto_open_depth) +{ + ImGuiContext &g = *GImGui; + if (g.LogEnabled) + return; + LogBegin(ImGuiLogType_Buffer, auto_open_depth); +} + +void ImGui::LogFinish() +{ + ImGuiContext &g = *GImGui; + if (!g.LogEnabled) + return; + + LogText(IM_NEWLINE); + switch (g.LogType) { + case ImGuiLogType_TTY: +#ifndef IMGUI_DISABLE_TTY_FUNCTIONS + fflush(g.LogFile); +#endif + break; + case ImGuiLogType_File: + ImFileClose(g.LogFile); + break; + case ImGuiLogType_Buffer: + break; + case ImGuiLogType_Clipboard: + if (!g.LogBuffer.empty()) + SetClipboardText(g.LogBuffer.begin()); + break; + case ImGuiLogType_None: + IM_ASSERT(0); + break; + } + + g.LogEnabled = false; + g.LogType = ImGuiLogType_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(); +#else + const bool log_to_tty = false; +#endif + const bool log_to_file = Button("Log To File"); + SameLine(); + const bool log_to_clipboard = Button("Log To Clipboard"); + SameLine(); + PushTabStop(false); + SetNextItemWidth(80.0f); + SliderInt("Default Depth", &g.LogDepthToExpandDefault, 0, 9, NULL); + PopTabStop(); + PopID(); + + // Start logging at the end of the function so that the buttons don't appear in the log + if (log_to_tty) + LogToTTY(); + if (log_to_file) + LogToFile(); + if (log_to_clipboard) + LogToClipboard(); +} + +//----------------------------------------------------------------------------- +// [SECTION] SETTINGS +//----------------------------------------------------------------------------- +// - UpdateSettings() [Internal] +// - MarkIniSettingsDirty() [Internal] +// - FindSettingsHandler() [Internal] +// - ClearIniSettings() [Internal] +// - LoadIniSettingsFromDisk() +// - LoadIniSettingsFromMemory() +// - SaveIniSettingsToDisk() +// - SaveIniSettingsToMemory() +//----------------------------------------------------------------------------- +// - CreateNewWindowSettings() [Internal] +// - FindWindowSettingsByID() [Internal] +// - FindWindowSettingsByWindow() [Internal] +// - ClearWindowSettings() [Internal] +// - WindowSettingsHandler_***() [Internal] +//----------------------------------------------------------------------------- + +// Called by NewFrame() +void ImGui::UpdateSettings() +{ + // Load settings on first frame (if not explicitly loaded manually before) + ImGuiContext &g = *GImGui; + if (!g.SettingsLoaded) { + IM_ASSERT(g.SettingsWindows.empty()); + if (g.IO.IniFilename) + 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) { + g.SettingsDirtyTimer -= g.IO.DeltaTime; + if (g.SettingsDirtyTimer <= 0.0f) { + if (g.IO.IniFilename != NULL) + SaveIniSettingsToDisk(g.IO.IniFilename); + else + g.IO.WantSaveIniSettings = + true; // Let user know they can call SaveIniSettingsToMemory(). user will need to clear io.WantSaveIniSettings themselves. + g.SettingsDirtyTimer = 0.0f; + } + } +} + +void ImGui::MarkIniSettingsDirty() +{ + ImGuiContext &g = *GImGui; + if (g.SettingsDirtyTimer <= 0.0f) + g.SettingsDirtyTimer = g.IO.IniSavingRate; +} + +void ImGui::MarkIniSettingsDirty(ImGuiWindow *window) +{ + ImGuiContext &g = *GImGui; + if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings)) + 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; + const ImGuiID type_hash = ImHashStr(type_name); + for (ImGuiSettingsHandler &handler : g.SettingsHandlers) + if (handler.TypeHash == type_hash) + return &handler; + return NULL; +} + +// Clear all settings (windows, tables, docking etc.) +void ImGui::ClearIniSettings() +{ + ImGuiContext &g = *GImGui; + g.SettingsIniData.clear(); + for (ImGuiSettingsHandler &handler : g.SettingsHandlers) + if (handler.ClearAllFn != NULL) + handler.ClearAllFn(&g, &handler); +} + +void ImGui::LoadIniSettingsFromDisk(const char *ini_filename) +{ + size_t file_data_size = 0; + char *file_data = (char *)ImFileLoadToMemory(ini_filename, "rb", &file_data_size); + if (!file_data) + return; + if (file_data_size > 0) + 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) +{ + ImGuiContext &g = *GImGui; + 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) + ini_size = strlen(ini_data); + g.SettingsIniData.Buf.resize((int)ini_size + 1); + char *const buf = g.SettingsIniData.Buf.Data; + 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) { + // Skip new lines markers, then find end of the line + while (*line == '\n' || *line == '\r') + line++; + line_end = line; + while (line_end < buf_end && *line_end != '\n' && *line_end != '\r') + line_end++; + line_end[0] = 0; + if (line[0] == ';') + continue; + if (line[0] == '[' && line_end > line && line_end[-1] == ']') { + // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code. + line_end[-1] = 0; + const char *name_end = line_end - 1; + const char *type_start = line + 1; + char *type_end = (char *)(void *)ImStrchrRange(type_start, name_end, ']'); + const char *name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL; + if (!type_end || !name_start) + continue; + *type_end = 0; // Overwrite first ']' + name_start++; // Skip second '[' + entry_handler = FindSettingsHandler(type_start); + entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL; + } else if (entry_handler != NULL && entry_data != NULL) { + // Let type handler parse the line + entry_handler->ReadLineFn(&g, entry_handler, entry_data, line); + } + } + 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"); + if (!f) + return; + 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) +{ + ImGuiContext &g = *GImGui; + g.SettingsDirtyTimer = 0.0f; + g.SettingsIniData.Buf.resize(0); + g.SettingsIniData.Buf.push_back(0); + for (ImGuiSettingsHandler &handler : g.SettingsHandlers) + handler.WriteAllFn(&g, &handler, &g.SettingsIniData); + if (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() + // Preserve the full string when ConfigDebugVerboseIniSettings is set to make .ini inspection easier. + if (const char *p = strstr(name, "###")) + name = p; + } + const size_t name_len = strlen(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) +{ + ImGuiContext &g = *GImGui; + for (ImGuiWindowSettings *settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + if (settings->ID == id && !settings->WantDelete) + 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) +{ + ImGuiContext &g = *GImGui; + if (window->SettingsOffset != -1) + 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) +{ + // IMGUI_DEBUG_LOG("ClearWindowSettings('%s')\n", name); + ImGuiWindow *window = FindWindowByName(name); + if (window != NULL) { + window->Flags |= ImGuiWindowFlags_NoSavedSettings; + InitOrLoadWindowSettings(window, NULL); + } + if (ImGuiWindowSettings *settings = window ? FindWindowSettingsByWindow(window) : FindWindowSettingsByID(ImHashStr(name))) + settings->WantDelete = true; +} + +static void WindowSettingsHandler_ClearAll(ImGuiContext *ctx, ImGuiSettingsHandler *) +{ + ImGuiContext &g = *ctx; + for (ImGuiWindow *window : g.Windows) + window->SettingsOffset = -1; + g.SettingsWindows.clear(); +} + +static void *WindowSettingsHandler_ReadOpen(ImGuiContext *, ImGuiSettingsHandler *, const char *name) +{ + ImGuiID id = ImHashStr(name); + ImGuiWindowSettings *settings = ImGui::FindWindowSettingsByID(id); + if (settings) + *settings = ImGuiWindowSettings(); // Clear existing if recycling previous entry + else + settings = ImGui::CreateNewWindowSettings(name); + settings->ID = id; + settings->WantApply = true; + return (void *)settings; +} + +static void WindowSettingsHandler_ReadLine(ImGuiContext *, ImGuiSettingsHandler *, void *entry, const char *line) +{ + ImGuiWindowSettings *settings = (ImGuiWindowSettings *)entry; + int x, y; + int i; + if (sscanf(line, "Pos=%i,%i", &x, &y) == 2) { + settings->Pos = ImVec2ih((short)x, (short)y); + } else if (sscanf(line, "Size=%i,%i", &x, &y) == 2) { + settings->Size = ImVec2ih((short)x, (short)y); + } else if (sscanf(line, "Collapsed=%d", &i) == 1) { + settings->Collapsed = (i != 0); + } +} + +// Apply to existing windows (if any) +static void WindowSettingsHandler_ApplyAll(ImGuiContext *ctx, ImGuiSettingsHandler *) +{ + ImGuiContext &g = *ctx; + for (ImGuiWindowSettings *settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + if (settings->WantApply) { + if (ImGuiWindow *window = ImGui::FindWindowByID(settings->ID)) + ApplyWindowSettings(window, settings); + settings->WantApply = false; + } +} + +static void WindowSettingsHandler_WriteAll(ImGuiContext *ctx, ImGuiSettingsHandler *handler, ImGuiTextBuffer *buf) +{ + // Gather data from windows that were active during this session + // (if a window wasn't opened in this session we preserve its settings) + ImGuiContext &g = *ctx; + for (ImGuiWindow *window : g.Windows) { + if (window->Flags & ImGuiWindowFlags_NoSavedSettings) + continue; + + ImGuiWindowSettings *settings = ImGui::FindWindowSettingsByWindow(window); + if (!settings) { + settings = ImGui::CreateNewWindowSettings(window->Name); + window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings); + } + IM_ASSERT(settings->ID == window->ID); + settings->Pos = ImVec2ih(window->Pos); + settings->Size = ImVec2ih(window->SizeFull); + + settings->Collapsed = window->Collapsed; + 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)) { + if (settings->WantDelete) + continue; + const char *settings_name = settings->GetName(); + buf->appendf("[%s][%s]\n", handler->TypeName, settings_name); + buf->appendf("Pos=%d,%d\n", settings->Pos.x, settings->Pos.y); + buf->appendf("Size=%d,%d\n", settings->Size.x, settings->Size.y); + buf->appendf("Collapsed=%d\n", settings->Collapsed); + 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 +//----------------------------------------------------------------------------- +// - GetMainViewport() +// - SetWindowViewport() [Internal] +// - UpdateViewportsNewFrame() [Internal] +// (this section is more complete in the 'docking' branch) +//----------------------------------------------------------------------------- + +ImGuiViewport *ImGui::GetMainViewport() +{ + ImGuiContext &g = *GImGui; + return g.Viewports[0]; +} + +void ImGui::SetWindowViewport(ImGuiWindow *window, ImGuiViewportP *viewport) +{ + window->Viewport = viewport; +} + +// Update viewports and monitor infos +static void ImGui::UpdateViewportsNewFrame() +{ + ImGuiContext &g = *GImGui; + IM_ASSERT(g.Viewports.Size == 1); + + // 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]; + main_viewport->Flags = ImGuiViewportFlags_IsPlatformWindow | ImGuiViewportFlags_OwnedByApp; + main_viewport->Pos = ImVec2(0.0f, 0.0f); + main_viewport->Size = g.IO.DisplaySize; + + for (ImGuiViewportP *viewport : g.Viewports) { + // Lock down space taken by menu bars and status bars, reset the offset for fucntions like BeginMainMenuBar() to alter them again. + viewport->WorkOffsetMin = viewport->BuildWorkOffsetMin; + viewport->WorkOffsetMax = viewport->BuildWorkOffsetMax; + viewport->BuildWorkOffsetMin = viewport->BuildWorkOffsetMax = ImVec2(0.0f, 0.0f); + viewport->UpdateWorkRect(); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] DOCKING +//----------------------------------------------------------------------------- + +// (this section is filled in the 'docking' branch) + +//----------------------------------------------------------------------------- +// [SECTION] PLATFORM DEPENDENT HELPERS +//----------------------------------------------------------------------------- + +#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 *GetClipboardTextFn_DefaultImpl(void *user_data_ctx) +{ + ImGuiContext &g = *(ImGuiContext *)user_data_ctx; + g.ClipboardHandlerData.clear(); + if (!::OpenClipboard(NULL)) + return NULL; + HANDLE wbuf_handle = ::GetClipboardData(CF_UNICODETEXT); + if (wbuf_handle == NULL) { + ::CloseClipboard(); + return NULL; + } + if (const WCHAR *wbuf_global = (const WCHAR *)::GlobalLock(wbuf_handle)) { + int buf_len = ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, NULL, 0, NULL, NULL); + g.ClipboardHandlerData.resize(buf_len); + ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, g.ClipboardHandlerData.Data, buf_len, NULL, NULL); + } + ::GlobalUnlock(wbuf_handle); + ::CloseClipboard(); + return g.ClipboardHandlerData.Data; +} + +static void SetClipboardTextFn_DefaultImpl(void *, const char *text) +{ + if (!::OpenClipboard(NULL)) + return; + const int wbuf_length = ::MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0); + HGLOBAL wbuf_handle = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(WCHAR)); + if (wbuf_handle == NULL) { + ::CloseClipboard(); + return; + } + WCHAR *wbuf_global = (WCHAR *)::GlobalLock(wbuf_handle); + ::MultiByteToWideChar(CP_UTF8, 0, text, -1, wbuf_global, wbuf_length); + ::GlobalUnlock(wbuf_handle); + ::EmptyClipboard(); + if (::SetClipboardData(CF_UNICODETEXT, wbuf_handle) == NULL) + ::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 SetClipboardTextFn_DefaultImpl(void *, const char *text) +{ + if (!main_clipboard) + PasteboardCreate(kPasteboardClipboard, &main_clipboard); + PasteboardClear(main_clipboard); + CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)text, strlen(text)); + if (cf_data) { + PasteboardPutItemFlavor(main_clipboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), cf_data, 0); + CFRelease(cf_data); + } +} + +static const char *GetClipboardTextFn_DefaultImpl(void *user_data_ctx) +{ + ImGuiContext &g = *(ImGuiContext *)user_data_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++) { + PasteboardItemID item_id = 0; + PasteboardGetItemIdentifier(main_clipboard, i + 1, &item_id); + CFArrayRef flavor_type_array = 0; + PasteboardCopyItemFlavors(main_clipboard, item_id, &flavor_type_array); + for (CFIndex j = 0, nj = CFArrayGetCount(flavor_type_array); j < nj; j++) { + CFDataRef cf_data; + if (PasteboardCopyItemFlavorData(main_clipboard, item_id, CFSTR("public.utf8-plain-text"), &cf_data) == noErr) { + g.ClipboardHandlerData.clear(); + int length = (int)CFDataGetLength(cf_data); + g.ClipboardHandlerData.resize(length + 1); + CFDataGetBytes(cf_data, CFRangeMake(0, length), (UInt8 *)g.ClipboardHandlerData.Data); + g.ClipboardHandlerData[length] = 0; + CFRelease(cf_data); + return g.ClipboardHandlerData.Data; + } + } + } + return NULL; +} + +#else + +// Local Dear ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers. +static const char *GetClipboardTextFn_DefaultImpl(void *user_data_ctx) +{ + ImGuiContext &g = *(ImGuiContext *)user_data_ctx; + return g.ClipboardHandlerData.empty() ? NULL : g.ClipboardHandlerData.begin(); +} + +static void SetClipboardTextFn_DefaultImpl(void *user_data_ctx, const char *text) +{ + ImGuiContext &g = *(ImGuiContext *)user_data_ctx; + g.ClipboardHandlerData.clear(); + const char *text_end = text + strlen(text); + g.ClipboardHandlerData.resize((int)(text_end - text) + 1); + memcpy(&g.ClipboardHandlerData[0], text, (size_t)(text_end - text)); + g.ClipboardHandlerData[(int)(text_end - text)] = 0; +} + +#endif + +// 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 SetPlatformImeDataFn_DefaultImpl(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)) { + COMPOSITIONFORM composition_form = {}; + composition_form.ptCurrentPos.x = (LONG)data->InputPos.x; + composition_form.ptCurrentPos.y = (LONG)data->InputPos.y; + composition_form.dwStyle = CFS_FORCE_POSITION; + ::ImmSetCompositionWindow(himc, &composition_form); + CANDIDATEFORM candidate_form = {}; + candidate_form.dwStyle = CFS_CANDIDATEPOS; + candidate_form.ptCurrentPos.x = (LONG)data->InputPos.x; + candidate_form.ptCurrentPos.y = (LONG)data->InputPos.y; + ::ImmSetCandidateWindow(himc, &candidate_form); + ::ImmReleaseContext(hwnd, himc); + } +} + +#else + +static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport *, ImGuiPlatformImeData *) +{ +} + +#endif + +//----------------------------------------------------------------------------- +// [SECTION] METRICS/DEBUGGER WINDOW +//----------------------------------------------------------------------------- +// - RenderViewportThumbnail() [Internal] +// - RenderViewportsThumbnails() [Internal] +// - DebugTextEncoding() +// - MetricsHelpMarker() [Internal] +// - ShowFontAtlas() [Internal] +// - ShowMetricsWindow() +// - DebugNodeColumns() [Internal] +// - DebugNodeDrawList() [Internal] +// - DebugNodeDrawCmdShowMeshAndBoundingBox() [Internal] +// - DebugNodeFont() [Internal] +// - DebugNodeFontGlyph() [Internal] +// - DebugNodeStorage() [Internal] +// - DebugNodeTabBar() [Internal] +// - DebugNodeViewport() [Internal] +// - DebugNodeWindow() [Internal] +// - DebugNodeWindowSettings() [Internal] +// - DebugNodeWindowsList() [Internal] +// - DebugNodeWindowsListByBeginStackParent() [Internal] +//----------------------------------------------------------------------------- + +#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 = 1.0f; + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border, alpha_mul * 0.40f)); + for (ImGuiWindow *thumb_window : g.Windows) { + if (!thumb_window->WasActive || (thumb_window->Flags & ImGuiWindowFlags_ChildWindow)) + continue; + + ImRect thumb_r = thumb_window->Rect(); + ImRect title_r = thumb_window->TitleBarRect(); + thumb_r = ImRect(ImFloor(off + thumb_r.Min * scale), ImFloor(off + thumb_r.Max * scale)); + title_r = ImRect(ImFloor(off + title_r.Min * scale), + ImFloor(off + ImVec2(title_r.Max.x, title_r.Min.y) * scale) + ImVec2(0, 5)); // Exaggerate title bar height + thumb_r.ClipWithFull(bb); + title_r.ClipWithFull(bb); + const bool window_is_focused = (g.NavWindow && thumb_window->RootWindowForTitleBarHighlight == g.NavWindow->RootWindowForTitleBarHighlight); + window->DrawList->AddRectFilled(thumb_r.Min, thumb_r.Max, GetColorU32(ImGuiCol_WindowBg, alpha_mul)); + window->DrawList->AddRectFilled(title_r.Min, title_r.Max, GetColorU32(window_is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg, alpha_mul)); + window->DrawList->AddRect(thumb_r.Min, thumb_r.Max, GetColorU32(ImGuiCol_Border, alpha_mul)); + window->DrawList->AddText(g.Font, + g.FontSize * 1.0f, + title_r.Min, + GetColorU32(ImGuiCol_Text, alpha_mul), + thumb_window->Name, + FindRenderedTextEnd(thumb_window->Name)); + } + draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border, alpha_mul)); +} + +static void RenderViewportsThumbnails() +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + + // We don't display full monitor bounds (we could, but it often looks awkward), instead we display just enough to cover all of our viewports. + float SCALE = 1.0f / 8.0f; + ImRect bb_full(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); + for (ImGuiViewportP *viewport : g.Viewports) + bb_full.Add(viewport->GetMainRect()); + ImVec2 p = window->DC.CursorPos; + ImVec2 off = p - bb_full.Min * SCALE; + for (ImGuiViewportP *viewport : g.Viewports) { + ImRect viewport_draw_bb(off + (viewport->Pos) * SCALE, off + (viewport->Pos + viewport->Size) * SCALE); + ImGui::DebugRenderViewportThumbnail(window->DrawList, viewport, viewport_draw_bb); + } + ImGui::Dummy(bb_full.GetSize() * SCALE); +} + +// Draw an arbitrary US keyboard layout to visualize translated keys +void ImGui::DebugRenderKeyboardPreview(ImDrawList *draw_list) +{ + const ImVec2 key_size = ImVec2(35.0f, 35.0f); + const float key_rounding = 3.0f; + const ImVec2 key_face_size = ImVec2(25.0f, 25.0f); + const ImVec2 key_face_pos = ImVec2(5.0f, 3.0f); + const float key_face_rounding = 2.0f; + const ImVec2 key_label_pos = ImVec2(7.0f, 4.0f); + const ImVec2 key_step = ImVec2(key_size.x - 1.0f, key_size.y - 1.0f); + const float key_row_offset = 9.0f; + + 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[] = {{0, 0, "", ImGuiKey_Tab}, + {0, 1, "Q", ImGuiKey_Q}, + {0, 2, "W", ImGuiKey_W}, + {0, 3, "E", ImGuiKey_E}, + {0, 4, "R", ImGuiKey_R}, + {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); + if (!IsItemVisible()) + return; + draw_list->PushClipRect(board_min, board_max, true); + for (int n = 0; n < IM_ARRAYSIZE(keys_to_display); n++) { + const KeyLayoutData *key_data = &keys_to_display[n]; + ImVec2 key_min = ImVec2(start_pos.x + key_data->Col * key_step.x + key_data->Row * key_row_offset, start_pos.y + key_data->Row * key_step.y); + ImVec2 key_max = key_min + key_size; + draw_list->AddRectFilled(key_min, key_max, IM_COL32(204, 204, 204, 255), key_rounding); + draw_list->AddRect(key_min, key_max, IM_COL32(24, 24, 24, 255), key_rounding); + ImVec2 face_min = ImVec2(key_min.x + key_face_pos.x, key_min.y + key_face_pos.y); + ImVec2 face_max = ImVec2(face_min.x + key_face_size.x, face_min.y + key_face_size.y); + draw_list->AddRect(face_min, face_max, IM_COL32(193, 193, 193, 255), key_face_rounding, ImDrawFlags_None, 2.0f); + draw_list->AddRectFilled(face_min, face_max, IM_COL32(252, 252, 252, 255), key_face_rounding); + ImVec2 label_min = ImVec2(key_min.x + key_label_pos.x, key_min.y + key_label_pos.y); + draw_list->AddText(label_min, IM_COL32(64, 64, 64, 255), key_data->Label); + if (IsKeyDown(key_data->Key)) + draw_list->AddRectFilled(key_min, key_max, IM_COL32(255, 0, 0, 128), key_rounding); + } + 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) +{ + Text("Text: \"%s\"", str); + if (!BeginTable("##DebugTextEncoding", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable)) + return; + TableSetupColumn("Offset"); + TableSetupColumn("UTF-8"); + TableSetupColumn("Glyph"); + TableSetupColumn("Codepoint"); + TableHeadersRow(); + for (const char *p = str; *p != 0;) { + unsigned int c; + const int c_utf8_len = ImTextCharFromUtf8(&c, p, NULL); + TableNextColumn(); + Text("%d", (int)(p - str)); + TableNextColumn(); + for (int byte_index = 0; byte_index < c_utf8_len; byte_index++) { + if (byte_index > 0) + SameLine(); + 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]"); + TableNextColumn(); + Text("U+%04X", (int)c); + p += c_utf8_len; + } + EndTable(); +} + +// 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(); + } +} + +// [DEBUG] List fonts in a font atlas and display its texture +void ImGui::ShowFontAtlas(ImFontAtlas *atlas) +{ + for (ImFont *font : atlas->Fonts) { + PushID(font); + DebugNodeFont(font); + PopID(); + } + if (TreeNode("Font Atlas", "Font Atlas (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) { + ImGuiContext &g = *GImGui; + ImGuiMetricsConfig *cfg = &g.DebugMetricsConfig; + Checkbox("Tint with Text Color", + &cfg->ShowAtlasTintedWithTextColor); // Using text color ensure visibility of core atlas data, but will alter custom colored icons + ImVec4 tint_col = cfg->ShowAtlasTintedWithTextColor ? GetStyleColorVec4(ImGuiCol_Text) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + ImVec4 border_col = GetStyleColorVec4(ImGuiCol_Border); + Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), tint_col, border_col); + TreePop(); + } +} + +void ImGui::ShowMetricsWindow(bool *p_open) +{ + ImGuiContext &g = *GImGui; + ImGuiIO &io = g.IO; + ImGuiMetricsConfig *cfg = &g.DebugMetricsConfig; + if (cfg->ShowDebugLog) + ShowDebugLogWindow(&cfg->ShowDebugLog); + if (cfg->ShowStackTool) + ShowStackToolWindow(&cfg->ShowStackTool); + + if (!Begin("Dear ImGui Metrics/Debugger", p_open) || GetCurrentWindow()->BeginCount > 1) { + End(); + return; + } + + // Basic info + Text("Dear ImGui %s", GetVersion()); + Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + Text("%d vertices, %d indices (%d triangles)", io.MetricsRenderVertices, io.MetricsRenderIndices, io.MetricsRenderIndices / 3); + Text("%d visible windows, %d active allocations", io.MetricsRenderWindows, io.MetricsActiveAllocations); + // 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"}; + enum { + TRT_OuterRect, + TRT_InnerRect, + TRT_WorkRect, + TRT_HostClipRect, + TRT_InnerClipRect, + TRT_BackgroundClipRect, + TRT_ColumnsRect, + TRT_ColumnsWorkRect, + TRT_ColumnsClipRect, + TRT_ColumnsContentHeadersUsed, + TRT_ColumnsContentHeadersIdeal, + TRT_ColumnsContentFrozen, + TRT_ColumnsContentUnfrozen, + TRT_Count + }; // Tables Rect Type + const char *trt_rects_names[TRT_Count] = {"OuterRect", + "InnerRect", + "WorkRect", + "HostClipRect", + "InnerClipRect", + "BackgroundClipRect", + "ColumnsRect", + "ColumnsWorkRect", + "ColumnsClipRect", + "ColumnsContentHeadersUsed", + "ColumnsContentHeadersIdeal", + "ColumnsContentFrozen", + "ColumnsContentUnfrozen"}; + if (cfg->ShowWindowsRectsType < 0) + cfg->ShowWindowsRectsType = WRT_WorkRect; + if (cfg->ShowTablesRectsType < 0) + cfg->ShowTablesRectsType = TRT_WorkRect; + + struct Funcs { + static ImRect GetTableRect(ImGuiTable *table, int rect_type, int n) + { + ImGuiTableInstanceData *table_instance = TableGetInstanceData(table, table->InstanceCurrent); // Always using last submitted instance + if (rect_type == TRT_OuterRect) { + return table->OuterRect; + } else if (rect_type == TRT_InnerRect) { + return table->InnerRect; + } else if (rect_type == TRT_WorkRect) { + return table->WorkRect; + } else if (rect_type == TRT_HostClipRect) { + return table->HostClipRect; + } else if (rect_type == TRT_InnerClipRect) { + return table->InnerClipRect; + } else if (rect_type == TRT_BackgroundClipRect) { + return table->BgClipRect; + } else if (rect_type == TRT_ColumnsRect) { + ImGuiTableColumn *c = &table->Columns[n]; + return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MaxX, table->InnerClipRect.Min.y + table_instance->LastOuterHeight); + } else if (rect_type == TRT_ColumnsWorkRect) { + ImGuiTableColumn *c = &table->Columns[n]; + return ImRect(c->WorkMinX, table->WorkRect.Min.y, c->WorkMaxX, table->WorkRect.Max.y); + } else if (rect_type == TRT_ColumnsClipRect) { + ImGuiTableColumn *c = &table->Columns[n]; + return c->ClipRect; + } else if (rect_type == TRT_ColumnsContentHeadersUsed) { + ImGuiTableColumn *c = &table->Columns[n]; + return ImRect(c->WorkMinX, + table->InnerClipRect.Min.y, + c->ContentMaxXHeadersUsed, + table->InnerClipRect.Min.y + table_instance->LastFirstRowHeight); + } // Note: y1/y2 not always accurate + else if (rect_type == TRT_ColumnsContentHeadersIdeal) { + ImGuiTableColumn *c = &table->Columns[n]; + return ImRect(c->WorkMinX, + table->InnerClipRect.Min.y, + c->ContentMaxXHeadersIdeal, + table->InnerClipRect.Min.y + table_instance->LastFirstRowHeight); + } else if (rect_type == TRT_ColumnsContentFrozen) { + ImGuiTableColumn *c = &table->Columns[n]; + return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXFrozen, table->InnerClipRect.Min.y + table_instance->LastFrozenHeight); + } else if (rect_type == TRT_ColumnsContentUnfrozen) { + ImGuiTableColumn *c = &table->Columns[n]; + return ImRect(c->WorkMinX, table->InnerClipRect.Min.y + table_instance->LastFrozenHeight, c->ContentMaxXUnfrozen, table->InnerClipRect.Max.y); + } + IM_ASSERT(0); + return ImRect(); + } + + static ImRect GetWindowRect(ImGuiWindow *window, int rect_type) + { + if (rect_type == WRT_OuterRect) { + return window->Rect(); + } else if (rect_type == WRT_OuterRectClipped) { + return window->OuterRectClipped; + } else if (rect_type == WRT_InnerRect) { + return window->InnerRect; + } else if (rect_type == WRT_InnerClipRect) { + return window->InnerClipRect; + } else if (rect_type == WRT_WorkRect) { + return window->WorkRect; + } else if (rect_type == WRT_Content) { + ImVec2 min = window->InnerRect.Min - window->Scroll + window->WindowPadding; + return ImRect(min, min + window->ContentSize); + } else if (rect_type == WRT_ContentIdeal) { + ImVec2 min = window->InnerRect.Min - window->Scroll + window->WindowPadding; + return ImRect(min, min + window->ContentSizeIdeal); + } else if (rect_type == WRT_ContentRegionRect) { + return window->ContentRegionRect; + } + IM_ASSERT(0); + return ImRect(); + } + }; + + // Tools + if (TreeNode("Tools")) { + bool show_encoding_viewer = TreeNode("UTF-8 Encoding viewer"); + 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."); + if (show_encoding_viewer) { + static char buf[100] = ""; + SetNextItemWidth(-FLT_MIN); + InputText("##Text", buf, IM_ARRAYSIZE(buf)); + if (buf[0] != 0) + DebugTextEncoding(buf); + TreePop(); + } + + // The Item Picker tool is super useful to visually select an item and break into the call-stack of where it was submitted. + if (Checkbox("Show Item Picker", &g.DebugItemPickerActive) && g.DebugItemPickerActive) + DebugStartItemPicker(); + SameLine(); + MetricsHelpMarker( + "Will call the IM_DEBUG_BREAK() macro to break in debugger.\nWarning: If you don't have a debugger attached, this will probably crash."); + + // Stack Tool is your best friend! + Checkbox("Show Debug Log", &cfg->ShowDebugLog); + SameLine(); + MetricsHelpMarker("You can also call ImGui::ShowDebugLogWindow() from your code."); + + // Stack Tool is your best friend! + Checkbox("Show Stack Tool", &cfg->ShowStackTool); + SameLine(); + MetricsHelpMarker("You can also call ImGui::ShowStackToolWindow() from your code."); + + Checkbox("Show windows begin order", &cfg->ShowWindowsBeginOrder); + Checkbox("Show windows rectangles", &cfg->ShowWindowsRects); + SameLine(); + SetNextItemWidth(GetFontSize() * 12); + cfg->ShowWindowsRects |= Combo("##show_windows_rect_type", &cfg->ShowWindowsRectsType, wrt_rects_names, WRT_Count, WRT_Count); + if (cfg->ShowWindowsRects && g.NavWindow != NULL) { + BulletText("'%s':", g.NavWindow->Name); + Indent(); + for (int rect_n = 0; rect_n < WRT_Count; rect_n++) { + ImRect r = Funcs::GetWindowRect(g.NavWindow, rect_n); + Text("(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", + r.Min.x, + r.Min.y, + r.Max.x, + r.Max.y, + r.GetWidth(), + r.GetHeight(), + wrt_rects_names[rect_n]); + } + Unindent(); + } + + Checkbox("Show tables rectangles", &cfg->ShowTablesRects); + SameLine(); + SetNextItemWidth(GetFontSize() * 12); + cfg->ShowTablesRects |= Combo("##show_table_rects_type", &cfg->ShowTablesRectsType, trt_rects_names, TRT_Count, TRT_Count); + if (cfg->ShowTablesRects && g.NavWindow != NULL) { + for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++) { + 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); + Indent(); + char buf[128]; + for (int rect_n = 0; rect_n < TRT_Count; rect_n++) { + if (rect_n >= TRT_ColumnsRect) { + if (rect_n != TRT_ColumnsRect && rect_n != TRT_ColumnsClipRect) + continue; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { + ImRect r = Funcs::GetTableRect(table, rect_n, column_n); + ImFormatString(buf, + IM_ARRAYSIZE(buf), + "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) Col %d %s", + r.Min.x, + r.Min.y, + r.Max.x, + r.Max.y, + r.GetWidth(), + r.GetHeight(), + column_n, + trt_rects_names[rect_n]); + Selectable(buf); + if (IsItemHovered()) + GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); + } + } else { + ImRect r = Funcs::GetTableRect(table, rect_n, -1); + ImFormatString(buf, + IM_ARRAYSIZE(buf), + "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", + r.Min.x, + r.Min.y, + r.Max.x, + r.Max.y, + r.GetWidth(), + r.GetHeight(), + trt_rects_names[rect_n]); + Selectable(buf); + if (IsItemHovered()) + GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); + } + } + Unindent(); + } + } + + 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."); + + TreePop(); + } + + // Windows + if (TreeNode("Windows", "Windows (%d)", g.Windows.Size)) { + // SetNextItemOpen(true, ImGuiCond_Once); + DebugNodeWindowsList(&g.Windows, "By display order"); + DebugNodeWindowsList(&g.WindowsFocusOrder, "By focus order (root windows)"); + if (TreeNode("By submission order (begin stack)")) { + // Here we display windows in their submitted order/hierarchy, however note that the Begin stack doesn't constitute a Parent<>Child relationship! + ImVector &temp_buffer = g.WindowsTempSortBuffer; + temp_buffer.resize(0); + for (ImGuiWindow *window : g.Windows) + if (window->LastFrameActive + 1 >= g.FrameCount) + temp_buffer.push_back(window); + struct Func { + static int IMGUI_CDECL WindowComparerByBeginOrder(const void *lhs, const void *rhs) + { + return ((int)(*(const ImGuiWindow *const *)lhs)->BeginOrderWithinContext - (*(const ImGuiWindow *const *)rhs)->BeginOrderWithinContext); + } + }; + ImQsort(temp_buffer.Data, (size_t)temp_buffer.Size, sizeof(ImGuiWindow *), Func::WindowComparerByBeginOrder); + DebugNodeWindowsListByBeginStackParent(temp_buffer.Data, temp_buffer.Size, NULL); + TreePop(); + } + + TreePop(); + } + + // DrawLists + int drawlist_count = 0; + for (ImGuiViewportP *viewport : g.Viewports) + drawlist_count += viewport->DrawDataP.CmdLists.Size; + if (TreeNode("DrawLists", "DrawLists (%d)", drawlist_count)) { + Checkbox("Show ImDrawCmd mesh when hovering", &cfg->ShowDrawCmdMesh); + Checkbox("Show ImDrawCmd bounding boxes when hovering", &cfg->ShowDrawCmdBoundingBoxes); + for (ImGuiViewportP *viewport : g.Viewports) + for (ImDrawList *draw_list : viewport->DrawDataP.CmdLists) + DebugNodeDrawList(NULL, viewport, draw_list, "DrawList"); + TreePop(); + } + + // Viewports + if (TreeNode("Viewports", "Viewports (%d)", g.Viewports.Size)) { + Indent(GetTreeNodeToLabelSpacing()); + RenderViewportsThumbnails(); + Unindent(GetTreeNodeToLabelSpacing()); + for (ImGuiViewportP *viewport : g.Viewports) + DebugNodeViewport(viewport); + TreePop(); + } + + // Details for Popups + if (TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size)) { + for (const ImGuiPopupData &popup_data : g.OpenPopupStack) { + // As it's difficult to interact with tree nodes while popups are open, we display everything inline. + ImGuiWindow *window = popup_data.Window; + BulletText("PopupID: %08x, Window: '%s' (%s%s), BackupNavWindow '%s', ParentWindow '%s'", + popup_data.PopupId, + window ? window->Name : "NULL", + window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? "Child;" : "", + window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? "Menu;" : "", + popup_data.BackupNavWindow ? popup_data.BackupNavWindow->Name : "NULL", + window && window->ParentWindow ? window->ParentWindow->Name : "NULL"); + } + TreePop(); + } + + // Details for TabBars + if (TreeNode("TabBars", "Tab Bars (%d)", g.TabBars.GetAliveCount())) { + for (int n = 0; n < g.TabBars.GetMapSize(); n++) + if (ImGuiTabBar *tab_bar = g.TabBars.TryGetMapData(n)) { + PushID(tab_bar); + DebugNodeTabBar(tab_bar, "TabBar"); + PopID(); + } + TreePop(); + } + + // Details for Tables + if (TreeNode("Tables", "Tables (%d)", g.Tables.GetAliveCount())) { + for (int n = 0; n < g.Tables.GetMapSize(); n++) + if (ImGuiTable *table = g.Tables.TryGetMapData(n)) + 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 Docking +#ifdef IMGUI_HAS_DOCK + if (TreeNode("Docking")) { + TreePop(); + } +#endif // #ifdef IMGUI_HAS_DOCK + + // Settings + if (TreeNode("Settings")) { + if (SmallButton("Clear")) + ClearIniSettings(); + SameLine(); + if (SmallButton("Save to memory")) + SaveIniSettingsToMemory(); + SameLine(); + if (SmallButton("Save to disk")) + SaveIniSettingsToDisk(g.IO.IniFilename); + SameLine(); + if (g.IO.IniFilename) + Text("\"%s\"", g.IO.IniFilename); + else + TextUnformatted(""); + Checkbox("io.ConfigDebugIniSettings", &io.ConfigDebugIniSettings); + Text("SettingsDirtyTimer %.2f", g.SettingsDirtyTimer); + if (TreeNode("SettingsHandlers", "Settings handlers: (%d)", g.SettingsHandlers.Size)) { + for (ImGuiSettingsHandler &handler : g.SettingsHandlers) + BulletText("\"%s\"", handler.TypeName); + TreePop(); + } + if (TreeNode("SettingsWindows", "Settings packed data: Windows: %d bytes", g.SettingsWindows.size())) { + for (ImGuiWindowSettings *settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + 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 +#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); + TreePop(); + } + TreePop(); + } + + if (TreeNode("Inputs")) { + Text("KEYBOARD/GAMEPAD/MOUSE KEYS"); + { + // We iterate both legacy native range and named ImGuiKey ranges, which is a little odd but this allows displaying the data for old/new backends. + // User code should never have to go through such hoops! You can generally iterate between ImGuiKey_NamedKey_BEGIN and ImGuiKey_NamedKey_END. + Indent(); +#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO + struct funcs { + static bool IsLegacyNativeDupe(ImGuiKey) + { + return false; + } + }; +#else + struct funcs { + static bool IsLegacyNativeDupe(ImGuiKey key) + { + return key < 512 && GetIO().KeyMap[key] != -1; + } + }; // Hide Native<>ImGuiKey duplicates when both exists in the array + // Text("Legacy raw:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key++) { if (io.KeysDown[key]) { SameLine(); + // Text("\"%s\" %d", GetKeyName(key), key); } } +#endif + Text("Keys down:"); + for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { + if (funcs::IsLegacyNativeDupe(key) || !IsKeyDown(key)) + continue; + SameLine(); + Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); + SameLine(); + Text("(%.02f)", GetKeyData(key)->DownDuration); + } + Text("Keys pressed:"); + for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { + if (funcs::IsLegacyNativeDupe(key) || !IsKeyPressed(key)) + continue; + SameLine(); + Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); + } + Text("Keys released:"); + for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { + if (funcs::IsLegacyNativeDupe(key) || !IsKeyReleased(key)) + continue; + SameLine(); + Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); + } + Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); + Text("Chars queue:"); + for (int i = 0; i < io.InputQueueCharacters.Size; i++) { + ImWchar c = io.InputQueueCharacters[i]; + SameLine(); + 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. + DebugRenderKeyboardPreview(GetWindowDrawList()); + Unindent(); + } + + Text("MOUSE STATE"); + { + Indent(); + if (IsMousePosValid()) + Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y); + else + Text("Mouse pos: "); + Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y); + int count = IM_ARRAYSIZE(io.MouseDown); + Text("Mouse down:"); + for (int i = 0; i < count; i++) + if (IsMouseDown(i)) { + SameLine(); + Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); + } + Text("Mouse clicked:"); + for (int i = 0; i < count; i++) + if (IsMouseClicked(i)) { + SameLine(); + Text("b%d (%d)", i, io.MouseClickedCount[i]); + } + Text("Mouse released:"); + for (int i = 0; i < count; i++) + if (IsMouseReleased(i)) { + SameLine(); + Text("b%d", i); + } + Text("Mouse wheel: %.1f", io.MouseWheel); + Text("MouseStationaryTimer: %.2f", g.MouseStationaryTimer); + Text("Mouse source: %s", GetMouseSourceName(io.MouseSource)); + Text("Pen Pressure: %.1f", io.PenPressure); // Note: currently unused + Unindent(); + } + + Text("MOUSE WHEELING"); + { + Indent(); + Text("WheelingWindow: '%s'", g.WheelingWindow ? g.WheelingWindow->Name : "NULL"); + Text("WheelingWindowReleaseTimer: %.2f", g.WheelingWindowReleaseTimer); + 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(); + if (BeginListBox("##owners", ImVec2(-FLT_MIN, GetTextLineHeightWithSpacing() * 6))) { + for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { + ImGuiKeyOwnerData *owner_data = GetKeyOwnerData(&g, key); + if (owner_data->OwnerCurr == ImGuiKeyOwner_None) + continue; + Text("%s: 0x%08X%s", + GetKeyName(key), + owner_data->OwnerCurr, + owner_data->LockUntilRelease ? " LockUntilRelease" + : owner_data->LockThisFrame ? " LockThisFrame" + : ""); + DebugLocateItemOnHover(owner_data->OwnerCurr); + } + EndListBox(); + } + Unindent(); + } + Text("SHORTCUT ROUTING"); + { + Indent(); + if (BeginListBox("##routes", ImVec2(-FLT_MIN, GetTextLineHeightWithSpacing() * 6))) { + for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { + ImGuiKeyRoutingTable *rt = &g.KeysRoutingTable; + for (ImGuiKeyRoutingIndex idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; idx != -1;) { + char key_chord_name[64]; + ImGuiKeyRoutingData *routing_data = &rt->Entries[idx]; + GetKeyChordName(key | routing_data->Mods, key_chord_name, IM_ARRAYSIZE(key_chord_name)); + Text("%s: 0x%08X", key_chord_name, routing_data->RoutingCurr); + DebugLocateItemOnHover(routing_data->RoutingCurr); + idx = routing_data->NextEntryIndex; + } + } + EndListBox(); + } + Text("(ActiveIdUsing: AllKeyboardKeys: %d, NavDirMask: 0x%X)", g.ActiveIdUsingAllKeyboardKeys, g.ActiveIdUsingNavDirMask); + Unindent(); + } + TreePop(); + } + + if (TreeNode("Internal state")) { + Text("WINDOWING"); + Indent(); + Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL"); + Text("HoveredWindow->Root: '%s'", g.HoveredWindow ? g.HoveredWindow->RootWindow->Name : "NULL"); + Text("HoveredWindowUnderMovingWindow: '%s'", g.HoveredWindowUnderMovingWindow ? g.HoveredWindowUnderMovingWindow->Name : "NULL"); + Text("MovingWindow: '%s'", g.MovingWindow ? g.MovingWindow->Name : "NULL"); + 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)); + DebugLocateItemOnHover(g.ActiveId); + Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL"); + Text("ActiveIdUsing: AllKeyboardKeys: %d, NavDirMask: %X", g.ActiveIdUsingAllKeyboardKeys, g.ActiveIdUsingNavDirMask); + Text("HoveredId: 0x%08X (%.2f sec), AllowOverlap: %d", + g.HoveredIdPreviousFrame, + g.HoveredIdTimer, + g.HoveredIdAllowOverlap); // Not displaying g.HoveredId as it is update mid-frame + Text("HoverItemDelayId: 0x%08X, Timer: %.2f, ClearTimer: %.2f", g.HoverItemDelayId, g.HoverItemDelayTimer, g.HoverItemDelayClearTimer); + 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"); + Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer); + DebugLocateItemOnHover(g.NavId); + Text("NavInputSource: %s", GetInputSourceName(g.NavInputSource)); + Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible); + Text("NavActivateId/DownId/PressedId: %08X/%08X/%08X", g.NavActivateId, g.NavActivateDownId, g.NavActivatePressedId); + Text("NavActivateFlags: %04X", g.NavActivateFlags); + Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover); + Text("NavFocusScopeId = 0x%08X", g.NavFocusScopeId); + Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL"); + Unindent(); + + TreePop(); + } + + // Overlay: Display windows Rectangles and Begin Order + if (cfg->ShowWindowsRects || cfg->ShowWindowsBeginOrder) { + for (ImGuiWindow *window : g.Windows) { + if (!window->WasActive) + continue; + ImDrawList *draw_list = GetForegroundDrawList(window); + if (cfg->ShowWindowsRects) { + ImRect r = Funcs::GetWindowRect(window, cfg->ShowWindowsRectsType); + draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 0, 128, 255)); + } + if (cfg->ShowWindowsBeginOrder && !(window->Flags & ImGuiWindowFlags_ChildWindow)) { + char buf[32]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "%d", window->BeginOrderWithinContext); + float font_size = GetFontSize(); + draw_list->AddRectFilled(window->Pos, window->Pos + ImVec2(font_size, font_size), IM_COL32(200, 100, 100, 255)); + draw_list->AddText(window->Pos, IM_COL32(255, 255, 255, 255), buf); + } + } + } + + // Overlay: Display Tables Rectangles + if (cfg->ShowTablesRects) { + for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++) { + ImGuiTable *table = g.Tables.TryGetMapData(table_n); + if (table == NULL || table->LastFrameActive < g.FrameCount - 1) + continue; + ImDrawList *draw_list = GetForegroundDrawList(table->OuterWindow); + if (cfg->ShowTablesRectsType >= TRT_ColumnsRect) { + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { + ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, column_n); + ImU32 col = (table->HoveredColumnBody == column_n) ? IM_COL32(255, 255, 128, 255) : IM_COL32(255, 0, 128, 255); + float thickness = (table->HoveredColumnBody == column_n) ? 3.0f : 1.0f; + draw_list->AddRect(r.Min, r.Max, col, 0.0f, 0, thickness); + } + } else { + ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, -1); + draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 0, 128, 255)); + } + } + } + +#ifdef IMGUI_HAS_DOCK + // Overlay: Display Docking info + if (show_docking_nodes && g.IO.KeyCtrl) { } +#endif // #ifdef IMGUI_HAS_DOCK + + End(); +} + +// [DEBUG] Display contents of Columns +void ImGui::DebugNodeColumns(ImGuiOldColumns *columns) +{ + if (!TreeNode((void *)(uintptr_t)columns->ID, "Columns Id: 0x%08X, Count: %d, Flags: 0x%04X", columns->ID, columns->Count, columns->Flags)) + return; + BulletText("Width: %.1f (MinX: %.1f, MaxX: %.1f)", columns->OffMaxX - columns->OffMinX, columns->OffMinX, columns->OffMaxX); + for (ImGuiOldColumnData &column : columns->Columns) + BulletText("Column %02d: OffsetNorm %.3f (= %.1f px)", + (int)columns->Columns.index_from_ptr(&column), + column.OffsetNorm, + GetColumnOffsetFromNorm(columns, column.OffsetNorm)); + TreePop(); +} + +// [DEBUG] Display contents of ImDrawList +void ImGui::DebugNodeDrawList(ImGuiWindow *window, ImGuiViewportP *viewport, const ImDrawList *draw_list, const char *label) +{ + ImGuiContext &g = *GImGui; + IM_UNUSED(viewport); // Used in docking branch + ImGuiMetricsConfig *cfg = &g.DebugMetricsConfig; + int cmd_count = draw_list->CmdBuffer.Size; + if (cmd_count > 0 && draw_list->CmdBuffer.back().ElemCount == 0 && draw_list->CmdBuffer.back().UserCallback == NULL) + cmd_count--; + bool node_open = TreeNode(draw_list, + "%s: '%s' %d vtx, %d indices, %d cmds", + label, + draw_list->_OwnerName ? draw_list->_OwnerName : "", + draw_list->VtxBuffer.Size, + draw_list->IdxBuffer.Size, + cmd_count); + if (draw_list == GetWindowDrawList()) { + SameLine(); + TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), + "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered) + if (node_open) + TreePop(); + return; + } + + ImDrawList *fg_draw_list = GetForegroundDrawList(window); // 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) { + BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData); + continue; + } + + char buf[300]; + ImFormatString(buf, + IM_ARRAYSIZE(buf), + "DrawCmd:%5d tris, Tex 0x%p, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)", + pcmd->ElemCount / 3, + (void *)(intptr_t)pcmd->TextureId, + pcmd->ClipRect.x, + pcmd->ClipRect.y, + pcmd->ClipRect.z, + pcmd->ClipRect.w); + bool pcmd_node_open = TreeNode((void *)(pcmd - draw_list->CmdBuffer.begin()), "%s", buf); + if (IsItemHovered() && (cfg->ShowDrawCmdMesh || cfg->ShowDrawCmdBoundingBoxes) && fg_draw_list) + 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; + const ImDrawVert *vtx_buffer = draw_list->VtxBuffer.Data + pcmd->VtxOffset; + float total_area = 0.0f; + for (unsigned int idx_n = pcmd->IdxOffset; idx_n < pcmd->IdxOffset + pcmd->ElemCount;) { + ImVec2 triangle[3]; + for (int n = 0; n < 3; n++, idx_n++) + 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. + while (clipper.Step()) + for (int prim = clipper.DisplayStart, idx_i = pcmd->IdxOffset + clipper.DisplayStart * 3; prim < clipper.DisplayEnd; prim++) { + char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf); + ImVec2 triangle[3]; + for (int n = 0; n < 3; n++, idx_i++) { + const ImDrawVert &v = vtx_buffer[idx_buffer ? idx_buffer[idx_i] : idx_i]; + triangle[n] = v.pos; + 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()) { + ImDrawListFlags backup_flags = fg_draw_list->Flags; + fg_draw_list->Flags &= + ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles. + fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f); + fg_draw_list->Flags = backup_flags; + } + } + TreePop(); + } + 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); + ImDrawListFlags backup_flags = out_draw_list->Flags; + out_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles. + for (unsigned int idx_n = draw_cmd->IdxOffset, idx_end = draw_cmd->IdxOffset + draw_cmd->ElemCount; idx_n < idx_end;) { + 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)); + if (show_mesh) + out_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f); // In yellow: mesh triangles + } + // Draw bounding boxes + if (show_aabb) { + out_draw_list->AddRect(ImFloor(clip_rect.Min), ImFloor(clip_rect.Max), IM_COL32(255, 0, 255, 255)); // In pink: clipping rectangle submitted to GPU + out_draw_list->AddRect(ImFloor(vtxs_rect.Min), ImFloor(vtxs_rect.Max), IM_COL32(0, 255, 255, 255)); // In cyan: bounding box of triangles + } + out_draw_list->Flags = backup_flags; +} + +// [DEBUG] Display details for a single font, called by ShowStyleEditor(). +void ImGui::DebugNodeFont(ImFont *font) +{ + bool opened = TreeNode(font, + "Font: \"%s\"\n%.2f px, %d glyphs, %d file(s)", + font->ConfigData ? font->ConfigData[0].Name : "", + font->FontSize, + font->Glyphs.Size, + font->ConfigDataCount); + SameLine(); + if (SmallButton("Set as default")) + GetIO().FontDefault = font; + if (!opened) + return; + + // Display preview text + PushFont(font); + Text("The quick brown fox jumps over the lazy dog"); + PopFont(); + + // Display details + SetNextItemWidth(GetFontSize() * 8); + DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); + SameLine(); + MetricsHelpMarker( + "Note than 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); + 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->ConfigDataCount; config_i++) + if (font->ConfigData) + if (const ImFontConfig *cfg = &font->ConfigData[config_i]) + BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", + config_i, + cfg->Name, + cfg->OversampleH, + cfg->OversampleV, + cfg->PixelSnapH, + cfg->GlyphOffset.x, + cfg->GlyphOffset.y); + + // Display all glyphs of the fonts in separate pages of 256 characters + if (TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) { + 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) { + // 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 & 4095) && font->IsGlyphRangeUnused(base, base + 4095)) { + base += 4096 - 256; + continue; + } + + 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(); + } + TreePop(); + } + TreePop(); +} + +void ImGui::DebugNodeFontGlyph(ImFont *, const ImFontGlyph *glyph) +{ + Text("Codepoint: U+%04X", glyph->Codepoint); + Separator(); + Text("Visible: %d", glyph->Visible); + 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); +} + +// [DEBUG] Display contents of ImGuiStorage +void ImGui::DebugNodeStorage(ImGuiStorage *storage, const char *label) +{ + if (!TreeNode(label, "%s: %d entries, %d bytes", label, storage->Data.Size, storage->Data.size_in_bytes())) + return; + for (const ImGuiStorage::ImGuiStoragePair &p : storage->Data) + BulletText("Key 0x%08X Value { i: %d }", p.key, p.val_i); // Important: we currently don't store a type, real value may not be integer. + TreePop(); +} + +// [DEBUG] Display contents of ImGuiTabBar +void ImGui::DebugNodeTabBar(ImGuiTabBar *tab_bar, const char *label) +{ + // Standalone tab bars (not associated to docking/windows functionality) currently hold no discernible strings. + char buf[256]; + char *p = buf; + const char *buf_end = buf + IM_ARRAYSIZE(buf); + const bool is_active = (tab_bar->PrevFrameVisible >= GetFrameCount() - 2); + p += ImFormatString(p, buf_end - p, "%s 0x%08X (%d tabs)%s {", label, tab_bar->ID, tab_bar->Tabs.Size, is_active ? "" : " *Inactive*"); + for (int tab_n = 0; tab_n < ImMin(tab_bar->Tabs.Size, 3); tab_n++) { + ImGuiTabItem *tab = &tab_bar->Tabs[tab_n]; + p += ImFormatString(p, buf_end - p, "%s'%s'", tab_n > 0 ? ", " : "", TabBarGetTabName(tab_bar, tab)); + } + p += ImFormatString(p, buf_end - p, (tab_bar->Tabs.Size > 3) ? " ... }" : " } "); + if (!is_active) { + PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); + } + bool open = TreeNode(label, "%s", buf); + if (!is_active) { + PopStyleColor(); + } + if (is_active && IsItemHovered()) { + ImDrawList *draw_list = GetForegroundDrawList(); + draw_list->AddRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255)); + draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Min.y), + ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Max.y), + IM_COL32(0, 255, 0, 255)); + draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Min.y), + ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Max.y), + IM_COL32(0, 255, 0, 255)); + } + if (open) { + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) { + ImGuiTabItem *tab = &tab_bar->Tabs[tab_n]; + PushID(tab); + if (SmallButton("<")) { + TabBarQueueReorder(tab_bar, tab, -1); + } + SameLine(0, 2); + if (SmallButton(">")) { + TabBarQueueReorder(tab_bar, tab, +1); + } + SameLine(); + Text("%02d%c Tab 0x%08X '%s' Offset: %.2f, Width: %.2f/%.2f", + tab_n, + (tab->ID == tab_bar->SelectedTabId) ? '*' : ' ', + tab->ID, + TabBarGetTabName(tab_bar, tab), + tab->Offset, + tab->Width, + tab->ContentWidth); + PopID(); + } + TreePop(); + } +} + +void ImGui::DebugNodeViewport(ImGuiViewportP *viewport) +{ + SetNextItemOpen(true, ImGuiCond_Once); + if (TreeNode("viewport0", "Viewport #%d", 0)) { + ImGuiWindowFlags flags = viewport->Flags; + BulletText("Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\nWorkArea Offset Left: %.0f Top: %.0f, Right: %.0f, Bottom: %.0f", + viewport->Pos.x, + viewport->Pos.y, + viewport->Size.x, + viewport->Size.y, + viewport->WorkOffsetMin.x, + viewport->WorkOffsetMin.y, + viewport->WorkOffsetMax.x, + viewport->WorkOffsetMax.y); + BulletText("Flags: 0x%04X =%s%s%s", + viewport->Flags, + (flags & ImGuiViewportFlags_IsPlatformWindow) ? " IsPlatformWindow" : "", + (flags & ImGuiViewportFlags_IsPlatformMonitor) ? " IsPlatformMonitor" : "", + (flags & ImGuiViewportFlags_OwnedByApp) ? " OwnedByApp" : ""); + for (ImDrawList *draw_list : viewport->DrawDataP.CmdLists) + DebugNodeDrawList(NULL, viewport, draw_list, "DrawList"); + TreePop(); + } +} + +void ImGui::DebugNodeWindow(ImGuiWindow *window, const char *label) +{ + if (window == NULL) { + 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; + if (!is_active) { + PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); + } + const bool open = TreeNodeEx(label, tree_node_flags, "%s '%s'%s", label, window->Name, is_active ? "" : " *Inactive*"); + if (!is_active) { + PopStyleColor(); + } + if (IsItemHovered() && is_active) + 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."); + + 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); + BulletText("Flags: 0x%08X (%s%s%s%s%s%s%s%s%s..)", + flags, + (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", + (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", + (flags & ImGuiWindowFlags_Popup) ? "Popup " : "", + (flags & ImGuiWindowFlags_Modal) ? "Modal " : "", + (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "", + (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : "", + (flags & ImGuiWindowFlags_NoMouseInputs) ? "NoMouseInputs" : "", + (flags & ImGuiWindowFlags_NoNavInputs) ? "NoNavInputs" : "", + (flags & ImGuiWindowFlags_AlwaysAutoResize) ? "AlwaysAutoResize" : ""); + BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f) Scrollbar:%s%s", + window->Scroll.x, + window->ScrollMax.x, + window->Scroll.y, + window->ScrollMax.y, + window->ScrollbarX ? "X" : "", + window->ScrollbarY ? "Y" : ""); + BulletText("Active: %d/%d, WriteAccessed: %d, BeginOrderWithinContext: %d", + window->Active, + window->WasActive, + window->WriteAccessed, + (window->Active || window->WasActive) ? window->BeginOrderWithinContext : -1); + BulletText("Appearing: %d, Hidden: %d (CanSkip %d Cannot %d), SkipItems: %d", + window->Appearing, + window->Hidden, + window->HiddenFramesCanSkipItems, + window->HiddenFramesCannotSkipItems, + window->SkipItems); + for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++) { + ImRect r = window->NavRectRel[layer]; + if (r.Min.x >= r.Max.y && r.Min.y >= r.Max.y) + BulletText("NavLastIds[%d]: 0x%08X", layer, window->NavLastIds[layer]); + else + BulletText("NavLastIds[%d]: 0x%08X at +(%.1f,%.1f)(%.1f,%.1f)", layer, window->NavLastIds[layer], r.Min.x, r.Min.y, r.Max.x, r.Max.y); + DebugLocateItemOnHover(window->NavLastIds[layer]); + } + const ImVec2 *pr = window->NavPreferredScoringPosRel; + 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"); + if (window->RootWindow != window) { + DebugNodeWindow(window->RootWindow, "RootWindow"); + } + if (window->ParentWindow != NULL) { + DebugNodeWindow(window->ParentWindow, "ParentWindow"); + } + if (window->DC.ChildWindows.Size > 0) { + DebugNodeWindowsList(&window->DC.ChildWindows, "ChildWindows"); + } + if (window->ColumnsStorage.Size > 0 && TreeNode("Columns", "Columns sets (%d)", window->ColumnsStorage.Size)) { + for (ImGuiOldColumns &columns : window->ColumnsStorage) + DebugNodeColumns(&columns); + TreePop(); + } + DebugNodeStorage(&window->StateStorage, "Storage"); + TreePop(); +} + +void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings *settings) +{ + if (settings->WantDelete) + BeginDisabled(); + Text("0x%08X \"%s\" Pos (%d,%d) Size (%d,%d) Collapsed=%d", + settings->ID, + settings->GetName(), + settings->Pos.x, + settings->Pos.y, + settings->Size.x, + settings->Size.y, + settings->Collapsed); + if (settings->WantDelete) + EndDisabled(); +} + +void ImGui::DebugNodeWindowsList(ImVector *windows, const char *label) +{ + if (!TreeNode(label, "%s (%d)", label, windows->Size)) + return; + for (int i = windows->Size - 1; i >= 0; i--) // Iterate front to back + { + PushID((*windows)[i]); + DebugNodeWindow((*windows)[i], "Window"); + PopID(); + } + 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) +{ + for (int i = 0; i < windows_size; i++) { + ImGuiWindow *window = windows[i]; + if (window->ParentWindowInBeginStack != parent_in_begin_stack) + continue; + char buf[20]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "[%04d] Window", window->BeginOrderWithinContext); + // BulletText("[%04d] Window '%s'", window->BeginOrderWithinContext, window->Name); + DebugNodeWindow(window, buf); + Indent(); + DebugNodeWindowsListByBeginStackParent(windows + i + 1, windows_size - i - 1, window); + Unindent(); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] DEBUG LOG WINDOW +//----------------------------------------------------------------------------- + +void ImGui::DebugLog(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DebugLogV(fmt, args); + va_end(args); +} + +void ImGui::DebugLogV(const char *fmt, va_list args) +{ + ImGuiContext &g = *GImGui; + const int old_size = g.DebugLogBuf.size(); + g.DebugLogBuf.appendf("[%05d] ", g.FrameCount); + g.DebugLogBuf.appendfv(fmt, args); + if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToTTY) + IMGUI_DEBUG_PRINTF("%s", g.DebugLogBuf.begin() + old_size); + g.DebugLogIndex.append(g.DebugLogBuf.c_str(), old_size, g.DebugLogBuf.size()); +} + +void ImGui::ShowDebugLogWindow(bool *p_open) +{ + ImGuiContext &g = *GImGui; + if (!(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize)) + SetNextWindowSize(ImVec2(0.0f, GetFontSize() * 12.0f), ImGuiCond_FirstUseEver); + if (!Begin("Dear ImGui Debug Log", p_open) || GetCurrentWindow()->BeginCount > 1) { + End(); + return; + } + + CheckboxFlags("All", &g.DebugLogFlags, ImGuiDebugLogFlags_EventMask_); + SameLine(); + CheckboxFlags("ActiveId", &g.DebugLogFlags, ImGuiDebugLogFlags_EventActiveId); + SameLine(); + CheckboxFlags("Focus", &g.DebugLogFlags, ImGuiDebugLogFlags_EventFocus); + SameLine(); + CheckboxFlags("Popup", &g.DebugLogFlags, ImGuiDebugLogFlags_EventPopup); + SameLine(); + CheckboxFlags("Nav", &g.DebugLogFlags, ImGuiDebugLogFlags_EventNav); + SameLine(); + if (CheckboxFlags("Clipper", &g.DebugLogFlags, ImGuiDebugLogFlags_EventClipper)) { + g.DebugLogClipperAutoDisableFrames = 2; + } + if (IsItemHovered()) + SetTooltip("Clipper log auto-disabled after 2 frames"); + // SameLine(); CheckboxFlags("Selection", &g.DebugLogFlags, ImGuiDebugLogFlags_EventSelection); + SameLine(); + CheckboxFlags("IO", &g.DebugLogFlags, ImGuiDebugLogFlags_EventIO); + + if (SmallButton("Clear")) { + g.DebugLogBuf.clear(); + g.DebugLogIndex.clear(); + } + SameLine(); + if (SmallButton("Copy")) + SetClipboardText(g.DebugLogBuf.c_str()); + BeginChild("##log", ImVec2(0.0f, 0.0f), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); + + ImGuiListClipper clipper; + clipper.Begin(g.DebugLogIndex.size()); + while (clipper.Step()) + for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) { + const char *line_begin = g.DebugLogIndex.get_line_begin(g.DebugLogBuf.c_str(), line_no); + const char *line_end = g.DebugLogIndex.get_line_end(g.DebugLogBuf.c_str(), line_no); + TextUnformatted(line_begin, line_end); + ImRect text_rect = g.LastItemData.Rect; + if (IsItemHovered()) + for (const char *p = line_begin; p <= line_end - 10; p++) { + ImGuiID id = 0; + if (p[0] != '0' || (p[1] != 'x' && p[1] != 'X') || sscanf(p + 2, "%X", &id) != 1) + continue; + ImVec2 p0 = CalcTextSize(line_begin, p); + ImVec2 p1 = CalcTextSize(p, p + 10); + g.LastItemData.Rect = ImRect(text_rect.Min + ImVec2(p0.x, 0.0f), text_rect.Min + ImVec2(p0.x + p1.x, p1.y)); + if (IsMouseHoveringRect(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, true)) + DebugLocateItemOnHover(id); + p += 10; + } + } + if (GetScrollY() >= GetScrollMaxY()) + SetScrollHereY(1.0f); + EndChild(); + + End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] OTHER DEBUG TOOLS (ITEM PICKER, STACK TOOL) +//----------------------------------------------------------------------------- + +// Draw a small cross at current CursorPos in current window's DrawList +void ImGui::DebugDrawCursorPos(ImU32 col) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + ImVec2 pos = window->DC.CursorPos; + 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) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + float curr_x = window->DC.CursorPos.x; + float line_y1 = (window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y); + float line_y2 = line_y1 + (window->DC.IsSameLine ? window->DC.PrevLineSize.y : window->DC.CurrLineSize.y); + window->DrawList->AddLine(ImVec2(curr_x - 5.0f, line_y1), ImVec2(curr_x + 5.0f, line_y1), col, 1.0f); + 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) +{ + ImGuiContext &g = *GImGui; + 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; + g.DebugLocateId = target_id; + g.DebugLocateFrames = 2; +} + +void ImGui::DebugLocateItemOnHover(ImGuiID target_id) +{ + if (target_id == 0 || !IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + return; + 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); +} + +void ImGui::DebugLocateItemResolveWithLastItem() +{ + ImGuiContext &g = *GImGui; + ImGuiLastItemData item_data = g.LastItemData; + g.DebugLocateId = 0; + ImDrawList *draw_list = GetForegroundDrawList(g.CurrentWindow); + ImRect r = item_data.Rect; + r.Expand(3.0f); + ImVec2 p1 = g.IO.MousePos; + ImVec2 p2 = ImVec2((p1.x < r.Min.x) ? r.Min.x : (p1.x > r.Max.x) ? r.Max.x : p1.x, (p1.y < r.Min.y) ? r.Min.y : (p1.y > r.Max.y) ? r.Max.y : p1.y); + draw_list->AddRect(r.Min, r.Max, DEBUG_LOCATE_ITEM_COLOR); + draw_list->AddLine(p1, p2, DEBUG_LOCATE_ITEM_COLOR); +} + +// [DEBUG] Item picker tool - start with DebugStartItemPicker() - useful to visually select an item and break into its call-stack. +void ImGui::UpdateDebugToolItemPicker() +{ + ImGuiContext &g = *GImGui; + g.DebugItemPickerBreakId = 0; + if (!g.DebugItemPickerActive) + return; + + const ImGuiID hovered_id = g.HoveredIdPreviousFrame; + SetMouseCursor(ImGuiMouseCursor_Hand); + if (IsKeyPressed(ImGuiKey_Escape)) + g.DebugItemPickerActive = false; + const bool change_mapping = g.IO.KeyMods == (ImGuiMod_Ctrl | ImGuiMod_Shift); + if (!change_mapping && IsMouseClicked(g.DebugItemPickerMouseButton) && hovered_id) { + g.DebugItemPickerBreakId = hovered_id; + g.DebugItemPickerActive = false; + } + for (int mouse_button = 0; mouse_button < 3; mouse_button++) + if (change_mapping && IsMouseClicked(mouse_button)) + g.DebugItemPickerMouseButton = (ImU8)mouse_button; + SetNextWindowBgAlpha(0.70f); + if (!BeginTooltip()) + return; + Text("HoveredId: 0x%08X", hovered_id); + Text("Press ESC to abort picking."); + const char *mouse_button_names[] = {"Left", "Right", "Middle"}; + if (change_mapping) + Text("Remap w/ Ctrl+Shift: click anywhere to select new mouse button."); + else + 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] Stack Tool: update queries. Called by NewFrame() +void ImGui::UpdateDebugToolStackQueries() +{ + ImGuiContext &g = *GImGui; + ImGuiStackTool *tool = &g.DebugStackTool; + + // Clear hook when 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; + if (tool->QueryId != query_id) { + tool->QueryId = query_id; + tool->StackLevel = -1; + tool->Results.resize(0); + } + 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) + g.DebugHookIdInfo = query_id; + if (stack_level >= 0 && stack_level < tool->Results.Size) { + g.DebugHookIdInfo = tool->Results[stack_level].ID; + tool->Results[stack_level].QueryFrameCount++; + } +} + +// [DEBUG] 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; + ImGuiStackTool *tool = &g.DebugStackTool; + + // 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) { + tool->StackLevel++; + tool->Results.resize(window->IDStack.Size + 1, ImGuiStackLevelInfo()); + for (int n = 0; n < window->IDStack.Size + 1; n++) + 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: + ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), "%d", (int)(intptr_t)data_id); + break; + case ImGuiDataType_String: + ImFormatString(info->Desc, + IM_ARRAYSIZE(info->Desc), + "%.*s", + data_id_end ? (int)((const char *)data_id_end - (const char *)data_id) : (int)strlen((const char *)data_id), + (const char *)data_id); + break; + case ImGuiDataType_Pointer: + ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), "(void*)0x%p", data_id); + break; + case ImGuiDataType_ID: + if (info->Desc[0] + != 0) // PushOverrideID() is often used to avoid hashing twice, which would lead to 2 calls to DebugHookIdInfo(). We prioritize the first one. + return; + ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), "0x%08X [override]", id); + break; + default: + IM_ASSERT(0); + } + info->QuerySuccess = true; + info->DataType = data_type; +} + +static int StackToolFormatLevelInfo(ImGuiStackTool *tool, int n, bool format_for_ui, char *buf, size_t buf_size) +{ + ImGuiStackLevelInfo *info = &tool->Results[n]; + ImGuiWindow *window = (info->Desc[0] == 0 && n == 0) ? ImGui::FindWindowByID(info->ID) : NULL; + if (window) // Source: window name (because the root ID don't call GetID() and so doesn't get hooked) + return ImFormatString(buf, buf_size, format_for_ui ? "\"%s\" [window]" : "%s", window->Name); + if (info->QuerySuccess) // Source: GetID() hooks (prioritize over ItemInfo() because we frequently use patterns like: PushID(str), Button("") where they + // both have same id) + return ImFormatString(buf, buf_size, (format_for_ui && info->DataType == ImGuiDataType_String) ? "\"%s\"" : "%s", info->Desc); + if (tool->StackLevel < tool->Results.Size) // Only start using fallback below when all queries are done, so during queries we don't flickering ??? markers. + return (*buf = 0); +#ifdef IMGUI_ENABLE_TEST_ENGINE + if (const char *label = ImGuiTestEngine_FindItemDebugLabel(GImGui, info->ID)) // Source: ImGuiTestEngine's ItemInfo() + return ImFormatString(buf, buf_size, format_for_ui ? "??? \"%s\"" : "%s", label); +#endif + return ImFormatString(buf, buf_size, "???"); +} + +// Stack Tool: Display UI +void ImGui::ShowStackToolWindow(bool *p_open) +{ + ImGuiContext &g = *GImGui; + if (!(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize)) + SetNextWindowSize(ImVec2(0.0f, GetFontSize() * 8.0f), ImGuiCond_FirstUseEver); + if (!Begin("Dear ImGui Stack Tool", p_open) || GetCurrentWindow()->BeginCount > 1) { + End(); + return; + } + + // Display hovered/active status + ImGuiStackTool *tool = &g.DebugStackTool; + const ImGuiID hovered_id = g.HoveredIdPreviousFrame; + const ImGuiID active_id = g.ActiveId; +#ifdef IMGUI_ENABLE_TEST_ENGINE + Text("HoveredId: 0x%08X (\"%s\"), ActiveId: 0x%08X (\"%s\")", + hovered_id, + hovered_id ? ImGuiTestEngine_FindItemDebugLabel(&g, hovered_id) : "", + active_id, + active_id ? ImGuiTestEngine_FindItemDebugLabel(&g, active_id) : ""); +#else + Text("HoveredId: 0x%08X, ActiveId: 0x%08X", hovered_id, active_id); +#endif + 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; + Checkbox("Ctrl+C: copy path to clipboard", &tool->CopyToClipboardOnCtrlC); + SameLine(); + TextColored((time_since_copy >= 0.0f && time_since_copy < 0.75f && ImFmod(time_since_copy, 0.25f) < 0.25f * 0.5f) ? ImVec4(1.f, 1.f, 0.3f, 1.f) : ImVec4(), + "*COPIED*"); + if (tool->CopyToClipboardOnCtrlC && IsKeyDown(ImGuiMod_Ctrl) && IsKeyPressed(ImGuiKey_C)) { + tool->CopyToClipboardLastTime = (float)g.Time; + char *p = g.TempBuffer.Data; + char *p_end = p + g.TempBuffer.Size; + for (int stack_n = 0; stack_n < tool->Results.Size && p + 3 < p_end; stack_n++) { + *p++ = '/'; + char level_desc[256]; + StackToolFormatLevelInfo(tool, stack_n, false, level_desc, IM_ARRAYSIZE(level_desc)); + for (int n = 0; level_desc[n] && p + 2 < p_end; n++) { + if (level_desc[n] == '/') + *p++ = '\\'; + *p++ = level_desc[n]; + } + } + *p = '\0'; + SetClipboardText(g.TempBuffer.Data); + } + + // Display decorated stack + tool->LastActiveFrame = g.FrameCount; + if (tool->Results.Size > 0 && BeginTable("##table", 3, ImGuiTableFlags_Borders)) { + const float id_width = CalcTextSize("0xDDDDDDDD").x; + TableSetupColumn("Seed", ImGuiTableColumnFlags_WidthFixed, id_width); + TableSetupColumn("PushID", ImGuiTableColumnFlags_WidthStretch); + TableSetupColumn("Result", ImGuiTableColumnFlags_WidthFixed, id_width); + TableHeadersRow(); + for (int n = 0; n < tool->Results.Size; n++) { + ImGuiStackLevelInfo *info = &tool->Results[n]; + TableNextColumn(); + Text("0x%08X", (n > 0) ? tool->Results[n - 1].ID : 0); + TableNextColumn(); + StackToolFormatLevelInfo(tool, n, true, g.TempBuffer.Data, g.TempBuffer.Size); + TextUnformatted(g.TempBuffer.Data); + TableNextColumn(); + Text("0x%08X", info->ID); + if (n == tool->Results.Size - 1) + TableSetBgColor(ImGuiTableBgTarget_CellBg, GetColorU32(ImGuiCol_Header)); + } + EndTable(); + } + 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::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::DebugLog(const char *, ...) +{ +} +void ImGui::DebugLogV(const char *, va_list) +{ +} +void ImGui::ShowDebugLogWindow(bool *) +{ +} +void ImGui::ShowStackToolWindow(bool *) +{ +} +void ImGui::DebugHookIdInfo(ImGuiID, ImGuiDataType, const void *, const void *) +{ +} +void ImGui::UpdateDebugToolItemPicker() +{ +} +void ImGui::UpdateDebugToolStackQueries() +{ +} + +#endif // #ifndef 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 diff --git a/extern/imgui/src/imgui_demo.cpp b/extern/imgui/src/imgui_demo.cpp new file mode 100644 index 0000000..bc2b373 --- /dev/null +++ b/extern/imgui/src/imgui_demo.cpp @@ -0,0 +1,8542 @@ +// dear imgui, v1.89.9 +// (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. +// - Need help integrating Dear ImGui in your codebase? +// - Read Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started +// - Read 'Programmer guide' in imgui.cpp for notes on how to setup Dear ImGui in your codebase. +// Read imgui.cpp for more details, documentation and comments. +// Get the latest version at https://github.com/ocornut/imgui + +//--------------------------------------------------- +// PLEASE DO NOT REMOVE THIS FILE FROM YOUR PROJECT! +//--------------------------------------------------- +// Message to the person tempted to delete this file when integrating Dear ImGui into their codebase: +// Think again! It is the most useful reference code that you and other coders will want to refer to and call. +// Have the ImGui::ShowDemoWindow() function wired in an always-available debug menu of your game/app! +// Also include Metrics! ItemPicker! DebugLog! and other debug features. +// Removing this file from your project is hindering access to documentation for everyone in your team, +// likely leading you to poorer usage of the library. +// Everything in this file will be stripped out by the linker if you don't call ImGui::ShowDemoWindow(). +// If you want to link core Dear ImGui in your shipped builds but want a thorough guarantee that the demo will not be +// linked, you can setup your imconfig.h with #define IMGUI_DISABLE_DEMO_WINDOWS and those functions will be empty. +// 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: +//-------------------------------------------- +// In this demo code, we frequently use 'static' variables inside functions. +// A static variable persists across calls. It is essentially a global variable but declared inside the scope of the function. +// Think of "static int n = 0;" as "global int n = 0;" ! +// We do this IN THE DEMO because we want: +// - to gather code and data in the same place. +// - to make the demo source code faster to read, faster to change, smaller in size. +// - it is also a convenient way of storing simple UI related information as long as your function +// 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 +//----------------------------------------- +// The Demo code in this file is designed to be easy to copy-and-paste into your application! +// Because of this: +// - We never omit the ImGui:: prefix when calling functions, even though most code here is in the same namespace. +// - We try to declare static variables in the local scope, as close as possible to the code using them. +// - We never use any of the helpers/facilities used internally by Dear ImGui, unless available in the public API. +// - We never use maths operators on ImVec2/ImVec4. For our other sources files we use them, and they are provided +// 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 IDE: CTRL+comma ("Edit.GoToAll") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// - With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. + +/* + +Index of this file: + +// [SECTION] Forward Declarations +// [SECTION] Helpers +// [SECTION] Demo Window / ShowDemoWindow() +// - ShowDemoWindow() +// - sub section: ShowDemoWindowWidgets() +// - sub section: ShowDemoWindowLayout() +// - sub section: ShowDemoWindowPopups() +// - sub section: ShowDemoWindowTables() +// - sub section: ShowDemoWindowInputs() +// [SECTION] About Window / ShowAboutWindow() +// [SECTION] Style Editor / ShowStyleEditor() +// [SECTION] User Guide / ShowUserGuide() +// [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar() +// [SECTION] Example App: Debug Console / ShowExampleAppConsole() +// [SECTION] Example App: Debug Log / ShowExampleAppLog() +// [SECTION] Example App: Simple Layout / ShowExampleAppLayout() +// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() +// [SECTION] Example App: Long Text / ShowExampleAppLongText() +// [SECTION] Example App: Auto Resize / ShowExampleAppAutoResize() +// [SECTION] Example App: Constrained Resize / ShowExampleAppConstrainedResize() +// [SECTION] Example App: Simple overlay / ShowExampleAppSimpleOverlay() +// [SECTION] Example App: Fullscreen window / ShowExampleAppFullscreen() +// [SECTION] Example App: Manipulating window titles / ShowExampleAppWindowTitles() +// [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() +// [SECTION] Example App: Documents Handling / ShowExampleAppDocuments() + +*/ + +#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 +#include // sqrtf, powf, cosf, sinf, floorf, ceilf +#include // intptr_t +#include // vsnprintf, sscanf, printf +#include // NULL, malloc, free, atoi +#if !defined(_MSC_VER) || _MSC_VER >= 1800 +#include // PRId64/PRIu64, not avail in some MinGW headers. +#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") +#pragma clang diagnostic ignored \ + "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to + // be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wdeprecated-declarations" // warning: 'xx' is deprecated: The POSIX name for this.. // for strdup used in demo code (so + // user can copy & paste the code) +#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning: cast to 'void *' from smaller integer type +#pragma clang diagnostic ignored "-Wformat-security" // warning: format string is not a string literal +#pragma clang diagnostic ignored \ + "-Wexit-time-destructors" // warning: declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to + // users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. +#pragma clang diagnostic ignored "-Wunused-macros" // warning: macro is not used // we define snprintf/vsnprintf on Windows so + // they are available, but not always used. +#pragma clang diagnostic ignored \ + "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using + // printf() is a misery with this as C++ va_arg ellipsis changes float to double. +#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning: macro name is a reserved identifier +#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wformat-security" // warning: format string is not a string literal (potentially insecure) +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored \ + "-Wmisleading-indentation" // [__GNUC__ >= 6] warning: this 'if' clause does not guard this statement // GCC 6.0+ only. See #883 on GitHub. +#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 +#endif +#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" +#define PRIu64 "I64u" +#elif !defined(PRId64) +#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... +// In other imgui sources we can use nicer internal functions from imgui_internal.h (ImMin/ImMax) but not in the demo. +#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 +#ifdef _MSC_VER +#define IMGUI_CDECL __cdecl +#else +#define IMGUI_CDECL +#endif +#endif + +//----------------------------------------------------------------------------- +// [SECTION] Forward Declarations, Helpers +//----------------------------------------------------------------------------- + +#if !defined(IMGUI_DISABLE_DEMO_WINDOWS) + +// Forward Declarations +static void ShowExampleAppMainMenuBar(); +static void ShowExampleAppConsole(bool *p_open); +static void ShowExampleAppCustomRendering(bool *p_open); +static void ShowExampleAppDocuments(bool *p_open); +static void ShowExampleAppLog(bool *p_open); +static void ShowExampleAppLayout(bool *p_open); +static void ShowExampleAppPropertyEditor(bool *p_open); +static void ShowExampleAppSimpleOverlay(bool *p_open); +static void ShowExampleAppAutoResize(bool *p_open); +static void ShowExampleAppConstrainedResize(bool *p_open); +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 grow non-linearly) +static void ShowDemoWindowWidgets(); +static void ShowDemoWindowLayout(); +static void ShowDemoWindowPopups(); +static void ShowDemoWindowTables(); +static void ShowDemoWindowColumns(); +static void ShowDemoWindowInputs(); + +//----------------------------------------------------------------------------- +// [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) +{ + ImGui::TextDisabled("(?)"); + if (ImGui::BeginItemTooltip()) { + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +// 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; +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() +//----------------------------------------------------------------------------- +// - ShowDemoWindow() +// - ShowDemoWindowWidgets() +// - ShowDemoWindowLayout() +// - ShowDemoWindowPopups() +// - ShowDemoWindowTables() +// - ShowDemoWindowColumns() +// - ShowDemoWindowInputs() +//----------------------------------------------------------------------------- + +// 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. +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!"); + + // Examples Apps (accessible from the "Examples" menu) + static bool show_app_main_menu_bar = false; + static bool show_app_console = false; + static bool show_app_custom_rendering = false; + static bool show_app_documents = false; + static bool show_app_log = false; + static bool show_app_layout = false; + static bool show_app_property_editor = false; + static bool show_app_simple_overlay = false; + static bool show_app_auto_resize = false; + static bool show_app_constrained_resize = false; + static bool show_app_fullscreen = false; + static bool show_app_long_text = false; + static bool show_app_window_titles = false; + + if (show_app_main_menu_bar) + ShowExampleAppMainMenuBar(); + if (show_app_documents) + ShowExampleAppDocuments(&show_app_documents); + if (show_app_console) + ShowExampleAppConsole(&show_app_console); + if (show_app_custom_rendering) + ShowExampleAppCustomRendering(&show_app_custom_rendering); + if (show_app_log) + ShowExampleAppLog(&show_app_log); + if (show_app_layout) + ShowExampleAppLayout(&show_app_layout); + if (show_app_property_editor) + ShowExampleAppPropertyEditor(&show_app_property_editor); + if (show_app_simple_overlay) + ShowExampleAppSimpleOverlay(&show_app_simple_overlay); + if (show_app_auto_resize) + ShowExampleAppAutoResize(&show_app_auto_resize); + if (show_app_constrained_resize) + ShowExampleAppConstrainedResize(&show_app_constrained_resize); + if (show_app_fullscreen) + ShowExampleAppFullscreen(&show_app_fullscreen); + if (show_app_long_text) + ShowExampleAppLongText(&show_app_long_text); + if (show_app_window_titles) + ShowExampleAppWindowTitles(&show_app_window_titles); + + // Dear ImGui Tools (accessible from the "Tools" menu) + static bool show_tool_metrics = false; + static bool show_tool_debug_log = false; + static bool show_tool_stack_tool = false; + static bool show_tool_style_editor = false; + static bool show_tool_about = false; + + if (show_tool_metrics) + ImGui::ShowMetricsWindow(&show_tool_metrics); + if (show_tool_debug_log) + ImGui::ShowDebugLogWindow(&show_tool_debug_log); + if (show_tool_stack_tool) + ImGui::ShowStackToolWindow(&show_tool_stack_tool); + if (show_tool_style_editor) { + ImGui::Begin("Dear ImGui Style Editor", &show_tool_style_editor); + ImGui::ShowStyleEditor(); + ImGui::End(); + } + if (show_tool_about) + ImGui::ShowAboutWindow(&show_tool_about); + + // Demonstrate the various window flags. Typically you would just use the default! + static bool no_titlebar = false; + static bool no_scrollbar = false; + static bool no_menu = false; + static bool no_move = false; + static bool no_resize = false; + static bool no_collapse = false; + static bool no_close = false; + static bool no_nav = false; + static bool no_background = false; + static bool no_bring_to_front = false; + static bool unsaved_document = false; + + ImGuiWindowFlags window_flags = 0; + if (no_titlebar) + window_flags |= ImGuiWindowFlags_NoTitleBar; + if (no_scrollbar) + window_flags |= ImGuiWindowFlags_NoScrollbar; + if (!no_menu) + window_flags |= ImGuiWindowFlags_MenuBar; + if (no_move) + window_flags |= ImGuiWindowFlags_NoMove; + if (no_resize) + window_flags |= ImGuiWindowFlags_NoResize; + if (no_collapse) + window_flags |= ImGuiWindowFlags_NoCollapse; + if (no_nav) + window_flags |= ImGuiWindowFlags_NoNav; + if (no_background) + window_flags |= ImGuiWindowFlags_NoBackground; + if (no_bring_to_front) + window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus; + 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)) { + // Early out if the window is collapsed, as an optimization. + ImGui::End(); + return; + } + + // Most "big" widgets share a common width settings by default. See 'Demo->Layout->Widgets Width' for details. + // e.g. Use 2/3 of the space for widgets and 1/3 for labels (right align) + // ImGui::PushItemWidth(-ImGui::GetWindowWidth() * 0.35f); + // e.g. Leave a fixed amount of width for labels (by passing a negative value), the rest goes to widgets. + ImGui::PushItemWidth(ImGui::GetFontSize() * -12); + + // Menu Bar + if (ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu("Menu")) { + IMGUI_DEMO_MARKER("Menu/File"); + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Examples")) { + IMGUI_DEMO_MARKER("Menu/Examples"); + ImGui::MenuItem("Main menu bar", NULL, &show_app_main_menu_bar); + + ImGui::SeparatorText("Mini apps"); + ImGui::MenuItem("Console", NULL, &show_app_console); + ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering); + ImGui::MenuItem("Documents", NULL, &show_app_documents); + ImGui::MenuItem("Log", NULL, &show_app_log); + ImGui::MenuItem("Property editor", NULL, &show_app_property_editor); + ImGui::MenuItem("Simple layout", NULL, &show_app_layout); + ImGui::MenuItem("Simple overlay", NULL, &show_app_simple_overlay); + + ImGui::SeparatorText("Concepts"); + ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize); + ImGui::MenuItem("Constrained-resizing window", NULL, &show_app_constrained_resize); + ImGui::MenuItem("Fullscreen window", NULL, &show_app_fullscreen); + ImGui::MenuItem("Long text display", NULL, &show_app_long_text); + ImGui::MenuItem("Manipulating window titles", NULL, &show_app_window_titles); + + ImGui::EndMenu(); + } + // if (ImGui::MenuItem("MenuItem")) {} // You can also use MenuItem() inside a menu bar! + if (ImGui::BeginMenu("Tools")) { + IMGUI_DEMO_MARKER("Menu/Tools"); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + const bool has_debug_tools = true; +#else + const bool has_debug_tools = false; +#endif + ImGui::MenuItem("Metrics/Debugger", NULL, &show_tool_metrics, has_debug_tools); + ImGui::MenuItem("Debug Log", NULL, &show_tool_debug_log, has_debug_tools); + ImGui::MenuItem("Stack Tool", NULL, &show_tool_stack_tool, has_debug_tools); + ImGui::MenuItem("Style Editor", NULL, &show_tool_style_editor); + ImGui::MenuItem("About Dear ImGui", NULL, &show_tool_about); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + ImGui::Text("dear imgui says hello! (%s) (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); + ImGui::Spacing(); + + IMGUI_DEMO_MARKER("Help"); + if (ImGui::CollapsingHeader("Help")) { + ImGui::SeparatorText("ABOUT THIS DEMO:"); + ImGui::BulletText("Sections below are demonstrating many aspects of the library."); + 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."); + ImGui::BulletText("See example applications in the examples/ folder."); + ImGui::BulletText("Read the FAQ at http://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"); + ImGui::CheckboxFlags("io.ConfigFlags: NavEnableKeyboard", &io.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard); + ImGui::SameLine(); + HelpMarker("Enable keyboard controls."); + ImGui::CheckboxFlags("io.ConfigFlags: NavEnableGamepad", &io.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad); + 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: NavEnableSetMousePos", &io.ConfigFlags, ImGuiConfigFlags_NavEnableSetMousePos); + ImGui::SameLine(); + HelpMarker("Instruct navigation to move the mouse cursor. See comment for ImGuiConfigFlags_NavEnableSetMousePos."); + ImGui::CheckboxFlags("io.ConfigFlags: NoMouse", &io.ConfigFlags, ImGuiConfigFlags_NoMouse); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) { + // The "NoMouse" option can get us stuck with a disabled mouse! Let's provide an alternative way to fix it: + if (fmodf((float)ImGui::GetTime(), 0.40f) < 0.20f) { + ImGui::SameLine(); + ImGui::Text("<>"); + } + if (ImGui::IsKeyPressed(ImGuiKey_Space)) + 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::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("Widgets"); + ImGui::Checkbox("io.ConfigInputTextCursorBlink", &io.ConfigInputTextCursorBlink); + ImGui::SameLine(); + HelpMarker("Enable blinking cursor (optional as some users consider it to be distracting)."); + ImGui::Checkbox("io.ConfigInputTextEnterKeepActive", &io.ConfigInputTextEnterKeepActive); + ImGui::SameLine(); + HelpMarker("Pressing Enter will keep item active and select contents (single-line only)."); + ImGui::Checkbox("io.ConfigDragClickToInputText", &io.ConfigDragClickToInputText); + ImGui::SameLine(); + HelpMarker("Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving)."); + ImGui::Checkbox("io.ConfigWindowsResizeFromEdges", &io.ConfigWindowsResizeFromEdges); + ImGui::SameLine(); + HelpMarker( + "Enable resizing of windows from their edges and from the lower-left corner.\nThis requires (io.BackendFlags & " + "ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback."); + ImGui::Checkbox("io.ConfigWindowsMoveFromTitleBarOnly", &io.ConfigWindowsMoveFromTitleBarOnly); + ImGui::Checkbox("io.ConfigMacOSXBehaviors", &io.ConfigMacOSXBehaviors); + ImGui::Text("Also see Style->Rendering for rendering options."); + + ImGui::SeparatorText("Debug"); + ImGui::BeginDisabled(); + ImGui::Checkbox("io.ConfigDebugBeginReturnValueOnce", &io.ConfigDebugBeginReturnValueOnce); // . + ImGui::EndDisabled(); + ImGui::SameLine(); + HelpMarker( + "First calls to Begin()/BeginChild() will return false.\n\nTHIS OPTION IS DISABLED because it needs to be set at application boot-time to make " + "sense. Showing the disabled option is a way to make this feature easier to discover"); + ImGui::Checkbox("io.ConfigDebugBeginReturnValueLoop", &io.ConfigDebugBeginReturnValueLoop); + ImGui::SameLine(); + HelpMarker( + "Some calls to Begin()/BeginChild() will return false.\n\nWill cycle through window depths then repeat. Windows should be flickering while " + "running."); + ImGui::Checkbox("io.ConfigDebugIgnoreFocusLoss", &io.ConfigDebugIgnoreFocusLoss); + 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."); + + // FIXME: Maybe we need a BeginReadonly() equivalent to keep label bright? + ImGui::BeginDisabled(); + ImGui::CheckboxFlags("io.BackendFlags: HasGamepad", &io.BackendFlags, ImGuiBackendFlags_HasGamepad); + ImGui::CheckboxFlags("io.BackendFlags: HasMouseCursors", &io.BackendFlags, ImGuiBackendFlags_HasMouseCursors); + ImGui::CheckboxFlags("io.BackendFlags: HasSetMousePos", &io.BackendFlags, ImGuiBackendFlags_HasSetMousePos); + ImGui::CheckboxFlags("io.BackendFlags: RendererHasVtxOffset", &io.BackendFlags, ImGuiBackendFlags_RendererHasVtxOffset); + ImGui::EndDisabled(); + ImGui::TreePop(); + ImGui::Spacing(); + } + + IMGUI_DEMO_MARKER("Configuration/Style"); + if (ImGui::TreeNode("Style")) { + HelpMarker("The same contents can be accessed in 'Tools->Style Editor' or by calling the ShowStyleEditor() function."); + ImGui::ShowStyleEditor(); + ImGui::TreePop(); + ImGui::Spacing(); + } + + IMGUI_DEMO_MARKER("Configuration/Capture, Logging"); + if (ImGui::TreeNode("Capture/Logging")) { + HelpMarker( + "The logging API redirects all text output so you can easily capture the content of " + "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")) { + ImGui::LogToClipboard(); + ImGui::LogText("Hello, world!"); + ImGui::LogFinish(); + } + ImGui::TreePop(); + } + } + + IMGUI_DEMO_MARKER("Window options"); + if (ImGui::CollapsingHeader("Window options")) { + if (ImGui::BeginTable("split", 3)) { + ImGui::TableNextColumn(); + ImGui::Checkbox("No titlebar", &no_titlebar); + ImGui::TableNextColumn(); + ImGui::Checkbox("No scrollbar", &no_scrollbar); + ImGui::TableNextColumn(); + ImGui::Checkbox("No menu", &no_menu); + ImGui::TableNextColumn(); + ImGui::Checkbox("No move", &no_move); + ImGui::TableNextColumn(); + ImGui::Checkbox("No resize", &no_resize); + ImGui::TableNextColumn(); + ImGui::Checkbox("No collapse", &no_collapse); + ImGui::TableNextColumn(); + ImGui::Checkbox("No close", &no_close); + ImGui::TableNextColumn(); + ImGui::Checkbox("No nav", &no_nav); + ImGui::TableNextColumn(); + ImGui::Checkbox("No background", &no_background); + ImGui::TableNextColumn(); + ImGui::Checkbox("No bring to front", &no_bring_to_front); + ImGui::TableNextColumn(); + ImGui::Checkbox("Unsaved document", &unsaved_document); + ImGui::EndTable(); + } + } + + // All demo contents + ShowDemoWindowWidgets(); + ShowDemoWindowLayout(); + ShowDemoWindowPopups(); + ShowDemoWindowTables(); + ShowDemoWindowInputs(); + + // End of ShowDemoWindow() + ImGui::PopItemWidth(); + ImGui::End(); +} + +static void ShowDemoWindowWidgets() +{ + IMGUI_DEMO_MARKER("Widgets"); + if (!ImGui::CollapsingHeader("Widgets")) + return; + + static bool disable_all = false; // The Checkbox for that is inside the "Disabled" section at the bottom + if (disable_all) + ImGui::BeginDisabled(); + + 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")) + clicked++; + if (clicked & 1) { + 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); + + // 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++) { + if (i > 0) + ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i / 7.0f, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i / 7.0f, 0.7f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i / 7.0f, 0.8f, 0.8f)); + ImGui::Button("Click"); + 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; + float spacing = ImGui::GetStyle().ItemInnerSpacing.x; + ImGui::PushButtonRepeat(true); + if (ImGui::ArrowButton("##left", ImGuiDir_Left)) { + counter--; + } + ImGui::SameLine(0.0f, spacing); + if (ImGui::ArrowButton("##right", ImGuiDir_Right)) { + counter++; + } + ImGui::PopButtonRepeat(); + 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. + IMGUI_DEMO_MARKER("Widgets/Basic/InputText"); + static char str0[128] = "Hello, world!"; + ImGui::InputText("input text", str0, IM_ARRAYSIZE(str0)); + ImGui::SameLine(); + HelpMarker( + "USER:\n" + "Hold SHIFT or use mouse to select text.\n" + "CTRL+Left/Right to word jump.\n" + "CTRL+A or Double-Click to select all.\n" + "CTRL+X,CTRL+C,CTRL+V clipboard.\n" + "CTRL+Z,CTRL+Y undo/redo.\n" + "ESCAPE to revert.\n\n" + "PROGRAMMER:\n" + "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; + ImGui::DragInt("drag int", &i1, 1); + ImGui::SameLine(); + HelpMarker( + "Click and drag to edit value.\n" + "Hold SHIFT/ALT for faster/slower edit.\n" + "Double-click or CTRL+click to input value."); + + ImGui::DragInt("drag int 0..100", &i2, 1, 0, 100, "%d%%", ImGuiSliderFlags_AlwaysClamp); + + 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::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(). + IMGUI_DEMO_MARKER("Widgets/Basic/Slider (enum)"); + enum Element { Element_Fire, Element_Earth, Element_Air, Element_Water, Element_COUNT }; + static int elem = Element_Fire; + const char *elems_names[Element_COUNT] = {"Fire", "Earth", "Air", "Water"}; + const char *elem_name = (elem >= 0 && elem < Element_COUNT) ? elems_names[elem] : "Unknown"; + 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}; + static float col2[4] = {0.4f, 0.7f, 0.0f, 0.5f}; + ImGui::ColorEdit3("color 1", col1); + ImGui::SameLine(); + HelpMarker( + "Click on the color square to open a color picker.\n" + "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. + IMGUI_DEMO_MARKER("Widgets/Basic/Combo"); + const char *items[] = {"AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIIIIII", "JJJJ", "KKKKKKK"}; + static int item_current = 0; + ImGui::Combo("combo", &item_current, items, IM_ARRAYSIZE(items)); + ImGui::SameLine(); + HelpMarker( + "Using the simplified one-liner Combo API here.\nRefer 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. + IMGUI_DEMO_MARKER("Widgets/Basic/ListBox"); + const char *items[] = {"Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon"}; + static int item_current = 1; + ImGui::ListBox("listbox", &item_current, items, IM_ARRAYSIZE(items), 4); + ImGui::SameLine(); + HelpMarker( + "Using the simplified one-liner ListBox API here.\nRefer to the \"List boxes\" section below for an explanation of how to use the more " + "flexible and general BeginListBox/EndListBox API."); + } + + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Tooltips"); + if (ImGui::TreeNode("Tooltips")) { + // 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()) { + ImGui::Text("I am a fancy tooltip"); + static float arr[] = {0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f}; + ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr)); + 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; + ImGui::RadioButton("Off", &always_on, 0); + ImGui::SameLine(); + ImGui::RadioButton("Always On (Simple)", &always_on, 1); + ImGui::SameLine(); + ImGui::RadioButton("Always On (Advanced)", &always_on, 2); + if (always_on == 1) + ImGui::SetTooltip("I am following you around."); + else if (always_on == 2 && ImGui::BeginTooltip()) { + ImGui::ProgressBar(sinf((float)ImGui::GetTime()) * 0.5f + 0.5f, ImVec2(ImGui::GetFontSize() * 25, 0.0f)); + ImGui::EndTooltip(); + } + + ImGui::SeparatorText("Custom"); + + // The following examples are passed for documentation purpose but may not be useful to most users. + // Passing ImGuiHoveredFlags_Tooltip to IsItemHovered() will pull ImGuiHoveredFlags flags values from + // 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav' depending on whether mouse or gamepad/keyboard is being used. + // With default settings, ImGuiHoveredFlags_Tooltip is equivalent to ImGuiHoveredFlags_DelayShort + ImGuiHoveredFlags_Stationary. + 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."); + + ImGui::TreePop(); + } + + // Testing ImGuiOnceUponAFrame helper. + // static ImGuiOnceUponAFrame once; + // for (int i = 0; i < 5; i++) + // if (once) + // ImGui::Text("This will be displayed only once."); + + IMGUI_DEMO_MARKER("Widgets/Trees"); + if (ImGui::TreeNode("Trees")) { + IMGUI_DEMO_MARKER("Widgets/Trees/Basic trees"); + if (ImGui::TreeNode("Basic trees")) { + for (int i = 0; i < 5; i++) { + // Use SetNextItemOpen() so set the default state of a node to be open. We could + // also use TreeNodeEx() with the ImGuiTreeNodeFlags_DefaultOpen flag to achieve the same thing! + if (i == 0) + ImGui::SetNextItemOpen(true, ImGuiCond_Once); + + if (ImGui::TreeNode((void *)(intptr_t)i, "Child %d", i)) { + ImGui::Text("blah blah"); + ImGui::SameLine(); + if (ImGui::SmallButton("button")) { } + ImGui::TreePop(); + } + } + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Trees/Advanced, with Selectable nodes"); + if (ImGui::TreeNode("Advanced, with Selectable nodes")) { + HelpMarker( + "This is a more typical looking tree with selectable nodes.\n" + "Click to select, CTRL+Click to toggle, click on arrows or double-click to open."); + static ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth; + static bool align_label_with_current_x_position = false; + static bool test_drag_and_drop = false; + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnArrow", &base_flags, ImGuiTreeNodeFlags_OpenOnArrow); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnDoubleClick", &base_flags, ImGuiTreeNodeFlags_OpenOnDoubleClick); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAvailWidth", &base_flags, ImGuiTreeNodeFlags_SpanAvailWidth); + ImGui::SameLine(); + HelpMarker("Extend hit area to all available width instead of allowing more items to be laid out after the node."); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanFullWidth", &base_flags, ImGuiTreeNodeFlags_SpanFullWidth); + 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 + /// of the loop. May be a pointer to your own node type, etc. + static int selection_mask = (1 << 2); + int node_clicked = -1; + for (int i = 0; i < 6; i++) { + // Disable the default "open on single-click behavior" + set Selected flag according to our selection. + // To alter selection we use IsItemClicked() && !IsItemToggledOpen(), so clicking on an arrow doesn't alter selection. + ImGuiTreeNodeFlags node_flags = base_flags; + const bool is_selected = (selection_mask & (1 << i)) != 0; + if (is_selected) + node_flags |= ImGuiTreeNodeFlags_Selected; + if (i < 3) { + // Items 0..2 are Tree Node + bool node_open = ImGui::TreeNodeEx((void *)(intptr_t)i, node_flags, "Selectable Node %d", i); + if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) + node_clicked = i; + if (test_drag_and_drop && ImGui::BeginDragDropSource()) { + ImGui::SetDragDropPayload("_TREENODE", NULL, 0); + ImGui::Text("This is a drag and drop source"); + ImGui::EndDragDropSource(); + } + if (node_open) { + ImGui::BulletText("Blah blah\nBlah Blah"); + ImGui::TreePop(); + } + } else { + // Items 3..5 are Tree Leaves + // The only reason we use TreeNode at all is to allow selection of the leaf. Otherwise we can + // use BulletText() or advance the cursor by GetTreeNodeToLabelSpacing() and call Text(). + node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet + ImGui::TreeNodeEx((void *)(intptr_t)i, node_flags, "Selectable Leaf %d", i); + if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) + node_clicked = i; + if (test_drag_and_drop && ImGui::BeginDragDropSource()) { + ImGui::SetDragDropPayload("_TREENODE", NULL, 0); + ImGui::Text("This is a drag and drop source"); + ImGui::EndDragDropSource(); + } + } + } + if (node_clicked != -1) { + // Update selection state + // (process outside of tree loop to avoid visual inconsistencies during the clicking frame) + if (ImGui::GetIO().KeyCtrl) + selection_mask ^= (1 << node_clicked); // CTRL+click to toggle + else // if (!(selection_mask & (1 << node_clicked))) // Depending on selection behavior you want, may want to preserve selection when clicking + // on item that is part of the selection + selection_mask = (1 << node_clicked); // Click to single-select + } + if (align_label_with_current_x_position) + ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Collapsing Headers"); + if (ImGui::TreeNode("Collapsing Headers")) { + static bool closable_group = true; + ImGui::Checkbox("Show 2nd header", &closable_group); + if (ImGui::CollapsingHeader("Header", ImGuiTreeNodeFlags_None)) { + ImGui::Text("IsItemHovered: %d", ImGui::IsItemHovered()); + for (int i = 0; i < 5; i++) + ImGui::Text("Some content %d", i); + } + if (ImGui::CollapsingHeader("Header with a close button", &closable_group)) { + ImGui::Text("IsItemHovered: %d", ImGui::IsItemHovered()); + for (int i = 0; i < 5; i++) + ImGui::Text("More content %d", i); + } + /* + if (ImGui::CollapsingHeader("Header with a bullet", ImGuiTreeNodeFlags_Bullet)) + ImGui::Text("IsItemHovered: %d", ImGui::IsItemHovered()); + */ + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Bullets"); + if (ImGui::TreeNode("Bullets")) { + ImGui::BulletText("Bullet point 1"); + ImGui::BulletText("Bullet point 2\nOn multiple lines"); + if (ImGui::TreeNode("Tree node")) { + ImGui::BulletText("Another bullet point"); + ImGui::TreePop(); + } + ImGui::Bullet(); + ImGui::Text("Bullet point 3 (two calls)"); + ImGui::Bullet(); + ImGui::SmallButton("Button"); + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Text"); + if (ImGui::TreeNode("Text")) { + IMGUI_DEMO_MARKER("Widgets/Text/Colored Text"); + if (ImGui::TreeNode("Colorful Text")) { + // Using shortcut. You can use PushStyleColor()/PopStyleColor() for more flexibility. + ImGui::TextColored(ImVec4(1.0f, 0.0f, 1.0f, 1.0f), "Pink"); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Yellow"); + ImGui::TextDisabled("Disabled"); + ImGui::SameLine(); + HelpMarker("The TextDisabled color is stored in ImGuiStyle."); + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Text/Word Wrapping"); + if (ImGui::TreeNode("Word Wrapping")) { + // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility. + ImGui::TextWrapped( + "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++) { + ImGui::Text("Test paragraph %d:", n); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImVec2 marker_min = ImVec2(pos.x + wrap_width, pos.y); + ImVec2 marker_max = ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); + if (n == 0) + 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")) { + // UTF-8 test with Japanese characters + // (Needs a suitable font? Try "Google Noto" or "Arial Unicode". See docs/FONTS.md for details.) + // - From C++11 you can use the u8"my text" syntax to encode literal strings as UTF-8 + // - For earlier compiler, you may be able to encode your sources as UTF-8 (e.g. in Visual Studio, you + // can save your source files as 'UTF-8 without signature'). + // - FOR THIS DEMO FILE ONLY, BECAUSE WE WANT TO SUPPORT OLD COMPILERS, WE ARE *NOT* INCLUDING RAW UTF-8 + // CHARACTERS IN THIS SOURCE FILE. Instead we are encoding a few strings with hexadecimal constants. + // Don't do this in your application! Please use u8"text in any language" in your application! + // Note that characters values are preserved even by InputText() if the font cannot be displayed, + // so you can safely copy & paste garbled characters into another application. + ImGui::TextWrapped( + "CJK text will only appear if the font was loaded with the appropriate CJK character ranges. " + "Call io.Fonts->AddFontFromFileTTF() manually to load extra character ranges. " + "Read docs/FONTS.md for details."); + ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)"); // Normally we would use u8"blah blah" with the + // proper characters directly in the string. + ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)"); + static char buf[32] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; + // static char buf[32] = u8"NIHONGO"; // <- this is how you would write it with C++11, using real kanjis + ImGui::InputText("UTF-8 input", buf, IM_ARRAYSIZE(buf)); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Images"); + if (ImGui::TreeNode("Images")) { + ImGuiIO &io = ImGui::GetIO(); + ImGui::TextWrapped( + "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. + // 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 + // - The imgui_impl_opengl3.cpp renderer expect a GLuint OpenGL texture identifier, etc. + // More: + // - 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, + // it will help you debug issues if you are confused about it. + // - 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; + { + static bool use_text_color_for_tint = false; + ImGui::Checkbox("Use Text Color for Tint", &use_text_color_for_tint); + 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 + ImVec4 tint_col = use_text_color_for_tint ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint + ImVec4 border_col = ImGui::GetStyleColorVec4(ImGuiCol_Border); + ImGui::Image(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, tint_col, border_col); + if (ImGui::BeginItemTooltip()) { + float region_sz = 32.0f; + float region_x = io.MousePos.x - pos.x - region_sz * 0.5f; + float region_y = io.MousePos.y - pos.y - region_sz * 0.5f; + float zoom = 4.0f; + if (region_x < 0.0f) { + region_x = 0.0f; + } else if (region_x > my_tex_w - region_sz) { + region_x = my_tex_w - region_sz; + } + if (region_y < 0.0f) { + region_y = 0.0f; + } else if (region_y > my_tex_h - region_sz) { + region_y = my_tex_h - region_sz; + } + ImGui::Text("Min: (%.2f, %.2f)", region_x, region_y); + ImGui::Text("Max: (%.2f, %.2f)", region_x + region_sz, region_y + region_sz); + ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h); + ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h); + ImGui::Image(my_tex_id, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, tint_col, border_col); + ImGui::EndTooltip(); + } + } + + IMGUI_DEMO_MARKER("Widgets/Images/Textured buttons"); + ImGui::TextWrapped("And now some textured buttons.."); + static int pressed_count = 0; + for (int i = 0; i < 8; i++) { + // UV coordinates are often (0.0f, 0.0f) and (1.0f, 1.0f) to display an entire textures. + // Here are trying to display only a 32x32 pixels area of the texture, hence the UV computation. + // Read about UV coordinates here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples + ImGui::PushID(i); + if (i > 0) + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(i - 1.0f, i - 1.0f)); + ImVec2 size = ImVec2(32.0f, 32.0f); // Size of the image we want to make visible + ImVec2 uv0 = ImVec2(0.0f, 0.0f); // UV coordinates for lower-left + ImVec2 uv1 = ImVec2(32.0f / my_tex_w, 32.0f / my_tex_h); // UV coordinates for (32,32) in our texture + ImVec4 bg_col = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); // Black background + ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint + if (ImGui::ImageButton("", my_tex_id, size, uv0, uv1, bg_col, tint_col)) + pressed_count += 1; + if (i > 0) + ImGui::PopStyleVar(); + ImGui::PopID(); + ImGui::SameLine(); + } + ImGui::NewLine(); + ImGui::Text("Pressed %d times.", pressed_count); + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Combo"); + if (ImGui::TreeNode("Combo")) { + // Combo Boxes are also called "Dropdown" in other systems + // Expose flags as checkbox for the demo + static ImGuiComboFlags flags = 0; + ImGui::CheckboxFlags("ImGuiComboFlags_PopupAlignLeft", &flags, ImGuiComboFlags_PopupAlignLeft); + ImGui::SameLine(); + HelpMarker("Only makes a difference if the popup is larger than the combo"); + if (ImGui::CheckboxFlags("ImGuiComboFlags_NoArrowButton", &flags, ImGuiComboFlags_NoArrowButton)) + flags &= ~ImGuiComboFlags_NoPreview; // Clear the other flag, as we cannot combine both + if (ImGui::CheckboxFlags("ImGuiComboFlags_NoPreview", &flags, ImGuiComboFlags_NoPreview)) + flags &= ~ImGuiComboFlags_NoArrowButton; // Clear the other flag, as we cannot combine both + + // 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_current_idx = 0; // Here we store our selection data as an index. + const char *combo_preview_value = items[item_current_idx]; // Pass in the preview value visible before opening the combo (it could be anything) + if (ImGui::BeginCombo("combo 1", combo_preview_value, flags)) { + for (int n = 0; n < IM_ARRAYSIZE(items); n++) { + const bool is_selected = (item_current_idx == n); + if (ImGui::Selectable(items[n], is_selected)) + item_current_idx = n; + + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + // 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 2 (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 3 (array)", &item_current_3, items, IM_ARRAYSIZE(items)); + + // Simplified one-liner Combo() using an accessor function + struct Funcs { + static bool ItemGetter(void *data, int n, const char **out_str) + { + *out_str = ((const char **)data)[n]; + return true; + } + }; + static int item_current_4 = 0; + ImGui::Combo("combo 4 (function)", &item_current_4, &Funcs::ItemGetter, items, IM_ARRAYSIZE(items)); + + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/List Boxes"); + if (ImGui::TreeNode("List boxes")) { + // 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_current_idx = 0; // Here we store our selection data as an index. + if (ImGui::BeginListBox("listbox 1")) { + for (int n = 0; n < IM_ARRAYSIZE(items); n++) { + const bool is_selected = (item_current_idx == n); + if (ImGui::Selectable(items[n], is_selected)) + item_current_idx = n; + + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndListBox(); + } + + // Custom size: use all width, 5 items tall + ImGui::Text("Full-width:"); + if (ImGui::BeginListBox("##listbox 2", ImVec2(-FLT_MIN, 5 * ImGui::GetTextLineHeightWithSpacing()))) { + for (int n = 0; n < IM_ARRAYSIZE(items); n++) { + const bool is_selected = (item_current_idx == n); + if (ImGui::Selectable(items[n], is_selected)) + item_current_idx = n; + + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndListBox(); + } + + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Selectables"); + if (ImGui::TreeNode("Selectables")) { + // Selectable() has 2 overloads: + // - The one taking "bool selected" as a read-only selection information. + // When Selectable() has been clicked it returns true and you can alter selection state accordingly. + // - The one taking "bool* p_selected" as a read-write selection information (convenient in some cases) + // The earlier is more flexible, as in real application your selection may be stored in many different ways + // and not necessarily inside a bool value (e.g. in flags within objects, as an external list, etc). + IMGUI_DEMO_MARKER("Widgets/Selectables/Basic"); + if (ImGui::TreeNode("Basic")) { + static bool selection[5] = {false, true, false, false}; + ImGui::Selectable("1. I am selectable", &selection[0]); + ImGui::Selectable("2. I am selectable", &selection[1]); + ImGui::Selectable("3. I am selectable", &selection[2]); + if (ImGui::Selectable("4. I am double clickable", selection[3], ImGuiSelectableFlags_AllowDoubleClick)) + if (ImGui::IsMouseDoubleClicked(0)) + selection[3] = !selection[3]; + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Selectables/Single Selection"); + if (ImGui::TreeNode("Selection State: Single Selection")) { + static int selected = -1; + for (int n = 0; n < 5; n++) { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selected == n)) + selected = n; + } + ImGui::TreePop(); + } + IMGUI_DEMO_MARKER("Widgets/Selectables/Multiple Selection"); + if (ImGui::TreeNode("Selection State: Multiple Selection")) { + HelpMarker("Hold CTRL and click to select multiple items."); + static bool selection[5] = {false, false, false, false, false}; + for (int n = 0; n < 5; n++) { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selection[n])) { + if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held + memset(selection, 0, sizeof(selection)); + selection[n] ^= 1; + } + } + ImGui::TreePop(); + } + IMGUI_DEMO_MARKER("Widgets/Selectables/Rendering more items on the same line"); + if (ImGui::TreeNode("Rendering more items on the same line")) { + // (1) Using SetNextItemAllowOverlap() + // (2) Using the Selectable() override that takes "bool* p_selected" parameter, the bool value is toggled automatically. + static bool selected[3] = {false, false, false}; + ImGui::SetNextItemAllowOverlap(); + ImGui::Selectable("main.c", &selected[0]); + ImGui::SameLine(); + ImGui::SmallButton("Link 1"); + ImGui::SetNextItemAllowOverlap(); + ImGui::Selectable("Hello.cpp", &selected[1]); + ImGui::SameLine(); + ImGui::SmallButton("Link 2"); + ImGui::SetNextItemAllowOverlap(); + ImGui::Selectable("Hello.h", &selected[2]); + ImGui::SameLine(); + ImGui::SmallButton("Link 3"); + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Selectables/In columns"); + if (ImGui::TreeNode("In columns")) { + static bool selected[10] = {}; + + if (ImGui::BeginTable("split1", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders)) { + for (int i = 0; i < 10; i++) { + char label[32]; + sprintf(label, "Item %d", i); + ImGui::TableNextColumn(); + ImGui::Selectable(label, &selected[i]); // FIXME-TABLE: Selection overlap + } + ImGui::EndTable(); + } + ImGui::Spacing(); + if (ImGui::BeginTable("split2", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders)) { + for (int i = 0; i < 10; i++) { + char label[32]; + sprintf(label, "Item %d", i); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Selectable(label, &selected[i], ImGuiSelectableFlags_SpanAllColumns); + ImGui::TableNextColumn(); + ImGui::Text("Some other contents"); + ImGui::TableNextColumn(); + ImGui::Text("123456"); + } + ImGui::EndTable(); + } + 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++) { + if (x > 0) + ImGui::SameLine(); + ImGui::PushID(y * 4 + x); + if (ImGui::Selectable("Sailor", selected[y][x] != 0, 0, ImVec2(50, 50))) { + // Toggle clicked cell + toggle neighbors + selected[y][x] ^= 1; + if (x > 0) { + selected[y][x - 1] ^= 1; + } + if (x < 3) { + selected[y][x + 1] ^= 1; + } + if (y > 0) { + selected[y - 1][x] ^= 1; + } + if (y < 3) { + selected[y + 1][x] ^= 1; + } + } + ImGui::PopID(); + } + + if (winning_state) + ImGui::PopStyleVar(); + ImGui::TreePop(); + } + IMGUI_DEMO_MARKER("Widgets/Selectables/Alignment"); + if (ImGui::TreeNode("Alignment")) { + HelpMarker( + "By default, Selectables uses style.SelectableTextAlign but it can be overridden on a per-item " + "basis using PushStyleVar(). You'll probably want to always keep your default situation to " + "left-align otherwise it becomes difficult to layout multiple items on a same line"); + static bool selected[3 * 3] = {true, false, true, false, true, false, true, false, true}; + for (int y = 0; y < 3; y++) { + for (int x = 0; x < 3; x++) { + ImVec2 alignment = ImVec2((float)x / 2.0f, (float)y / 2.0f); + char name[32]; + sprintf(name, "(%.1f,%.1f)", alignment.x, alignment.y); + if (x > 0) + ImGui::SameLine(); + ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, alignment); + ImGui::Selectable(name, &selected[3 * y + x], ImGuiSelectableFlags_None, ImVec2(80, 80)); + ImGui::PopStyleVar(); + } + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + // 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. + IMGUI_DEMO_MARKER("Widgets/Text Input"); + if (ImGui::TreeNode("Text Input")) { + IMGUI_DEMO_MARKER("Widgets/Text Input/Multi-line Text Input"); + if (ImGui::TreeNode("Multi-line Text Input")) { + // Note: we are using a fixed-sized buffer for simplicity here. See ImGuiInputTextFlags_CallbackResize + // and the code in misc/cpp/imgui_stdlib.h for how to setup InputText() for dynamically resizing strings. + static char text[1024 * 16] = + "/*\n" + " The Pentium F00F bug, shorthand for F0 0F C7 C8,\n" + " the hexadecimal encoding of one offending instruction,\n" + " more formally, the invalid operand with locked CMPXCHG8B\n" + " instruction bug, is a design flaw in the majority of\n" + " Intel Pentium, Pentium MMX, and Pentium OverDrive\n" + " processors (all in the P5 microarchitecture).\n" + "*/\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); + ImGui::CheckboxFlags("ImGuiInputTextFlags_AllowTabInput", &flags, ImGuiInputTextFlags_AllowTabInput); + ImGui::CheckboxFlags("ImGuiInputTextFlags_CtrlEnterForNewLine", &flags, ImGuiInputTextFlags_CtrlEnterForNewLine); + 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")) { + struct TextFilters { + // Modify character input by altering 'data->Eventchar' (ImGuiInputTextFlags_CallbackCharFilter callback) + static int FilterCasingSwap(ImGuiInputTextCallbackData *data) + { + if (data->EventChar >= 'a' && data->EventChar <= 'z') { + data->EventChar -= 'a' - 'A'; + } // Lowercase becomes uppercase + 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) + { + if (data->EventChar < 256 && strchr("imgui", (char)data->EventChar)) + return 0; + 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. + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Text Input/Password input"); + if (ImGui::TreeNode("Password Input")) { + static char password[64] = "password123"; + ImGui::InputText("password", password, IM_ARRAYSIZE(password), ImGuiInputTextFlags_Password); + ImGui::SameLine(); + HelpMarker("Display all characters as '*'.\nDisable clipboard cut and copy.\nDisable logging.\n"); + ImGui::InputTextWithHint("password (w/ hint)", "", password, IM_ARRAYSIZE(password), ImGuiInputTextFlags_Password); + 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")) { + struct Funcs { + static int MyCallback(ImGuiInputTextCallbackData *data) + { + if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion) { + data->InsertChars(data->CursorPos, ".."); + } else if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory) { + if (data->EventKey == ImGuiKey_UpArrow) { + data->DeleteChars(0, data->BufTextLen); + data->InsertChars(0, "Pressed Up!"); + data->SelectAll(); + } else if (data->EventKey == ImGuiKey_DownArrow) { + data->DeleteChars(0, data->BufTextLen); + data->InsertChars(0, "Pressed Down!"); + data->SelectAll(); + } + } else if (data->EventFlag == ImGuiInputTextFlags_CallbackEdit) { + // Toggle casing of first character + 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; + } + return 0; + } + }; + static char buf1[64]; + ImGui::InputText("Completion", buf1, 64, 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::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::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")) { + // To wire InputText() with std::string or any other custom string type, + // you can use the ImGuiInputTextFlags_CallbackResize flag + create a custom ImGui::InputText() wrapper + // using your preferred type. See misc/cpp/imgui_stdlib.h for an implementation of this using std::string. + HelpMarker( + "Using ImGuiInputTextFlags_CallbackResize to wire your custom string type to InputText().\n\n" + "See misc/cpp/imgui_stdlib.h for an implementation of this for std::string."); + struct Funcs { + static int MyResizeCallback(ImGuiInputTextCallbackData *data) + { + if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) { + ImVector *my_str = (ImVector *)data->UserData; + IM_ASSERT(my_str->begin() == data->Buf); + my_str->resize(data->BufSize); // NB: On resizing calls, generally data->BufSize == data->BufTextLen + 1 + data->Buf = my_str->begin(); + } + 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) + { + IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); + 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. + static ImVector my_str; + if (my_str.empty()) + my_str.push_back(0); + Funcs::MyInputTextMultiline("##MyStr", &my_str, ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 16)); + 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/Miscellaneous"); + if (ImGui::TreeNode("Miscellaneous")) { + static char buf1[16]; + static ImGuiInputTextFlags flags = ImGuiInputTextFlags_EscapeClearsAll; + ImGui::CheckboxFlags("ImGuiInputTextFlags_EscapeClearsAll", &flags, ImGuiInputTextFlags_EscapeClearsAll); + ImGui::CheckboxFlags("ImGuiInputTextFlags_ReadOnly", &flags, ImGuiInputTextFlags_ReadOnly); + ImGui::CheckboxFlags("ImGuiInputTextFlags_NoUndoRedo", &flags, ImGuiInputTextFlags_NoUndoRedo); + ImGui::InputText("Hello", buf1, IM_ARRAYSIZE(buf1), flags); + ImGui::TreePop(); + } + + ImGui::TreePop(); + } + + // Tabs + IMGUI_DEMO_MARKER("Widgets/Tabs"); + if (ImGui::TreeNode("Tabs")) { + IMGUI_DEMO_MARKER("Widgets/Tabs/Basic"); + if (ImGui::TreeNode("Basic")) { + ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_None; + if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) { + if (ImGui::BeginTabItem("Avocado")) { + ImGui::Text("This is the Avocado tab!\nblah blah blah blah blah"); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Broccoli")) { + ImGui::Text("This is the Broccoli tab!\nblah blah blah blah blah"); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Cucumber")) { + ImGui::Text("This is the Cucumber tab!\nblah blah blah blah blah"); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + ImGui::Separator(); + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Tabs/Advanced & Close Button"); + if (ImGui::TreeNode("Advanced & Close Button")) { + // Expose a couple of the available flags. In most cases you may just call BeginTabBar() with no flags (0). + static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable; + ImGui::CheckboxFlags("ImGuiTabBarFlags_Reorderable", &tab_bar_flags, ImGuiTabBarFlags_Reorderable); + ImGui::CheckboxFlags("ImGuiTabBarFlags_AutoSelectNewTabs", &tab_bar_flags, ImGuiTabBarFlags_AutoSelectNewTabs); + ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", &tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton); + ImGui::CheckboxFlags("ImGuiTabBarFlags_NoCloseWithMiddleMouseButton", &tab_bar_flags, ImGuiTabBarFlags_NoCloseWithMiddleMouseButton); + 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); + + // Tab Bar + const char *names[4] = {"Artichoke", "Beetroot", "Celery", "Daikon"}; + static bool opened[4] = {true, true, true, true}; // Persistent user state + for (int n = 0; n < IM_ARRAYSIZE(opened); n++) { + if (n > 0) { + 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)) { + for (int n = 0; n < IM_ARRAYSIZE(opened); n++) + if (opened[n] && ImGui::BeginTabItem(names[n], &opened[n], ImGuiTabItemFlags_None)) { + ImGui::Text("This is the %s tab!", names[n]); + if (n & 1) + ImGui::Text("I am an odd tab."); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + ImGui::Separator(); + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Tabs/TabItemButton & Leading-Trailing flags"); + if (ImGui::TreeNode("TabItemButton & Leading/Trailing flags")) { + static ImVector active_tabs; + static int next_tab_id = 0; + 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) + static bool show_leading_button = true; + 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); + + if (ImGui::BeginTabBar("MyTabBar", tab_bar_flags)) { + // Demo a Leading TabItemButton(): click the "?" button to open a menu + if (show_leading_button) + if (ImGui::TabItemButton("?", ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_NoTooltip)) + ImGui::OpenPopup("MyHelpMenu"); + if (ImGui::BeginPopup("MyHelpMenu")) { + 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 "+") + // Note that we submit it before the regular tabs, but because of 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;) { + bool open = true; + char name[16]; + snprintf(name, IM_ARRAYSIZE(name), "%04d", active_tabs[n]); + if (ImGui::BeginTabItem(name, &open, ImGuiTabItemFlags_None)) { + 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(); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + + // Plot/Graph widgets are not very good. + // Consider using a third-party library such as ImPlot: https://github.com/epezent/implot + // (see others https://github.com/ocornut/imgui/wiki/Useful-Extensions) + IMGUI_DEMO_MARKER("Widgets/Plotting"); + if (ImGui::TreeNode("Plotting")) { + static bool animate = true; + ImGui::Checkbox("Animate", &animate); + + // Plot as lines and plot as histogram + IMGUI_DEMO_MARKER("Widgets/Plotting/PlotLines, PlotHistogram"); + 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)); + + // 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. + static float values[90] = {}; + static int values_offset = 0; + static double refresh_time = 0.0; + if (!animate || refresh_time == 0.0) + refresh_time = ImGui::GetTime(); + while (refresh_time < ImGui::GetTime()) // Create data at fixed 60 Hz rate for the demo + { + static float phase = 0.0f; + values[values_offset] = cosf(phase); + values_offset = (values_offset + 1) % IM_ARRAYSIZE(values); + 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) + { + float average = 0.0f; + for (int n = 0; n < IM_ARRAYSIZE(values); n++) + average += values[n]; + average /= (float)IM_ARRAYSIZE(values); + char overlay[32]; + 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. + struct Funcs { + static float Sin(void *, int i) + { + return sinf(i * 0.1f); + } + static float Saw(void *, int i) + { + return (i & 1) ? 1.0f : -1.0f; + } + }; + static int func_type = 0, display_count = 70; + ImGui::SeparatorText("Functions"); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); + ImGui::Combo("func", &func_type, "Sin\0Saw\0"); + ImGui::SameLine(); + ImGui::SliderInt("Sample count", &display_count, 1, 400); + float (*func)(void *, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw; + ImGui::PlotLines("Lines", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); + ImGui::PlotHistogram("Histogram", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); + ImGui::Separator(); + + // Animate a simple progress bar + IMGUI_DEMO_MARKER("Widgets/Plotting/ProgressBar"); + static float progress = 0.0f, progress_dir = 1.0f; + if (animate) { + 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); + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Color"); + if (ImGui::TreeNode("Color/Picker Widgets")) { + static ImVec4 color = ImVec4(114.0f / 255.0f, 144.0f / 255.0f, 154.0f / 255.0f, 200.0f / 255.0f); + + static bool alpha_preview = true; + static bool alpha_half_preview = false; + static bool drag_and_drop = true; + static bool options_menu = true; + static bool hdr = false; + ImGui::SeparatorText("Options"); + ImGui::Checkbox("With Alpha Preview", &alpha_preview); + ImGui::Checkbox("With Half Alpha Preview", &alpha_half_preview); + ImGui::Checkbox("With Drag and Drop", &drag_and_drop); + ImGui::Checkbox("With Options Menu", &options_menu); + ImGui::SameLine(); + HelpMarker("Right-click on the individual color widget to show options."); + ImGui::Checkbox("With HDR", &hdr); + ImGui::SameLine(); + HelpMarker("Currently all this does is to lift the 0..1 limits on dragging widgets."); + ImGuiColorEditFlags misc_flags = (hdr ? ImGuiColorEditFlags_HDR : 0) | (drag_and_drop ? 0 : ImGuiColorEditFlags_NoDragDrop) + | (alpha_half_preview ? ImGuiColorEditFlags_AlphaPreviewHalf : (alpha_preview ? ImGuiColorEditFlags_AlphaPreview : 0)) + | (options_menu ? 0 : ImGuiColorEditFlags_NoOptions); + + IMGUI_DEMO_MARKER("Widgets/Color/ColorEdit"); + ImGui::SeparatorText("Inline color editor"); + ImGui::Text("Color widget:"); + ImGui::SameLine(); + HelpMarker( + "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, misc_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 | misc_flags); + + IMGUI_DEMO_MARKER("Widgets/Color/ColorEdit (float display)"); + ImGui::Text("Color widget with Float Display:"); + ImGui::ColorEdit4("MyColor##2f", (float *)&color, ImGuiColorEditFlags_Float | misc_flags); + + IMGUI_DEMO_MARKER("Widgets/Color/ColorButton (with Picker)"); + ImGui::Text("Color button with Picker:"); + ImGui::SameLine(); + HelpMarker( + "With the ImGuiColorEditFlags_NoInputs flag you can hide all the slider/text inputs.\n" + "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 | misc_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] = {}; + if (saved_palette_init) { + for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) { + ImGui::ColorConvertHSVtoRGB(n / 31.0f, 0.8f, 0.8f, saved_palette[n].x, saved_palette[n].y, saved_palette[n].z); + saved_palette[n].w = 1.0f; // Alpha + } + saved_palette_init = false; + } + + static ImVec4 backup_color; + bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags); + ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x); + open_popup |= ImGui::Button("Palette"); + if (open_popup) { + ImGui::OpenPopup("mypicker"); + backup_color = color; + } + if (ImGui::BeginPopup("mypicker")) { + ImGui::Text("MY CUSTOM COLOR PICKER WITH AN AMAZING PALETTE!"); + ImGui::Separator(); + ImGui::ColorPicker4("##picker", (float *)&color, misc_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)); + ImGui::Text("Previous"); + if (ImGui::ColorButton("##previous", backup_color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60, 40))) + color = backup_color; + ImGui::Separator(); + ImGui::Text("Palette"); + for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) { + 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()) { + if (const ImGuiPayload *payload = ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) + memcpy((float *)&saved_palette[n], payload->Data, sizeof(float) * 3); + if (const ImGuiPayload *payload = ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) + 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, misc_flags | (no_border ? ImGuiColorEditFlags_NoBorder : 0), ImVec2(80, 80)); + + IMGUI_DEMO_MARKER("Widgets/Color/ColorPicker"); + ImGui::SeparatorText("Color picker"); + static bool alpha = true; + static bool alpha_bar = true; + static bool side_preview = true; + static bool ref_color = false; + static ImVec4 ref_color_v(1.0f, 0.0f, 1.0f, 0.5f); + static int display_mode = 0; + static int picker_mode = 0; + ImGui::Checkbox("With Alpha", &alpha); + ImGui::Checkbox("With Alpha Bar", &alpha_bar); + ImGui::Checkbox("With Side Preview", &side_preview); + if (side_preview) { + ImGui::SameLine(); + ImGui::Checkbox("With Ref Color", &ref_color); + if (ref_color) { + ImGui::SameLine(); + ImGui::ColorEdit4("##RefColor", &ref_color_v.x, ImGuiColorEditFlags_NoInputs | misc_flags); + } + } + ImGui::Combo("Display Mode", &display_mode, "Auto/Current\0None\0RGB Only\0HSV Only\0Hex Only\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()."); + ImGui::SameLine(); + HelpMarker("When not specified explicitly (Auto/Current mode), user can right-click the picker to change mode."); + ImGuiColorEditFlags flags = misc_flags; + if (!alpha) + flags |= ImGuiColorEditFlags_NoAlpha; // This is by default if you call ColorPicker3() instead of ColorPicker4() + if (alpha_bar) + flags |= ImGuiColorEditFlags_AlphaBar; + if (!side_preview) + flags |= ImGuiColorEditFlags_NoSidePreview; + if (picker_mode == 1) + flags |= ImGuiColorEditFlags_PickerHueBar; + if (picker_mode == 2) + flags |= ImGuiColorEditFlags_PickerHueWheel; + if (display_mode == 1) + flags |= ImGuiColorEditFlags_NoInputs; // Disable all RGB/HSV/Hex displays + if (display_mode == 2) + flags |= ImGuiColorEditFlags_DisplayRGB; // Override display mode + 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" + "We don't have Push/Pop functions because you can force options on a per-widget basis if needed," + "and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid" + "encouraging you to persistently save values that aren't forward-compatible."); + if (ImGui::Button("Default: Uint8 + HSV + Hue Bar")) + 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 both a small version of both types of pickers (to make it more visible in the demo to people who are skimming quickly through it) + ImGui::Text("Both types:"); + float w = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.y) * 0.40f; + ImGui::SetNextItemWidth(w); + ImGui::ColorPicker3("##MyColor##5", + (float *)&color, + ImGuiColorEditFlags_PickerHueBar | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha); + ImGui::SameLine(); + ImGui::SetNextItemWidth(w); + ImGui::ColorPicker3("##MyColor##6", + (float *)&color, + ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoInputs + | ImGuiColorEditFlags_NoAlpha); + + // 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(); + ImGui::Text("HSV encoded colors"); + ImGui::SameLine(); + HelpMarker( + "By default, colors are given to ColorEdit and ColorPicker in RGB, but ImGuiColorEditFlags_InputHSV" + "allows you to store colors as HSV and pass them to ColorEdit and ColorPicker as HSV. This comes with the" + "added benefit that you can manipulate hue values with the picker even when saturation or value are zero."); + ImGui::Text("Color widget with InputHSV:"); + 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(); + } + + IMGUI_DEMO_MARKER("Widgets/Drag and Slider Flags"); + if (ImGui::TreeNode("Drag/Slider Flags")) { + // Demonstrate using advanced flags for DragXXX and SliderXXX functions. Note that the flags are the same! + static ImGuiSliderFlags flags = ImGuiSliderFlags_None; + ImGui::CheckboxFlags("ImGuiSliderFlags_AlwaysClamp", &flags, ImGuiSliderFlags_AlwaysClamp); + ImGui::SameLine(); + HelpMarker("Always clamp value to min/max bounds (if any) when input manually with CTRL+Click."); + ImGui::CheckboxFlags("ImGuiSliderFlags_Logarithmic", &flags, ImGuiSliderFlags_Logarithmic); + ImGui::SameLine(); + HelpMarker("Enable logarithmic editing (more precision for small values)."); + ImGui::CheckboxFlags("ImGuiSliderFlags_NoRoundToFormat", &flags, ImGuiSliderFlags_NoRoundToFormat); + ImGui::SameLine(); + HelpMarker("Disable rounding underlying value to match precision of the format string (e.g. %.3f values are rounded to those 3 digits)."); + ImGui::CheckboxFlags("ImGuiSliderFlags_NoInput", &flags, ImGuiSliderFlags_NoInput); + ImGui::SameLine(); + HelpMarker("Disable CTRL+Click or Enter key allowing to input text directly into the widget."); + + // Drags + static float drag_f = 0.5f; + static int drag_i = 50; + ImGui::Text("Underlying float value: %f", drag_f); + ImGui::DragFloat("DragFloat (0 -> 1)", &drag_f, 0.005f, 0.0f, 1.0f, "%.3f", flags); + ImGui::DragFloat("DragFloat (0 -> +inf)", &drag_f, 0.005f, 0.0f, FLT_MAX, "%.3f", flags); + ImGui::DragFloat("DragFloat (-inf -> 1)", &drag_f, 0.005f, -FLT_MAX, 1.0f, "%.3f", flags); + ImGui::DragFloat("DragFloat (-inf -> +inf)", &drag_f, 0.005f, -FLT_MAX, +FLT_MAX, "%.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; + ImGui::Text("Underlying float value: %f", slider_f); + ImGui::SliderFloat("SliderFloat (0 -> 1)", &slider_f, 0.0f, 1.0f, "%.3f", flags); + ImGui::SliderInt("SliderInt (0 -> 100)", &slider_i, 0, 100, "%d", flags); + + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Range Widgets"); + if (ImGui::TreeNode("Range Widgets")) { + 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(); + } + + IMGUI_DEMO_MARKER("Widgets/Data Types"); + if (ImGui::TreeNode("Data Types")) { +// DragScalar/InputScalar/SliderScalar functions allow various data types +// - signed/unsigned +// - 8/16/32/64-bits +// - integer/float/double +// To avoid polluting the public API with all possible combinations, we use the ImGuiDataType enum +// to pass the type, and passing all arguments by pointer. +// This is the reason the test code below creates local variables to hold "zero" "one" etc. for each type. +// In practice, if you frequently use a given type that is not covered by the normal API entry points, +// you can wrap it yourself inside a 1 line function which can take typed argument as value instead of void*, +// and then pass their address to the generic function. For example: +// bool MySliderU64(const char *label, u64* value, u64 min = 0, u64 max = 0, const char* format = "%lld") +// { +// 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 + ImS64 LLONG_MIN = -9223372036854775807LL - 1; + ImS64 LLONG_MAX = 9223372036854775807LL; + ImU64 ULLONG_MAX = (2ULL * 9223372036854775807LL + 1); +#endif + const char s8_zero = 0, s8_one = 1, s8_fifty = 50, s8_min = -128, s8_max = 127; + const ImU8 u8_zero = 0, u8_one = 1, u8_fifty = 50, u8_min = 0, u8_max = 255; + const short s16_zero = 0, s16_one = 1, s16_fifty = 50, s16_min = -32768, s16_max = 32767; + const ImU16 u16_zero = 0, u16_one = 1, u16_fifty = 50, u16_min = 0, u16_max = 65535; + const ImS32 s32_zero = 0, s32_one = 1, s32_fifty = 50, s32_min = INT_MIN / 2, s32_max = INT_MAX / 2, s32_hi_a = INT_MAX / 2 - 100, + s32_hi_b = INT_MAX / 2; + const ImU32 u32_zero = 0, u32_one = 1, u32_fifty = 50, u32_min = 0, u32_max = UINT_MAX / 2, u32_hi_a = UINT_MAX / 2 - 100, u32_hi_b = UINT_MAX / 2; + const ImS64 s64_zero = 0, s64_one = 1, s64_fifty = 50, s64_min = LLONG_MIN / 2, s64_max = LLONG_MAX / 2, s64_hi_a = LLONG_MAX / 2 - 100, + s64_hi_b = LLONG_MAX / 2; + 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; + static short s16_v = 32767; + static ImU16 u16_v = 65535; + static ImS32 s32_v = -1; + static ImU32 u32_v = (ImU32)-1; + static ImS64 s64_v = -1; + 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"); + ImGui::SeparatorText("Drags"); + ImGui::Checkbox("Clamp integers to 0..50", &drag_clamp); + ImGui::SameLine(); + HelpMarker( + "As with every widget in dear imgui, we never modify values unless there is a user interaction.\n" + "You can override the clamping limits by using CTRL+Click to input a value."); + ImGui::DragScalar("drag s8", ImGuiDataType_S8, &s8_v, drag_speed, drag_clamp ? &s8_zero : NULL, drag_clamp ? &s8_fifty : NULL); + ImGui::DragScalar("drag u8", ImGuiDataType_U8, &u8_v, drag_speed, drag_clamp ? &u8_zero : NULL, drag_clamp ? &u8_fifty : NULL, "%u ms"); + ImGui::DragScalar("drag s16", ImGuiDataType_S16, &s16_v, drag_speed, drag_clamp ? &s16_zero : NULL, drag_clamp ? &s16_fifty : NULL); + ImGui::DragScalar("drag u16", ImGuiDataType_U16, &u16_v, drag_speed, drag_clamp ? &u16_zero : NULL, drag_clamp ? &u16_fifty : NULL, "%u ms"); + ImGui::DragScalar("drag s32", ImGuiDataType_S32, &s32_v, drag_speed, drag_clamp ? &s32_zero : NULL, drag_clamp ? &s32_fifty : NULL); + ImGui::DragScalar("drag s32 hex", ImGuiDataType_S32, &s32_v, drag_speed, drag_clamp ? &s32_zero : NULL, drag_clamp ? &s32_fifty : NULL, "0x%08X"); + ImGui::DragScalar("drag u32", ImGuiDataType_U32, &u32_v, drag_speed, drag_clamp ? &u32_zero : NULL, drag_clamp ? &u32_fifty : NULL, "%u ms"); + ImGui::DragScalar("drag s64", ImGuiDataType_S64, &s64_v, drag_speed, drag_clamp ? &s64_zero : NULL, drag_clamp ? &s64_fifty : NULL); + ImGui::DragScalar("drag u64", ImGuiDataType_U64, &u64_v, drag_speed, drag_clamp ? &u64_zero : NULL, drag_clamp ? &u64_fifty : NULL); + ImGui::DragScalar("drag float", ImGuiDataType_Float, &f32_v, 0.005f, &f32_zero, &f32_one, "%f"); + 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"); + ImGui::SliderScalar("slider u8 full", ImGuiDataType_U8, &u8_v, &u8_min, &u8_max, "%u"); + ImGui::SliderScalar("slider s16 full", ImGuiDataType_S16, &s16_v, &s16_min, &s16_max, "%d"); + ImGui::SliderScalar("slider u16 full", ImGuiDataType_U16, &u16_v, &u16_min, &u16_max, "%u"); + ImGui::SliderScalar("slider s32 low", ImGuiDataType_S32, &s32_v, &s32_zero, &s32_fifty, "%d"); + ImGui::SliderScalar("slider s32 high", ImGuiDataType_S32, &s32_v, &s32_hi_a, &s32_hi_b, "%d"); + ImGui::SliderScalar("slider s32 full", ImGuiDataType_S32, &s32_v, &s32_min, &s32_max, "%d"); + ImGui::SliderScalar("slider s32 hex", ImGuiDataType_S32, &s32_v, &s32_zero, &s32_fifty, "0x%04X"); + ImGui::SliderScalar("slider u32 low", ImGuiDataType_U32, &u32_v, &u32_zero, &u32_fifty, "%u"); + ImGui::SliderScalar("slider u32 high", ImGuiDataType_U32, &u32_v, &u32_hi_a, &u32_hi_b, "%u"); + ImGui::SliderScalar("slider u32 full", ImGuiDataType_U32, &u32_v, &u32_min, &u32_max, "%u"); + ImGui::SliderScalar("slider s64 low", ImGuiDataType_S64, &s64_v, &s64_zero, &s64_fifty, "%" PRId64); + ImGui::SliderScalar("slider s64 high", ImGuiDataType_S64, &s64_v, &s64_hi_a, &s64_hi_b, "%" PRId64); + ImGui::SliderScalar("slider s64 full", ImGuiDataType_S64, &s64_v, &s64_min, &s64_max, "%" PRId64); + ImGui::SliderScalar("slider u64 low", ImGuiDataType_U64, &u64_v, &u64_zero, &u64_fifty, "%" PRIu64 " ms"); + ImGui::SliderScalar("slider u64 high", ImGuiDataType_U64, &u64_v, &u64_hi_a, &u64_hi_b, "%" PRIu64 " ms"); + ImGui::SliderScalar("slider u64 full", ImGuiDataType_U64, &u64_v, &u64_min, &u64_max, "%" PRIu64 " ms"); + ImGui::SliderScalar("slider float low", ImGuiDataType_Float, &f32_v, &f32_zero, &f32_one); + ImGui::SliderScalar("slider float low log", ImGuiDataType_Float, &f32_v, &f32_zero, &f32_one, "%.10f", ImGuiSliderFlags_Logarithmic); + ImGui::SliderScalar("slider float high", ImGuiDataType_Float, &f32_v, &f32_lo_a, &f32_hi_a, "%e"); + 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"); + ImGui::SliderScalar("slider s32 reverse", ImGuiDataType_S32, &s32_v, &s32_fifty, &s32_zero, "%d"); + 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; + ImGui::SeparatorText("Inputs"); + ImGui::Checkbox("Show step buttons", &inputs_step); + ImGui::InputScalar("input s8", ImGuiDataType_S8, &s8_v, inputs_step ? &s8_one : NULL, NULL, "%d"); + ImGui::InputScalar("input u8", ImGuiDataType_U8, &u8_v, inputs_step ? &u8_one : NULL, NULL, "%u"); + ImGui::InputScalar("input s16", ImGuiDataType_S16, &s16_v, inputs_step ? &s16_one : NULL, NULL, "%d"); + ImGui::InputScalar("input u16", ImGuiDataType_U16, &u16_v, inputs_step ? &u16_one : NULL, NULL, "%u"); + ImGui::InputScalar("input s32", ImGuiDataType_S32, &s32_v, inputs_step ? &s32_one : NULL, NULL, "%d"); + ImGui::InputScalar("input s32 hex", ImGuiDataType_S32, &s32_v, inputs_step ? &s32_one : NULL, NULL, "%04X"); + ImGui::InputScalar("input u32", ImGuiDataType_U32, &u32_v, inputs_step ? &u32_one : NULL, NULL, "%u"); + ImGui::InputScalar("input u32 hex", ImGuiDataType_U32, &u32_v, inputs_step ? &u32_one : NULL, NULL, "%08X"); + ImGui::InputScalar("input s64", ImGuiDataType_S64, &s64_v, inputs_step ? &s64_one : NULL); + ImGui::InputScalar("input u64", ImGuiDataType_U64, &u64_v, inputs_step ? &u64_one : NULL); + ImGui::InputScalar("input float", ImGuiDataType_Float, &f32_v, inputs_step ? &f32_one : NULL); + ImGui::InputScalar("input double", ImGuiDataType_Double, &f64_v, inputs_step ? &f64_one : NULL); + + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Multi-component Widgets"); + if (ImGui::TreeNode("Multi-component Widgets")) { + 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); + ImGui::SliderFloat2("slider float2", vec4f, 0.0f, 1.0f); + 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); + ImGui::SliderFloat3("slider float3", vec4f, 0.0f, 1.0f); + 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); + ImGui::SliderFloat4("slider float4", vec4f, 0.0f, 1.0f); + ImGui::InputInt4("input int4", vec4i); + ImGui::DragInt4("drag int4", vec4i, 1, 0, 255); + ImGui::SliderInt4("slider int4", vec4i, 0, 255); + + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Vertical Sliders"); + if (ImGui::TreeNode("Vertical Sliders")) { + 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++) { + if (i > 0) + ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor::HSV(i / 7.0f, 0.5f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4)ImColor::HSV(i / 7.0f, 0.6f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4)ImColor::HSV(i / 7.0f, 0.7f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4)ImColor::HSV(i / 7.0f, 0.9f, 0.9f)); + ImGui::VSliderFloat("##v", ImVec2(18, 160), &values[i], 0.0f, 1.0f, ""); + if (ImGui::IsItemActive() || ImGui::IsItemHovered()) + ImGui::SetTooltip("%.3f", values[i]); + ImGui::PopStyleColor(4); + ImGui::PopID(); + } + ImGui::PopID(); + + ImGui::SameLine(); + ImGui::PushID("set2"); + static float values2[4] = {0.20f, 0.80f, 0.40f, 0.25f}; + const int rows = 3; + const ImVec2 small_slider_size(18, (float)(int)((160.0f - (rows - 1) * spacing) / rows)); + for (int nx = 0; nx < 4; nx++) { + if (nx > 0) + ImGui::SameLine(); + ImGui::BeginGroup(); + for (int ny = 0; ny < rows; ny++) { + ImGui::PushID(nx * rows + ny); + ImGui::VSliderFloat("##v", small_slider_size, &values2[nx], 0.0f, 1.0f, ""); + if (ImGui::IsItemActive() || ImGui::IsItemHovered()) + ImGui::SetTooltip("%.3f", values2[nx]); + ImGui::PopID(); + } + ImGui::EndGroup(); + } + ImGui::PopID(); + + ImGui::SameLine(); + ImGui::PushID("set3"); + for (int i = 0; i < 4; i++) { + if (i > 0) + ImGui::SameLine(); + ImGui::PushID(i); + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 40); + ImGui::VSliderFloat("##v", ImVec2(40, 160), &values[i], 0.0f, 1.0f, "%.2f\nsec"); + ImGui::PopStyleVar(); + ImGui::PopID(); + } + ImGui::PopID(); + ImGui::PopStyleVar(); + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Drag and drop"); + if (ImGui::TreeNode("Drag and Drop")) { + IMGUI_DEMO_MARKER("Widgets/Drag and drop/Standard widgets"); + if (ImGui::TreeNode("Drag and drop in standard widgets")) { + // ColorEdit widgets automatically act as drag source and drag target. + // They are using standardized payload strings IMGUI_PAYLOAD_TYPE_COLOR_3F and IMGUI_PAYLOAD_TYPE_COLOR_4F + // to allow your own widgets to use colors in their drag and drop interaction. + // Also see 'Demo->Widgets->Color/Picker Widgets->Palette' demo. + HelpMarker("You can drag from the color squares."); + static float col1[3] = {1.0f, 0.0f, 0.2f}; + static float col2[4] = {0.4f, 0.7f, 0.0f, 0.5f}; + ImGui::ColorEdit3("color 1", col1); + 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")) { + enum Mode { Mode_Copy, Mode_Move, Mode_Swap }; + static int mode = 0; + if (ImGui::RadioButton("Copy", mode == Mode_Copy)) { + mode = Mode_Copy; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Move", mode == Mode_Move)) { + mode = Mode_Move; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Swap", mode == Mode_Swap)) { + mode = Mode_Swap; + } + static const char *names[9] = {"Bobby", "Beatrice", "Betty", "Brianna", "Barry", "Bernard", "Bibi", "Blaine", "Bryn"}; + for (int n = 0; n < IM_ARRAYSIZE(names); n++) { + ImGui::PushID(n); + 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]); + } + if (mode == Mode_Move) { + ImGui::Text("Move %s", names[n]); + } + if (mode == Mode_Swap) { + ImGui::Text("Swap %s", names[n]); + } + ImGui::EndDragDropSource(); + } + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload *payload = ImGui::AcceptDragDropPayload("DND_DEMO_CELL")) { + IM_ASSERT(payload->DataSize == sizeof(int)); + int payload_n = *(const int *)payload->Data; + if (mode == Mode_Copy) { + names[n] = names[payload_n]; + } + if (mode == Mode_Move) { + names[n] = names[payload_n]; + names[payload_n] = ""; + } + if (mode == Mode_Swap) { + const char *tmp = names[n]; + names[n] = names[payload_n]; + names[payload_n] = tmp; + } + } + ImGui::EndDragDropTarget(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Drag and Drop/Drag to reorder items (simple)"); + if (ImGui::TreeNode("Drag to reorder items (simple)")) { + // Simple reordering + HelpMarker( + "We don't use the drag and drop api at all here! " + "Instead we query when the item is held but not hovered, and order items accordingly."); + static const char *item_names[] = {"Item One", "Item Two", "Item Three", "Item Four", "Item Five"}; + for (int n = 0; n < IM_ARRAYSIZE(item_names); n++) { + 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); + if (n_next >= 0 && n_next < IM_ARRAYSIZE(item_names)) { + item_names[n] = item_names[n_next]; + item_names[n_next] = item; + ImGui::ResetMouseDragDelta(); + } + } + } + ImGui::TreePop(); + } + + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Querying Item Status (Edited,Active,Hovered etc.)"); + if (ImGui::TreeNode("Querying Item Status (Edited/Active/Hovered etc.)")) { + // Select an item type + const char *item_names[] = {"Text", + "Button", + "Button (w/ repeat)", + "Checkbox", + "SliderFloat", + "InputText", + "InputTextMultiline", + "InputFloat", + "InputFloat3", + "ColorEdit4", + "Selectable", + "MenuItem", + "TreeNode", + "TreeNode (w/ double-click)", + "Combo", + "ListBox"}; + static int item_type = 4; + static bool item_disabled = false; + ImGui::Combo("Item Type", &item_type, item_names, IM_ARRAYSIZE(item_names), IM_ARRAYSIZE(item_names)); + 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; + static float col4f[4] = {1.0f, 0.5, 0.0f, 1.0f}; + static char str[16] = {}; + if (item_disabled) + ImGui::BeginDisabled(true); + if (item_type == 0) { + ImGui::Text("ITEM: Text"); + } // Testing text items with no identifier/interaction + if (item_type == 1) { + ret = ImGui::Button("ITEM: Button"); + } // Testing button + if (item_type == 2) { + ImGui::PushButtonRepeat(true); + ret = ImGui::Button("ITEM: Button"); + ImGui::PopButtonRepeat(); + } // Testing button (with repeater) + if (item_type == 3) { + ret = ImGui::Checkbox("ITEM: Checkbox", &b); + } // Testing checkbox + if (item_type == 4) { + ret = ImGui::SliderFloat("ITEM: SliderFloat", &col4f[0], 0.0f, 1.0f); + } // Testing basic item + if (item_type == 5) { + ret = ImGui::InputText("ITEM: InputText", &str[0], IM_ARRAYSIZE(str)); + } // Testing input text (which handles tabbing) + if (item_type == 6) { + ret = ImGui::InputTextMultiline("ITEM: InputTextMultiline", &str[0], IM_ARRAYSIZE(str)); + } // Testing input text (which uses a child window) + if (item_type == 7) { + ret = ImGui::InputFloat("ITEM: InputFloat", col4f, 1.0f); + } // Testing +/- buttons on scalar input + if (item_type == 8) { + ret = ImGui::InputFloat3("ITEM: InputFloat3", col4f); + } // Testing multi-component items (IsItemXXX flags are reported merged) + if (item_type == 9) { + ret = ImGui::ColorEdit4("ITEM: ColorEdit4", col4f); + } // Testing multi-component items (IsItemXXX flags are reported merged) + if (item_type == 10) { + ret = ImGui::Selectable("ITEM: Selectable"); + } // Testing selectable item + if (item_type == 11) { + ret = ImGui::MenuItem("ITEM: MenuItem"); + } // Testing menu item (they use ImGuiButtonFlags_PressedOnRelease button policy) + if (item_type == 12) { + ret = ImGui::TreeNode("ITEM: TreeNode"); + if (ret) + ImGui::TreePop(); + } // Testing tree node + 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, + // we query every state in a single call to avoid storing them and to simplify the code. + ImGui::BulletText( + "Return value = %d\n" + "IsItemFocused() = %d\n" + "IsItemHovered() = %d\n" + "IsItemHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsItemHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsItemHovered(_AllowWhenOverlappedByItem) = %d\n" + "IsItemHovered(_AllowWhenOverlappedByWindow) = %d\n" + "IsItemHovered(_AllowWhenDisabled) = %d\n" + "IsItemHovered(_RectOnly) = %d\n" + "IsItemActive() = %d\n" + "IsItemEdited() = %d\n" + "IsItemActivated() = %d\n" + "IsItemDeactivated() = %d\n" + "IsItemDeactivatedAfterEdit() = %d\n" + "IsItemVisible() = %d\n" + "IsItemClicked() = %d\n" + "IsItemToggledOpen() = %d\n" + "GetItemRectMin() = (%.1f, %.1f)\n" + "GetItemRectMax() = (%.1f, %.1f)\n" + "GetItemRectSize() = (%.1f, %.1f)", + ret, + ImGui::IsItemFocused(), + ImGui::IsItemHovered(), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlappedByItem), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlappedByWindow), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled), + ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly), + ImGui::IsItemActive(), + ImGui::IsItemEdited(), + ImGui::IsItemActivated(), + ImGui::IsItemDeactivated(), + ImGui::IsItemDeactivatedAfterEdit(), + ImGui::IsItemVisible(), + ImGui::IsItemClicked(), + ImGui::IsItemToggledOpen(), + ImGui::GetItemRectMin().x, + ImGui::GetItemRectMin().y, + ImGui::GetItemRectMax().x, + ImGui::GetItemRectMax().y, + ImGui::GetItemRectSize().x, + ImGui::GetItemRectSize().y); + ImGui::BulletText( + "with Hovering Delay or Stationary test:\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.)")) { + static bool embed_all_inside_a_child_window = false; + 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), true); + + // Testing IsWindowFocused() function with its various flags. + ImGui::BulletText( + "IsWindowFocused() = %d\n" + "IsWindowFocused(_ChildWindows) = %d\n" + "IsWindowFocused(_ChildWindows|_NoPopupHierarchy) = %d\n" + "IsWindowFocused(_ChildWindows|_RootWindow) = %d\n" + "IsWindowFocused(_ChildWindows|_RootWindow|_NoPopupHierarchy) = %d\n" + "IsWindowFocused(_RootWindow) = %d\n" + "IsWindowFocused(_RootWindow|_NoPopupHierarchy) = %d\n" + "IsWindowFocused(_AnyWindow) = %d\n", + ImGui::IsWindowFocused(), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_NoPopupHierarchy), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_RootWindow), + ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_NoPopupHierarchy), + ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow), + ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_NoPopupHierarchy), + ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)); + + // Testing IsWindowHovered() function with its various flags. + ImGui::BulletText( + "IsWindowHovered() = %d\n" + "IsWindowHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsWindowHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsWindowHovered(_ChildWindows) = %d\n" + "IsWindowHovered(_ChildWindows|_NoPopupHierarchy) = %d\n" + "IsWindowHovered(_ChildWindows|_RootWindow) = %d\n" + "IsWindowHovered(_ChildWindows|_RootWindow|_NoPopupHierarchy) = %d\n" + "IsWindowHovered(_RootWindow) = %d\n" + "IsWindowHovered(_RootWindow|_NoPopupHierarchy) = %d\n" + "IsWindowHovered(_ChildWindows|_AllowWhenBlockedByPopup) = %d\n" + "IsWindowHovered(_AnyWindow) = %d\n" + "IsWindowHovered(_Stationary) = %d\n", + ImGui::IsWindowHovered(), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_NoPopupHierarchy), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_NoPopupHierarchy), + ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow), + ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_NoPopupHierarchy), + ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow), + ImGui::IsWindowHovered(ImGuiHoveredFlags_Stationary)); + + ImGui::BeginChild("child", ImVec2(0, 50), true); + 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. + static bool test_window = false; + ImGui::Checkbox("Hovered/Active tests after Begin() for title bar testing", &test_window); + if (test_window) { + ImGui::Begin("Title bar Hovered/Active tests", &test_window); + if (ImGui::BeginPopupContextItem()) // <-- This is using IsItemHovered() + { + if (ImGui::MenuItem("Close")) { + test_window = false; + } + ImGui::EndPopup(); + } + ImGui::Text( + "IsItemHovered() after begin = %d (== is title bar hovered)\n" + "IsItemActive() after begin = %d (== is window being clicked/moved)\n", + ImGui::IsItemHovered(), + ImGui::IsItemActive()); + ImGui::End(); + } + + ImGui::TreePop(); + } + + // Demonstrate BeginDisabled/EndDisabled using a checkbox located at the bottom of the section (which is a bit odd: + // logically we'd have this checkbox at the top of the section, but we don't want this feature to steal that space) + if (disable_all) + ImGui::EndDisabled(); + + IMGUI_DEMO_MARKER("Widgets/Disable Block"); + if (ImGui::TreeNode("Disable block")) { + ImGui::Checkbox("Disable entire section above", &disable_all); + ImGui::SameLine(); + HelpMarker("Demonstrate using BeginDisabled()/EndDisabled() across this section."); + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Text Filter"); + if (ImGui::TreeNode("Text Filter")) { + // Helper class to easy setup a text filter. + // You may want to implement a more feature-full filtering scheme in your own application. + HelpMarker("Not a widget per-se, but ImGuiTextFilter is a helper to perform simple filtering on text strings."); + static ImGuiTextFilter filter; + ImGui::Text( + "Filter usage:\n" + " \"\" display all lines\n" + " \"xxx\" display lines containing \"xxx\"\n" + " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" + " \"-xxx\" hide lines containing \"xxx\""); + filter.Draw(); + const char *lines[] = {"aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world"}; + for (int i = 0; i < IM_ARRAYSIZE(lines); i++) + if (filter.PassFilter(lines[i])) + ImGui::BulletText("%s", lines[i]); + ImGui::TreePop(); + } +} + +static void ShowDemoWindowLayout() +{ + 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; + if (disable_mouse_wheel) + window_flags |= ImGuiWindowFlags_NoScrollWithMouse; + ImGui::BeginChild("ChildL", ImVec2(ImGui::GetContentRegionAvail().x * 0.5f, 260), false, window_flags); + for (int i = 0; i < 100; i++) + ImGui::Text("%04d: scrollable region", i); + ImGui::EndChild(); + } + + ImGui::SameLine(); + + // Child 2: rounded border + { + ImGuiWindowFlags window_flags = ImGuiWindowFlags_None; + if (disable_mouse_wheel) + window_flags |= ImGuiWindowFlags_NoScrollWithMouse; + if (!disable_menu) + window_flags |= ImGuiWindowFlags_MenuBar; + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); + ImGui::BeginChild("ChildR", ImVec2(0, 260), true, window_flags); + if (!disable_menu && ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu("Menu")) { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + if (ImGui::BeginTable("split", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings)) { + for (int i = 0; i < 100; i++) { + char buf[32]; + sprintf(buf, "%03d", i); + ImGui::TableNextColumn(); + ImGui::Button(buf, ImVec2(-FLT_MIN, 0.0f)); + } + ImGui::EndTable(); + } + ImGui::EndChild(); + ImGui::PopStyleVar(); + } + + 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) + // You can also call SetNextWindowPos() to position the child window. The parent window will effectively + // layout from this position. + // - Using ImGui::GetItemRectMin/Max() to query the "item" state (because the child window is an item from + // the POV of the parent window). See 'Demo->Querying Status (Edited/Active/Hovered etc.)' for details. + { + static int offset_x = 0; + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); + ImGui::DragInt("Offset X", &offset_x, 1.0f, -1000, 1000); + + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (float)offset_x); + ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(255, 0, 0, 100)); + ImGui::BeginChild("Red", ImVec2(200, 100), true, ImGuiWindowFlags_None); + for (int n = 0; n < 50; n++) + ImGui::Text("Some test %d", n); + ImGui::EndChild(); + bool child_is_hovered = ImGui::IsItemHovered(); + ImVec2 child_rect_min = ImGui::GetItemRectMin(); + ImVec2 child_rect_max = ImGui::GetItemRectMax(); + ImGui::PopStyleColor(); + 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); + ImGui::DragFloat("float##1b", &f); + if (show_indented_items) { + ImGui::Indent(); + ImGui::DragFloat("float (indented)##1b", &f); + ImGui::Unindent(); + } + ImGui::PopItemWidth(); + + ImGui::Text("SetNextItemWidth/PushItemWidth(-100)"); + ImGui::SameLine(); + HelpMarker("Align to right edge minus 100"); + ImGui::PushItemWidth(-100); + ImGui::DragFloat("float##2a", &f); + if (show_indented_items) { + ImGui::Indent(); + ImGui::DragFloat("float (indented)##2b", &f); + 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); + ImGui::DragFloat("float##3a", &f); + if (show_indented_items) { + ImGui::Indent(); + ImGui::DragFloat("float (indented)##3b", &f); + 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); + ImGui::DragFloat("float##4a", &f); + if (show_indented_items) { + ImGui::Indent(); + ImGui::DragFloat("float (indented)##4b", &f); + 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); + if (show_indented_items) { + ImGui::Indent(); + ImGui::DragFloat("float (indented)##5b", &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"); + ImGui::SameLine(150); + ImGui::Text("x=150"); + ImGui::SameLine(300); + ImGui::Text("x=300"); + 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; + ImGui::Checkbox("My", &c1); + ImGui::SameLine(); + 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); + const char *items[] = {"AAAA", "BBBB", "CCCC", "DDDD"}; + static int item = -1; + ImGui::Combo("Combo", &item, items, IM_ARRAYSIZE(items)); + ImGui::SameLine(); + ImGui::SliderFloat("X", &f0, 0.0f, 5.0f); + ImGui::SameLine(); + 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}; + for (int i = 0; i < 4; i++) { + if (i > 0) + ImGui::SameLine(); + ImGui::PushID(i); + ImGui::ListBox("", &selection[i], items, IM_ARRAYSIZE(items)); + ImGui::PopID(); + // 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"); + ImGui::Text("Manual wrapping:"); + ImGuiStyle &style = ImGui::GetStyle(); + int buttons_count = 20; + float window_visible_x2 = ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x; + for (int n = 0; n < buttons_count; n++) { + ImGui::PushID(n); + ImGui::Button("Box", button_sz); + float last_button_x2 = ImGui::GetItemRectMax().x; + float next_button_x2 = last_button_x2 + style.ItemSpacing.x + button_sz.x; // Expected position if next button was on same line + if (n + 1 < buttons_count && next_button_x2 < window_visible_x2) + ImGui::SameLine(); + ImGui::PopID(); + } + + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Layout/Groups"); + if (ImGui::TreeNode("Groups")) { + HelpMarker( + "BeginGroup() basically locks the horizontal position for new line. " + "EndGroup() bundles the whole group so that you can use \"item\" functions such as " + "IsItemHovered()/IsItemActive() or SameLine() etc. on the whole group."); + ImGui::BeginGroup(); + { + ImGui::BeginGroup(); + ImGui::Button("AAA"); + ImGui::SameLine(); + ImGui::Button("BBB"); + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Button("CCC"); + ImGui::Button("DDD"); + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::Button("EEE"); + ImGui::EndGroup(); + ImGui::SetItemTooltip("First group hovered"); + } + // Capture the group size and create widgets using the same size + 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")) { + { + ImGui::BulletText("Text baseline:"); + ImGui::SameLine(); + HelpMarker( + "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) + ImGui::AlignTextToFramePadding(); + ImGui::Text("OK Blahblah"); + ImGui::SameLine(); + ImGui::Button("Some framed item"); + 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(); + ImGui::Button("Item##1"); + ImGui::SameLine(); + 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(); + ImGui::Button("50x50", ImVec2(50, 50)); + ImGui::SameLine(); + ImGui::Button("Button()"); + ImGui::SameLine(); + ImGui::SmallButton("SmallButton()"); + + // Tree + const float spacing = ImGui::GetStyle().ItemInnerSpacing.x; + ImGui::Button("Button##1"); + ImGui::SameLine(0.0f, spacing); + if (ImGui::TreeNode("Node##1")) { + // 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"); + ImGui::SameLine(0.0f, spacing); + ImGui::Button("Button##2"); + if (node_open) { + // Placeholder tree data + for (int i = 0; i < 6; i++) + 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) + child_w = 1.0f; + ImGui::PushID("##VerticalScrolling"); + for (int i = 0; i < 5; i++) { + if (i > 0) + ImGui::SameLine(); + 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), true, child_flags); + if (ImGui::BeginMenuBar()) { + ImGui::TextUnformatted("abc"); + ImGui::EndMenuBar(); + } + if (scroll_to_off) + ImGui::SetScrollY(scroll_to_off_px); + if (scroll_to_pos) + ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + scroll_to_pos_px, i * 0.25f); + if (child_is_visible) // Avoid calling SetScrollHereY when running with culled items + { + for (int item = 0; item < 100; item++) { + if (enable_track && item == track_item) { + ImGui::TextColored(ImVec4(1, 1, 0, 1), "Item %d", item); + ImGui::SetScrollHereY(i * 0.25f); // 0.0f:top, 0.5f:center, 1.0f:bottom + } else { + ImGui::Text("Item %d", item); + } + } + } + float scroll_y = ImGui::GetScrollY(); + float scroll_max_y = ImGui::GetScrollMaxY(); + ImGui::EndChild(); + ImGui::Text("%.0f/%.0f", scroll_y, scroll_max_y); + ImGui::EndGroup(); + } + ImGui::PopID(); + + // Horizontal scroll functions + IMGUI_DEMO_MARKER("Layout/Scrolling/Horizontal"); + ImGui::Spacing(); + HelpMarker( + "Use SetScrollHereX() or SetScrollFromPosX() to scroll to a given horizontal position.\n\n" + "Because the clipping rectangle of most window hides half worth of WindowPadding on the " + "left/right, using SetScrollFromPosX(+1) will usually result in clipped text whereas the " + "equivalent SetScrollFromPosY(+1) wouldn't."); + ImGui::PushID("##HorizontalScrolling"); + for (int i = 0; i < 5; i++) { + float child_height = ImGui::GetTextLineHeight() + style.ScrollbarSize + style.WindowPadding.y * 2.0f; + ImGuiWindowFlags child_flags = ImGuiWindowFlags_HorizontalScrollbar | (enable_extra_decorations ? ImGuiWindowFlags_AlwaysVerticalScrollbar : 0); + ImGuiID child_id = ImGui::GetID((void *)(intptr_t)i); + bool child_is_visible = ImGui::BeginChild(child_id, ImVec2(-100, child_height), true, child_flags); + if (scroll_to_off) + ImGui::SetScrollX(scroll_to_off_px); + if (scroll_to_pos) + ImGui::SetScrollFromPosX(ImGui::GetCursorStartPos().x + scroll_to_pos_px, i * 0.25f); + if (child_is_visible) // Avoid calling SetScrollHereY when running with culled items + { + for (int item = 0; item < 100; item++) { + if (item > 0) + ImGui::SameLine(); + if (enable_track && item == track_item) { + ImGui::TextColored(ImVec4(1, 1, 0, 1), "Item %d", item); + ImGui::SetScrollHereX(i * 0.25f); // 0.0f:left, 0.5f:center, 1.0f:right + } else { + ImGui::Text("Item %d", item); + } + } + } + float scroll_x = ImGui::GetScrollX(); + float scroll_max_x = ImGui::GetScrollMaxX(); + ImGui::EndChild(); + ImGui::SameLine(); + const char *names[] = {"Left", "25%", "Center", "75%", "Right"}; + ImGui::Text("%s\n%.0f/%.0f", names[i], scroll_x, scroll_max_x); + ImGui::Spacing(); + } + ImGui::PopID(); + + // Miscellaneous Horizontal Scrolling Demo + IMGUI_DEMO_MARKER("Layout/Scrolling/Horizontal (more)"); + HelpMarker( + "Horizontal scrolling for a window is enabled via the ImGuiWindowFlags_HorizontalScrollbar flag.\n\n" + "You may want to also explicitly specify content width by using SetNextWindowContentWidth() before Begin()."); + static int lines = 7; + ImGui::SliderInt("Lines", &lines, 1, 15); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f, 1.0f)); + ImVec2 scrolling_child_size = ImVec2(0, ImGui::GetFrameHeightWithSpacing() * 7 + 30); + ImGui::BeginChild("scrolling", scrolling_child_size, true, ImGuiWindowFlags_HorizontalScrollbar); + for (int line = 0; line < lines; line++) { + // Display random stuff. For the sake of this trivial demo we are using basic Button() + SameLine() + // If you want to create your own time line for a real application you may be better off manipulating + // the cursor position yourself, aka using SetCursorPos/SetCursorScreenPos to position the widgets + // yourself. You may also want to use the lower-level ImDrawList API. + int num_buttons = 10 + ((line & 1) ? line * 9 : line * 3); + for (int n = 0; n < num_buttons; n++) { + if (n > 0) + ImGui::SameLine(); + ImGui::PushID(n + line * 1000); + char num_buf[16]; + sprintf(num_buf, "%d", n); + const char *label = (!(n % 15)) ? "FizzBuzz" : (!(n % 3)) ? "Fizz" : (!(n % 5)) ? "Buzz" : num_buf; + float hue = n * 0.05f; + ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(hue, 0.6f, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(hue, 0.7f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(hue, 0.8f, 0.8f)); + ImGui::Button(label, ImVec2(40.0f + sinf((float)(line + n)) * 20.0f, 0.0f)); + ImGui::PopStyleColor(3); + ImGui::PopID(); + } + } + float scroll_x = ImGui::GetScrollX(); + float scroll_max_x = ImGui::GetScrollMaxX(); + ImGui::EndChild(); + ImGui::PopStyleVar(2); + float scroll_x_delta = 0.0f; + ImGui::SmallButton("<<"); + if (ImGui::IsItemActive()) + scroll_x_delta = -ImGui::GetIO().DeltaTime * 1000.0f; + ImGui::SameLine(); + ImGui::Text("Scroll from code"); + ImGui::SameLine(); + ImGui::SmallButton(">>"); + if (ImGui::IsItemActive()) + scroll_x_delta = +ImGui::GetIO().DeltaTime * 1000.0f; + ImGui::SameLine(); + ImGui::Text("%.0f/%.0f", scroll_x, scroll_max_x); + if (scroll_x_delta != 0.0f) { + // Demonstrate a trick: you can use Begin to set yourself in the context of another window + // (here we are already out of your child window) + ImGui::BeginChild("scrolling"); + ImGui::SetScrollX(ImGui::GetScrollX() + scroll_x_delta); + 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; + static bool show_button = true; + static bool show_tree_nodes = true; + static bool show_text_wrapped = false; + static bool show_columns = true; + static bool show_tab_bar = true; + static bool show_child = false; + static bool explicit_content_size = false; + static float contents_size_x = 300.0f; + if (explicit_content_size) + ImGui::SetNextWindowContentSize(ImVec2(contents_size_x, 0.0f)); + ImGui::Begin("Horizontal contents size demo window", + &show_horizontal_contents_size_demo_window, + show_h_scrollbar ? ImGuiWindowFlags_HorizontalScrollbar : 0); + IMGUI_DEMO_MARKER("Layout/Scrolling/Horizontal contents size demo window"); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 0)); + HelpMarker( + "Test of different widgets react and impact the work rectangle growing when horizontal scrolling is enabled.\n\nUse 'Metrics->Tools->Show " + "windows rectangles' to visualize rectangles."); + ImGui::Checkbox("H-scrollbar", &show_h_scrollbar); + ImGui::Checkbox("Button", &show_button); // Will grow contents size (unless explicitly overwritten) + ImGui::Checkbox("Tree nodes", &show_tree_nodes); // Will grow contents size and display highlight over full width + ImGui::Checkbox("Text wrapped", &show_text_wrapped); // Will grow and use contents size + ImGui::Checkbox("Columns", &show_columns); // Will use contents size + ImGui::Checkbox("Tab bar", &show_tab_bar); // Will use contents size + ImGui::Checkbox("Child", &show_child); // Will grow and use contents size + ImGui::Checkbox("Explicit content size", &explicit_content_size); + ImGui::Text("Scroll %.1f/%.1f %.1f/%.1f", ImGui::GetScrollX(), ImGui::GetScrollMaxX(), ImGui::GetScrollY(), ImGui::GetScrollMaxY()); + if (explicit_content_size) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(100); + ImGui::DragFloat("##csx", &contents_size_x); + ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + 10, p.y + 10), IM_COL32_WHITE); + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(p.x + contents_size_x - 10, p.y), ImVec2(p.x + contents_size_x, p.y + 10), IM_COL32_WHITE); + ImGui::Dummy(ImVec2(0, 10)); + } + ImGui::PopStyleVar(2); + ImGui::Separator(); + if (show_button) { + ImGui::Button("this is a 300-wide button", ImVec2(300, 0)); + } + if (show_tree_nodes) { + bool open = true; + if (ImGui::TreeNode("this is a tree node")) { + if (ImGui::TreeNode("another one of those tree node...")) { + ImGui::Text("Some tree contents"); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + ImGui::CollapsingHeader("CollapsingHeader", &open); + } + if (show_text_wrapped) { + ImGui::TextWrapped("This text should automatically wrap on the edge of the work rectangle."); + } + if (show_columns) { + ImGui::Text("Tables:"); + if (ImGui::BeginTable("table", 4, ImGuiTableFlags_Borders)) { + for (int n = 0; n < 4; n++) { + ImGui::TableNextColumn(); + ImGui::Text("Width %.2f", ImGui::GetContentRegionAvail().x); + } + ImGui::EndTable(); + } + ImGui::Text("Columns:"); + ImGui::Columns(4); + for (int n = 0; n < 4; n++) { + ImGui::Text("Width %.2f", ImGui::GetColumnWidth()); + ImGui::NextColumn(); + } + ImGui::Columns(1); + } + if (show_tab_bar && ImGui::BeginTabBar("Hello")) { + if (ImGui::BeginTabItem("OneOneOne")) { + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("TwoTwoTwo")) { + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("ThreeThreeThree")) { + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("FourFourFour")) { + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + if (show_child) { + ImGui::BeginChild("child", ImVec2(0, 0), true); + ImGui::EndChild(); + } + ImGui::End(); + } + + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Layout/Clipping"); + if (ImGui::TreeNode("Clipping")) { + static ImVec2 size(100.0f, 100.0f); + 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" + "(use this if you want your clipping rectangle to affect interactions)\n\n" + "(Center) Using ImDrawList::PushClipRect():\n" + "Will alter ImDrawList rendering only.\n" + "(use this as a shortcut if you are only using ImDrawList calls)\n\n" + "(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)) { + offset.x += ImGui::GetIO().MouseDelta.x; + offset.y += ImGui::GetIO().MouseDelta.y; + } + 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!"; + const ImVec2 text_pos = ImVec2(p0.x + offset.x, p0.y + offset.y); + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + switch (n) { + case 0: + ImGui::PushClipRect(p0, p1, true); + draw_list->AddRectFilled(p0, p1, IM_COL32(90, 90, 120, 255)); + draw_list->AddText(text_pos, IM_COL32_WHITE, text_str); + ImGui::PopClipRect(); + break; + case 1: + draw_list->PushClipRect(p0, p1, true); + draw_list->AddRectFilled(p0, p1, IM_COL32(90, 90, 120, 255)); + draw_list->AddText(text_pos, IM_COL32_WHITE, text_str); + draw_list->PopClipRect(); + break; + case 2: + ImVec4 clip_rect(p0.x, p0.y, p1.x, p1.y); // AddText() takes a ImVec4* here so let's convert. + draw_list->AddRectFilled(p0, p1, IM_COL32(90, 90, 120, 255)); + draw_list->AddText(ImGui::GetFont(), ImGui::GetFontSize(), text_pos, IM_COL32_WHITE, text_str, NULL, 0.0f, &clip_rect); + 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) + ImGui::SetNextItemAllowOverlap(); + 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.) + if (enable_allow_overlap) + ImGui::SetNextItemAllowOverlap(); + ImGui::Selectable("Some Selectable", false); + ImGui::SameLine(); + ImGui::SmallButton("++"); + + ImGui::TreePop(); + } +} + +static void ShowDemoWindowPopups() +{ + 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. + // - Their visibility state (~bool) is held internally by Dear ImGui instead of being held by the programmer as + // we are used to with regular Begin() calls. User can manipulate the visibility state by calling OpenPopup(). + // (*) One can use IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup) to bypass it and detect hovering even + // 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..")) + ImGui::OpenPopup("my_select_popup"); + ImGui::SameLine(); + ImGui::TextUnformatted(selected_fish == -1 ? "" : names[selected_fish]); + if (ImGui::BeginPopup("my_select_popup")) { + ImGui::SeparatorText("Aquarium"); + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + if (ImGui::Selectable(names[i])) + selected_fish = i; + ImGui::EndPopup(); + } + + // Showing a menu with toggles + if (ImGui::Button("Toggle..")) + ImGui::OpenPopup("my_toggle_popup"); + if (ImGui::BeginPopup("my_toggle_popup")) { + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) { + 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")) { + for (int i = 0; i < IM_ARRAYSIZE(names); i++) + ImGui::MenuItem(names[i], "", &toggles[i]); + if (ImGui::BeginMenu("Sub-menu")) { + ImGui::MenuItem("Click me"); + if (ImGui::Button("Stacked Popup")) + ImGui::OpenPopup("another popup"); + if (ImGui::BeginPopup("another popup")) { + ImGui::Text("I am the last one here."); + ImGui::EndPopup(); + } + ImGui::EndMenu(); + } + ImGui::EndPopup(); + } + 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"); + if (ImGui::BeginPopup("my_file_popup", ImGuiWindowFlags_MenuBar)) { + if (ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu("File")) { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Edit")) { + ImGui::MenuItem("Dummy"); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + ImGui::Text("Hello from popup!"); + 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 + // if (IsItemHovered() && IsMouseReleased(ImGuiMouseButton_Right)) + // OpenPopup(id); + // 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. + { + const char *names[5] = {"Label1", "Label2", "Label3", "Label4", "Label5"}; + static int selected = -1; + for (int n = 0; n < 5; n++) { + if (ImGui::Selectable(names[n], selected == n)) + selected = n; + if (ImGui::BeginPopupContextItem()) // <-- use last item id as popup id + { + selected = n; + ImGui::Text("This a popup for \"%s\"!", names[n]); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + 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. + { + HelpMarker("Text() elements don't have stable identifiers so we need to provide one."); + static float value = 0.5f; + ImGui::Text("Value = %.3f <-- (1) right-click this text", value); + if (ImGui::BeginPopupContextItem("my popup")) { + if (ImGui::Selectable("Set to zero")) + value = 0.0f; + if (ImGui::Selectable("Set to PI")) + value = 3.1415f; + ImGui::SetNextItemWidth(-FLT_MIN); + 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. + // In this example we showcase altering the item label while preserving its identifier, using the ### operator (see FAQ). + { + HelpMarker("Showcase using a popup ID linked to item ID, with the item having a changing label + stable ID using the ### operator."); + static char name[32] = "Label1"; + char buf[64]; + sprintf(buf, "Button: %s###Button", name); // ### operator override ID ignoring the preceding label + ImGui::Button(buf); + if (ImGui::BeginPopupContextItem()) { + ImGui::Text("Edit name:"); + ImGui::InputText("##edit", name, IM_ARRAYSIZE(name)); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + 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)) { + if (ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("Some menu item")) { } + ImGui::EndMenu(); + } + 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. + bool unused_open = true; + if (ImGui::BeginPopupModal("Stacked 2", &unused_open)) { + ImGui::Text("Hello from Stacked The Second!"); + if (ImGui::Button("Close")) + 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")) { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::Separator(); + 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 +{ +// We are passing our own identifier to TableSetupColumn() to facilitate identifying columns in the sorting code. +// This identifier will be passed down into ImGuiTableSortSpec::ColumnUserID. +// But it is possible to omit the user id parameter of TableSetupColumn() and just use the column index instead! (ImGuiTableSortSpec::ColumnIndex) +// If you don't use sorting, you will generally never care about giving column an ID! +enum MyItemColumnID { MyItemColumnID_ID, MyItemColumnID_Name, MyItemColumnID_Action, 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. + // As a workaround, we are storing the sort specs in a static/global for the comparing function to access. + // In your own use case you would probably pass the sort specs to your sorting/comparing functions directly and not use a global. + // 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. + if (items_count > 1) + 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) + { + const MyItem *a = (const MyItem *)lhs; + const MyItem *b = (const MyItem *)rhs; + for (int n = 0; n < s_current_sort_specs->SpecsCount; n++) { + // Here we identify columns using the ColumnUserID value that we ourselves passed to TableSetupColumn() + // We could also choose to identify columns based on their index (sort_spec->ColumnIndex), which is simpler! + const ImGuiTableColumnSortSpecs *sort_spec = &s_current_sort_specs->Specs[n]; + int delta = 0; + switch (sort_spec->ColumnUserID) { + case MyItemColumnID_ID: + delta = (a->ID - b->ID); + break; + case MyItemColumnID_Name: + delta = (strcmp(a->Name, b->Name)); + break; + case MyItemColumnID_Quantity: + delta = (a->Quantity - b->Quantity); + break; + case MyItemColumnID_Description: + delta = (strcmp(a->Name, b->Name)); + break; + default: + IM_ASSERT(0); + break; + } + if (delta > 0) + return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? +1 : -1; + if (delta < 0) + return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? -1 : +1; + } + + // qsort() is instable so always return a way to differenciate 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); + } +}; +const ImGuiTableSortSpecs *MyItem::s_current_sort_specs = NULL; +} + +// Make the UI compact because there are so many fields +static void PushStyleCompact() +{ + ImGuiStyle &style = ImGui::GetStyle(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, (float)(int)(style.FramePadding.y * 0.60f))); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, (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) +{ + struct EnumDesc { + ImGuiTableFlags Value; + const char *Name; + const char *Tooltip; + }; + static const EnumDesc policies[] = { + {ImGuiTableFlags_None, + "Default", + "Use default sizing policy:\n- ImGuiTableFlags_SizingFixedFit if ScrollX is on or if host window has ImGuiWindowFlags_AlwaysAutoResize.\n- " + "ImGuiTableFlags_SizingStretchSame otherwise."}, + {ImGuiTableFlags_SizingFixedFit, + "ImGuiTableFlags_SizingFixedFit", + "Columns default to _WidthFixed (if resizable) or _WidthAuto (if not resizable), matching contents width."}, + {ImGuiTableFlags_SizingFixedSame, + "ImGuiTableFlags_SizingFixedSame", + "Columns are all the same width, matching the maximum contents width.\nImplicitly disable ImGuiTableFlags_Resizable and enable " + "ImGuiTableFlags_NoKeepColumnsVisible."}, + {ImGuiTableFlags_SizingStretchProp, "ImGuiTableFlags_SizingStretchProp", "Columns default to _WidthStretch with weights proportional to their widths."}, + {ImGuiTableFlags_SizingStretchSame, "ImGuiTableFlags_SizingStretchSame", "Columns default to _WidthStretch with same weights."}}; + int idx; + for (idx = 0; idx < IM_ARRAYSIZE(policies); idx++) + if (policies[idx].Value == (*p_flags & ImGuiTableFlags_SizingMask_)) + break; + const char *preview_text = (idx < IM_ARRAYSIZE(policies)) ? policies[idx].Name + (idx > 0 ? strlen("ImGuiTableFlags") : 0) : ""; + if (ImGui::BeginCombo("Sizing Policy", preview_text)) { + for (int n = 0; n < IM_ARRAYSIZE(policies); n++) + if (ImGui::Selectable(policies[n].Name, idx == n)) + *p_flags = (*p_flags & ~ImGuiTableFlags_SizingMask_) | policies[n].Value; + ImGui::EndCombo(); + } + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::BeginItemTooltip()) { + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 50.0f); + for (int m = 0; m < IM_ARRAYSIZE(policies); m++) { + ImGui::Separator(); + ImGui::Text("%s:", policies[m].Name); + ImGui::Separator(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetStyle().IndentSpacing * 0.5f); + ImGui::TextUnformatted(policies[m].Tooltip); + } + ImGui::PopTextWrapPos(); + 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)"); + ImGui::CheckboxFlags("_DefaultHide", p_flags, ImGuiTableColumnFlags_DefaultHide); + ImGui::CheckboxFlags("_DefaultSort", p_flags, ImGuiTableColumnFlags_DefaultSort); + if (ImGui::CheckboxFlags("_WidthStretch", p_flags, ImGuiTableColumnFlags_WidthStretch)) + *p_flags &= ~(ImGuiTableColumnFlags_WidthMask_ ^ ImGuiTableColumnFlags_WidthStretch); + if (ImGui::CheckboxFlags("_WidthFixed", p_flags, ImGuiTableColumnFlags_WidthFixed)) + *p_flags &= ~(ImGuiTableColumnFlags_WidthMask_ ^ ImGuiTableColumnFlags_WidthFixed); + ImGui::CheckboxFlags("_NoResize", p_flags, ImGuiTableColumnFlags_NoResize); + ImGui::CheckboxFlags("_NoReorder", p_flags, ImGuiTableColumnFlags_NoReorder); + ImGui::CheckboxFlags("_NoHide", p_flags, ImGuiTableColumnFlags_NoHide); + ImGui::CheckboxFlags("_NoClip", p_flags, ImGuiTableColumnFlags_NoClip); + ImGui::CheckboxFlags("_NoSort", p_flags, ImGuiTableColumnFlags_NoSort); + ImGui::CheckboxFlags("_NoSortAscending", p_flags, ImGuiTableColumnFlags_NoSortAscending); + ImGui::CheckboxFlags("_NoSortDescending", p_flags, ImGuiTableColumnFlags_NoSortDescending); + ImGui::CheckboxFlags("_NoHeaderLabel", p_flags, ImGuiTableColumnFlags_NoHeaderLabel); + ImGui::CheckboxFlags("_NoHeaderWidth", p_flags, ImGuiTableColumnFlags_NoHeaderWidth); + ImGui::CheckboxFlags("_PreferSortAscending", p_flags, ImGuiTableColumnFlags_PreferSortAscending); + ImGui::CheckboxFlags("_PreferSortDescending", p_flags, ImGuiTableColumnFlags_PreferSortDescending); + ImGui::CheckboxFlags("_IndentEnable", p_flags, ImGuiTableColumnFlags_IndentEnable); + ImGui::SameLine(); + HelpMarker("Default for column 0"); + ImGui::CheckboxFlags("_IndentDisable", p_flags, ImGuiTableColumnFlags_IndentDisable); + ImGui::SameLine(); + HelpMarker("Default for column >0"); +} + +static void ShowTableColumnsStatusFlags(ImGuiTableColumnFlags flags) +{ + ImGui::CheckboxFlags("_IsEnabled", &flags, ImGuiTableColumnFlags_IsEnabled); + ImGui::CheckboxFlags("_IsVisible", &flags, ImGuiTableColumnFlags_IsVisible); + ImGui::CheckboxFlags("_IsSorted", &flags, ImGuiTableColumnFlags_IsSorted); + ImGui::CheckboxFlags("_IsHovered", &flags, ImGuiTableColumnFlags_IsHovered); +} + +static void ShowDemoWindowTables() +{ + // 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("Open all")) + open_action = 1; + ImGui::SameLine(); + if (ImGui::Button("Close all")) + open_action = 0; + ImGui::SameLine(); + + // Options + static bool disable_indent = false; + ImGui::Checkbox("Disable tree indentation", &disable_indent); + ImGui::SameLine(); + HelpMarker("Disable the indenting of tree nodes so demo tables can use the full window width."); + 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: + // style.CellPadding // Padding within each cell + // style.Colors[ImGuiCol_TableHeaderBg] // Table header background + // style.Colors[ImGuiCol_TableBorderStrong] // Table outer and header borders + // 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); + IMGUI_DEMO_MARKER("Tables/Basic"); + if (ImGui::TreeNode("Basic")) { + // 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."); + if (ImGui::BeginTable("table1", 3)) { + for (int row = 0; row < 4; row++) { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Row %d Column %d", row, column); + } + } + 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."); + if (ImGui::BeginTable("table2", 3)) { + for (int row = 0; row < 4; row++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Row %d", row); + ImGui::TableNextColumn(); + ImGui::Text("Some contents"); + ImGui::TableNextColumn(); + ImGui::Text("123.456"); + } + 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. + HelpMarker( + "Only using TableNextColumn(), which tends to be convenient for tables where every cell contains the same type of contents.\n" + "This is also more similar to the old NextColumn() function of the Columns API, and provided to facilitate the Columns->Tables API transition."); + if (ImGui::BeginTable("table3", 3)) { + for (int item = 0; item < 14; item++) { + ImGui::TableNextColumn(); + ImGui::Text("Item %d", item); + } + ImGui::EndTable(); + } + + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Borders, background"); + if (ImGui::TreeNode("Borders, background")) { + // Expose a few Borders related flags interactively + enum ContentsType { CT_Text, CT_FillButton }; + 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_BordersInnerV\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. + // (Headers are not the main purpose of this section of the demo, so we are not elaborating on them too much. See other sections for details) + if (display_headers) { + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + ImGui::TableHeadersRow(); + } + + for (int row = 0; row < 5; row++) { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) { + ImGui::TableSetColumnIndex(column); + char buf[32]; + sprintf(buf, "Hello %d,%d", column, row); + if (contents_type == CT_Text) + ImGui::TextUnformatted(buf); + else if (contents_type == CT_FillButton) + ImGui::Button(buf, ImVec2(-FLT_MIN, 0.0f)); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Resizable, stretch"); + if (ImGui::TreeNode("Resizable, stretch")) { + // By default, if we don't enable ScrollX the sizing policy for each column is "Stretch" + // All columns maintain a sizing weight, and they will occupy all available width. + static ImGuiTableFlags flags = ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV + | ImGuiTableFlags_ContextMenuInBody; + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", &flags, ImGuiTableFlags_BordersV); + ImGui::SameLine(); + HelpMarker( + "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++) { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Hello %d,%d", column, row); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Resizable, fixed"); + if (ImGui::TreeNode("Resizable, fixed")) { + // Here we use ImGuiTableFlags_SizingFixedFit (even though _ScrollX is not set) + // So columns will adopt the "Fixed" policy and will maintain a fixed width regardless of the whole available width (unless table is small) + // If there is not enough available width to fit all columns, they will however be resized down. + // FIXME-TABLE: Providing a stretch-on-init would make sense especially for tables which don't have saved settings + HelpMarker( + "Using _Resizable + _SizingFixedFit flags.\n" + "Fixed-width columns generally makes more sense if you want to use horizontal scrolling.\n\n" + "Double-click a column border to auto-fit the column to its contents."); + PushStyleCompact(); + 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++) { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Hello %d,%d", column, row); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Resizable, mixed"); + if (ImGui::TreeNode("Resizable, mixed")) { + HelpMarker( + "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); + ImGui::TableSetupColumn("BBB", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("CCC", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableHeadersRow(); + for (int row = 0; row < 5; row++) { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) { + ImGui::TableSetColumnIndex(column); + ImGui::Text("%s %d,%d", (column == 2) ? "Stretch" : "Fixed", column, row); + } + } + ImGui::EndTable(); + } + if (ImGui::BeginTable("table2", 6, flags)) { + ImGui::TableSetupColumn("AAA", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("BBB", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("CCC", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_DefaultHide); + ImGui::TableSetupColumn("DDD", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("EEE", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("FFF", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_DefaultHide); + ImGui::TableHeadersRow(); + for (int row = 0; row < 5; row++) { + ImGui::TableNextRow(); + for (int column = 0; column < 6; column++) { + ImGui::TableSetColumnIndex(column); + ImGui::Text("%s %d,%d", (column >= 3) ? "Stretch" : "Fixed", column, row); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Reorderable, hideable, with headers"); + if (ImGui::TreeNode("Reorderable, hideable, with headers")) { + HelpMarker( + "Click and drag column headers to reorder columns.\n\n" + "Right-click on a header to open a context menu."); + static ImGuiTableFlags flags = + ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV; + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("ImGuiTableFlags_Reorderable", &flags, ImGuiTableFlags_Reorderable); + ImGui::CheckboxFlags("ImGuiTableFlags_Hideable", &flags, ImGuiTableFlags_Hideable); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", &flags, ImGuiTableFlags_NoBordersInBody); + 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)"); + 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. + // (Later we will show how TableSetupColumn() has other uses, optional flags, sizing weight etc.) + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + ImGui::TableHeadersRow(); + for (int row = 0; row < 6; row++) { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Hello %d,%d", column, row); + } + } + 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))) { + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + ImGui::TableHeadersRow(); + for (int row = 0; row < 6; row++) { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Fixed %d,%d", column, row); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Padding"); + if (ImGui::TreeNode("Padding")) { + // First example: showcase use of padding flags and effect of BorderOuterV/BorderInnerV on X padding. + // We don't expose BorderOuterH/BorderInnerH here because they have no effect on X padding. + HelpMarker( + "We often want outer padding activated when any using features which makes the edges of a column visible:\n" + "e.g.:\n" + "- BorderOuterV\n" + "- any form of row selection\n" + "Because of this, activating BorderOuterV sets the default to PadOuterX. 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); + ImGui::SameLine(); + HelpMarker("Enable outer-most padding (default if ImGuiTableFlags_BordersOuterV is set)"); + ImGui::CheckboxFlags("ImGuiTableFlags_NoPadOuterX", &flags1, ImGuiTableFlags_NoPadOuterX); + ImGui::SameLine(); + HelpMarker("Disable outer-most padding (default if ImGuiTableFlags_BordersOuterV is not set)"); + ImGui::CheckboxFlags("ImGuiTableFlags_NoPadInnerX", &flags1, ImGuiTableFlags_NoPadInnerX); + ImGui::SameLine(); + HelpMarker("Disable inner padding between columns (double inner padding if BordersOuterV is on, single inner padding if BordersOuterV is off)"); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterV", &flags1, ImGuiTableFlags_BordersOuterV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", &flags1, ImGuiTableFlags_BordersInnerV); + static bool show_headers = false; + ImGui::Checkbox("show_headers", &show_headers); + PopStyleCompact(); + + if (ImGui::BeginTable("table_padding", 3, flags1)) { + if (show_headers) { + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + ImGui::TableHeadersRow(); + } + + for (int row = 0; row < 5; row++) { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) { + ImGui::TableSetColumnIndex(column); + if (row == 0) { + ImGui::Text("Avail %.2f", ImGui::GetContentRegionAvail().x); + } else { + char buf[32]; + sprintf(buf, "Hello %d,%d", column, row); + ImGui::Button(buf, ImVec2(-FLT_MIN, 0.0f)); + } + // if (ImGui::TableGetColumnFlags() & ImGuiTableColumnFlags_IsHovered) + // ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, IM_COL32(0, 100, 0, 255)); + } + } + 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); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", &flags2, ImGuiTableFlags_BordersV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInner", &flags2, ImGuiTableFlags_BordersInner); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuter", &flags2, ImGuiTableFlags_BordersOuter); + ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", &flags2, ImGuiTableFlags_RowBg); + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags2, ImGuiTableFlags_Resizable); + 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)) { + static char text_bufs[3 * 5][16]; // Mini text storage for 3x5 cells + static bool init = true; + if (!show_widget_frame_bg) + ImGui::PushStyleColor(ImGuiCol_FrameBg, 0); + for (int cell = 0; cell < 3 * 5; cell++) { + ImGui::TableNextColumn(); + if (init) + strcpy(text_bufs[cell], "edit me"); + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::PushID(cell); + ImGui::InputText("##cell", text_bufs[cell], IM_ARRAYSIZE(text_bufs[cell])); + ImGui::PopID(); + } + if (!show_widget_frame_bg) + ImGui::PopStyleColor(); + init = false; + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Explicit widths"); + if (ImGui::TreeNode("Sizing policies")) { + static ImGuiTableFlags flags1 = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_RowBg | ImGuiTableFlags_ContextMenuInBody; + PushStyleCompact(); + 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. + if (ImGui::BeginTable("table1", 3, sizing_policy_flags[table_n] | flags1)) { + for (int row = 0; row < 3; row++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Oh dear"); + ImGui::TableNextColumn(); + ImGui::Text("Oh dear"); + ImGui::TableNextColumn(); + ImGui::Text("Oh dear"); + } + ImGui::EndTable(); + } + if (ImGui::BeginTable("table2", 3, sizing_policy_flags[table_n] | flags1)) { + for (int row = 0; row < 3; row++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("AAAA"); + ImGui::TableNextColumn(); + ImGui::Text("BBBBBBBB"); + ImGui::TableNextColumn(); + ImGui::Text("CCCCCCCCCCCC"); + } + ImGui::EndTable(); + } + 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); + EditTableSizingFlags(&flags); + ImGui::Combo("Contents", &contents_type, "Show width\0Short Text\0Long Text\0Button\0Fill Button\0InputText\0"); + if (contents_type == CT_FillButton) { + ImGui::SameLine(); + HelpMarker( + "Be mindful that using right-alignment (e.g. size.x = -FLT_MIN) creates a feedback loop where contents width can feed into auto-column width " + "can feed into contents width."); + } + ImGui::DragInt("Columns", &column_count, 0.1f, 1, 64, "%d", ImGuiSliderFlags_AlwaysClamp); + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); + ImGui::CheckboxFlags("ImGuiTableFlags_PreciseWidths", &flags, ImGuiTableFlags_PreciseWidths); + ImGui::SameLine(); + HelpMarker( + "Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: 33,33,34. With " + "this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth."); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", &flags, ImGuiTableFlags_ScrollX); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", &flags, ImGuiTableFlags_ScrollY); + ImGui::CheckboxFlags("ImGuiTableFlags_NoClip", &flags, ImGuiTableFlags_NoClip); + 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++) { + ImGui::TableNextColumn(); + int column = ImGui::TableGetColumnIndex(); + int row = ImGui::TableGetRowIndex(); + + ImGui::PushID(cell); + char label[32]; + static char text_buf[32] = ""; + sprintf(label, "Hello %d,%d", column, row); + switch (contents_type) { + case CT_ShortText: + ImGui::TextUnformatted(label); + break; + case CT_LongText: + ImGui::Text("Some %s text %d,%d\nOver two lines..", column == 0 ? "long" : "longeeer", column, row); + break; + case CT_ShowWidth: + ImGui::Text("W: %.1f", ImGui::GetContentRegionAvail().x); + break; + case CT_Button: + ImGui::Button(label); + break; + case CT_FillButton: + ImGui::Button(label, ImVec2(-FLT_MIN, 0.0f)); + break; + case CT_InputText: + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::InputText("##", text_buf, IM_ARRAYSIZE(text_buf)); + break; + } + ImGui::PopID(); + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Vertical scrolling, with clipping"); + if (ImGui::TreeNode("Vertical scrolling, with clipping")) { + HelpMarker( + "Here we activate ScrollY, which will create a child window container to allow hosting scrollable contents.\n\nWe 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); + if (ImGui::BeginTable("table_scrolly", 3, flags, outer_size)) { + ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible + ImGui::TableSetupColumn("One", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Two", ImGuiTableColumnFlags_None); + ImGui::TableSetupColumn("Three", ImGuiTableColumnFlags_None); + ImGui::TableHeadersRow(); + + // Demonstrate using clipper for large vertical lists + ImGuiListClipper clipper; + clipper.Begin(1000); + while (clipper.Step()) { + for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Hello %d,%d", column, row); + } + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Horizontal scrolling"); + if (ImGui::TreeNode("Horizontal scrolling")) { + HelpMarker( + "When ScrollX is enabled, the default sizing policy becomes ImGuiTableFlags_SizingFixedFit, " + "as automatically stretching columns doesn't make much sense with horizontal scrolling.\n\n" + "Also note that as of the current version, you will almost always want to enable ScrollY along with ScrollX," + "because the container window won't automatically extend vertically to fix contents (this may be improved in future versions)."); + 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); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", &flags, ImGuiTableFlags_ScrollY); + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + ImGui::DragInt("freeze_cols", &freeze_cols, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); + 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); + if (ImGui::BeginTable("table_scrollx", 7, flags, outer_size)) { + ImGui::TableSetupScrollFreeze(freeze_cols, freeze_rows); + ImGui::TableSetupColumn("Line #", ImGuiTableColumnFlags_NoHide); // Make the first column not hideable to match our use of TableSetupScrollFreeze() + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + ImGui::TableSetupColumn("Four"); + ImGui::TableSetupColumn("Five"); + ImGui::TableSetupColumn("Six"); + ImGui::TableHeadersRow(); + for (int row = 0; row < 20; row++) { + ImGui::TableNextRow(); + for (int column = 0; column < 7; column++) { + // Both TableNextColumn() and TableSetColumnIndex() return true when a column is visible or performing width measurement. + // Because here we know that: + // - A) all our columns are contributing the same to row height + // - B) column 0 is always visible, + // We only always submit this one column and can skip others. + // More advanced per-column clipping behaviors may benefit from polling the status flags via TableGetColumnFlags(). + if (!ImGui::TableSetColumnIndex(column) && column > 0) + continue; + if (column == 0) + ImGui::Text("Line %d", row); + else + ImGui::Text("Hello world %d,%d", column, row); + } + } + ImGui::EndTable(); + } + + ImGui::Spacing(); + ImGui::TextUnformatted("Stretch + ScrollX"); + ImGui::SameLine(); + HelpMarker( + "Showcase using Stretch columns + ScrollX together: " + "this is rather unusual and only makes sense when specifying an 'inner_width' for the table!\n" + "Without an explicit value, inner_width is == outer_size.x and therefore using Stretch columns + ScrollX together doesn't make sense."); + static ImGuiTableFlags flags2 = ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter + | ImGuiTableFlags_RowBg | ImGuiTableFlags_ContextMenuInBody; + static float inner_width = 1000.0f; + PushStyleCompact(); + ImGui::PushID("flags3"); + ImGui::PushItemWidth(TEXT_BASE_WIDTH * 30); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", &flags2, ImGuiTableFlags_ScrollX); + ImGui::DragFloat("inner_width", &inner_width, 1.0f, 0.0f, FLT_MAX, "%.1f"); + ImGui::PopItemWidth(); + ImGui::PopID(); + PopStyleCompact(); + if (ImGui::BeginTable("table2", 7, flags2, outer_size, inner_width)) { + for (int cell = 0; cell < 20 * 7; cell++) { + ImGui::TableNextColumn(); + ImGui::Text("Hello world %d,%d", ImGui::TableGetColumnIndex(), ImGui::TableGetRowIndex()); + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Columns flags"); + if (ImGui::TreeNode("Columns flags")) { + // Create a first table just to show all the options/flags we want to make visible in our example! + const int column_count = 3; + 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(); + for (int column = 0; column < column_count; column++) { + ImGui::TableNextColumn(); + ImGui::PushID(column); + ImGui::AlignTextToFramePadding(); // FIXME-TABLE: Workaround for wrong text baseline propagation across columns + ImGui::Text("'%s'", column_names[column]); + ImGui::Spacing(); + ImGui::Text("Input flags:"); + EditTableColumnsFlags(&column_flags[column]); + ImGui::Spacing(); + ImGui::Text("Output flags:"); + ImGui::BeginDisabled(); + ShowTableColumnsStatusFlags(column_flags_out[column]); + ImGui::EndDisabled(); + ImGui::PopID(); + } + 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 + resizing the parent window down) + const ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg + | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable + | ImGuiTableFlags_Sortable; + ImVec2 outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 9); + if (ImGui::BeginTable("table_columns_flags", column_count, flags, outer_size)) { + for (int column = 0; column < column_count; column++) + ImGui::TableSetupColumn(column_names[column], column_flags[column]); + ImGui::TableHeadersRow(); + for (int column = 0; column < column_count; column++) + column_flags_out[column] = ImGui::TableGetColumnFlags(column); + float indent_step = (float)((int)TEXT_BASE_WIDTH / 2); + for (int row = 0; row < 8; row++) { + ImGui::Indent(indent_step); // Add some indentation to demonstrate usage of per-column IndentEnable/IndentDisable flags. + ImGui::TableNextRow(); + for (int column = 0; column < column_count; column++) { + ImGui::TableSetColumnIndex(column); + ImGui::Text("%s %s", (column == 0) ? "Indented" : "Hello", ImGui::TableGetColumnName(column)); + } + } + 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); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBodyUntilResize", &flags1, ImGuiTableFlags_NoBordersInBodyUntilResize); + PopStyleCompact(); + if (ImGui::BeginTable("table1", 3, flags1)) { + // We could also set ImGuiTableFlags_SizingFixedFit on the table and all columns will default to ImGuiTableColumnFlags_WidthFixed. + ImGui::TableSetupColumn("one", ImGuiTableColumnFlags_WidthFixed, 100.0f); // Default to 100.0f + ImGui::TableSetupColumn("two", ImGuiTableColumnFlags_WidthFixed, 200.0f); // Default to 200.0f + ImGui::TableSetupColumn("three", ImGuiTableColumnFlags_WidthFixed); // Default to auto + ImGui::TableHeadersRow(); + for (int row = 0; row < 4; row++) { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) { + ImGui::TableSetColumnIndex(column); + if (row == 0) + ImGui::Text("(w: %5.1f)", ImGui::GetContentRegionAvail().x); + else + ImGui::Text("Hello %d,%d", column, row); + } + } + 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); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", &flags2, ImGuiTableFlags_BordersInnerV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterV", &flags2, ImGuiTableFlags_BordersOuterV); + PopStyleCompact(); + if (ImGui::BeginTable("table2", 4, flags2)) { + // We could also set ImGuiTableFlags_SizingFixedFit on the table and all columns will default to ImGuiTableColumnFlags_WidthFixed. + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 100.0f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 15.0f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 30.0f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 15.0f); + for (int row = 0; row < 5; row++) { + ImGui::TableNextRow(); + for (int column = 0; column < 4; column++) { + ImGui::TableSetColumnIndex(column); + if (row == 0) + ImGui::Text("(w: %5.1f)", ImGui::GetContentRegionAvail().x); + else + ImGui::Text("Hello %d,%d", column, row); + } + } + ImGui::EndTable(); + } + 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"); + { + float rows_height = TEXT_BASE_HEIGHT * 2; + if (ImGui::BeginTable("table_nested2", + 2, + ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) { + ImGui::TableSetupColumn("B0"); + ImGui::TableSetupColumn("B1"); + ImGui::TableHeadersRow(); + + ImGui::TableNextRow(ImGuiTableRowFlags_None, rows_height); + ImGui::TableNextColumn(); + ImGui::Text("B0 Row 0"); + ImGui::TableNextColumn(); + ImGui::Text("B1 Row 0"); + ImGui::TableNextRow(ImGuiTableRowFlags_None, rows_height); + ImGui::TableNextColumn(); + ImGui::Text("B0 Row 1"); + ImGui::TableNextColumn(); + ImGui::Text("B1 Row 1"); + + ImGui::EndTable(); + } + } + ImGui::TableNextColumn(); + ImGui::Text("A1 Row 0"); + ImGui::TableNextColumn(); + ImGui::Text("A0 Row 1"); + ImGui::TableNextColumn(); + ImGui::Text("A1 Row 1"); + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Row height"); + if (ImGui::TreeNode("Row height")) { + HelpMarker( + "You can pass a 'min_row_height' to TableNextRow().\n\nRows are padded with 'style.CellPadding.y' on top and bottom, so effectively the minimum " + "row height will always be >= 'style.CellPadding.y * 2.0f'.\n\nWe cannot honor a _maximum_ row height as that would require a unique clipping " + "rectangle per row."); + if (ImGui::BeginTable("table_row_height", 1, ImGuiTableFlags_Borders)) { + for (int row = 0; row < 8; row++) { + float min_row_height = (float)(int)(TEXT_BASE_HEIGHT * 0.30f * row); + ImGui::TableNextRow(ImGuiTableRowFlags_None, min_row_height); + ImGui::TableNextColumn(); + ImGui::Text("min_row_height = %.2f", min_row_height); + } + ImGui::EndTable(); + } + + HelpMarker( + "Showcase using SameLine(0,0) to share Current Line Height between cells.\n\nPlease note that Tables Row Height is not the same thing as Current " + "Line Height, as a table cell may contains multiple lines."); + if (ImGui::BeginTable("table_share_lineheight", 2, ImGuiTableFlags_Borders)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::ColorButton("##1", ImVec4(0.13f, 0.26f, 0.40f, 1.0f), ImGuiColorEditFlags_None, ImVec2(40, 40)); + 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)); + ImGui::TableNextColumn(); + 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)) { + ImGuiStyle &style = ImGui::GetStyle(); + for (int row = 0; row < 8; row++) { + if ((row % 3) == 2) + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(style.CellPadding.x, 20.0f)); + ImGui::TableNextRow(ImGuiTableRowFlags_None); + ImGui::TableNextColumn(); + ImGui::Text("CellPadding.y = %.2f", style.CellPadding.y); + if ((row % 3) == 2) + ImGui::PopStyleVar(); + } + ImGui::EndTable(); + } + + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Outer size"); + if (ImGui::TreeNode("Outer size")) { + // Showcasing use of ImGuiTableFlags_NoHostExtendX and ImGuiTableFlags_NoHostExtendY + // Important to that note how the two flags have slightly different behaviors! + ImGui::Text("Using NoHostExtendX and NoHostExtendY:"); + PushStyleCompact(); + static ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_ContextMenuInBody | ImGuiTableFlags_RowBg + | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX; + ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendX", &flags, ImGuiTableFlags_NoHostExtendX); + ImGui::SameLine(); + HelpMarker( + "Make outer width auto-fit to columns, overriding outer_size.x value.\n\nOnly available when ScrollX/ScrollY are disabled and Stretch columns are " + "not used."); + 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)) { + for (int row = 0; row < 10; row++) { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) { + ImGui::TableNextColumn(); + ImGui::Text("Cell %d,%d", column, row); + } + } + ImGui::EndTable(); + } + 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))) { + for (int row = 0; row < 5; row++) { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) { + ImGui::TableNextColumn(); + ImGui::Text("Cell %d,%d", column, row); + } + } + ImGui::EndTable(); + } + ImGui::SameLine(); + if (ImGui::BeginTable("table3", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg, ImVec2(TEXT_BASE_WIDTH * 30, 0.0f))) { + for (int row = 0; row < 3; row++) { + ImGui::TableNextRow(0, TEXT_BASE_HEIGHT * 1.5f); + for (int column = 0; column < 3; column++) { + ImGui::TableNextColumn(); + ImGui::Text("Cell %d,%d", column, row); + } + } + ImGui::EndTable(); + } + + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Background color"); + if (ImGui::TreeNode("Background color")) { + static ImGuiTableFlags flags = ImGuiTableFlags_RowBg; + 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); + ImGui::SameLine(); + HelpMarker("ImGuiTableFlags_RowBg automatically sets RowBg0 to alternative colors pulled from the Style."); + ImGui::Combo("row bg type", (int *)&row_bg_type, "None\0Red\0Gradient\0"); + ImGui::Combo("row bg target", (int *)&row_bg_target, "RowBg0\0RowBg1\0"); + ImGui::SameLine(); + HelpMarker("Target RowBg0 to override the alternating odd/even colors,\nTarget RowBg1 to blend with them."); + ImGui::Combo("cell bg type", (int *)&cell_bg_type, "None\0Blue\0"); + ImGui::SameLine(); + HelpMarker("We are colorizing cells to B1->C2 here."); + IM_ASSERT(row_bg_type >= 0 && row_bg_type <= 2); + 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) { + 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) + // We can also pass a column number as a third parameter to TableSetBgColor() and do this outside the column loop. + if (row >= 1 && row <= 2 && column >= 1 && column <= 2 && cell_bg_type == 1) { + ImU32 cell_bg_color = ImGui::GetColorU32(ImVec4(0.3f, 0.3f, 0.7f, 0.65f)); + ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, cell_bg_color); + } + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Tree view"); + if (ImGui::TreeNode("Tree view")) { + static ImGuiTableFlags flags = + ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; + + if (ImGui::BeginTable("3ways", 3, flags)) { + // The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide); + 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 { + const char *Name; + const char *Type; + int Size; + int ChildIdx; + int ChildCount; + static void DisplayNode(const MyTreeNode *node, const MyTreeNode *all_nodes) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + const bool is_folder = (node->ChildCount > 0); + if (is_folder) { + bool open = ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_SpanFullWidth); + ImGui::TableNextColumn(); + ImGui::TextDisabled("--"); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(node->Type); + if (open) { + for (int child_n = 0; child_n < node->ChildCount; child_n++) + DisplayNode(&all_nodes[node->ChildIdx + child_n], all_nodes); + ImGui::TreePop(); + } + } else { + ImGui::TreeNodeEx(node->Name, + ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_NoTreePushOnOpen + | ImGuiTreeNodeFlags_SpanFullWidth); + ImGui::TableNextColumn(); + ImGui::Text("%d", node->Size); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(node->Type); + } + } + }; + static const MyTreeNode nodes[] = { + {"Root", "Folder", -1, 1, 3}, // 0 + {"Music", "Folder", -1, 4, 2}, // 1 + {"Textures", "Folder", -1, 6, 3}, // 2 + {"desktop.ini", "System file", 1024, -1, -1}, // 3 + {"File1_a.wav", "Audio file", 123000, -1, -1}, // 4 + {"File1_b.wav", "Audio file", 456000, -1, -1}, // 5 + {"Image001.png", "Image file", 203128, -1, -1}, // 6 + {"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"); + if (ImGui::TreeNode("Item width")) { + HelpMarker( + "Showcase using PushItemWidth() and how it is preserved on a per-column basis.\n\n" + "Note that on auto-resizing non-resizable fixed columns, querying the content width for e.g. right-alignment doesn't make sense."); + if (ImGui::BeginTable("table_item_width", 3, ImGuiTableFlags_Borders)) { + ImGui::TableSetupColumn("small"); + ImGui::TableSetupColumn("half"); + ImGui::TableSetupColumn("right-align"); + ImGui::TableHeadersRow(); + + for (int row = 0; row < 3; row++) { + ImGui::TableNextRow(); + if (row == 0) { + // Setup ItemWidth once (instead of setting up every time, which is also possible but less efficient) + ImGui::TableSetColumnIndex(0); + ImGui::PushItemWidth(TEXT_BASE_WIDTH * 3.0f); // Small + ImGui::TableSetColumnIndex(1); + ImGui::PushItemWidth(-ImGui::GetContentRegionAvail().x * 0.5f); + ImGui::TableSetColumnIndex(2); + ImGui::PushItemWidth(-FLT_MIN); // Right-aligned + } + + // Draw our contents + static float dummy_f = 0.0f; + ImGui::PushID(row); + ImGui::TableSetColumnIndex(0); + ImGui::SliderFloat("float0", &dummy_f, 0.0f, 1.0f); + ImGui::TableSetColumnIndex(1); + ImGui::SliderFloat("float1", &dummy_f, 0.0f, 1.0f); + ImGui::TableSetColumnIndex(2); + ImGui::SliderFloat("##float2", &dummy_f, 0.0f, 1.0f); // No visible label since right-aligned + ImGui::PopID(); + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + // Demonstrate using TableHeader() calls instead of TableHeadersRow() + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Custom headers"); + if (ImGui::TreeNode("Custom headers")) { + const int COLUMNS_COUNT = 3; + if (ImGui::BeginTable("table_custom_headers", COLUMNS_COUNT, ImGuiTableFlags_Borders | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) { + 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 + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + for (int column = 0; column < COLUMNS_COUNT; column++) { + ImGui::TableSetColumnIndex(column); + const char *column_name = ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn() + ImGui::PushID(column); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::Checkbox("##checkall", &column_selected[column]); + ImGui::PopStyleVar(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TableHeader(column_name); + ImGui::PopID(); + } + + for (int row = 0; row < 5; row++) { + ImGui::TableNextRow(); + for (int column = 0; column < 3; column++) { + char buf[32]; + sprintf(buf, "Cell %d,%d", column, row); + ImGui::TableSetColumnIndex(column); + ImGui::Selectable(buf, column_selected[column]); + } + } + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + // Demonstrate creating custom context menus inside columns, while playing it nice with context menus provided by TableHeadersRow()/TableHeader() + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Context menus"); + if (ImGui::TreeNode("Context menus")) { + HelpMarker( + "By default, right-clicking over a TableHeadersRow()/TableHeader() line will open the default context-menu.\nUsing " + "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) + const int COLUMNS_COUNT = 3; + if (ImGui::BeginTable("table_context_menu", COLUMNS_COUNT, flags1)) { + 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++) { + ImGui::TableNextRow(); + for (int column = 0; column < COLUMNS_COUNT; column++) { + ImGui::TableSetColumnIndex(column); + ImGui::Text("Cell %d,%d", column, row); + } + } + 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 + // [2.3] Right-click in columns to open another custom popup + HelpMarker( + "Demonstrate mixing table context menu (over header), item context button (over button) and custom per-colum context menu (over column body)."); + ImGuiTableFlags flags2 = + ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders; + if (ImGui::BeginTable("table_context_menu_2", COLUMNS_COUNT, flags2)) { + 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++) { + ImGui::TableNextRow(); + for (int column = 0; column < COLUMNS_COUNT; column++) { + // Submit dummy contents + 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(".."); + if (ImGui::BeginPopupContextItem()) { + ImGui::Text("This is the popup for Button(\"..\") in Cell %d,%d", column, row); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + 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) + int hovered_column = -1; + for (int column = 0; column < COLUMNS_COUNT + 1; column++) { + ImGui::PushID(column); + if (ImGui::TableGetColumnFlags(column) & ImGuiTableColumnFlags_IsHovered) + hovered_column = column; + if (hovered_column == column && !ImGui::IsAnyItemHovered() && ImGui::IsMouseReleased(1)) + ImGui::OpenPopup("MyPopup"); + if (ImGui::BeginPopup("MyPopup")) { + if (column == COLUMNS_COUNT) + ImGui::Text("This is a custom popup for unused space after the last column."); + else + ImGui::Text("This is a custom popup for Column %d", column); + if (ImGui::Button("Close")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + 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); + IMGUI_DEMO_MARKER("Tables/Synced instances"); + 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_ScrollY", &flags, ImGuiTableFlags_ScrollY); + ImGui::CheckboxFlags("ImGuiTableFlags_SizingFixedFit", &flags, ImGuiTableFlags_SizingFixedFit); + for (int n = 0; n < 3; n++) { + char buf[32]; + sprintf(buf, "Synced Table %d", n); + bool open = ImGui::CollapsingHeader(buf, ImGuiTreeNodeFlags_DefaultOpen); + if (open && ImGui::BeginTable("Table", 3, flags, ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 5))) { + ImGui::TableSetupColumn("One"); + ImGui::TableSetupColumn("Two"); + ImGui::TableSetupColumn("Three"); + ImGui::TableHeadersRow(); + const int cell_count = + (n == 1) ? 27 : 9; // Make second table have a scrollbar to verify that additional decoration is not affecting column positions. + for (int cell = 0; cell < cell_count; cell++) { + ImGui::TableNextColumn(); + ImGui::Text("this cell %d", cell); + } + ImGui::EndTable(); + } + } + 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) + static const char *template_items_names[] = {"Banana", + "Apple", + "Cherry", + "Watermelon", + "Grapefruit", + "Strawberry", + "Mango", + "Kiwi", + "Orange", + "Pineapple", + "Blueberry", + "Plum", + "Coconut", + "Pear", + "Apricot"}; + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Sorting"); + if (ImGui::TreeNode("Sorting")) { + // Create item list + static ImVector items; + if (items.Size == 0) { + items.resize(50, MyItem()); + for (int n = 0; n < items.Size; n++) { + const int template_n = n % IM_ARRAYSIZE(template_items_names); + MyItem &item = items[n]; + item.ID = n; + item.Name = template_items_names[template_n]; + item.Quantity = (n * n - n) % 20; // Assign default quantities + } + } + + // Options + static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable + | ImGuiTableFlags_SortMulti | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_NoBordersInBody + | ImGuiTableFlags_ScrollY; + PushStyleCompact(); + ImGui::CheckboxFlags("ImGuiTableFlags_SortMulti", &flags, ImGuiTableFlags_SortMulti); + ImGui::SameLine(); + HelpMarker( + "When sorting is enabled: hold shift when clicking headers to sort on multiple column. TableGetSortSpecs() may return specs where (SpecsCount > " + "1)."); + 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 + // We use the "user_id" parameter of TableSetupColumn() to specify a user id that will be stored in the sort specifications. + // This is so our sort function can identify a column given our own identifier. We could also identify them based on their index! + // Demonstrate using a mixture of flags among available sort-related flags: + // - ImGuiTableColumnFlags_DefaultSort + // - ImGuiTableColumnFlags_NoSort / ImGuiTableColumnFlags_NoSortAscending / ImGuiTableColumnFlags_NoSortDescending + // - ImGuiTableColumnFlags_PreferSortAscending / ImGuiTableColumnFlags_PreferSortDescending + ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, MyItemColumnID_ID); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 0.0f, MyItemColumnID_Name); + ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, MyItemColumnID_Action); + 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) { + 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); + while (clipper.Step()) + for (int row_n = clipper.DisplayStart; row_n < clipper.DisplayEnd; row_n++) { + // Display a data item + MyItem *item = &items[row_n]; + ImGui::PushID(item->ID); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%04d", item->ID); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(item->Name); + ImGui::TableNextColumn(); + ImGui::SmallButton("None"); + ImGui::TableNextColumn(); + ImGui::Text("%d", item->Quantity); + ImGui::PopID(); + } + ImGui::EndTable(); + } + 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. + // ImGui::SetNextItemOpen(true, ImGuiCond_Once); // [DEBUG] + if (open_action != -1) + ImGui::SetNextItemOpen(open_action != 0); + IMGUI_DEMO_MARKER("Tables/Advanced"); + if (ImGui::TreeNode("Advanced")) { + static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable + | ImGuiTableFlags_SortMulti | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollX + | ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingFixedFit; + + 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)"}; + static int freeze_cols = 1; + static int freeze_rows = 1; + static int items_count = IM_ARRAYSIZE(template_items_names) * 2; + static ImVec2 outer_size_value = ImVec2(0.0f, TEXT_BASE_HEIGHT * 12); + static float row_min_height = 0.0f; // Auto + static float inner_width_with_scroll = 0.0f; // Auto-extend + static bool outer_size_enabled = true; + static bool show_headers = true; + static bool show_wrapped_text = false; + // static ImGuiTextFilter filter; + // ImGui::SetNextItemOpen(true, ImGuiCond_Once); // FIXME-TABLE: Enabling this results in initial clipped first pass on table which tend to affect + // column sizing + if (ImGui::TreeNode("Options")) { + // 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); + ImGui::CheckboxFlags("ImGuiTableFlags_Reorderable", &flags, ImGuiTableFlags_Reorderable); + ImGui::CheckboxFlags("ImGuiTableFlags_Hideable", &flags, ImGuiTableFlags_Hideable); + ImGui::CheckboxFlags("ImGuiTableFlags_Sortable", &flags, ImGuiTableFlags_Sortable); + ImGui::CheckboxFlags("ImGuiTableFlags_NoSavedSettings", &flags, ImGuiTableFlags_NoSavedSettings); + ImGui::CheckboxFlags("ImGuiTableFlags_ContextMenuInBody", &flags, ImGuiTableFlags_ContextMenuInBody); + ImGui::TreePop(); + } + + if (ImGui::TreeNodeEx("Decorations:", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", &flags, ImGuiTableFlags_RowBg); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", &flags, ImGuiTableFlags_BordersV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterV", &flags, ImGuiTableFlags_BordersOuterV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerV", &flags, ImGuiTableFlags_BordersInnerV); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", &flags, ImGuiTableFlags_BordersH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersOuterH", &flags, ImGuiTableFlags_BordersOuterH); + ImGui::CheckboxFlags("ImGuiTableFlags_BordersInnerH", &flags, ImGuiTableFlags_BordersInnerH); + ImGui::CheckboxFlags("ImGuiTableFlags_NoBordersInBody", &flags, ImGuiTableFlags_NoBordersInBody); + ImGui::SameLine(); + HelpMarker("Disable vertical borders in columns Body (borders will always appear in Headers"); + 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); + ImGui::SameLine(); + HelpMarker("In the Advanced demo we override the policy of each column so those table-wide settings have less effect that typical."); + ImGui::CheckboxFlags("ImGuiTableFlags_NoHostExtendX", &flags, ImGuiTableFlags_NoHostExtendX); + ImGui::SameLine(); + HelpMarker( + "Make outer width auto-fit to columns, overriding outer_size.x value.\n\nOnly available when ScrollX/ScrollY are disabled and Stretch " + "columns are not used."); + 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."); + ImGui::CheckboxFlags("ImGuiTableFlags_NoKeepColumnsVisible", &flags, ImGuiTableFlags_NoKeepColumnsVisible); + ImGui::SameLine(); + HelpMarker("Only available if ScrollX is disabled."); + ImGui::CheckboxFlags("ImGuiTableFlags_PreciseWidths", &flags, ImGuiTableFlags_PreciseWidths); + ImGui::SameLine(); + HelpMarker( + "Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: " + "33,33,34. With this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth."); + ImGui::CheckboxFlags("ImGuiTableFlags_NoClip", &flags, ImGuiTableFlags_NoClip); + 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); + ImGui::CheckboxFlags("ImGuiTableFlags_NoPadOuterX", &flags, ImGuiTableFlags_NoPadOuterX); + ImGui::CheckboxFlags("ImGuiTableFlags_NoPadInnerX", &flags, ImGuiTableFlags_NoPadInnerX); + ImGui::TreePop(); + } + + if (ImGui::TreeNodeEx("Scrolling:", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollX", &flags, ImGuiTableFlags_ScrollX); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + ImGui::DragInt("freeze_cols", &freeze_cols, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput); + ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", &flags, ImGuiTableFlags_ScrollY); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + 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); + ImGui::SameLine(); + HelpMarker( + "When sorting is enabled: hold shift when clicking headers to sort on multiple column. TableGetSortSpecs() may return specs where " + "(SpecsCount > 1)."); + 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)."); + ImGui::TreePop(); + } + + if (ImGui::TreeNodeEx("Other:", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Checkbox("show_headers", &show_headers); + 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); + ImGui::SameLine(); + HelpMarker( + "If scrolling is disabled (ScrollX and ScrollY not set):\n" + "- The table is output directly in the parent window.\n" + "- 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; + static bool items_need_sort = false; + if (items.Size != items_count) { + items.resize(items_count, MyItem()); + for (int n = 0; n < items_count; n++) { + const int template_n = n % IM_ARRAYSIZE(template_items_names); + MyItem &item = items[n]; + item.ID = n; + item.Name = template_items_names[template_n]; + 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)) { + // Declare columns + // We use the "user_id" parameter of TableSetupColumn() to specify a user id that will be stored in the sort specifications. + // This is so our sort function can identify a column given our own identifier. We could also identify them based on their index! + ImGui::TableSetupColumn("ID", + ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHide, + 0.0f, + MyItemColumnID_ID); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 0.0f, MyItemColumnID_Name); + ImGui::TableSetupColumn("Action", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, MyItemColumnID_Action); + ImGui::TableSetupColumn("Quantity", ImGuiTableColumnFlags_PreferSortDescending, 0.0f, MyItemColumnID_Quantity); + ImGui::TableSetupColumn("Description", + (flags & ImGuiTableFlags_NoHostExtendX) ? 0 : ImGuiTableColumnFlags_WidthStretch, + 0.0f, + MyItemColumnID_Description); + ImGui::TableSetupColumn("Hidden", 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) + items_need_sort = true; + if (sort_specs && items_need_sort && items.Size > 1) { + MyItem::SortWithSortSpecs(sort_specs, items.Data, items.Size); + 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) + ImGui::TableHeadersRow(); + + // Show data + // FIXME-TABLE FIXME-NAV: How we can get decent up/down even though we have the buttons here? + ImGui::PushButtonRepeat(true); +#if 1 + // Demonstrate using clipper for large vertical lists + ImGuiListClipper clipper; + clipper.Begin(items.Size); + while (clipper.Step()) { + for (int row_n = clipper.DisplayStart; row_n < clipper.DisplayEnd; row_n++) +#else + // Without clipper + { + for (int row_n = 0; row_n < items.Size; row_n++) +#endif + { + 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]; + sprintf(label, "%04d", item->ID); + if (contents_type == CT_Text) + ImGui::TextUnformatted(label); + else if (contents_type == CT_Button) + ImGui::Button(label); + else if (contents_type == CT_SmallButton) + ImGui::SmallButton(label); + else if (contents_type == CT_FillButton) + ImGui::Button(label, ImVec2(-FLT_MIN, 0.0f)); + else if (contents_type == CT_Selectable || contents_type == CT_SelectableSpanRow) { + ImGuiSelectableFlags selectable_flags = (contents_type == CT_SelectableSpanRow) + ? ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap + : ImGuiSelectableFlags_None; + if (ImGui::Selectable(label, item_is_selected, selectable_flags, ImVec2(0, row_min_height))) { + if (ImGui::GetIO().KeyCtrl) { + if (item_is_selected) + selection.find_erase_unsorted(item->ID); + else + selection.push_back(item->ID); + } else { + selection.clear(); + selection.push_back(item->ID); + } + } + } + + 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. + // You will probably need a more advanced system in your code if you want to automatically sort when a specific entry changes. + if (ImGui::TableSetColumnIndex(2)) { + if (ImGui::SmallButton("Chop")) { + item->Quantity += 1; + } + if (sorts_specs_using_quantity && ImGui::IsItemDeactivated()) { + items_need_sort = true; + } + ImGui::SameLine(); + 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(); + } + } + ImGui::PopButtonRepeat(); + + // Store some info to display debug details below + table_scroll_cur = ImVec2(ImGui::GetScrollX(), ImGui::GetScrollY()); + table_scroll_max = ImVec2(ImGui::GetScrollMaxX(), ImGui::GetScrollMaxY()); + table_draw_list = ImGui::GetWindowDrawList(); + ImGui::EndTable(); + } + static bool show_debug_details = false; + ImGui::Checkbox("Debug details", &show_debug_details); + if (show_debug_details && table_draw_list) { + ImGui::SameLine(0.0f, 0.0f); + const int table_draw_list_draw_cmd_count = table_draw_list->CmdBuffer.Size; + if (table_draw_list == parent_draw_list) + ImGui::Text(": DrawCmd: +%d (in same window)", table_draw_list_draw_cmd_count - parent_draw_list_draw_cmd_count); + else + ImGui::Text(": DrawCmd: +%d (in child window), Scroll: (%.f/%.f) (%.f/%.f)", + table_draw_list_draw_cmd_count - 1, + table_scroll_cur.x, + table_scroll_max.x, + table_scroll_cur.y, + table_scroll_max.y); + } + ImGui::TreePop(); + } + + ImGui::PopID(); + + ShowDemoWindowColumns(); + + 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 ShowDemoWindowColumns() +{ + IMGUI_DEMO_MARKER("Columns (legacy API)"); + bool open = ImGui::TreeNode("Legacy Columns API"); + ImGui::SameLine(); + 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")) { + ImGui::Text("Without border:"); + ImGui::Columns(3, "mycolumns3", false); // 3-ways, no border + ImGui::Separator(); + for (int n = 0; n < 14; n++) { + char label[32]; + sprintf(label, "Item %d", n); + if (ImGui::Selectable(label)) { } + // if (ImGui::Button(label, ImVec2(-FLT_MIN,0.0f))) {} + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + + ImGui::Text("With border:"); + ImGui::Columns(4, "mycolumns"); // 4-ways, with border + ImGui::Separator(); + ImGui::Text("ID"); + ImGui::NextColumn(); + ImGui::Text("Name"); + ImGui::NextColumn(); + ImGui::Text("Path"); + ImGui::NextColumn(); + ImGui::Text("Hovered"); + ImGui::NextColumn(); + ImGui::Separator(); + const char *names[3] = {"One", "Two", "Three"}; + const char *paths[3] = {"/path/one", "/path/two", "/path/three"}; + static int selected = -1; + for (int i = 0; i < 3; i++) { + char label[32]; + sprintf(label, "%04d", i); + if (ImGui::Selectable(label, selected == i, ImGuiSelectableFlags_SpanAllColumns)) + selected = i; + bool hovered = ImGui::IsItemHovered(); + ImGui::NextColumn(); + ImGui::Text(names[i]); + ImGui::NextColumn(); + ImGui::Text(paths[i]); + ImGui::NextColumn(); + ImGui::Text("%d", hovered); + ImGui::NextColumn(); + } + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Columns (legacy API)/Borders"); + if (ImGui::TreeNode("Borders")) { + // NB: Future columns API should allow automatic horizontal borders. + static bool h_borders = true; + static bool v_borders = true; + static int columns_count = 4; + const int lines_count = 3; + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); + ImGui::DragInt("##columns_count", &columns_count, 0.1f, 2, 10, "%d columns"); + if (columns_count < 2) + columns_count = 2; + ImGui::SameLine(); + ImGui::Checkbox("horizontal", &h_borders); + ImGui::SameLine(); + ImGui::Checkbox("vertical", &v_borders); + ImGui::Columns(columns_count, NULL, v_borders); + for (int i = 0; i < columns_count * lines_count; i++) { + if (h_borders && ImGui::GetColumnIndex() == 0) + ImGui::Separator(); + ImGui::Text("%c%c%c", 'a' + i, 'a' + i, 'a' + i); + ImGui::Text("Width %.2f", ImGui::GetColumnWidth()); + ImGui::Text("Avail %.2f", ImGui::GetContentRegionAvail().x); + ImGui::Text("Offset %.2f", ImGui::GetColumnOffset()); + ImGui::Text("Long text that is likely to clip"); + ImGui::Button("Button", ImVec2(-FLT_MIN, 0.0f)); + ImGui::NextColumn(); + } + ImGui::Columns(1); + if (h_borders) + 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(); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + // Word wrapping + IMGUI_DEMO_MARKER("Columns (legacy API)/Word-wrapping"); + if (ImGui::TreeNode("Word-wrapping")) { + ImGui::Columns(2, "word-wrapping"); + ImGui::Separator(); + ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); + ImGui::TextWrapped("Hello Left"); + ImGui::NextColumn(); + ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); + ImGui::TextWrapped("Hello Right"); + ImGui::Columns(1); + ImGui::Separator(); + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Columns (legacy API)/Horizontal Scrolling"); + if (ImGui::TreeNode("Horizontal Scrolling")) { + ImGui::SetNextWindowContentSize(ImVec2(1500.0f, 0.0f)); + ImVec2 child_size = ImVec2(0, ImGui::GetFontSize() * 20.0f); + ImGui::BeginChild("##ScrollingRegion", child_size, false, ImGuiWindowFlags_HorizontalScrollbar); + ImGui::Columns(10); + + // Also demonstrate using clipper for large vertical lists + int ITEMS_COUNT = 2000; + ImGuiListClipper clipper; + clipper.Begin(ITEMS_COUNT); + while (clipper.Step()) { + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + for (int j = 0; j < 10; j++) { + ImGui::Text("Line %d Column %d...", i, j); + ImGui::NextColumn(); + } + } + ImGui::Columns(1); + ImGui::EndChild(); + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Columns (legacy API)/Tree"); + if (ImGui::TreeNode("Tree")) { + ImGui::Columns(2, "tree", true); + for (int x = 0; x < 3; x++) { + bool open1 = ImGui::TreeNode((void *)(intptr_t)x, "Node%d", x); + ImGui::NextColumn(); + ImGui::Text("Node contents"); + ImGui::NextColumn(); + if (open1) { + for (int y = 0; y < 3; y++) { + bool open2 = ImGui::TreeNode((void *)(intptr_t)y, "Node%d.%d", x, y); + ImGui::NextColumn(); + ImGui::Text("Node contents"); + if (open2) { + ImGui::Text("Even more contents"); + if (ImGui::TreeNode("Tree in column")) { + ImGui::Text("The quick brown fox jumps over the lazy dog"); + ImGui::TreePop(); + } + } + ImGui::NextColumn(); + if (open2) + ImGui::TreePop(); + } + ImGui::TreePop(); + } + } + ImGui::Columns(1); + ImGui::TreePop(); + } + + ImGui::TreePop(); +} + +static void ShowDemoWindowInputs() +{ + 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); + if (ImGui::TreeNode("Inputs")) { + HelpMarker( + "This is a simplified view. See more detailed input state:\n" + "- in 'Tools->Metrics/Debugger->Inputs'.\n" + "- in 'Tools->Debug Log->IO'."); + if (ImGui::IsMousePosValid()) + ImGui::Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y); + else + ImGui::Text("Mouse pos: "); + ImGui::Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y); + ImGui::Text("Mouse down:"); + for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) + if (ImGui::IsMouseDown(i)) { + ImGui::SameLine(); + ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); + } + ImGui::Text("Mouse wheel: %.1f", io.MouseWheel); + + // We iterate both legacy native range and named ImGuiKey ranges, which is a little odd but this allows displaying the data for old/new backends. + // User code should never have to go through such hoops! You can generally iterate between ImGuiKey_NamedKey_BEGIN and ImGuiKey_NamedKey_END. +#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO + struct funcs { + static bool IsLegacyNativeDupe(ImGuiKey) + { + return false; + } + }; + ImGuiKey start_key = ImGuiKey_NamedKey_BEGIN; +#else + struct funcs { + static bool IsLegacyNativeDupe(ImGuiKey key) + { + return key < 512 && ImGui::GetIO().KeyMap[key] != -1; + } + }; // Hide Native<>ImGuiKey duplicates when both exists in the array + ImGuiKey start_key = (ImGuiKey)0; +#endif + 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); + if (ImGui::TreeNode("Outputs")) { + HelpMarker( + "The value of io.WantCaptureMouse and io.WantCaptureKeyboard are normally set by Dear ImGui " + "to instruct your application of how to route inputs. Typically, when a value is true, it means " + "Dear ImGui wants the corresponding inputs and we expect the underlying application to ignore them.\n\n" + "The most typical case is: when hovering a window, Dear ImGui set io.WantCaptureMouse to true, " + "and underlying application should ignore mouse inputs (in practice there are many and more subtle " + "rules leading to how those flags are set)."); + ImGui::Text("io.WantCaptureMouse: %d", io.WantCaptureMouse); + ImGui::Text("io.WantCaptureMouseUnlessPopupClose: %d", io.WantCaptureMouseUnlessPopupClose); + ImGui::Text("io.WantCaptureKeyboard: %d", io.WantCaptureKeyboard); + 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")) { + HelpMarker( + "Hovering the colored canvas will override io.WantCaptureXXX fields.\n" + "Notice how normally (when set to none), the value of io.WantCaptureKeyboard would be false when hovering and true when clicking."); + static int capture_override_mouse = -1; + static int capture_override_keyboard = -1; + const char *capture_override_desc[] = {"None", "Set to false", "Set to true"}; + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); + 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(); + } + + // 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", "NotAllowed"}; + IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_COUNT); + + ImGuiMouseCursor current = ImGui::GetMouseCursor(); + ImGui::Text("Current mouse cursor = %d: %s", current, mouse_cursors_names[current]); + 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. " + "If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, " + "otherwise your backend needs to handle it."); + for (int i = 0; i < ImGuiMouseCursor_COUNT; i++) { + char label[32]; + sprintf(label, "Mouse cursor %d: %s", i, mouse_cursors_names[i]); + ImGui::Bullet(); + ImGui::Selectable(label, false); + if (ImGui::IsItemHovered()) + ImGui::SetMouseCursor(i); + } + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Inputs & Focus/Tabbing"); + if (ImGui::TreeNode("Tabbing")) { + ImGui::Text("Use TAB/SHIFT+TAB to cycle through keyboard editable fields."); + static char buf[32] = "hello"; + ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); + ImGui::InputText("3", buf, IM_ARRAYSIZE(buf)); + ImGui::PushTabStop(false); + ImGui::InputText("4 (tab skip)", buf, IM_ARRAYSIZE(buf)); + ImGui::SameLine(); + HelpMarker("Item won't be cycled through when using TAB or Shift+Tab."); + ImGui::PopTabStop(); + ImGui::InputText("5", buf, IM_ARRAYSIZE(buf)); + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Inputs & Focus/Focus from code"); + if (ImGui::TreeNode("Focus from code")) { + bool focus_1 = ImGui::Button("Focus on 1"); + ImGui::SameLine(); + bool focus_2 = ImGui::Button("Focus on 2"); + ImGui::SameLine(); + 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::PushTabStop(false); + 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::PopTabStop(); + + 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; + if (ImGui::Button("Focus on X")) { + focus_ahead = 0; + } + ImGui::SameLine(); + if (ImGui::Button("Focus on Y")) { + focus_ahead = 1; + } + ImGui::SameLine(); + 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")) { + ImGui::TextWrapped("You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget."); + for (int button = 0; button < 3; button++) { + ImGui::Text("IsMouseDragging(%d):", button); + ImGui::Text(" w/ default threshold: %d,", ImGui::IsMouseDragging(button)); + 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(). + ImVec2 value_raw = ImGui::GetMouseDragDelta(0, 0.0f); + ImVec2 value_with_lock_threshold = ImGui::GetMouseDragDelta(0); + ImVec2 mouse_delta = io.MouseDelta; + ImGui::Text("GetMouseDragDelta(0):"); + ImGui::Text(" w/ default threshold: (%.1f, %.1f)", value_with_lock_threshold.x, value_with_lock_threshold.y); + ImGui::Text(" w/ zero threshold: (%.1f, %.1f)", value_raw.x, value_raw.y); + ImGui::Text("io.MouseDelta: (%.1f, %.1f)", mouse_delta.x, mouse_delta.y); + ImGui::TreePop(); + } + } +} + +//----------------------------------------------------------------------------- +// [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)) { + ImGui::End(); + return; + } + IMGUI_DEMO_MARKER("Tools/About Dear ImGui"); + ImGui::Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); + ImGui::Separator(); + ImGui::Text("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 sponsoring 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::BeginChildFrame(ImGui::GetID("cfg_infos"), child_size, ImGuiWindowFlags_NoMove); + if (copy_to_clipboard) { + ImGui::LogToClipboard(); + ImGui::LogText("```\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_DISABLE_OBSOLETE_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_OBSOLETE_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO + ImGui::Text("define: IMGUI_DISABLE_OBSOLETE_KEYIO"); +#endif +#ifdef IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_WIN32_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_WIN32_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_FILE_FUNCTIONS + ImGui::Text("define: IMGUI_DISABLE_FILE_FUNCTIONS"); +#endif +#ifdef IMGUI_DISABLE_DEFAULT_ALLOCATORS + ImGui::Text("define: IMGUI_DISABLE_DEFAULT_ALLOCATORS"); +#endif +#ifdef IMGUI_USE_BGRA_PACKED_COLOR + ImGui::Text("define: IMGUI_USE_BGRA_PACKED_COLOR"); +#endif +#ifdef _WIN32 + ImGui::Text("define: _WIN32"); +#endif +#ifdef _WIN64 + ImGui::Text("define: _WIN64"); +#endif +#ifdef __linux__ + ImGui::Text("define: __linux__"); +#endif +#ifdef __APPLE__ + ImGui::Text("define: __APPLE__"); +#endif +#ifdef _MSC_VER + ImGui::Text("define: _MSC_VER=%d", _MSC_VER); +#endif +#ifdef _MSVC_LANG + ImGui::Text("define: _MSVC_LANG=%d", (int)_MSVC_LANG); +#endif +#ifdef __MINGW32__ + ImGui::Text("define: __MINGW32__"); +#endif +#ifdef __MINGW64__ + ImGui::Text("define: __MINGW64__"); +#endif +#ifdef __GNUC__ + ImGui::Text("define: __GNUC__=%d", (int)__GNUC__); +#endif +#ifdef __clang_version__ + ImGui::Text("define: __clang_version__=%s", __clang_version__); +#endif +#ifdef __EMSCRIPTEN__ + ImGui::Text("define: __EMSCRIPTEN__"); +#endif + ImGui::Separator(); + ImGui::Text("io.BackendPlatformName: %s", io.BackendPlatformName ? io.BackendPlatformName : "NULL"); + ImGui::Text("io.BackendRendererName: %s", io.BackendRendererName ? io.BackendRendererName : "NULL"); + ImGui::Text("io.ConfigFlags: 0x%08X", io.ConfigFlags); + if (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) + ImGui::Text(" NavEnableKeyboard"); + if (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) + ImGui::Text(" NavEnableGamepad"); + if (io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) + ImGui::Text(" NavEnableSetMousePos"); + if (io.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard) + ImGui::Text(" NavNoCaptureKeyboard"); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) + ImGui::Text(" NoMouse"); + if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) + ImGui::Text(" NoMouseCursorChange"); + if (io.MouseDrawCursor) + ImGui::Text("io.MouseDrawCursor"); + if (io.ConfigMacOSXBehaviors) + ImGui::Text("io.ConfigMacOSXBehaviors"); + if (io.ConfigInputTextCursorBlink) + ImGui::Text("io.ConfigInputTextCursorBlink"); + if (io.ConfigWindowsResizeFromEdges) + ImGui::Text("io.ConfigWindowsResizeFromEdges"); + if (io.ConfigWindowsMoveFromTitleBarOnly) + ImGui::Text("io.ConfigWindowsMoveFromTitleBarOnly"); + if (io.ConfigMemoryCompactTimer >= 0.0f) + ImGui::Text("io.ConfigMemoryCompactTimer = %.1f", io.ConfigMemoryCompactTimer); + ImGui::Text("io.BackendFlags: 0x%08X", io.BackendFlags); + if (io.BackendFlags & ImGuiBackendFlags_HasGamepad) + ImGui::Text(" HasGamepad"); + if (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) + ImGui::Text(" HasMouseCursors"); + if (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos) + ImGui::Text(" HasSetMousePos"); + if (io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) + ImGui::Text(" RendererHasVtxOffset"); + 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.DisplaySize: %.2f,%.2f", io.DisplaySize.x, io.DisplaySize.y); + ImGui::Text("io.DisplayFramebufferScale: %.2f,%.2f", io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); + ImGui::Separator(); + ImGui::Text("style.WindowPadding: %.2f,%.2f", style.WindowPadding.x, style.WindowPadding.y); + ImGui::Text("style.WindowBorderSize: %.2f", style.WindowBorderSize); + ImGui::Text("style.FramePadding: %.2f,%.2f", style.FramePadding.x, style.FramePadding.y); + ImGui::Text("style.FrameRounding: %.2f", style.FrameRounding); + 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"); + ImGui::LogFinish(); + } + ImGui::EndChildFrame(); + } + 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; + 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. +bool ImGui::ShowStyleSelector(const char *label) +{ + static int style_idx = -1; + if (ImGui::Combo(label, &style_idx, "Dark\0Light\0Classic\0")) { + switch (style_idx) { + case 0: + ImGui::StyleColorsDark(); + break; + case 1: + ImGui::StyleColorsLight(); + break; + case 2: + ImGui::StyleColorsClassic(); + break; + } + return true; + } + return false; +} + +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(); + static ImGuiStyle ref_saved_style; + + // Default to using internal storage as reference + static bool init = true; + if (init && ref == NULL) + ref_saved_style = style; + 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; + } + } + + // Save/Revert button + if (ImGui::Button("Save Ref")) + *ref = ref_saved_style = style; + ImGui::SameLine(); + if (ImGui::Button("Revert Ref")) + style = *ref; + ImGui::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)) { + if (ImGui::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("CellPadding", (float *)&style.CellPadding, 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::SliderFloat("TabBorderSize", &style.TabBorderSize, 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::SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f, "%.0f"); + + ImGui::SeparatorText("Widgets"); + ImGui::SliderFloat2("WindowTitleAlign", (float *)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); + int window_menu_button_position = style.WindowMenuButtonPosition + 1; + if (ImGui::Combo("WindowMenuButtonPosition", (int *)&window_menu_button_position, "None\0Left\0Right\0")) + style.WindowMenuButtonPosition = window_menu_button_position - 1; + 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::SeparatorText("Tooltips"); + for (int n = 0; n < 2; n++) + if (ImGui::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(); + } + + ImGui::SeparatorText("Misc"); + ImGui::SliderFloat2("DisplaySafeAreaPadding", (float *)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); + ImGui::SameLine(); + HelpMarker("Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Colors")) { + static int output_dest = 0; + static bool output_only_modified = true; + if (ImGui::Button("Export")) { + if (output_dest == 0) + ImGui::LogToClipboard(); + else + ImGui::LogToTTY(); + ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); + for (int i = 0; i < ImGuiCol_COUNT; i++) { + const ImVec4 &col = style.Colors[i]; + const char *name = ImGui::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, + name, + 23 - (int)strlen(name), + "", + col.x, + col.y, + col.z, + col.w); + } + ImGui::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); + + static ImGuiTextFilter filter; + filter.Draw("Filter colors", ImGui::GetFontSize() * 16); + + static ImGuiColorEditFlags alpha_flags = 0; + if (ImGui::RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_None)) { + alpha_flags = ImGuiColorEditFlags_None; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_AlphaPreview)) { + alpha_flags = ImGuiColorEditFlags_AlphaPreview; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { + alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; + } + ImGui::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::BeginChild("##colors", + ImVec2(0, 0), + true, + ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened); + ImGui::PushItemWidth(-160); + for (int i = 0; i < ImGuiCol_COUNT; i++) { + const char *name = ImGui::GetStyleColorName(i); + if (!filter.PassFilter(name)) + continue; + ImGui::PushID(i); + ImGui::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]; + } + } + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); + ImGui::TextUnformatted(name); + ImGui::PopID(); + } + ImGui::PopItemWidth(); + ImGui::EndChild(); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Fonts")) { + ImGuiIO &io = ImGui::GetIO(); + ImFontAtlas *atlas = io.Fonts; + HelpMarker("Read FAQ and docs/FONTS.md for details on font loading."); + ImGui::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). + const float MIN_SCALE = 0.3f; + const float MAX_SCALE = 2.0f; + HelpMarker( + "Those are old settings provided for convenience.\n" + "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(); + } + + if (ImGui::BeginTabItem("Rendering")) { + ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); + ImGui::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(); + 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"); + 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(); + if (show_samples) + ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); + if (show_samples && ImGui::BeginTooltip()) { + ImGui::TextUnformatted("(R = radius, N = number of segments)"); + ImGui::Spacing(); + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + const float min_widget_width = ImGui::CalcTextSize("N: MMM\nR: 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(); + + ImGui::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 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)); + */ + + ImGui::EndGroup(); + ImGui::SameLine(); + } + ImGui::EndTooltip(); + } + ImGui::SameLine(); + HelpMarker("When drawing circle primitives with \"num_segments == 0\" tesselation 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(); + } + + ImGui::EndTabBar(); + } + + ImGui::PopItemWidth(); +} + +//----------------------------------------------------------------------------- +// [SECTION] User Guide / ShowUserGuide() +//----------------------------------------------------------------------------- + +void ImGui::ShowUserGuide() +{ + ImGuiIO &io = ImGui::GetIO(); + ImGui::BulletText("Double-click on title bar to collapse window."); + ImGui::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."); + if (io.FontAllowUserScaling) + ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents."); + ImGui::BulletText("While inputing 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,CTRL+Y to undo/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(); +} + +//----------------------------------------------------------------------------- +// [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!) +// - BeginMainMenuBar() = helper to create menu-bar-sized window at the top of the main viewport + call BeginMenuBar() into it. +static void ShowExampleAppMainMenuBar() +{ + if (ImGui::BeginMainMenuBar()) { + if (ImGui::BeginMenu("File")) { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Edit")) { + if (ImGui::MenuItem("Undo", "CTRL+Z")) { } + if (ImGui::MenuItem("Redo", "CTRL+Y", false, false)) { } // Disabled item + ImGui::Separator(); + if (ImGui::MenuItem("Cut", "CTRL+X")) { } + if (ImGui::MenuItem("Copy", "CTRL+C")) { } + if (ImGui::MenuItem("Paste", "CTRL+V")) { } + ImGui::EndMenu(); + } + 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() +{ + IMGUI_DEMO_MARKER("Examples/Menu"); + ImGui::MenuItem("(demo menu)", NULL, false, false); + if (ImGui::MenuItem("New")) { } + if (ImGui::MenuItem("Open", "Ctrl+O")) { } + if (ImGui::BeginMenu("Open Recent")) { + ImGui::MenuItem("fish_hat.c"); + ImGui::MenuItem("fish_hat.inl"); + ImGui::MenuItem("fish_hat.h"); + if (ImGui::BeginMenu("More..")) { + ImGui::MenuItem("Hello"); + ImGui::MenuItem("Sailor"); + if (ImGui::BeginMenu("Recurse..")) { + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + ImGui::EndMenu(); + } + if (ImGui::MenuItem("Save", "Ctrl+S")) { } + if (ImGui::MenuItem("Save As..")) { } + + ImGui::Separator(); + IMGUI_DEMO_MARKER("Examples/Menu/Options"); + if (ImGui::BeginMenu("Options")) { + static bool enabled = true; + ImGui::MenuItem("Enabled", "", &enabled); + ImGui::BeginChild("child", ImVec2(0, 60), true); + for (int i = 0; i < 10; i++) + ImGui::Text("Scrolling Text %d", i); + ImGui::EndChild(); + static float f = 0.5f; + static int n = 0; + ImGui::SliderFloat("Value", &f, 0.0f, 1.0f); + ImGui::InputFloat("Input", &f, 0.1f); + ImGui::Combo("Combo", &n, "Yes\0No\0Maybe\0\0"); + ImGui::EndMenu(); + } + + IMGUI_DEMO_MARKER("Examples/Menu/Colors"); + if (ImGui::BeginMenu("Colors")) { + float sz = ImGui::GetTextLineHeight(); + for (int i = 0; i < ImGuiCol_COUNT; i++) { + const char *name = ImGui::GetStyleColorName((ImGuiCol)i); + ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + sz, p.y + sz), ImGui::GetColorU32((ImGuiCol)i)); + ImGui::Dummy(ImVec2(sz, sz)); + ImGui::SameLine(); + ImGui::MenuItem(name); + } + 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. + if (ImGui::BeginMenu("Options")) // <-- Append! + { + IMGUI_DEMO_MARKER("Examples/Menu/Append to an existing menu"); + static bool b = true; + ImGui::Checkbox("SomeOption", &b); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Disabled", false)) // Disabled + { + IM_ASSERT(0); + } + if (ImGui::MenuItem("Checked", NULL, true)) { } + 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 { + char InputBuf[256]; + ImVector Items; + ImVector Commands; + ImVector History; + int HistoryPos; // -1: new line, 0..History.Size-1 browsing history. + 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"); + Commands.push_back("CLEAR"); + Commands.push_back("CLASSIFY"); + AutoScroll = true; + ScrollToBottom = false; + AddLog("Welcome to Dear ImGui!"); + } + ~ExampleAppConsole() + { + ClearLog(); + for (int i = 0; i < History.Size; i++) + free(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 = malloc(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++) + free(Items[i]); + Items.clear(); + } + + void AddLog(const char *fmt, ...) IM_FMTARGS(2) + { + // FIXME-OPT + char buf[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); + buf[IM_ARRAYSIZE(buf) - 1] = 0; + va_end(args); + Items.push_back(Strdup(buf)); + } + + void Draw(const char *title, bool *p_open) + { + ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin(title, p_open)) { + 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. + if (ImGui::BeginPopupContextItem()) { + if (ImGui::MenuItem("Close Console")) + *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"); + } + ImGui::SameLine(); + if (ImGui::SmallButton("Clear")) { + ClearLog(); + } + 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 + if (ImGui::Button("Options")) + ImGui::OpenPopup("Options"); + 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), false, ImGuiWindowFlags_HorizontalScrollbar)) { + if (ImGui::BeginPopupContextWindow()) { + 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 + // to only process visible items. The clipper will automatically measure the height of your first item and then + // "seek" to display only items in the visible area. + // To use the clipper we can replace your standard loop: + // for (int i = 0; i < Items.Size; i++) + // With: + // ImGuiListClipper clipper; + // clipper.Begin(Items.Size); + // while (clipper.Step()) + // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + // - That your items are evenly spaced (same height) + // - That you have cheap random access to your elements (you can access them given their index, + // without processing all the ones before) + // You cannot this code as-is if a filter is active because it breaks the 'cheap random-access' property. + // We would need random-access on the post-filtered list. + // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices + // or offsets of items that passed the filtering test, recomputing this array when user changes the filter, + // and appending newly elements as they are inserted. This is left as a task to the user until we can manage + // to improve this example code! + // If your items are of variable height: + // - Split them into same height items would be simpler and facilitate random-seeking into your list. + // - Consider using manual call to IsRectVisible() and skipping extraneous decoration from your items. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing + if (copy_to_clipboard) + ImGui::LogToClipboard(); + for (const char *item : Items) { + 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; + bool has_color = false; + if (strstr(item, "[error]")) { + color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); + has_color = true; + } else if (strncmp(item, "# ", 2) == 0) { + color = ImVec4(1.0f, 0.8f, 0.6f, 1.0f); + has_color = true; + } + if (has_color) + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(item); + if (has_color) + ImGui::PopStyleColor(); + } + 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; + if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), input_text_flags, &TextEditCallbackStub, (void *)this)) { + char *s = InputBuf; + Strtrim(s); + if (s[0]) + ExecCommand(s); + 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; + for (int i = History.Size - 1; i >= 0; i--) + if (Stricmp(History[i], command_line) == 0) { + free(History[i]); + History.erase(History.begin() + i); + break; + } + History.push_back(Strdup(command_line)); + + // Process command + if (Stricmp(command_line, "CLEAR") == 0) { + ClearLog(); + } else if (Stricmp(command_line, "HELP") == 0) { + AddLog("Commands:"); + for (int i = 0; i < Commands.Size; i++) + AddLog("- %s", Commands[i]); + } else if (Stricmp(command_line, "HISTORY") == 0) { + int first = History.Size - 10; + for (int i = first > 0 ? first : 0; i < History.Size; i++) + AddLog("%3d: %s\n", i, History[i]); + } else { + 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); + switch (data->EventFlag) { + 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; + while (word_start > data->Buf) { + const char c = word_start[-1]; + if (c == ' ' || c == '\t' || c == ',' || c == ';') + 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 + AddLog("No match for \"%.*s\"!\n", (int)(word_end - word_start), word_start); + } else if (candidates.Size == 1) { + // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing. + data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start)); + data->InsertChars(data->CursorPos, candidates[0]); + data->InsertChars(data->CursorPos, " "); + } else { + // Multiple matches. Complete as much as we can.. + // So inputing "C"+Tab will complete to "CL" then display "CLEAR" and "CLASSIFY" as matches. + int match_len = (int)(word_end - word_start); + for (;;) { + int c = 0; + bool all_candidates_matches = true; + for (int i = 0; i < candidates.Size && all_candidates_matches; i++) + if (i == 0) + c = toupper(candidates[i][match_len]); + else if (c == 0 || c != toupper(candidates[i][match_len])) + all_candidates_matches = false; + if (!all_candidates_matches) + 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: { + // Example of HISTORY + const int prev_history_pos = HistoryPos; + if (data->EventKey == ImGuiKey_UpArrow) { + if (HistoryPos == -1) + HistoryPos = History.Size - 1; + else if (HistoryPos > 0) + HistoryPos--; + } else if (data->EventKey == ImGuiKey_DownArrow) { + if (HistoryPos != -1) + 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) { + const char *history_str = (HistoryPos >= 0) ? History[HistoryPos] : ""; + data->DeleteChars(0, data->BufTextLen); + data->InsertChars(0, history_str); + } + } + } + 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); +// my_log.Draw("title"); +struct ExampleAppLog { + ImGuiTextBuffer Buf; + 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(); + va_list args; + va_start(args, fmt); + Buf.appendfv(fmt, args); + va_end(args); + for (int new_size = Buf.size(); old_size < new_size; old_size++) + 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)) { + 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"); + ImGui::SameLine(); + bool clear = ImGui::Button("Clear"); + ImGui::SameLine(); + bool copy = ImGui::Button("Copy"); + ImGui::SameLine(); + Filter.Draw("Filter", -100.0f); + + ImGui::Separator(); + + if (ImGui::BeginChild("scrolling", ImVec2(0, 0), false, 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(); + if (Filter.IsActive()) { + // In this example we don't use the clipper when Filter is enabled. + // This is because we don't have random access to the result of our filter. + // A real application processing logs with ten of thousands of entries may want to store the result of + // search/filter.. especially if the filtering function is not trivial (e.g. reg-exp). + for (int line_no = 0; line_no < LineOffsets.Size; line_no++) { + const char *line_start = buf + LineOffsets[line_no]; + const char *line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; + if (Filter.PassFilter(line_start, line_end)) + ImGui::TextUnformatted(line_start, line_end); + } + } else { + // The simplest and easy way to display the entire buffer: + // ImGui::TextUnformatted(buf_begin, buf_end); + // And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward + // to skip non-visible lines. Here we instead demonstrate using the clipper to only process lines that are + // within the visible area. + // If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them + // on your side is recommended. Using ImGuiListClipper requires + // - A) random access into your data + // - B) items all being the same height, + // both of which we can handle since we have an array pointing to the beginning of each line of text. + // When using the filter (in the block of code above) we don't have random access into the data to display + // anymore, which is why we don't use the clipper. Storing or skimming through the search result would make + // it possible (and would be recommended if you want to search through tens of thousands of entries). + ImGuiListClipper clipper; + clipper.Begin(LineOffsets.Size); + while (clipper.Step()) { + for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) { + const char *line_start = buf + LineOffsets[line_no]; + const char *line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; + ImGui::TextUnformatted(line_start, line_end); + } + } + 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()) + ImGui::SetScrollHereY(1.0f); + } + ImGui::EndChild(); + 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. + ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver); + ImGui::Begin("Example: Log", p_open); + IMGUI_DEMO_MARKER("Examples/Log"); + if (ImGui::SmallButton("[Debug] Add 5 entries")) { + static int counter = 0; + const char *categories[3] = {"info", "warn", "error"}; + const char *words[] = {"Bumfuzzled", "Cattywampus", "Snickersnee", "Abibliophobia", "Absquatulate", "Nincompoop", "Pauciloquent"}; + for (int n = 0; n < 5; n++) { + const char *category = categories[counter % IM_ARRAYSIZE(categories)]; + const char *word = words[counter % IM_ARRAYSIZE(words)]; + log.AddLog("[%05d] [%s] Hello, current time is %.1f, here's a word: '%s'\n", ImGui::GetFrameCount(), category, ImGui::GetTime(), word); + counter++; + } + } + 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) +{ + ImGui::SetNextWindowSize(ImVec2(500, 440), ImGuiCond_FirstUseEver); + if (ImGui::Begin("Example: Simple layout", p_open, ImGuiWindowFlags_MenuBar)) { + IMGUI_DEMO_MARKER("Examples/Simple layout"); + if (ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("Close", "Ctrl+W")) { + *p_open = false; + } + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + // Left + static int selected = 0; + { + ImGui::BeginChild("left pane", ImVec2(150, 0), true); + for (int i = 0; i < 100; i++) { + // FIXME: Good candidate to use ImGuiSelectableFlags_SelectOnNav + char label[128]; + sprintf(label, "MyObject %d", i); + if (ImGui::Selectable(label, selected == i)) + selected = i; + } + ImGui::EndChild(); + } + ImGui::SameLine(); + + // Right + { + ImGui::BeginGroup(); + ImGui::BeginChild("item view", ImVec2(0, -ImGui::GetFrameHeightWithSpacing())); // Leave room for 1 line below us + ImGui::Text("MyObject: %d", selected); + ImGui::Separator(); + if (ImGui::BeginTabBar("##Tabs", ImGuiTabBarFlags_None)) { + if (ImGui::BeginTabItem("Description")) { + ImGui::TextWrapped( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Details")) { + ImGui::Text("ID: 0123456789"); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + ImGui::EndChild(); + if (ImGui::Button("Revert")) { } + ImGui::SameLine(); + if (ImGui::Button("Save")) { } + ImGui::EndGroup(); + } + } + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() +//----------------------------------------------------------------------------- + +static void ShowPlaceholderObject(const char *prefix, int uid) +{ + // Use object uid as identifier. Most commonly you could also use the object pointer as a base ID. + ImGui::PushID(uid); + + // Text and Tree nodes are less high than framed widgets, using AlignTextToFramePadding() we add vertical spacing to make the tree lines equal high. + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + bool node_open = ImGui::TreeNode("Object", "%s_%u", prefix, uid); + ImGui::TableSetColumnIndex(1); + ImGui::Text("my sailor is rich"); + + if (node_open) { + static float placeholder_members[8] = {0.0f, 0.0f, 1.0f, 3.1416f, 100.0f, 999.0f}; + for (int i = 0; i < 8; i++) { + ImGui::PushID(i); // Use field index as identifier. + if (i < 2) { + ShowPlaceholderObject("Child", 424242); + } else { + // Here we use a TreeNode to highlight on hover (we could use e.g. Selectable as well) + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_Bullet; + ImGui::TreeNodeEx("Field", flags, "Field_%d", i); + + ImGui::TableSetColumnIndex(1); + ImGui::SetNextItemWidth(-FLT_MIN); + if (i >= 5) + ImGui::InputFloat("##value", &placeholder_members[i], 1.0f); + else + ImGui::DragFloat("##value", &placeholder_members[i], 0.01f); + ImGui::NextColumn(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + ImGui::PopID(); +} + +// Demonstrate create a simple property editor. +// This demo is a bit lackluster nowadays, would be nice to improve. +static void ShowExampleAppPropertyEditor(bool *p_open) +{ + ImGui::SetNextWindowSize(ImVec2(430, 450), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Property editor", p_open)) { + ImGui::End(); + return; + } + + IMGUI_DEMO_MARKER("Examples/Property Editor"); + HelpMarker( + "This example shows how you may implement a property editor using two columns.\n" + "All objects/fields data are dummies here.\n"); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2)); + if (ImGui::BeginTable("##split", 2, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) { + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableSetupColumn("Object"); + ImGui::TableSetupColumn("Contents"); + ImGui::TableHeadersRow(); + + // Iterate placeholder objects (all the same data) + for (int obj_i = 0; obj_i < 4; obj_i++) + ShowPlaceholderObject("Object", obj_i); + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + 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) +{ + ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Example: Long text display", p_open)) { + ImGui::End(); + return; + } + IMGUI_DEMO_MARKER("Examples/Long text display"); + + static int test_type = 0; + static ImGuiTextBuffer log; + static int lines = 0; + ImGui::Text("Printing unusually long amount of text."); + ImGui::Combo("Test type", + &test_type, + "Single call to TextUnformatted()\0" + "Multiple calls to Text(), clipped\0" + "Multiple calls to Text(), not clipped (slow)\0"); + ImGui::Text("Buffer contents: %d lines, %d bytes", lines, log.size()); + if (ImGui::Button("Clear")) { + log.clear(); + lines = 0; + } + ImGui::SameLine(); + if (ImGui::Button("Add 1000 lines")) { + for (int i = 0; i < 1000; i++) + log.appendf("%i The quick brown fox jumps over the lazy dog\n", lines + i); + lines += 1000; + } + ImGui::BeginChild("Log"); + switch (test_type) { + case 0: + // Single call to TextUnformatted() with a big buffer + ImGui::TextUnformatted(log.begin(), log.end()); + break; + case 1: { + // Multiple calls to Text(), manually coarsely clipped - demonstrate how to use the ImGuiListClipper helper. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGuiListClipper clipper; + clipper.Begin(lines); + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); + ImGui::PopStyleVar(); + break; + } + case 2: + // Multiple calls to Text(), not clipped (slow) + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + for (int i = 0; i < lines; i++) + ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); + ImGui::PopStyleVar(); + break; + } + 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) +{ + if (!ImGui::Begin("Example: Auto-resizing window", p_open, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::End(); + 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" + "Note that you probably don't want to query the window size to\n" + "output your content because that would create a feedback loop."); + ImGui::SliderInt("Number of lines", &lines, 1, 20); + for (int i = 0; i < lines; i++) + 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) +{ + struct CustomConstraints { + // Helper functions to demonstrate programmatic constraints + // FIXME: This doesn't take account of decoration size (e.g. title bar), library should make this easier. + static void AspectRatio(ImGuiSizeCallbackData *data) + { + float aspect_ratio = *(float *)data->UserData; + data->DesiredSize.x = IM_MAX(data->CurrentSize.x, data->CurrentSize.y); + data->DesiredSize.y = (float)(int)(data->DesiredSize.x / aspect_ratio); + } + static void Square(ImGuiSizeCallbackData *data) + { + data->DesiredSize.x = data->DesiredSize.y = IM_MAX(data->CurrentSize.x, data->CurrentSize.y); + } + static void Step(ImGuiSizeCallbackData *data) + { + float step = *(float *)data->UserData; + data->DesiredSize = ImVec2((int)(data->CurrentSize.x / step + 0.5f) * step, (int)(data->CurrentSize.y / step + 0.5f) * step); + } + }; + + const char *test_desc[] = { + "Between 100x100 and 500x500", + "At least 100x100", + "Resize vertical only", + "Resize horizontal only", + "Width Between 400 and 500", + "Custom: Aspect Ratio 16:9", + "Custom: Always Square", + "Custom: Fixed Steps (100)", + }; + + // Options + static bool auto_resize = false; + static bool window_padding = true; + static int type = 5; // Aspect Ratio + static int display_lines = 10; + + // Submit constraint + float aspect_ratio = 16.0f / 9.0f; + float fixed_step = 100.0f; + if (type == 0) + ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(500, 500)); // Between 100x100 and 500x500 + if (type == 1) + ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(FLT_MAX, FLT_MAX)); // Width > 100, Height > 100 + if (type == 2) + ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 0), ImVec2(-1, FLT_MAX)); // Vertical only + if (type == 3) + ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1), ImVec2(FLT_MAX, -1)); // Horizontal only + if (type == 4) + ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1), ImVec2(500, -1)); // Width Between and 400 and 500 + if (type == 5) + ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::AspectRatio, (void *)&aspect_ratio); // Aspect ratio + if (type == 6) + ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square); // Always Square + if (type == 7) + 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)); + const ImGuiWindowFlags window_flags = auto_resize ? ImGuiWindowFlags_AlwaysAutoResize : 0; + const bool window_open = ImGui::Begin("Example: Constrained Resize", p_open, window_flags); + if (!window_padding) + ImGui::PopStyleVar(); + if (window_open) { + IMGUI_DEMO_MARKER("Examples/Constrained Resizing window"); + if (ImGui::GetIO().KeyShift) { + // Display a dummy viewport (in your real app you would likely use ImageButton() to display a texture. + ImVec2 avail_size = ImGui::GetContentRegionAvail(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::ColorButton("viewport", ImVec4(0.5f, 0.2f, 0.5f, 1.0f), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop, avail_size); + ImGui::SetCursorScreenPos(ImVec2(pos.x + 10, pos.y + 10)); + ImGui::Text("%.2f x %.2f", avail_size.x, avail_size.y); + } else { + ImGui::Text("(Hold SHIFT to display a dummy viewport)"); + if (ImGui::Button("Set 200x200")) { + ImGui::SetWindowSize(ImVec2(200, 200)); + } + ImGui::SameLine(); + if (ImGui::Button("Set 500x500")) { + ImGui::SetWindowSize(ImVec2(500, 500)); + } + ImGui::SameLine(); + if (ImGui::Button("Set 800x200")) { + ImGui::SetWindowSize(ImVec2(800, 200)); + } + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20); + ImGui::Combo("Constraint", &type, test_desc, IM_ARRAYSIZE(test_desc)); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20); + ImGui::DragInt("Lines", &display_lines, 0.2f, 1, 100); + ImGui::Checkbox("Auto-resize", &auto_resize); + ImGui::Checkbox("Window padding", &window_padding); + for (int i = 0; i < display_lines; i++) + ImGui::Text("%*sHello, sailor! Making this line long enough for the example.", i * 4, ""); + } + } + 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) +{ + static int location = 0; + ImGuiIO &io = ImGui::GetIO(); + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings + | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; + if (location >= 0) { + const float PAD = 10.0f; + const ImGuiViewport *viewport = ImGui::GetMainViewport(); + ImVec2 work_pos = viewport->WorkPos; // Use work area to avoid menu-bar/task-bar, if any! + ImVec2 work_size = viewport->WorkSize; + ImVec2 window_pos, window_pos_pivot; + window_pos.x = (location & 1) ? (work_pos.x + work_size.x - PAD) : (work_pos.x + PAD); + window_pos.y = (location & 2) ? (work_pos.y + work_size.y - PAD) : (work_pos.y + PAD); + window_pos_pivot.x = (location & 1) ? 1.0f : 0.0f; + window_pos_pivot.y = (location & 2) ? 1.0f : 0.0f; + ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); + window_flags |= ImGuiWindowFlags_NoMove; + } else if (location == -2) { + // Center window + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + window_flags |= ImGuiWindowFlags_NoMove; + } + ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background + if (ImGui::Begin("Example: Simple overlay", p_open, window_flags)) { + IMGUI_DEMO_MARKER("Examples/Simple Overlay"); + ImGui::Text( + "Simple overlay\n" + "(right-click to change position)"); + ImGui::Separator(); + if (ImGui::IsMousePosValid()) + ImGui::Text("Mouse Position: (%.1f,%.1f)", io.MousePos.x, io.MousePos.y); + else + ImGui::Text("Mouse Position: "); + if (ImGui::BeginPopupContextWindow()) { + if (ImGui::MenuItem("Custom", NULL, location == -1)) + location = -1; + if (ImGui::MenuItem("Center", NULL, location == -2)) + location = -2; + if (ImGui::MenuItem("Top-left", NULL, location == 0)) + location = 0; + if (ImGui::MenuItem("Top-right", NULL, location == 1)) + location = 1; + if (ImGui::MenuItem("Bottom-left", NULL, location == 2)) + location = 2; + if (ImGui::MenuItem("Bottom-right", NULL, location == 3)) + location = 3; + if (p_open && ImGui::MenuItem("Close")) + *p_open = false; + ImGui::EndPopup(); + } + } + 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(); + ImGui::CheckboxFlags("ImGuiWindowFlags_NoTitleBar", &flags, ImGuiWindowFlags_NoTitleBar); + 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. +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()); + ImGui::SetNextWindowPos(ImVec2(base_pos.x + 100, base_pos.y + 300), ImGuiCond_FirstUseEver); + ImGui::Begin(buf); + ImGui::Text("This window has a changing title."); + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() +//----------------------------------------------------------------------------- + +// Demonstrate using the low-level ImDrawList to draw custom shapes. +static void ShowExampleAppCustomRendering(bool *p_open) +{ + if (!ImGui::Begin("Example: Custom rendering", p_open)) { + ImGui::End(); + 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.. + ImGui::Text("Gradients"); + ImVec2 gradient_size = ImVec2(ImGui::CalcItemWidth(), ImGui::GetFrameHeight()); + { + ImVec2 p0 = ImGui::GetCursorScreenPos(); + ImVec2 p1 = ImVec2(p0.x + gradient_size.x, p0.y + gradient_size.y); + ImU32 col_a = ImGui::GetColorU32(IM_COL32(0, 0, 0, 255)); + ImU32 col_b = ImGui::GetColorU32(IM_COL32(255, 255, 255, 255)); + draw_list->AddRectFilledMultiColor(p0, p1, col_a, col_b, col_b, col_a); + ImGui::InvisibleButton("##gradient1", gradient_size); + } + { + ImVec2 p0 = ImGui::GetCursorScreenPos(); + ImVec2 p1 = ImVec2(p0.x + gradient_size.x, p0.y + gradient_size.y); + ImU32 col_a = ImGui::GetColorU32(IM_COL32(0, 255, 0, 255)); + ImU32 col_b = ImGui::GetColorU32(IM_COL32(255, 0, 0, 255)); + 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; + static float thickness = 3.0f; + static int ngon_sides = 6; + static bool circle_segments_override = false; + static int circle_segments_override_v = 12; + static bool curve_segments_override = false; + static int curve_segments_override_v = 8; + static ImVec4 colf = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); + ImGui::DragFloat("Size", &sz, 0.2f, 2.0f, 100.0f, "%.0f"); + ImGui::DragFloat("Thickness", &thickness, 0.05f, 1.0f, 8.0f, "%.02f"); + ImGui::SliderInt("N-gon sides", &ngon_sides, 3, 12); + ImGui::Checkbox("##circlesegmentoverride", &circle_segments_override); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + circle_segments_override |= ImGui::SliderInt("Circle segments override", &circle_segments_override_v, 3, 40); + ImGui::Checkbox("##curvessegmentoverride", &curve_segments_override); + 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; + const ImDrawFlags corners_tl_br = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBottomRight; + const float rounding = sz / 5.0f; + const int circle_segments = circle_segments_override ? circle_segments_override_v : 0; + const int curve_segments = curve_segments_override ? curve_segments_override_v : 0; + float x = p.x + 4.0f; + float y = p.y + 4.0f; + for (int n = 0; n < 2; n++) { + // First line uses a thickness of 1.0f, second line uses the configurable thickness + float th = (n == 0) ? 1.0f : thickness; + draw_list->AddNgon(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, ngon_sides, th); + x += sz + spacing; // N-gon + draw_list->AddCircle(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, circle_segments, th); + x += sz + spacing; // Circle + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, ImDrawFlags_None, th); + x += sz + spacing; // Square + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, ImDrawFlags_None, th); + x += sz + spacing; // Square with all rounded corners + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, corners_tl_br, th); + x += sz + spacing; // Square with two rounded corners + draw_list->AddTriangle(ImVec2(x + sz * 0.5f, y), ImVec2(x + sz, y + sz - 0.5f), ImVec2(x, y + sz - 0.5f), col, th); + x += sz + spacing; // Triangle + // draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin + // triangle + 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 + + // Quadratic Bezier Curve (3 control points) + ImVec2 cp3[3] = {ImVec2(x, y + sz * 0.6f), ImVec2(x + sz * 0.5f, y - sz * 0.4f), ImVec2(x + sz, y + sz)}; + draw_list->AddBezierQuadratic(cp3[0], cp3[1], cp3[2], col, th, curve_segments); + x += sz + spacing; + + // Cubic Bezier Curve (4 control points) + ImVec2 cp4[4] = {ImVec2(x, y), ImVec2(x + sz * 1.3f, y + sz * 0.3f), ImVec2(x + sz - sz * 1.3f, y + sz - sz * 0.3f), ImVec2(x + sz, y + sz)}; + draw_list->AddBezierCubic(cp4[0], cp4[1], cp4[2], cp4[3], col, th, curve_segments); + + x = p.x + 4; + y += sz + spacing; + } + 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 + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col); + x += sz + spacing; // Square + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f); + x += sz + spacing; // Square with all rounded corners + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f, corners_tl_br); + x += sz + spacing; // Square with two rounded corners + draw_list->AddTriangleFilled(ImVec2(x + sz * 0.5f, y), ImVec2(x + sz, y + sz - 0.5f), ImVec2(x, y + sz - 0.5f), col); + x += sz + spacing; // Triangle + // draw_list->AddTriangleFilled(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col); x += sz*0.4f + spacing; // Thin + // triangle + 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) + 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)); + + ImGui::Dummy(ImVec2((sz + spacing) * 10.2f, (sz + spacing) * 3.0f)); + ImGui::PopItemWidth(); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Canvas")) { + static ImVector points; + static ImVec2 scrolling(0.0f, 0.0f); + 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: + // ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Disable padding + // ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(50, 50, 50, 255)); // Set a background color + // ImGui::BeginChild("canvas", ImVec2(0.0f, 0.0f), true, ImGuiWindowFlags_NoMove); + // ImGui::PopStyleColor(); + // 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)) { + points.push_back(mouse_pos_in_canvas); + points.push_back(mouse_pos_in_canvas); + adding_line = true; + } + if (adding_line) { + points.back() = mouse_pos_in_canvas; + 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; + if (is_active && ImGui::IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) { + 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) + ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight); + if (ImGui::BeginPopup("context")) { + if (adding_line) + points.resize(points.size() - 2); + adding_line = false; + if (ImGui::MenuItem("Remove one", NULL, false, points.Size > 0)) { + points.resize(points.size() - 2); + } + 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) { + const float GRID_STEP = 64.0f; + for (float x = fmodf(scrolling.x, GRID_STEP); x < canvas_sz.x; x += GRID_STEP) + draw_list->AddLine(ImVec2(canvas_p0.x + x, canvas_p0.y), ImVec2(canvas_p0.x + x, canvas_p1.y), IM_COL32(200, 200, 200, 40)); + for (float y = fmodf(scrolling.y, GRID_STEP); y < canvas_sz.y; y += GRID_STEP) + draw_list->AddLine(ImVec2(canvas_p0.x, canvas_p0.y + y), ImVec2(canvas_p1.x, canvas_p0.y + y), IM_COL32(200, 200, 200, 40)); + } + 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; + static bool draw_fg = true; + ImGui::Checkbox("Draw in Background draw list", &draw_bg); + ImGui::SameLine(); + HelpMarker("The Background draw list will be rendered below every Dear ImGui windows."); + ImGui::Checkbox("Draw in Foreground draw list", &draw_fg); + ImGui::SameLine(); + HelpMarker("The Foreground draw list will be rendered over every Dear ImGui windows."); + ImVec2 window_pos = ImGui::GetWindowPos(); + ImVec2 window_size = ImGui::GetWindowSize(); + ImVec2 window_center = ImVec2(window_pos.x + window_size.x * 0.5f, window_pos.y + window_size.y * 0.5f); + if (draw_bg) + ImGui::GetBackgroundDrawList()->AddCircle(window_center, window_size.x * 0.6f, IM_COL32(255, 0, 0, 200), 0, 10 + 4); + if (draw_fg) + 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. + if (ImGui::BeginTabItem("Draw Channels")) { + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + { + ImGui::Text("Blue shape is drawn first: appears in back"); + ImGui::Text("Red shape is drawn after: appears in front"); + ImVec2 p0 = ImGui::GetCursorScreenPos(); + draw_list->AddRectFilled(ImVec2(p0.x, p0.y), ImVec2(p0.x + 50, p0.y + 50), IM_COL32(0, 0, 255, 255)); // Blue + draw_list->AddRectFilled(ImVec2(p0.x + 25, p0.y + 25), ImVec2(p0.x + 75, p0.y + 75), IM_COL32(255, 0, 0, 255)); // Red + ImGui::Dummy(ImVec2(75, 75)); + } + ImGui::Separator(); + { + 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); + draw_list->ChannelsSetCurrent(1); + 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(); + ImGui::Dummy(ImVec2(75, 75)); + ImGui::Text("After reordering, contents of channel 0 appears below channel 1."); + } + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + + ImGui::End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Documents Handling / ShowExampleAppDocuments() +//----------------------------------------------------------------------------- + +// Simplified structure to mimic a Document model +struct MyDocument { + const char *Name; // Document title + bool Open; // Set when open (we keep an array of all available documents to simplify demo code!) + bool OpenPrev; // Copy of Open from last update. + bool Dirty; // Set when the document has been modified + bool WantClose; // Set when the document + ImVec4 Color; // An arbitrary variable associated to the document + + MyDocument(const char *name, bool open = true, const ImVec4 &color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f)) + { + Name = name; + Open = OpenPrev = open; + Dirty = false; + WantClose = false; + Color = color; + } + void DoOpen() + { + Open = true; + } + void DoQueueClose() + { + WantClose = true; + } + void DoForceClose() + { + Open = false; + Dirty = false; + } + void DoSave() + { + Dirty = false; + } + + // Display placeholder contents for the Document + static void DisplayContents(MyDocument *doc) + { + ImGui::PushID(doc); + ImGui::Text("Document \"%s\"", doc->Name); + 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(); + if (ImGui::Button("Modify", ImVec2(100, 0))) + doc->Dirty = true; + ImGui::SameLine(); + if (ImGui::Button("Save", ImVec2(100, 0))) + doc->DoSave(); + 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 + static void DisplayContextMenu(MyDocument *doc) + { + if (!ImGui::BeginPopupContextItem()) + return; + + char buf[256]; + sprintf(buf, "Save %s", doc->Name); + if (ImGui::MenuItem(buf, "CTRL+S", false, doc->Open)) + doc->DoSave(); + if (ImGui::MenuItem("Close", "CTRL+W", false, doc->Open)) + doc->DoQueueClose(); + ImGui::EndPopup(); + } +}; + +struct ExampleAppDocuments { + ImVector Documents; + + ExampleAppDocuments() + { + Documents.push_back(MyDocument("Lettuce", true, ImVec4(0.4f, 0.8f, 0.4f, 1.0f))); + Documents.push_back(MyDocument("Eggplant", true, ImVec4(0.8f, 0.5f, 1.0f, 1.0f))); + Documents.push_back(MyDocument("Carrot", true, ImVec4(1.0f, 0.8f, 0.5f, 1.0f))); + Documents.push_back(MyDocument("Tomato", false, ImVec4(1.0f, 0.3f, 0.4f, 1.0f))); + Documents.push_back(MyDocument("A Rather Long Title", false)); + Documents.push_back(MyDocument("Some Document", false)); + } +}; + +// [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 +// the tab bar to notice its absence. During this frame there will be a gap in the tab bar, and if the tab that has +// disappeared was the selected one, the tab bar will report no selected tab during the frame. This will effectively +// give the impression of a flicker for one frame. +// We call SetTabItemClosed() to manually notify the Tab Bar or Docking system of removed tabs to avoid this glitch. +// Note that this completely optional, and only affect tab bars with the ImGuiTabBarFlags_Reorderable flag. +static void NotifyOfDocumentsClosedElsewhere(ExampleAppDocuments &app) +{ + for (MyDocument &doc : app.Documents) { + if (!doc.Open && doc.OpenPrev) + ImGui::SetTabItemClosed(doc.Name); + doc.OpenPrev = doc.Open; + } +} + +void ShowExampleAppDocuments(bool *p_open) +{ + static ExampleAppDocuments app; + + // Options + static bool opt_reorderable = true; + static ImGuiTabBarFlags opt_fitting_flags = ImGuiTabBarFlags_FittingPolicyDefault_; + + bool window_contents_visible = ImGui::Begin("Example: Documents", p_open, ImGuiWindowFlags_MenuBar); + if (!window_contents_visible) { + ImGui::End(); + return; + } + + // Menu + if (ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu("File")) { + 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) + if (!doc.Open && ImGui::MenuItem(doc.Name)) + doc.DoOpen(); + ImGui::EndMenu(); + } + if (ImGui::MenuItem("Close All Documents", NULL, false, open_count > 0)) + for (MyDocument &doc : app.Documents) + doc.DoQueueClose(); + if (ImGui::MenuItem("Exit", "Ctrl+F4") && p_open) + *p_open = false; + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + // [Debug] List documents with one checkbox for each + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) { + MyDocument &doc = app.Documents[doc_n]; + if (doc_n > 0) + ImGui::SameLine(); + ImGui::PushID(&doc); + if (ImGui::Checkbox(doc.Name, &doc.Open)) + if (!doc.Open) + doc.DoForceClose(); + ImGui::PopID(); + } + + ImGui::Separator(); + + // About the ImGuiWindowFlags_UnsavedDocument / ImGuiTabItemFlags_UnsavedDocument flags. + // They have multiple effects: + // - Display a dot next to the title. + // - Tab is selected when clicking the X close button. + // - Closure is not 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. + // 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. + + // Submit Tab Bar and Tabs + { + ImGuiTabBarFlags tab_bar_flags = (opt_fitting_flags) | (opt_reorderable ? ImGuiTabBarFlags_Reorderable : 0); + if (ImGui::BeginTabBar("##tabs", tab_bar_flags)) { + if (opt_reorderable) + NotifyOfDocumentsClosedElsewhere(app); + + // [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; + + ImGuiTabItemFlags tab_flags = (doc.Dirty ? ImGuiTabItemFlags_UnsavedDocument : 0); + bool visible = ImGui::BeginTabItem(doc.Name, &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; + doc.DoQueueClose(); + } + + MyDocument::DisplayContextMenu(&doc); + if (visible) { + MyDocument::DisplayContents(&doc); + ImGui::EndTabItem(); + } + } + + ImGui::EndTabBar(); + } + } + + // Update closing queue + static ImVector close_queue; + if (close_queue.empty()) { + // Close queue is locked once we started a popup + for (MyDocument &doc : app.Documents) + if (doc.WantClose) { + doc.WantClose = false; + close_queue.push_back(&doc); + } + } + + // Display closing confirmation UI + if (!close_queue.empty()) { + int close_queue_unsaved_documents = 0; + for (int n = 0; n < close_queue.Size; n++) + if (close_queue[n]->Dirty) + close_queue_unsaved_documents++; + + if (close_queue_unsaved_documents == 0) { + // Close documents when all are unsaved + for (int n = 0; n < close_queue.Size; n++) + close_queue[n]->DoForceClose(); + close_queue.clear(); + } else { + if (!ImGui::IsPopupOpen("Save?")) + ImGui::OpenPopup("Save?"); + if (ImGui::BeginPopupModal("Save?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("Save change to the following items?"); + float item_height = ImGui::GetTextLineHeightWithSpacing(); + if (ImGui::BeginChildFrame(ImGui::GetID("frame"), ImVec2(-FLT_MIN, 6.25f * item_height))) { + for (int n = 0; n < close_queue.Size; n++) + if (close_queue[n]->Dirty) + ImGui::Text("%s", close_queue[n]->Name); + } + ImGui::EndChildFrame(); + + ImVec2 button_size(ImGui::GetFontSize() * 7.0f, 0.0f); + if (ImGui::Button("Yes", button_size)) { + for (int n = 0; n < close_queue.Size; n++) { + if (close_queue[n]->Dirty) + close_queue[n]->DoSave(); + close_queue[n]->DoForceClose(); + } + close_queue.clear(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("No", button_size)) { + for (int n = 0; n < close_queue.Size; n++) + close_queue[n]->DoForceClose(); + close_queue.clear(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel", button_size)) { + close_queue.clear(); + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } + } + + ImGui::End(); +} + +// End of Demo code +#else + +void ImGui::ShowAboutWindow(bool *) +{ +} +void ImGui::ShowDemoWindow(bool *) +{ +} +void ImGui::ShowUserGuide() +{ +} +void ImGui::ShowStyleEditor(ImGuiStyle *) +{ +} + +#endif + +#endif // #ifndef IMGUI_DISABLE diff --git a/extern/imgui/src/imgui_draw.cpp b/extern/imgui/src/imgui_draw.cpp new file mode 100644 index 0000000..44525e7 --- /dev/null +++ b/extern/imgui/src/imgui_draw.cpp @@ -0,0 +1,4575 @@ +// dear imgui, v1.89.9 +// (drawing and font code) + +/* + +Index of this file: + +// [SECTION] STB libraries implementation +// [SECTION] Style functions +// [SECTION] ImDrawList +// [SECTION] ImDrawListSplitter +// [SECTION] ImDrawData +// [SECTION] Helpers ShadeVertsXXX functions +// [SECTION] ImFontConfig +// [SECTION] ImFontAtlas +// [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 + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning(disable : 4127) // condition expression is constant +#pragma warning(disable : 4505) // unreferenced local function has been removed (stb stuff) +#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 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") +#pragma clang diagnostic ignored \ + "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to + // be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored \ + "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants ok. +#pragma clang diagnostic ignored \ + "-Wglobal-constructors" // warning: declaration requires a global destructor // similar to above, not sure what the exact difference is. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#pragma clang diagnostic ignored \ + "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 +#pragma clang diagnostic ignored "-Wcomma" // warning: possible misuse of comma operator here +#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning: macro name is a reserved identifier +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using + // printf() is a misery with this as C++ va_arg ellipsis changes float to double. +#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#pragma clang diagnostic ignored "-Wreserved-identifier" // warning: identifier '_Xxx' is reserved because it starts with '_' followed by a capital letter +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wstack-protector" // warning: stack protector not protecting local variables: variable length buffer +#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] 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 +#pragma warning(disable : 6011) // (stb_rectpack) Dereferencing NULL pointer 'cur->next'. +#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" +#pragma clang diagnostic ignored "-Wmissing-prototypes" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#pragma clang diagnostic ignored "-Wcast-qual" // warning: cast from 'const xxxx *' to 'xxx *' drops const qualifier +#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 "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers +#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 +#define STBRP_ASSERT(x) \ + do { \ + IM_ASSERT(x); \ + } while (0) +#define STBRP_SORT ImQsort +#define STB_RECT_PACK_IMPLEMENTATION +#endif +#ifdef IMGUI_STB_RECT_PACK_FILENAME +#include IMGUI_STB_RECT_PACK_FILENAME +#else +#include +#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 +#define STBTT_malloc(x, u) ((void)(u), IM_ALLOC(x)) +#define STBTT_free(x, u) ((void)(u), IM_FREE(x)) +#define STBTT_assert(x) \ + do { \ + IM_ASSERT(x); \ + } while (0) +#define STBTT_fmod(x, y) ImFmod(x, y) +#define STBTT_sqrt(x) ImSqrt(x) +#define STBTT_pow(x, y) ImPow(x, y) +#define STBTT_fabs(x) ImFabs(x) +#define STBTT_ifloor(x) ((int)ImFloorSigned(x)) +#define STBTT_iceil(x) ((int)ImCeil(x)) +#define STBTT_STATIC +#define STB_TRUETYPE_IMPLEMENTATION +#else +#define STBTT_DEF extern +#endif +#ifdef IMGUI_STB_TRUETYPE_FILENAME +#include IMGUI_STB_TRUETYPE_FILENAME +#else +#include +#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); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); + colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.29f, 0.48f, 0.54f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.16f, 0.29f, 0.48f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Separator] = colors[ImGuiCol_Border]; + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.10f, 0.40f, 0.75f, 1.00f); + 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_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); + colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_TabActive] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); + colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = 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); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f); + colors[ImGuiCol_Border] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.47f, 0.47f, 0.69f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.42f, 0.41f, 0.64f, 0.69f); + colors[ImGuiCol_TitleBg] = ImVec4(0.27f, 0.27f, 0.54f, 0.83f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.32f, 0.32f, 0.63f, 0.87f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.40f, 0.40f, 0.80f, 0.20f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.40f, 0.40f, 0.55f, 0.80f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.25f, 0.30f, 0.60f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.40f, 0.40f, 0.80f, 0.30f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); + colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); + colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); + colors[ImGuiCol_Button] = ImVec4(0.35f, 0.40f, 0.61f, 0.62f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.40f, 0.48f, 0.71f, 0.79f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.46f, 0.54f, 0.80f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.40f, 0.40f, 0.90f, 0.45f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.45f, 0.45f, 0.90f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.53f, 0.53f, 0.87f, 0.80f); + colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.50f, 0.50f, 0.60f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.60f, 0.60f, 0.70f, 1.00f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.70f, 0.70f, 0.90f, 1.00f); + 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_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); + colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_TabActive] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); + colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.27f, 0.27f, 0.38f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.45f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableBorderLight] = ImVec4(0.26f, 0.26f, 0.28f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = 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); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.98f); + colors[ImGuiCol_Border] = ImVec4(0.00f, 0.00f, 0.00f, 0.30f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); + colors[ImGuiCol_TitleBg] = ImVec4(0.96f, 0.96f, 0.96f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.82f, 0.82f, 0.82f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.00f, 1.00f, 1.00f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.86f, 0.86f, 0.86f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.98f, 0.98f, 0.98f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.69f, 0.69f, 0.69f, 0.80f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.49f, 0.49f, 0.49f, 0.80f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.49f, 0.49f, 0.49f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.59f, 0.98f, 0.78f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.46f, 0.54f, 0.80f, 0.60f); + colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_Separator] = ImVec4(0.39f, 0.39f, 0.39f, 0.62f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.14f, 0.44f, 0.80f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.14f, 0.44f, 0.80f, 1.00f); + 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_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.90f); + colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_TabActive] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); + colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_PlotLines] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.45f, 0.00f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.78f, 0.87f, 0.98f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.57f, 0.57f, 0.64f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableBorderLight] = ImVec4(0.68f, 0.68f, 0.74f, 1.00f); // Prefer using Alpha=1.0 here + colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(0.30f, 0.30f, 0.30f, 0.09f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_NavHighlight] = 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)); + for (int i = 0; i < IM_ARRAYSIZE(ArcFastVtx); i++) { + const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(ArcFastVtx); + ArcFastVtx[i] = ImVec2(ImCos(a), ImSin(a)); + } + ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); +} + +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++) { + const float radius = (float)i; + CircleSegmentCounts[i] = (ImU8)((i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleSegmentMaxError) : IM_DRAWLIST_ARCFAST_SAMPLE_MAX); + } + ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); +} + +// Initialize before use in a new frame. We always have a command ready in the buffer. +void ImDrawList::_ResetForNewFrame() +{ + // Verify that the ImDrawCmd fields we want to memcmp() are contiguous in memory. + IM_STATIC_ASSERT(IM_OFFSETOF(ImDrawCmd, ClipRect) == 0); + IM_STATIC_ASSERT(IM_OFFSETOF(ImDrawCmd, TextureId) == sizeof(ImVec4)); + IM_STATIC_ASSERT(IM_OFFSETOF(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureID)); + if (_Splitter._Count > 1) + _Splitter.Merge(this); + + CmdBuffer.resize(0); + IdxBuffer.resize(0); + VtxBuffer.resize(0); + Flags = _Data->InitialFlags; + memset(&_CmdHeader, 0, sizeof(_CmdHeader)); + _VtxCurrentIdx = 0; + _VtxWritePtr = NULL; + _IdxWritePtr = NULL; + _ClipRectStack.resize(0); + _TextureIdStack.resize(0); + _Path.resize(0); + _Splitter.Clear(); + CmdBuffer.push_back(ImDrawCmd()); + _FringeScale = 1.0f; +} + +void ImDrawList::_ClearFreeMemory() +{ + CmdBuffer.clear(); + IdxBuffer.clear(); + VtxBuffer.clear(); + Flags = ImDrawListFlags_None; + _VtxCurrentIdx = 0; + _VtxWritePtr = NULL; + _IdxWritePtr = NULL; + _ClipRectStack.clear(); + _TextureIdStack.clear(); + _Path.clear(); + _Splitter.ClearFreeMemory(); +} + +ImDrawList *ImDrawList::CloneOutput() const +{ + ImDrawList *dst = IM_NEW(ImDrawList(_Data)); + dst->CmdBuffer = CmdBuffer; + dst->IdxBuffer = IdxBuffer; + dst->VtxBuffer = VtxBuffer; + 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.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() +{ + while (CmdBuffer.Size > 0) { + ImDrawCmd *curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + if (curr_cmd->ElemCount != 0 || curr_cmd->UserCallback != NULL) + return; // break; + CmdBuffer.pop_back(); + } +} + +void ImDrawList::AddCallback(ImDrawCallback callback, void *callback_data) +{ + IM_ASSERT_PARANOID(CmdBuffer.Size > 0); + ImDrawCmd *curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + IM_ASSERT(curr_cmd->UserCallback == NULL); + if (curr_cmd->ElemCount != 0) { + AddDrawCmd(); + curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + } + curr_cmd->UserCallback = callback; + curr_cmd->UserCallbackData = callback_data; + + AddDrawCmd(); // Force a new command after us (see comment below) +} + +// Compare ClipRect, TextureId and VtxOffset with a single memcmp() +#define ImDrawCmd_HeaderSize (IM_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_AreSequentialIdxOffset(CMD_0, CMD_1) (CMD_0->IdxOffset + CMD_0->ElemCount == CMD_1->IdxOffset) + +// Try to merge two last draw commands +void ImDrawList::_TryMergeDrawCmds() +{ + IM_ASSERT_PARANOID(CmdBuffer.Size > 0); + ImDrawCmd *curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + ImDrawCmd *prev_cmd = curr_cmd - 1; + if (ImDrawCmd_HeaderCompare(curr_cmd, prev_cmd) == 0 && ImDrawCmd_AreSequentialIdxOffset(prev_cmd, curr_cmd) && curr_cmd->UserCallback == NULL + && prev_cmd->UserCallback == NULL) { + prev_cmd->ElemCount += curr_cmd->ElemCount; + 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() +{ + // 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 && memcmp(&curr_cmd->ClipRect, &_CmdHeader.ClipRect, sizeof(ImVec4)) != 0) { + AddDrawCmd(); + 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) { + CmdBuffer.pop_back(); + return; + } + + curr_cmd->ClipRect = _CmdHeader.ClipRect; +} + +void ImDrawList::_OnChangedTextureID() +{ + // 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) { + AddDrawCmd(); + 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) { + CmdBuffer.pop_back(); + return; + } + + curr_cmd->TextureId = _CmdHeader.TextureId; +} + +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. + _VtxCurrentIdx = 0; + IM_ASSERT_PARANOID(CmdBuffer.Size > 0); + ImDrawCmd *curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + // IM_ASSERT(curr_cmd->VtxOffset != _CmdHeader.VtxOffset); // See #3349 + if (curr_cmd->ElemCount != 0) { + AddDrawCmd(); + return; + } + IM_ASSERT(curr_cmd->UserCallback == NULL); + curr_cmd->VtxOffset = _CmdHeader.VtxOffset; +} + +int ImDrawList::_CalcCircleAutoSegmentCount(float radius) const +{ + // Automatic segment count + const int radius_idx = (int)(radius + 0.999999f); // ceil to never reduce accuracy + if (radius_idx >= 0 && radius_idx < IM_ARRAYSIZE(_Data->CircleSegmentCounts)) + return _Data->CircleSegmentCounts[radius_idx]; // Use cached value + 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) +{ + ImVec4 cr(cr_min.x, cr_min.y, cr_max.x, cr_max.y); + if (intersect_with_current_clip_rect) { + ImVec4 current = _CmdHeader.ClipRect; + if (cr.x < current.x) + cr.x = current.x; + if (cr.y < current.y) + cr.y = current.y; + if (cr.z > current.z) + cr.z = current.z; + if (cr.w > current.w) + cr.w = current.w; + } + 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) +{ + _TextureIdStack.push_back(texture_id); + _CmdHeader.TextureId = texture_id; + _OnChangedTextureID(); +} + +void ImDrawList::PopTextureID() +{ + _TextureIdStack.pop_back(); + _CmdHeader.TextureId = (_TextureIdStack.Size == 0) ? (ImTextureID)NULL : _TextureIdStack.Data[_TextureIdStack.Size - 1]; + _OnChangedTextureID(); +} + +// 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. +void ImDrawList::PrimReserve(int idx_count, int vtx_count) +{ + // Large mesh support (when enabled) + IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0); + if (sizeof(ImDrawIdx) == 2 && (_VtxCurrentIdx + vtx_count >= (1 << 16)) && (Flags & ImDrawListFlags_AllowVtxOffset)) { + // FIXME: In theory we should be testing that vtx_count <64k here. + // In practice, RenderText() relies on reserving ahead for a worst case scenario so it is currently useful for us + // to not make that check until we rework the text functions to handle clipping and large horizontal lines better. + _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 a 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) +{ + ImVec2 b(c.x, a.y), d(a.x, c.y), uv(_Data->TexUvWhitePixel); + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; + _IdxWritePtr[1] = (ImDrawIdx)(idx + 1); + _IdxWritePtr[2] = (ImDrawIdx)(idx + 2); + _IdxWritePtr[3] = idx; + _IdxWritePtr[4] = (ImDrawIdx)(idx + 2); + _IdxWritePtr[5] = (ImDrawIdx)(idx + 3); + _VtxWritePtr[0].pos = a; + _VtxWritePtr[0].uv = uv; + _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; + _VtxWritePtr[1].uv = uv; + _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; + _VtxWritePtr[2].uv = uv; + _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; + _VtxWritePtr[3].uv = uv; + _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _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); + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + _IdxWritePtr[0] = idx; + _IdxWritePtr[1] = (ImDrawIdx)(idx + 1); + _IdxWritePtr[2] = (ImDrawIdx)(idx + 2); + _IdxWritePtr[3] = idx; + _IdxWritePtr[4] = (ImDrawIdx)(idx + 2); + _IdxWritePtr[5] = (ImDrawIdx)(idx + 3); + _VtxWritePtr[0].pos = a; + _VtxWritePtr[0].uv = uv_a; + _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; + _VtxWritePtr[1].uv = uv_b; + _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; + _VtxWritePtr[2].uv = uv_c; + _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; + _VtxWritePtr[3].uv = uv_d; + _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _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; + _IdxWritePtr[0] = idx; + _IdxWritePtr[1] = (ImDrawIdx)(idx + 1); + _IdxWritePtr[2] = (ImDrawIdx)(idx + 2); + _IdxWritePtr[3] = idx; + _IdxWritePtr[4] = (ImDrawIdx)(idx + 2); + _IdxWritePtr[5] = (ImDrawIdx)(idx + 3); + _VtxWritePtr[0].pos = a; + _VtxWritePtr[0].uv = uv_a; + _VtxWritePtr[0].col = col; + _VtxWritePtr[1].pos = b; + _VtxWritePtr[1].uv = uv_b; + _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = c; + _VtxWritePtr[2].uv = uv_c; + _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = d; + _VtxWritePtr[3].uv = uv_d; + _VtxWritePtr[3].col = col; + _VtxWritePtr += 4; + _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++) { + const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1; + float dx = points[i2].x - points[i1].x; + float dy = points[i2].y - points[i1].y; + IM_NORMALIZE2F_OVER_ZERO(dx, dy); + temp_normals[i1].x = dy; + temp_normals[i1].y = -dx; + } + 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) { + temp_points[0] = points[0] + temp_normals[0] * half_draw_size; + temp_points[1] = points[0] - temp_normals[0] * half_draw_size; + 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. + unsigned int idx1 = _VtxCurrentIdx; // Vertex index for start of line segment + for (int i1 = 0; i1 < count; i1++) // i1 is the first point of the line segment + { + 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 + 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 + _IdxWritePtr[0] = (ImDrawIdx)(idx2 + 0); + _IdxWritePtr[1] = (ImDrawIdx)(idx1 + 0); + _IdxWritePtr[2] = (ImDrawIdx)(idx1 + 1); // Right tri + _IdxWritePtr[3] = (ImDrawIdx)(idx2 + 1); + _IdxWritePtr[4] = (ImDrawIdx)(idx1 + 1); + _IdxWritePtr[5] = (ImDrawIdx)(idx2 + 0); // Left tri + _IdxWritePtr += 6; + } else { + // Add indexes for four triangles + _IdxWritePtr[0] = (ImDrawIdx)(idx2 + 0); + _IdxWritePtr[1] = (ImDrawIdx)(idx1 + 0); + _IdxWritePtr[2] = (ImDrawIdx)(idx1 + 2); // Right tri 1 + _IdxWritePtr[3] = (ImDrawIdx)(idx1 + 2); + _IdxWritePtr[4] = (ImDrawIdx)(idx2 + 2); + _IdxWritePtr[5] = (ImDrawIdx)(idx2 + 0); // Right tri 2 + _IdxWritePtr[6] = (ImDrawIdx)(idx2 + 1); + _IdxWritePtr[7] = (ImDrawIdx)(idx1 + 1); + _IdxWritePtr[8] = (ImDrawIdx)(idx1 + 0); // Left tri 1 + _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 + if (use_texture) { + // If we're using textures we only need to emit the left/right edge vertices + ImVec4 tex_uvs = _Data->TexUvLines[integer_thickness]; + /*if (fractional_thickness != 0.0f) // Currently always zero when use_texture==false! + { + const ImVec4 tex_uvs_1 = _Data->TexUvLines[integer_thickness + 1]; + tex_uvs.x = tex_uvs.x + (tex_uvs_1.x - tex_uvs.x) * fractional_thickness; // inlined ImLerp() + tex_uvs.y = tex_uvs.y + (tex_uvs_1.y - tex_uvs.y) * fractional_thickness; + tex_uvs.z = tex_uvs.z + (tex_uvs_1.z - tex_uvs.z) * fractional_thickness; + tex_uvs.w = tex_uvs.w + (tex_uvs_1.w - tex_uvs.w) * fractional_thickness; + }*/ + ImVec2 tex_uv0(tex_uvs.x, tex_uvs.y); + ImVec2 tex_uv1(tex_uvs.z, tex_uvs.w); + for (int i = 0; i < points_count; i++) { + _VtxWritePtr[0].pos = temp_points[i * 2 + 0]; + _VtxWritePtr[0].uv = tex_uv0; + _VtxWritePtr[0].col = col; // Left-side outer edge + _VtxWritePtr[1].pos = temp_points[i * 2 + 1]; + _VtxWritePtr[1].uv = tex_uv1; + _VtxWritePtr[1].col = col; // Right-side outer edge + _VtxWritePtr += 2; + } + } else { + // If we're not using a texture, we need the center vertex as well + for (int i = 0; i < points_count; i++) { + _VtxWritePtr[0].pos = points[i]; + _VtxWritePtr[0].uv = opaque_uv; + _VtxWritePtr[0].col = col; // Center of line + _VtxWritePtr[1].pos = temp_points[i * 2 + 0]; + _VtxWritePtr[1].uv = opaque_uv; + _VtxWritePtr[1].col = col_trans; // Left-side outer edge + _VtxWritePtr[2].pos = temp_points[i * 2 + 1]; + _VtxWritePtr[2].uv = opaque_uv; + _VtxWritePtr[2].col = col_trans; // Right-side outer edge + _VtxWritePtr += 3; + } + } + } else { + // [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) { + const int points_last = points_count - 1; + temp_points[0] = points[0] + temp_normals[0] * (half_inner_thickness + AA_SIZE); + temp_points[1] = points[0] + temp_normals[0] * (half_inner_thickness); + temp_points[2] = points[0] - temp_normals[0] * (half_inner_thickness); + temp_points[3] = points[0] - temp_normals[0] * (half_inner_thickness + AA_SIZE); + temp_points[points_last * 4 + 0] = points[points_last] + temp_normals[points_last] * (half_inner_thickness + AA_SIZE); + temp_points[points_last * 4 + 1] = points[points_last] + temp_normals[points_last] * (half_inner_thickness); + 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. + unsigned int idx1 = _VtxCurrentIdx; // Vertex index for start of line segment + for (int i1 = 0; i1 < count; i1++) // i1 is the first point of the line segment + { + 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; + IM_FIXNORMAL2F(dm_x, dm_y); + float dm_out_x = dm_x * (half_inner_thickness + AA_SIZE); + 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; + out_vtx[0].y = points[i2].y + dm_out_y; + out_vtx[1].x = points[i2].x + dm_in_x; + out_vtx[1].y = points[i2].y + dm_in_y; + out_vtx[2].x = points[i2].x - dm_in_x; + 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); + _IdxWritePtr[6] = (ImDrawIdx)(idx2 + 1); + _IdxWritePtr[7] = (ImDrawIdx)(idx1 + 1); + _IdxWritePtr[8] = (ImDrawIdx)(idx1 + 0); + _IdxWritePtr[9] = (ImDrawIdx)(idx1 + 0); + _IdxWritePtr[10] = (ImDrawIdx)(idx2 + 0); + _IdxWritePtr[11] = (ImDrawIdx)(idx2 + 1); + _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++) { + _VtxWritePtr[0].pos = temp_points[i * 4 + 0]; + _VtxWritePtr[0].uv = opaque_uv; + _VtxWritePtr[0].col = col_trans; + _VtxWritePtr[1].pos = temp_points[i * 4 + 1]; + _VtxWritePtr[1].uv = opaque_uv; + _VtxWritePtr[1].col = col; + _VtxWritePtr[2].pos = temp_points[i * 4 + 2]; + _VtxWritePtr[2].uv = opaque_uv; + _VtxWritePtr[2].col = col; + _VtxWritePtr[3].pos = temp_points[i * 4 + 3]; + _VtxWritePtr[3].uv = opaque_uv; + _VtxWritePtr[3].col = col_trans; + _VtxWritePtr += 4; + } + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } else { + // [PATH 4] Non texture-based, Non anti-aliased lines + 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; + _VtxCurrentIdx += 4; + } + } +} + +// - 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 + const float AA_SIZE = _FringeScale; + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + 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; + for (int i = 2; i < points_count; i++) { + _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; + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) { + const ImVec2 &p0 = points[i0]; + const ImVec2 &p1 = points[i1]; + float dx = p1.x - p0.x; + float dy = p1.y - p0.y; + IM_NORMALIZE2F_OVER_ZERO(dx, dy); + 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 + const ImVec2 &n0 = temp_normals[i0]; + const ImVec2 &n1 = temp_normals[i1]; + float dm_x = (n0.x + n1.x) * 0.5f; + float dm_y = (n0.y + n1.y) * 0.5f; + 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)); + _IdxWritePtr += 6; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } else { + // Non Anti-aliased Fill + const int idx_count = (points_count - 2) * 3; + const int vtx_count = points_count; + PrimReserve(idx_count, vtx_count); + for (int i = 0; i < vtx_count; i++) { + _VtxWritePtr[0].pos = points[i]; + _VtxWritePtr[0].uv = uv; + _VtxWritePtr[0].col = col; + _VtxWritePtr++; + } + for (int i = 2; i < points_count; i++) { + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); + _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + i - 1); + _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + i); + _IdxWritePtr += 3; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } +} + +void ImDrawList::_PathArcToFastEx(const ImVec2 ¢er, float radius, int a_min_sample, int a_max_sample, int a_step) +{ + if (radius < 0.5f) { + _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) { + sample_index = sample_index % IM_DRAWLIST_ARCFAST_SAMPLE_MAX; + 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) { + // 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; + out_ptr++; + } + } else { + for (int a = a_min_sample; a >= a_max_sample; a -= a_step, sample_index -= a_step, a_step = a_next_step) { + // 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 ¢er, float radius, float a_min, float a_max, int num_segments) +{ + if (radius < 0.5f) { + _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)); + for (int i = 0; i <= num_segments; i++) { + const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min); + _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 ¢er, float radius, int a_min_of_12, int a_max_of_12) +{ + if (radius < 0.5f) { + _Path.push_back(center); + return; + } + _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 ¢er, float radius, float a_min, float a_max, int num_segments) +{ + if (radius < 0.5f) { + _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)ImFloorSigned(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)ImFloorSigned(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)); + if (a_mid_samples > 0) + _PathArcToFastEx(center, radius, a_min_sample, a_max_sample, 0); + if (a_emit_end) + _Path.push_back(ImVec2(center.x + ImCos(a_max) * radius, center.y + ImSin(a_max) * radius)); + } else { + const float arc_length = ImAbs(a_max - a_min); + const int circle_segment_count = _CalcCircleAutoSegmentCount(radius); + const int arc_segment_count = ImMax((int)ImCeil(circle_segment_count * arc_length / (IM_PI * 2.0f)), (int)(2.0f * IM_PI / arc_length)); + _PathArcToN(center, radius, a_min, a_max, arc_segment_count); + } +} + +ImVec2 ImBezierCubicCalc(const ImVec2 &p1, const ImVec2 &p2, const ImVec2 &p3, const ImVec2 &p4, float t) +{ + float u = 1.0f - t; + float w1 = u * u * u; + float w2 = 3 * u * u * t; + float w3 = 3 * u * t * t; + 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; + float w1 = u * u; + float w2 = 2 * u * t; + 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) +{ + float dx = x4 - x1; + float dy = y4 - y1; + float d2 = (x2 - x4) * dy - (y2 - y4) * dx; + float d3 = (x3 - x4) * dy - (y3 - y4) * dx; + d2 = (d2 >= 0) ? d2 : -d2; + d3 = (d3 >= 0) ? d3 : -d3; + if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy)) { + path->push_back(ImVec2(x4, y4)); + } else if (level < 10) { + float x12 = (x1 + x2) * 0.5f, y12 = (y1 + y2) * 0.5f; + float x23 = (x2 + x3) * 0.5f, y23 = (y2 + y3) * 0.5f; + float x34 = (x3 + x4) * 0.5f, y34 = (y3 + y4) * 0.5f; + float x123 = (x12 + x23) * 0.5f, y123 = (y12 + y23) * 0.5f; + float x234 = (x23 + x34) * 0.5f, y234 = (y23 + y34) * 0.5f; + float x1234 = (x123 + x234) * 0.5f, y1234 = (y123 + y234) * 0.5f; + PathBezierCubicCurveToCasteljau(path, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1); + 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; + float det = (x2 - x3) * dy - (y2 - y3) * dx; + if (det * det * 4.0f < tess_tol * (dx * dx + dy * dy)) { + path->push_back(ImVec2(x3, y3)); + } else if (level < 10) { + float x12 = (x1 + x2) * 0.5f, y12 = (y1 + y2) * 0.5f; + float x23 = (x2 + x3) * 0.5f, y23 = (y2 + y3) * 0.5f; + float x123 = (x12 + x23) * 0.5f, y123 = (y12 + y23) * 0.5f; + PathBezierQuadraticCurveToCasteljau(path, x1, y1, x12, y12, x123, y123, tess_tol, level + 1); + 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(); + if (num_segments == 0) { + IM_ASSERT(_Data->CurveTessellationTol > 0.0f); + PathBezierCubicCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, _Data->CurveTessellationTol, 0); // Auto-tessellated + } else { + float t_step = 1.0f / (float)num_segments; + for (int i_step = 1; i_step <= num_segments; i_step++) + _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(); + if (num_segments == 0) { + IM_ASSERT(_Data->CurveTessellationTol > 0.0f); + PathBezierQuadraticCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, _Data->CurveTessellationTol, 0); // Auto-tessellated + } else { + float t_step = 1.0f / (float)num_segments; + for (int i_step = 1; i_step <= num_segments; i_step++) + _Path.push_back(ImBezierQuadraticCalc(p1, p2, p3, t_step * i_step)); + } +} + +IM_STATIC_ASSERT(ImDrawFlags_RoundCornersTopLeft == (1 << 4)); +static inline ImDrawFlags FixRectCornerFlags(ImDrawFlags flags) +{ +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // Obsoleted in 1.82 (from February 2021) + // Legacy Support for hard coded ~0 (used to be a suggested equivalent to ImDrawCornerFlags_All) + // ~0 --> ImDrawFlags_RoundCornersAll or 0 + if (flags == ~0) + return ImDrawFlags_RoundCornersAll; + + // Legacy Support for hard coded 0x01 to 0x0F (matching 15 out of 16 old flags combinations) + // 0x01 --> ImDrawFlags_RoundCornersTopLeft (VALUE 0x01 OVERLAPS ImDrawFlags_Closed but ImDrawFlags_Closed is never valid in this path!) + // 0x02 --> ImDrawFlags_RoundCornersTopRight + // 0x03 --> ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight + // 0x04 --> ImDrawFlags_RoundCornersBotLeft + // 0x05 --> ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBotLeft + // ... + // 0x0F --> ImDrawFlags_RoundCornersAll or 0 + // (See all values in ImDrawCornerFlags_) + if (flags >= 0x01 && flags <= 0x0F) + return (flags << 4); + + // We cannot support hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f' +#endif + + // If this triggers, please update your code replacing hardcoded values with new ImDrawFlags_RoundCorners* values. + // Note that ImDrawFlags_Closed (== 0x01) is an invalid flag for AddRect(), AddRectFilled(), PathRect() etc... + 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) { + flags = FixRectCornerFlags(flags); + rounding = ImMin(rounding, + ImFabs(b.x - a.x) + * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) + || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) + ? 0.5f + : 1.0f) + - 1.0f); + rounding = ImMin(rounding, + ImFabs(b.y - a.y) + * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) + || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) + ? 0.5f + : 1.0f) + - 1.0f); + } + if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { + PathLineTo(a); + PathLineTo(ImVec2(b.x, a.y)); + PathLineTo(b); + PathLineTo(ImVec2(a.x, b.y)); + } else { + const float rounding_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? rounding : 0.0f; + const float rounding_tr = (flags & ImDrawFlags_RoundCornersTopRight) ? rounding : 0.0f; + const float rounding_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? rounding : 0.0f; + const float rounding_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? rounding : 0.0f; + PathArcToFast(ImVec2(a.x + rounding_tl, a.y + rounding_tl), rounding_tl, 6, 9); + PathArcToFast(ImVec2(b.x - rounding_tr, a.y + rounding_tr), rounding_tr, 9, 12); + PathArcToFast(ImVec2(b.x - rounding_br, b.y - rounding_br), rounding_br, 0, 3); + 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) + return; + PathLineTo(p1 + ImVec2(0.5f, 0.5f)); + 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) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + if (Flags & ImDrawListFlags_AntiAliasedLines) + PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.50f, 0.50f), rounding, flags); + else + 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) + return; + if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { + PrimReserve(6, 4); + PrimRect(p_min, p_max, col); + } else { + PathRect(p_min, p_max, rounding, flags); + 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)); + PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); + PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 2)); + PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 3)); + PrimWriteVtx(p_min, uv, col_upr_left); + PrimWriteVtx(ImVec2(p_max.x, p_min.y), uv, col_upr_right); + 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 ¢er, 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 + _PathArcToFastEx(center, radius - 0.5f, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0); + _Path.Size--; + } else { + // 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 ¢er, 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 + _PathArcToFastEx(center, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0); + _Path.Size--; + } else { + // 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 ¢er, 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 ¢er, 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); +} + +// 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(const 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; + + if (text_end == NULL) + text_end = text_begin + strlen(text_begin); + if (text_begin == text_end) + return; + + // 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) { + clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect->x); + clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect->y); + clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z); + clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w); + } + 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(NULL, 0.0f, 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) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + if (push_texture_id) + PushTextureID(user_texture_id); + + PrimReserve(6, 4); + PrimRectUV(p_min, p_max, uv_min, uv_max, col); + + if (push_texture_id) + PopTextureID(); +} + +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) +{ + if ((col & IM_COL32_A_MASK) == 0) + return; + + const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + if (push_texture_id) + PushTextureID(user_texture_id); + + PrimReserve(6, 4); + PrimQuadUV(p1, p2, p3, p4, uv1, uv2, uv3, uv4, col); + + if (push_texture_id) + PopTextureID(); +} + +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) +{ + 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); + return; + } + + const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + if (push_texture_id) + PushTextureID(user_texture_id); + + 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(); +} + +//----------------------------------------------------------------------------- +// [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++) { + if (i == _Current) + memset(&_Channels[i], 0, sizeof(_Channels[i])); // Current channel is a copy of CmdBuffer/IdxBuffer, don't destruct again + _Channels[i]._CmdBuffer.clear(); + _Channels[i]._IdxBuffer.clear(); + } + _Current = 0; + _Count = 1; + _Channels.clear(); +} + +void ImDrawListSplitter::Split(ImDrawList *draw_list, int channels_count) +{ + IM_UNUSED(draw_list); + IM_ASSERT(_Current == 0 && _Count <= 1 && "Nested channel splitting is not supported. Please use separate instances of ImDrawListSplitter."); + int old_channels_count = _Channels.Size; + if (old_channels_count < channels_count) { + _Channels.reserve(channels_count); // Avoid over reserving since this is likely to stay stable + _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 + memset(&_Channels[0], 0, sizeof(ImDrawChannel)); + for (int i = 1; i < channels_count; i++) { + if (i >= old_channels_count) { + IM_PLACEMENT_NEW(&_Channels[i]) ImDrawChannel(); + } else { + _Channels[i]._CmdBuffer.resize(0); + _Channels[i]._IdxBuffer.resize(0); + } + } +} + +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; + ImDrawCmd *last_cmd = (_Count > 0 && draw_list->CmdBuffer.Size > 0) ? &draw_list->CmdBuffer.back() : NULL; + int idx_offset = last_cmd ? last_cmd->IdxOffset + last_cmd->ElemCount : 0; + for (int i = 1; i < _Count; i++) { + 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. + // Manipulating IdxOffset (e.g. by reordering draw commands like done by RenderDimmedBackgroundBehindWindow()) is not supported within a splitter. + ImDrawCmd *next_cmd = &ch._CmdBuffer[0]; + if (ImDrawCmd_HeaderCompare(last_cmd, next_cmd) == 0 && last_cmd->UserCallback == NULL && next_cmd->UserCallback == NULL) { + // Merge previous channel last draw command with current channel first draw command if matching. + last_cmd->ElemCount += next_cmd->ElemCount; + idx_offset += next_cmd->ElemCount; + ch._CmdBuffer.erase(ch._CmdBuffer.Data); // FIXME-OPT: Improve for multiple merges. + } + } + if (ch._CmdBuffer.Size > 0) + last_cmd = &ch._CmdBuffer.back(); + new_cmd_buffer_count += ch._CmdBuffer.Size; + new_idx_buffer_count += ch._IdxBuffer.Size; + for (int cmd_n = 0; cmd_n < ch._CmdBuffer.Size; cmd_n++) { + ch._CmdBuffer.Data[cmd_n].IdxOffset = idx_offset; + idx_offset += ch._CmdBuffer.Data[cmd_n].ElemCount; + } + } + 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; + for (int i = 1; i < _Count; i++) { + ImDrawChannel &ch = _Channels[i]; + if (int sz = ch._CmdBuffer.Size) { + memcpy(cmd_write, ch._CmdBuffer.Data, sz * sizeof(ImDrawCmd)); + cmd_write += sz; + } + 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 + 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)); + _Current = 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 + else if (ImDrawCmd_HeaderCompare(curr_cmd, &draw_list->_CmdHeader) != 0) + draw_list->AddDrawCmd(); +} + +//----------------------------------------------------------------------------- +// [SECTION] ImDrawData +//----------------------------------------------------------------------------- + +void ImDrawData::Clear() +{ + Valid = false; + CmdListsCount = TotalIdxCount = TotalVtxCount = 0; + 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; +} + +// 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) +{ + if (draw_list->CmdBuffer.Size == 0) + 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. + // Be mindful that the lower-level ImDrawList API doesn't filter vertices. Use the Metrics/Debugger window to inspect draw list contents. + // - If you want large meshes with more than 64K vertices, you can either: + // (A) Handle the ImDrawCmd::VtxOffset value in your renderer backend, and set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset'. + // Most example backends already support this from 1.71. Pre-1.71 backends won't. + // Some graphics API such as GL ES 1/2 don't have a way to offset the starting vertex so it is not supported for them. + // (B) Or handle 32-bit indices in your renderer backend, and uncomment '#define ImDrawIdx unsigned int' line in imconfig.h. + // Most example backends already support this. For example, the OpenGL example code detect index size at compile-time: + // glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset); + // Your own engine or render API may use different parameters or function calls to specify index sizes. + // 2 and 4 bytes indices are generally supported by most graphics API. + // - If for some reason neither of those solutions works for you, a workaround is to call BeginChild()/EndChild() before reaching + // 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"); + + // 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++) { + ImDrawList *cmd_list = CmdLists[i]; + if (cmd_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; + } +} + +// 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. +void ImDrawData::ScaleClipRects(const ImVec2 &fb_scale) +{ + for (ImDrawList *draw_list : CmdLists) + 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) +{ + ImVec2 gradient_extent = gradient_p1 - gradient_p0; + float gradient_inv_length2 = 1.0f / ImLengthSqr(gradient_extent); + ImDrawVert *vert_start = draw_list->VtxBuffer.Data + vert_start_idx; + ImDrawVert *vert_end = draw_list->VtxBuffer.Data + vert_end_idx; + const int col0_r = (int)(col0 >> IM_COL32_R_SHIFT) & 0xFF; + const int col0_g = (int)(col0 >> IM_COL32_G_SHIFT) & 0xFF; + const int col0_b = (int)(col0 >> IM_COL32_B_SHIFT) & 0xFF; + const int col_delta_r = ((int)(col1 >> IM_COL32_R_SHIFT) & 0xFF) - col0_r; + const int col_delta_g = ((int)(col1 >> IM_COL32_G_SHIFT) & 0xFF) - col0_g; + const int col_delta_b = ((int)(col1 >> IM_COL32_B_SHIFT) & 0xFF) - col0_b; + for (ImDrawVert *vert = vert_start; vert < vert_end; vert++) { + float d = ImDot(vert->pos - gradient_p0, gradient_extent); + float t = ImClamp(d * gradient_inv_length2, 0.0f, 1.0f); + int r = (int)(col0_r + col_delta_r * t); + int g = (int)(col0_g + col_delta_g * t); + int b = (int)(col0_b + col_delta_b * t); + 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) +{ + const ImVec2 size = b - a; + const ImVec2 uv_size = uv_b - uv_a; + 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) { + const ImVec2 min = ImMin(uv_a, uv_b); + const ImVec2 max = ImMax(uv_a, uv_b); + for (ImDrawVert *vertex = vert_start; vertex < vert_end; ++vertex) + vertex->uv = ImClamp(uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale), min, max); + } else { + for (ImDrawVert *vertex = vert_start; vertex < vert_end; ++vertex) + vertex->uv = uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] ImFontConfig +//----------------------------------------------------------------------------- + +ImFontConfig::ImFontConfig() +{ + memset(this, 0, sizeof(*this)); + FontDataOwnedByAtlas = true; + OversampleH = 2; + OversampleV = 1; + GlyphMaxAdvanceX = FLT_MAX; + RasterizerMultiply = 1.0f; + EllipsisChar = (ImWchar)-1; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImFontAtlas +//----------------------------------------------------------------------------- + +// 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) +const int FONT_ATLAS_DEFAULT_TEX_DATA_W = 122; // Actual texture will be 2 times that + 1 spacing. +const int FONT_ATLAS_DEFAULT_TEX_DATA_H = 27; +static const char FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[FONT_ATLAS_DEFAULT_TEX_DATA_W * FONT_ATLAS_DEFAULT_TEX_DATA_H + 1] = { + "..- -XXXXXXX- X - X -XXXXXXX - XXXXXXX- XX - XX XX " + "..- -X.....X- X.X - X.X -X.....X - X.....X- X..X -X..X X..X" + "--- -XXX.XXX- X...X - X...X -X....X - X....X- X..X -X...X X...X" + "X - X.X - X.....X - X.....X -X...X - X...X- X..X - X...X X...X " + "XX - X.X -X.......X- X.......X -X..X.X - X.X..X- X..X - X...X...X " + "X.X - X.X -XXXX.XXXX- XXXX.XXXX -X.X X.X - X.X X.X- X..XXX - X.....X " + "X..X - X.X - X.X - X.X -XX X.X - X.X XX- X..X..XXX - X...X " + "X...X - X.X - X.X - XX X.X XX - X.X - X.X - X..X..X..XX - X.X " + "X....X - X.X - X.X - X.X X.X X.X - X.X - X.X - X..X..X..X.X - X...X " + "X.....X - X.X - X.X - X..X X.X X..X - X.X - X.X -XXX X..X..X..X..X- X.....X " + "X......X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X XX-XX X.X -X..XX........X..X- X...X...X " + "X.......X - X.X - X.X -X.....................X- X.X X.X-X.X X.X -X...X...........X- X...X X...X " + "X........X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X..X-X..X.X - X..............X-X...X X...X" + "X.........X -XXX.XXX- X.X - X..X X.X X..X - X...X-X...X - X.............X-X..X X..X" + "X..........X-X.....X- X.X - X.X X.X X.X - X....X-X....X - X.............X- XX XX " + "X......XXXXX-XXXXXXX- X.X - XX X.X XX - X.....X-X.....X - X............X--------------" + "X...X..X --------- X.X - X.X - XXXXXXX-XXXXXXX - X...........X - " + "X..X X..X - -XXXX.XXXX- XXXX.XXXX ------------------------------------- X..........X - " + "X.X X..X - -X.......X- X.......X - XX XX - - X..........X - " + "XX X..X - - X.....X - X.....X - X.X X.X - - X........X - " + " X..X - - X...X - X...X - X..X X..X - - X........X - " + " XX - - X.X - X.X - X...XXXXXXXXXXXXX...X - - XXXXXXXXXX - " + "------------- - X - X -X.....................X- ------------------- " + " ----------------------------------- X...XXXXXXXXXXXXX...X - " + " - X..X X..X - " + " - X.X X.X - " + " - XX XX - "}; + +static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3] = { + // Pos ........ Size ......... Offset ...... + {ImVec2(0, 3), ImVec2(12, 19), ImVec2(0, 0)}, // ImGuiMouseCursor_Arrow + {ImVec2(13, 0), ImVec2(7, 16), ImVec2(1, 8)}, // ImGuiMouseCursor_TextInput + {ImVec2(31, 0), ImVec2(23, 23), ImVec2(11, 11)}, // ImGuiMouseCursor_ResizeAll + {ImVec2(21, 0), ImVec2(9, 23), ImVec2(4, 11)}, // ImGuiMouseCursor_ResizeNS + {ImVec2(55, 18), ImVec2(23, 9), ImVec2(11, 4)}, // ImGuiMouseCursor_ResizeEW + {ImVec2(73, 0), ImVec2(17, 17), ImVec2(8, 8)}, // ImGuiMouseCursor_ResizeNESW + {ImVec2(55, 0), ImVec2(17, 17), ImVec2(8, 8)}, // ImGuiMouseCursor_ResizeNWSE + {ImVec2(91, 0), ImVec2(17, 22), ImVec2(5, 0)}, // ImGuiMouseCursor_Hand + {ImVec2(109, 0), ImVec2(13, 15), ImVec2(6, 7)}, // ImGuiMouseCursor_NotAllowed +}; + +ImFontAtlas::ImFontAtlas() +{ + memset(this, 0, sizeof(*this)); + TexGlyphPadding = 1; + PackIdMouseCursors = PackIdLines = -1; +} + +ImFontAtlas::~ImFontAtlas() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + Clear(); +} + +void ImFontAtlas::ClearInputData() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + for (ImFontConfig &font_cfg : ConfigData) + 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. + for (ImFont *font : Fonts) + if (font->ConfigData >= ConfigData.Data && font->ConfigData < ConfigData.Data + ConfigData.Size) { + font->ConfigData = NULL; + font->ConfigDataCount = 0; + } + ConfigData.clear(); + CustomRects.clear(); + PackIdMouseCursors = PackIdLines = -1; + // Important: we leave TexReady untouched +} + +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 +} + +void ImFontAtlas::ClearFonts() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + 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) { + 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++)); + } + } + + *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) +{ + 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); + + // Create new font + if (!font_cfg->MergeMode) + Fonts.push_back(IM_NEW(ImFont)); + else + IM_ASSERT(!Fonts.empty() && "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. + + ConfigData.push_back(*font_cfg); + ImFontConfig &new_font_cfg = ConfigData.back(); + if (new_font_cfg.DstFont == NULL) + new_font_cfg.DstFont = Fonts.back(); + if (!new_font_cfg.FontDataOwnedByAtlas) { + 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); + } + + if (new_font_cfg.DstFont->EllipsisChar == (ImWchar)-1) + new_font_cfg.DstFont->EllipsisChar = font_cfg->EllipsisChar; + + // Invalidate texture + TexReady = false; + ClearTexData(); + return new_font_cfg.DstFont; +} + +// 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); +static const char *GetDefaultCompressedFontDataTTFBase85(); +static unsigned int Decode85Byte(char c) +{ + return c >= '\\' ? c - 36 : c - 35; +} +static void Decode85(const unsigned char *src, unsigned char *dst) +{ + while (*src) { + unsigned int tmp = + Decode85Byte(src[0]) + 85 * (Decode85Byte(src[1]) + 85 * (Decode85Byte(src[2]) + 85 * (Decode85Byte(src[3]) + 85 * Decode85Byte(src[4])))); + dst[0] = ((tmp >> 0) & 0xFF); + dst[1] = ((tmp >> 8) & 0xFF); + dst[2] = ((tmp >> 16) & 0xFF); + dst[3] = ((tmp >> 24) & 0xFF); // We can't assume little-endianness. + src += 5; + dst += 4; + } +} + +// Load embedded ProggyClean.ttf at size 13, disable oversampling +ImFont *ImFontAtlas::AddFontDefault(const ImFontConfig *font_cfg_template) +{ + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + if (!font_cfg_template) { + font_cfg.OversampleH = font_cfg.OversampleV = 1; + font_cfg.PixelSnapH = true; + } + 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); + font_cfg.EllipsisChar = (ImWchar)0x0085; + font_cfg.GlyphOffset.y = 1.0f * IM_FLOOR(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units + + const char *ttf_compressed_base85 = GetDefaultCompressedFontDataTTFBase85(); + const ImWchar *glyph_ranges = font_cfg.GlyphRanges != NULL ? font_cfg.GlyphRanges : GetGlyphRangesDefault(); + ImFont *font = AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, glyph_ranges); + return 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()!"); + 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!"); + return NULL; + } + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + if (font_cfg.Name[0] == '\0') { + // Store a short copy of filename into into the font name for convenience + const char *p; + for (p = filename + strlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) { } + ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", p, size_pixels); + } + 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 *ttf_data, int ttf_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()!"); + ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); + IM_ASSERT(font_cfg.FontData == NULL); + font_cfg.FontData = ttf_data; + font_cfg.FontDataSize = ttf_size; + font_cfg.SizePixels = size_pixels > 0.0f ? size_pixels : font_cfg.SizePixels; + if (glyph_ranges) + 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)strlen(compressed_ttf_data_base85) + 4) / 5) * 4; + void *compressed_ttf = IM_ALLOC((size_t)compressed_ttf_size); + Decode85((const unsigned char *)compressed_ttf_data_base85, (unsigned char *)compressed_ttf); + ImFont *font = AddFontFromMemoryCompressedTTF(compressed_ttf, compressed_ttf_size, size_pixels, font_cfg, glyph_ranges); + IM_FREE(compressed_ttf); + return font; +} + +int ImFontAtlas::AddCustomRectRegular(int width, int height) +{ + 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 +} + +int ImFontAtlas::AddCustomRectFontGlyph(ImFont *font, ImWchar id, int width, int height, float advance_x, const ImVec2 &offset) +{ +#ifdef IMGUI_USE_WCHAR32 + IM_ASSERT(id <= 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.GlyphAdvanceX = advance_x; + r.GlyphOffset = offset; + r.Font = font; + CustomRects.push_back(r); + return CustomRects.Size - 1; // Return index +} + +void ImFontAtlas::CalcCustomRectUV(const ImFontAtlasCustomRect *rect, ImVec2 *out_uv_min, ImVec2 *out_uv_max) 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); +} + +bool ImFontAtlas::GetMouseCursorTexData(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 (Flags & ImFontAtlasFlags_NoMouseCursors) + return false; + + IM_ASSERT(PackIdMouseCursors != -1); + ImFontAtlasCustomRect *r = GetCustomRectByIndex(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]; + out_uv_border[0] = (pos)*TexUvScale; + out_uv_border[1] = (pos + size) * TexUvScale; + pos.x += FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; + out_uv_fill[0] = (pos)*TexUvScale; + out_uv_fill[1] = (pos + size) * TexUvScale; + return true; +} + +bool ImFontAtlas::Build() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + + // Default font is none are specified + if (ConfigData.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); +} + +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]; +} + +#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 { + 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)); +} + +static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas *atlas) +{ + IM_ASSERT(atlas->ConfigData.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->ConfigData.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->ConfigData.Size; src_i++) { + ImFontBuildSrcData &src_tmp = src_tmp_array[src_i]; + ImFontConfig &cfg = atlas->ConfigData[src_i]; + IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas)); + + // Find index from cfg.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 (cfg.DstFont == atlas->Fonts[output_i]) + src_tmp.DstIndex = output_i; + if (src_tmp.DstIndex == -1) { + IM_ASSERT(src_tmp.DstIndex != -1); // cfg.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 *)cfg.FontData, cfg.FontNo); + IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found."); + if (!stbtt_InitFont(&src_tmp.FontInfo, (unsigned char *)cfg.FontData, font_offset)) + return false; + + // Measure highest codepoints + ImFontBuildDstData &dst_tmp = dst_tmp_array[src_tmp.DstIndex]; + src_tmp.SrcRanges = cfg.GlyphRanges ? cfg.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. + IM_ASSERT(src_range[0] <= src_range[1]); + 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; + 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; + + // Convert our ranges in the format stb_truetype wants + ImFontConfig &cfg = atlas->ConfigData[src_i]; + src_tmp.PackRange.font_size = cfg.SizePixels; + 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)cfg.OversampleH; + src_tmp.PackRange.v_oversample = (unsigned char)cfg.OversampleV; + + // Gather the sizes of all rectangles we will need to pack (this loop is based on stbtt_PackFontRangesGatherRects) + const float scale = (cfg.SizePixels > 0) ? stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels) + : stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -cfg.SizePixels); + const int padding = atlas->TexGlyphPadding; + 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 * cfg.OversampleH, scale * cfg.OversampleV, 0, 0, &x0, &y0, &x1, &y1); + src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + padding + cfg.OversampleH - 1); + src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + padding + cfg.OversampleV - 1); + total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h; + } + } + + // 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, atlas->TexGlyphPadding, NULL); + 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 &cfg = atlas->ConfigData[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 (cfg.RasterizerMultiply != 1.0f) { + unsigned char multiply_table[256]; + ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.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->ConfigData is != from cfg which is our source configuration. + ImFontBuildSrcData &src_tmp = src_tmp_array[src_i]; + ImFontConfig &cfg = atlas->ConfigData[src_i]; + ImFont *dst_font = cfg.DstFont; + + const float font_scale = stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels); + int unscaled_ascent, unscaled_descent, unscaled_line_gap; + stbtt_GetFontVMetrics(&src_tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); + + const float ascent = ImFloor(unscaled_ascent * font_scale + ((unscaled_ascent > 0.0f) ? +1 : -1)); + const float descent = ImFloor(unscaled_descent * font_scale + ((unscaled_descent > 0.0f) ? +1 : -1)); + ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent); + const float font_off_x = cfg.GlyphOffset.x; + const float font_off_y = cfg.GlyphOffset.y + IM_ROUND(dst_font->Ascent); + + 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); + dst_font->AddGlyph(&cfg, + (ImWchar)codepoint, + q.x0 + font_off_x, + q.y0 + font_off_y, + q.x1 + font_off_x, + q.y1 + font_off_y, + q.s0, + q.t0, + q.s1, + q.t1, + pc.xadvance); + } + } + + // 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 ImFontAtlasBuildSetupFont(ImFontAtlas *atlas, ImFont *font, ImFontConfig *font_config, float ascent, float descent) +{ + if (!font_config->MergeMode) { + font->ClearOutputData(); + font->FontSize = font_config->SizePixels; + font->ConfigData = font_config; + font->ConfigDataCount = 0; + font->ContainerAtlas = atlas; + font->Ascent = ascent; + font->Descent = descent; + } + font->ConfigDataCount++; +} + +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 + + 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_rects[i].h = user_rects[i].Height; + } + 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) { + 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_rects[i].h == user_rects[i].Height); + 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)) { + // Render/copy pixels + 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); + } + } else { + // Render 4 white pixels + 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; + } else { + atlas->TexPixelsRGBA32[offset] = atlas->TexPixelsRGBA32[offset + 1] = atlas->TexPixelsRGBA32[offset + w] = atlas->TexPixelsRGBA32[offset + w + 1] = + IM_COL32_WHITE; + } + } + atlas->TexUvWhitePixel = ImVec2((r->X + 0.5f) * atlas->TexUvScale.x, (r->Y + 0.5f) * atlas->TexUvScale.y); +} + +static void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas *atlas) +{ + if (atlas->Flags & ImFontAtlasFlags_NoBakedLines) + return; + + // 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 (unsigned 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 + unsigned int y = n; + unsigned int line_width = n; + unsigned int pad_left = (r->Width - line_width) / 2; + unsigned int pad_right = r->Width - (pad_left + line_width); + + // 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) { + unsigned char *write_ptr = &atlas->TexPixelsAlpha8[r->X + ((r->Y + y) * atlas->TexWidth)]; + for (unsigned int i = 0; i < pad_left; i++) + *(write_ptr + i) = 0x00; + + for (unsigned int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = 0xFF; + + for (unsigned int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = 0x00; + } else { + unsigned int *write_ptr = &atlas->TexPixelsRGBA32[r->X + ((r->Y + y) * atlas->TexWidth)]; + for (unsigned int i = 0; i < pad_left; i++) + *(write_ptr + i) = IM_COL32(255, 255, 255, 0); + + for (unsigned int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = IM_COL32_WHITE; + + for (unsigned 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; + 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 +void ImFontAtlasBuildInit(ImFontAtlas *atlas) +{ + // Register texture region for mouse cursors or standard white pixels + if (atlas->PackIdMouseCursors < 0) { + 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); + } +} + +// This is called/shared by both the stb_truetype and the FreeType builder. +void ImFontAtlasBuildFinish(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, GlyphExtraSpacing, 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); + } + + // Build all fonts lookup tables + for (ImFont *font : atlas->Fonts) + if (font->DirtyLookupTables) + font->BuildLookupTable(); + + atlas->TexReady = true; +} + +// Retrieve list of range (2 int per range, values are inclusive) +const ImWchar *ImFontAtlas::GetGlyphRangesDefault() +{ + static const ImWchar ranges[] = { + 0x0020, + 0x00FF, // Basic Latin + Latin Supplement + 0, + }; + return &ranges[0]; +} + +const ImWchar *ImFontAtlas::GetGlyphRangesGreek() +{ + static const ImWchar ranges[] = { + 0x0020, + 0x00FF, // Basic Latin + Latin Supplement + 0x0370, + 0x03FF, // Greek and Coptic + 0, + }; + return &ranges[0]; +} + +const ImWchar *ImFontAtlas::GetGlyphRangesKorean() +{ + static const ImWchar ranges[] = { + 0x0020, + 0x00FF, // Basic Latin + Latin Supplement + 0x3131, + 0x3163, // Korean alphabets + 0xAC00, + 0xD7A3, // Korean characters + 0xFFFD, + 0xFFFD, // Invalid + 0, + }; + return &ranges[0]; +} + +const ImWchar *ImFontAtlas::GetGlyphRangesChineseFull() +{ + static const ImWchar ranges[] = { + 0x0020, + 0x00FF, // Basic Latin + Latin Supplement + 0x2000, + 0x206F, // General Punctuation + 0x3000, + 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana + 0x31F0, + 0x31FF, // Katakana Phonetic Extensions + 0xFF00, + 0xFFEF, // Half-width characters + 0xFFFD, + 0xFFFD, // Invalid + 0x4e00, + 0x9FAF, // CJK Ideograms + 0, + }; + 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) { + out_ranges[0] = out_ranges[1] = (ImWchar)(base_codepoint + accumulative_offsets[n]); + base_codepoint += accumulative_offsets[n]; + } + out_ranges[0] = 0; +} + +//------------------------------------------------------------------------- +// [SECTION] ImFontAtlas glyph ranges helpers +//------------------------------------------------------------------------- + +const ImWchar *ImFontAtlas::GetGlyphRangesChineseSimplifiedCommon() +{ + // Store 2500 regularly used characters for Simplified Chinese. + // Sourced from https://zh.wiktionary.org/wiki/%E9%99%84%E5%BD%95:%E7%8E%B0%E4%BB%A3%E6%B1%89%E8%AF%AD%E5%B8%B8%E7%94%A8%E5%AD%97%E8%A1%A8 + // This table covers 97.97% of all characters used during the month in July, 1987. + // You can use ImFontGlyphRangesBuilder to create your own ranges derived from this, by merging existing ranges or adding new characters. + // (Stored as accumulative offsets from the initial unicode codepoint 0x4E00. This encoding is designed to helps us compact the source code size.) + static const short accumulative_offsets_from_0x4E00[] = { + 0, 1, 2, 4, 1, 1, 1, 1, 2, 1, 3, 2, 1, 2, 2, 1, 1, 1, 1, 1, 5, 2, 1, 2, 3, 3, 3, 2, 2, 4, 1, 1, 1, 2, + 1, 5, 2, 3, 1, 2, 1, 2, 1, 1, 2, 1, 1, 2, 2, 1, 4, 1, 1, 1, 1, 5, 10, 1, 2, 19, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 5, 1, 6, 3, 2, 1, 2, 2, 1, 1, 1, 4, 8, 5, 1, 1, 4, 1, 1, 3, 1, 2, 1, 5, 1, 2, 1, 1, 1, 10, 1, 1, + 5, 2, 4, 6, 1, 4, 2, 2, 2, 12, 2, 1, 1, 6, 1, 1, 1, 4, 1, 1, 4, 6, 5, 1, 4, 2, 2, 4, 10, 7, 1, 1, 4, 2, + 4, 2, 1, 4, 3, 6, 10, 12, 5, 7, 2, 14, 2, 9, 1, 1, 6, 7, 10, 4, 7, 13, 1, 5, 4, 8, 4, 1, 1, 2, 28, 5, 6, 1, + 1, 5, 2, 5, 20, 2, 2, 9, 8, 11, 2, 9, 17, 1, 8, 6, 8, 27, 4, 6, 9, 20, 11, 27, 6, 68, 2, 2, 1, 1, 1, 2, 1, 2, + 2, 7, 6, 11, 3, 3, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 3, 1, 1, 8, 3, 4, 1, 5, 7, 2, 1, 4, 4, 8, 4, 2, 1, 2, + 1, 1, 4, 5, 6, 3, 6, 2, 12, 3, 1, 3, 9, 2, 4, 3, 4, 1, 5, 3, 3, 1, 3, 7, 1, 5, 1, 1, 1, 1, 2, 3, 4, 5, + 2, 3, 2, 6, 1, 1, 2, 1, 7, 1, 7, 3, 4, 5, 15, 2, 2, 1, 5, 3, 22, 19, 2, 1, 1, 1, 1, 2, 5, 1, 1, 1, 6, 1, + 1, 12, 8, 2, 9, 18, 22, 4, 1, 1, 5, 1, 16, 1, 2, 7, 10, 15, 1, 1, 6, 2, 4, 1, 2, 4, 1, 6, 1, 1, 3, 2, 4, 1, + 6, 4, 5, 1, 2, 1, 1, 2, 1, 10, 3, 1, 3, 2, 1, 9, 3, 2, 5, 7, 2, 19, 4, 3, 6, 1, 1, 1, 1, 1, 4, 3, 2, 1, + 1, 1, 2, 5, 3, 1, 1, 1, 2, 2, 1, 1, 2, 1, 1, 2, 1, 3, 1, 1, 1, 3, 7, 1, 4, 1, 1, 2, 1, 1, 2, 1, 2, 4, + 4, 3, 8, 1, 1, 1, 2, 1, 3, 5, 1, 3, 1, 3, 4, 6, 2, 2, 14, 4, 6, 6, 11, 9, 1, 15, 3, 1, 28, 5, 2, 5, 5, 3, + 1, 3, 4, 5, 4, 6, 14, 3, 2, 3, 5, 21, 2, 7, 20, 10, 1, 2, 19, 2, 4, 28, 28, 2, 3, 2, 1, 14, 4, 1, 26, 28, 42, 12, + 40, 3, 52, 79, 5, 14, 17, 3, 2, 2, 11, 3, 4, 6, 3, 1, 8, 2, 23, 4, 5, 8, 10, 4, 2, 7, 3, 5, 1, 1, 6, 3, 1, 2, + 2, 2, 5, 28, 1, 1, 7, 7, 20, 5, 3, 29, 3, 17, 26, 1, 8, 4, 27, 3, 6, 11, 23, 5, 3, 4, 6, 13, 24, 16, 6, 5, 10, 25, + 35, 7, 3, 2, 3, 3, 14, 3, 6, 2, 6, 1, 4, 2, 3, 8, 2, 1, 1, 3, 3, 3, 4, 1, 1, 13, 2, 2, 4, 5, 2, 1, 14, 14, + 1, 2, 2, 1, 4, 5, 2, 3, 1, 14, 3, 12, 3, 17, 2, 16, 5, 1, 2, 1, 8, 9, 3, 19, 4, 2, 2, 4, 17, 25, 21, 20, 28, 75, + 1, 10, 29, 103, 4, 1, 2, 1, 1, 4, 2, 4, 1, 2, 3, 24, 2, 2, 2, 1, 1, 2, 1, 3, 8, 1, 1, 1, 2, 1, 1, 3, 1, 1, + 1, 6, 1, 5, 3, 1, 1, 1, 3, 4, 1, 1, 5, 2, 1, 5, 6, 13, 9, 16, 1, 1, 1, 1, 3, 2, 3, 2, 4, 5, 2, 5, 2, 2, + 3, 7, 13, 7, 2, 2, 1, 1, 1, 1, 2, 3, 3, 2, 1, 6, 4, 9, 2, 1, 14, 2, 14, 2, 1, 18, 3, 4, 14, 4, 11, 41, 15, 23, + 15, 23, 176, 1, 3, 4, 1, 1, 1, 1, 5, 3, 1, 2, 3, 7, 3, 1, 1, 2, 1, 2, 4, 4, 6, 2, 4, 1, 9, 7, 1, 10, 5, 8, + 16, 29, 1, 1, 2, 2, 3, 1, 3, 5, 2, 4, 5, 4, 1, 1, 2, 2, 3, 3, 7, 1, 6, 10, 1, 17, 1, 44, 4, 6, 2, 1, 1, 6, + 5, 4, 2, 10, 1, 6, 9, 2, 8, 1, 24, 1, 2, 13, 7, 8, 8, 2, 1, 4, 1, 3, 1, 3, 3, 5, 2, 5, 10, 9, 4, 9, 12, 2, + 1, 6, 1, 10, 1, 1, 7, 7, 4, 10, 8, 3, 1, 13, 4, 3, 1, 6, 1, 3, 5, 2, 1, 2, 17, 16, 5, 2, 16, 6, 1, 4, 2, 1, + 3, 3, 6, 8, 5, 11, 11, 1, 3, 3, 2, 4, 6, 10, 9, 5, 7, 4, 7, 4, 7, 1, 1, 4, 2, 1, 3, 6, 8, 7, 1, 6, 11, 5, + 5, 3, 24, 9, 4, 2, 7, 13, 5, 1, 8, 82, 16, 61, 1, 1, 1, 4, 2, 2, 16, 10, 3, 8, 1, 1, 6, 4, 2, 1, 3, 1, 1, 1, + 4, 3, 8, 4, 2, 2, 1, 1, 1, 1, 1, 6, 3, 5, 1, 1, 4, 6, 9, 2, 1, 1, 1, 2, 1, 7, 2, 1, 6, 1, 5, 4, 4, 3, + 1, 8, 1, 3, 3, 1, 3, 2, 2, 2, 2, 3, 1, 6, 1, 2, 1, 2, 1, 3, 7, 1, 8, 2, 1, 2, 1, 5, 2, 5, 3, 5, 10, 1, + 2, 1, 1, 3, 2, 5, 11, 3, 9, 3, 5, 1, 1, 5, 9, 1, 2, 1, 5, 7, 9, 9, 8, 1, 3, 3, 3, 6, 8, 2, 3, 2, 1, 1, + 32, 6, 1, 2, 15, 9, 3, 7, 13, 1, 3, 10, 13, 2, 14, 1, 13, 10, 2, 1, 3, 10, 4, 15, 2, 15, 15, 10, 1, 3, 9, 6, 9, 32, + 25, 26, 47, 7, 3, 2, 3, 1, 6, 3, 4, 3, 2, 8, 5, 4, 1, 9, 4, 2, 2, 19, 10, 6, 2, 3, 8, 1, 2, 2, 4, 2, 1, 9, + 4, 4, 4, 6, 4, 8, 9, 2, 3, 1, 1, 1, 1, 3, 5, 5, 1, 3, 8, 4, 6, 2, 1, 4, 12, 1, 5, 3, 7, 13, 2, 5, 8, 1, + 6, 1, 2, 5, 14, 6, 1, 5, 2, 4, 8, 15, 5, 1, 23, 6, 62, 2, 10, 1, 1, 8, 1, 2, 2, 10, 4, 2, 2, 9, 2, 1, 1, 3, + 2, 3, 1, 5, 3, 3, 2, 1, 3, 8, 1, 1, 1, 11, 3, 1, 1, 4, 3, 7, 1, 14, 1, 2, 3, 12, 5, 2, 5, 1, 6, 7, 5, 7, + 14, 11, 1, 3, 1, 8, 9, 12, 2, 1, 11, 8, 4, 4, 2, 6, 10, 9, 13, 1, 1, 3, 1, 5, 1, 3, 2, 4, 4, 1, 18, 2, 3, 14, + 11, 4, 29, 4, 2, 7, 1, 3, 13, 9, 2, 2, 5, 3, 5, 20, 7, 16, 8, 5, 72, 34, 6, 4, 22, 12, 12, 28, 45, 36, 9, 7, 39, 9, + 191, 1, 1, 1, 4, 11, 8, 4, 9, 2, 3, 22, 1, 1, 1, 1, 4, 17, 1, 7, 7, 1, 11, 31, 10, 2, 4, 8, 2, 3, 2, 1, 4, 2, + 16, 4, 32, 2, 3, 19, 13, 4, 9, 1, 5, 2, 14, 8, 1, 1, 3, 6, 19, 6, 5, 1, 16, 6, 2, 10, 8, 5, 1, 2, 3, 1, 5, 5, + 1, 11, 6, 6, 1, 3, 3, 2, 6, 3, 8, 1, 1, 4, 10, 7, 5, 7, 7, 5, 8, 9, 2, 1, 3, 4, 1, 1, 3, 1, 3, 3, 2, 6, + 16, 1, 4, 6, 3, 1, 10, 6, 1, 3, 15, 2, 9, 2, 10, 25, 13, 9, 16, 6, 2, 2, 10, 11, 4, 3, 9, 1, 2, 6, 6, 5, 4, 30, + 40, 1, 10, 7, 12, 14, 33, 6, 3, 6, 7, 3, 1, 3, 1, 11, 14, 4, 9, 5, 12, 11, 49, 18, 51, 31, 140, 31, 2, 2, 1, 5, 1, 8, + 1, 10, 1, 4, 4, 3, 24, 1, 10, 1, 3, 6, 6, 16, 3, 4, 5, 2, 1, 4, 2, 57, 10, 6, 22, 2, 22, 3, 7, 22, 6, 10, 11, 36, + 18, 16, 33, 36, 2, 5, 5, 1, 1, 1, 4, 10, 1, 4, 13, 2, 7, 5, 2, 9, 3, 4, 1, 7, 43, 3, 7, 3, 9, 14, 7, 9, 1, 11, + 1, 1, 3, 7, 4, 18, 13, 1, 14, 1, 3, 6, 10, 73, 2, 2, 30, 6, 1, 11, 18, 19, 13, 22, 3, 46, 42, 37, 89, 7, 3, 16, 34, 2, + 2, 3, 9, 1, 7, 1, 1, 1, 2, 2, 4, 10, 7, 3, 10, 3, 9, 5, 28, 9, 2, 6, 13, 7, 3, 1, 3, 10, 2, 7, 2, 11, 3, 6, + 21, 54, 85, 2, 1, 4, 2, 2, 1, 39, 3, 21, 2, 2, 5, 1, 1, 1, 4, 1, 1, 3, 4, 15, 1, 3, 2, 4, 4, 2, 3, 8, 2, 20, + 1, 8, 7, 13, 4, 1, 26, 6, 2, 9, 34, 4, 21, 52, 10, 4, 4, 1, 5, 12, 2, 11, 1, 7, 2, 30, 12, 44, 2, 30, 1, 1, 3, 6, + 16, 9, 17, 39, 82, 2, 2, 24, 7, 1, 7, 3, 16, 9, 14, 44, 2, 1, 2, 1, 2, 3, 5, 2, 4, 1, 6, 7, 5, 3, 2, 6, 1, 11, + 5, 11, 2, 1, 18, 19, 8, 1, 3, 24, 29, 2, 1, 3, 5, 2, 2, 1, 13, 6, 5, 1, 46, 11, 3, 5, 1, 1, 5, 8, 2, 10, 6, 12, + 6, 3, 7, 11, 2, 4, 16, 13, 2, 5, 1, 1, 2, 2, 5, 2, 28, 5, 2, 23, 10, 8, 4, 4, 22, 39, 95, 38, 8, 14, 9, 5, 1, 13, + 5, 4, 3, 13, 12, 11, 1, 9, 1, 27, 37, 2, 5, 4, 4, 63, 211, 95, 2, 2, 2, 1, 3, 5, 2, 1, 1, 2, 2, 1, 1, 1, 3, 2, + 4, 1, 2, 1, 1, 5, 2, 2, 1, 1, 2, 3, 1, 3, 1, 1, 1, 3, 1, 4, 2, 1, 3, 6, 1, 1, 3, 7, 15, 5, 3, 2, 5, 3, + 9, 11, 4, 2, 22, 1, 6, 3, 8, 7, 1, 4, 28, 4, 16, 3, 3, 25, 4, 4, 27, 27, 1, 4, 1, 2, 2, 7, 1, 3, 5, 2, 28, 8, + 2, 14, 1, 8, 6, 16, 25, 3, 3, 3, 14, 3, 3, 1, 1, 2, 1, 4, 6, 3, 8, 4, 1, 1, 1, 2, 3, 6, 10, 6, 2, 3, 18, 3, + 2, 5, 5, 4, 3, 1, 5, 2, 5, 4, 23, 7, 6, 12, 6, 4, 17, 11, 9, 5, 1, 1, 10, 5, 12, 1, 1, 11, 26, 33, 7, 3, 6, 1, + 17, 7, 1, 5, 12, 1, 11, 2, 4, 1, 8, 14, 17, 23, 1, 2, 1, 7, 8, 16, 11, 9, 6, 5, 2, 6, 4, 16, 2, 8, 14, 1, 11, 8, + 9, 1, 1, 1, 9, 25, 4, 11, 19, 7, 2, 15, 2, 12, 8, 52, 7, 5, 19, 2, 16, 4, 36, 8, 1, 16, 8, 24, 26, 4, 6, 2, 9, 5, + 4, 36, 3, 28, 12, 25, 15, 37, 27, 17, 12, 59, 38, 5, 32, 127, 1, 2, 9, 17, 14, 4, 1, 2, 1, 1, 8, 11, 50, 4, 14, 2, 19, 16, + 4, 17, 5, 4, 5, 26, 12, 45, 2, 23, 45, 104, 30, 12, 8, 3, 10, 2, 2, 3, 3, 1, 4, 20, 7, 2, 9, 6, 15, 2, 20, 1, 3, 16, + 4, 11, 15, 6, 134, 2, 5, 59, 1, 2, 2, 2, 1, 9, 17, 3, 26, 137, 10, 211, 59, 1, 2, 4, 1, 4, 1, 1, 1, 2, 6, 2, 3, 1, + 1, 2, 3, 2, 3, 1, 3, 4, 4, 2, 3, 3, 1, 4, 3, 1, 7, 2, 2, 3, 1, 2, 1, 3, 3, 3, 2, 2, 3, 2, 1, 3, 14, 6, + 1, 3, 2, 9, 6, 15, 27, 9, 34, 145, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 3, 1, 2, 1, 1, 1, 2, 3, 5, + 8, 3, 5, 2, 4, 1, 3, 2, 2, 2, 12, 4, 1, 1, 1, 10, 4, 5, 1, 20, 4, 16, 1, 15, 9, 5, 12, 2, 9, 2, 5, 4, 2, 26, + 19, 7, 1, 26, 4, 30, 12, 15, 42, 1, 6, 8, 172, 1, 1, 4, 2, 1, 1, 11, 2, 2, 4, 2, 1, 2, 1, 10, 8, 1, 2, 1, 4, 5, + 1, 2, 5, 1, 8, 4, 1, 3, 4, 2, 1, 6, 2, 1, 3, 4, 1, 2, 1, 1, 1, 1, 12, 5, 7, 2, 4, 3, 1, 1, 1, 3, 3, 6, + 1, 2, 2, 3, 3, 3, 2, 1, 2, 12, 14, 11, 6, 6, 4, 12, 2, 8, 1, 7, 10, 1, 35, 7, 4, 13, 15, 4, 3, 23, 21, 28, 52, 5, + 26, 5, 6, 1, 7, 10, 2, 7, 53, 3, 2, 1, 1, 1, 2, 163, 532, 1, 10, 11, 1, 3, 3, 4, 8, 2, 8, 6, 2, 2, 23, 22, 4, 2, + 2, 4, 2, 1, 3, 1, 3, 3, 5, 9, 8, 2, 1, 2, 8, 1, 10, 2, 12, 21, 20, 15, 105, 2, 3, 1, 1, 3, 2, 3, 1, 1, 2, 5, + 1, 4, 15, 11, 19, 1, 1, 1, 1, 5, 4, 5, 1, 1, 2, 5, 3, 5, 12, 1, 2, 5, 1, 11, 1, 1, 15, 9, 1, 4, 5, 3, 26, 8, + 2, 1, 3, 1, 1, 15, 19, 2, 12, 1, 2, 5, 2, 7, 2, 19, 2, 20, 6, 26, 7, 5, 2, 2, 7, 34, 21, 13, 70, 2, 128, 1, 1, 2, + 1, 1, 2, 1, 1, 3, 2, 2, 2, 15, 1, 4, 1, 3, 4, 42, 10, 6, 1, 49, 85, 8, 1, 2, 1, 1, 4, 4, 2, 3, 6, 1, 5, 7, + 4, 3, 211, 4, 1, 2, 1, 2, 5, 1, 2, 4, 2, 2, 6, 5, 6, 10, 3, 4, 48, 100, 6, 2, 16, 296, 5, 27, 387, 2, 2, 3, 7, 16, + 8, 5, 38, 15, 39, 21, 9, 10, 3, 7, 59, 13, 27, 21, 47, 5, 21, 6}; + static ImWchar base_ranges[] = // not zero-terminated + { + 0x0020, + 0x00FF, // Basic Latin + Latin Supplement + 0x2000, + 0x206F, // General Punctuation + 0x3000, + 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana + 0x31F0, + 0x31FF, // Katakana Phonetic Extensions + 0xFF00, + 0xFFEF, // Half-width characters + 0xFFFD, + 0xFFFD // Invalid + }; + static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00) * 2 + 1] = {0}; + if (!full_ranges[0]) { + memcpy(full_ranges, base_ranges, sizeof(base_ranges)); + UnpackAccumulativeOffsetsIntoRanges(0x4E00, + accumulative_offsets_from_0x4E00, + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00), + full_ranges + IM_ARRAYSIZE(base_ranges)); + } + return &full_ranges[0]; +} + +const ImWchar *ImFontAtlas::GetGlyphRangesJapanese() +{ + // 2999 ideograms code points for Japanese + // - 2136 Joyo (meaning "for regular use" or "for common use") Kanji code points + // - 863 Jinmeiyo (meaning "for personal name") Kanji code points + // - Sourced from official information provided by the government agencies of Japan: + // - List of Joyo Kanji by the Agency for Cultural Affairs + // - https://www.bunka.go.jp/kokugo_nihongo/sisaku/joho/joho/kijun/naikaku/kanji/ + // - List of Jinmeiyo Kanji by the Ministry of Justice + // - http://www.moj.go.jp/MINJI/minji86.html + // - Available under the terms of the Creative Commons Attribution 4.0 International (CC BY 4.0). + // - https://creativecommons.org/licenses/by/4.0/legalcode + // - You can generate this code by the script at: + // - https://github.com/vaiorabbit/everyday_use_kanji + // - References: + // - List of Joyo Kanji + // - (Wikipedia) https://en.wikipedia.org/wiki/List_of_j%C5%8Dy%C5%8D_kanji + // - List of Jinmeiyo Kanji + // - (Wikipedia) https://en.wikipedia.org/wiki/Jinmeiy%C5%8D_kanji + // - Missing 1 Joyo Kanji: U+20B9F (Kun'yomi: Shikaru, On'yomi: Shitsu,shichi), see https://github.com/ocornut/imgui/pull/3627 for details. + // You can use ImFontGlyphRangesBuilder to create your own ranges derived from this, by merging existing ranges or adding new characters. + // (Stored as accumulative offsets from the initial unicode codepoint 0x4E00. This encoding is designed to helps us compact the source code size.) + static const short accumulative_offsets_from_0x4E00[] = { + 0, 1, 2, 4, 1, 1, 1, 1, 2, 1, 3, 3, 2, 2, 1, 5, 3, 5, 7, 5, 6, 1, 2, 1, 7, 2, 6, 3, 1, 8, 1, 1, 4, 1, 1, 18, + 2, 11, 2, 6, 2, 1, 2, 1, 5, 1, 2, 1, 3, 1, 2, 1, 2, 3, 3, 1, 1, 2, 3, 1, 1, 1, 12, 7, 9, 1, 4, 5, 1, 1, 2, 1, + 10, 1, 1, 9, 2, 2, 4, 5, 6, 9, 3, 1, 1, 1, 1, 9, 3, 18, 5, 2, 2, 2, 2, 1, 6, 3, 7, 1, 1, 1, 1, 2, 2, 4, 2, 1, + 23, 2, 10, 4, 3, 5, 2, 4, 10, 2, 4, 13, 1, 6, 1, 9, 3, 1, 1, 6, 6, 7, 6, 3, 1, 2, 11, 3, 2, 2, 3, 2, 15, 2, 2, 5, + 4, 3, 6, 4, 1, 2, 5, 2, 12, 16, 6, 13, 9, 13, 2, 1, 1, 7, 16, 4, 7, 1, 19, 1, 5, 1, 2, 2, 7, 7, 8, 2, 6, 5, 4, 9, + 18, 7, 4, 5, 9, 13, 11, 8, 15, 2, 1, 1, 1, 2, 1, 2, 2, 1, 2, 2, 8, 2, 9, 3, 3, 1, 1, 4, 4, 1, 1, 1, 4, 9, 1, 4, + 3, 5, 5, 2, 7, 5, 3, 4, 8, 2, 1, 13, 2, 3, 3, 1, 14, 1, 1, 4, 5, 1, 3, 6, 1, 5, 2, 1, 1, 3, 3, 3, 3, 1, 1, 2, + 7, 6, 6, 7, 1, 4, 7, 6, 1, 1, 1, 1, 1, 12, 3, 3, 9, 5, 2, 6, 1, 5, 6, 1, 2, 3, 18, 2, 4, 14, 4, 1, 3, 6, 1, 1, + 6, 3, 5, 5, 3, 2, 2, 2, 2, 12, 3, 1, 4, 2, 3, 2, 3, 11, 1, 7, 4, 1, 2, 1, 3, 17, 1, 9, 1, 24, 1, 1, 4, 2, 2, 4, + 1, 2, 7, 1, 1, 1, 3, 1, 2, 2, 4, 15, 1, 1, 2, 1, 1, 2, 1, 5, 2, 5, 20, 2, 5, 9, 1, 10, 8, 7, 6, 1, 1, 1, 1, 1, + 1, 6, 2, 1, 2, 8, 1, 1, 1, 1, 5, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 12, 4, 1, 3, 1, 1, 1, 1, 1, 10, 3, 1, 7, 5, 13, + 1, 2, 3, 4, 6, 1, 1, 30, 2, 9, 9, 1, 15, 38, 11, 3, 1, 8, 24, 7, 1, 9, 8, 10, 2, 1, 9, 31, 2, 13, 6, 2, 9, 4, 49, 5, + 2, 15, 2, 1, 10, 2, 1, 1, 1, 2, 2, 6, 15, 30, 35, 3, 14, 18, 8, 1, 16, 10, 28, 12, 19, 45, 38, 1, 3, 2, 3, 13, 2, 1, 7, 3, + 6, 5, 3, 4, 3, 1, 5, 7, 8, 1, 5, 3, 18, 5, 3, 6, 1, 21, 4, 24, 9, 24, 40, 3, 14, 3, 21, 3, 2, 1, 2, 4, 2, 3, 1, 15, + 15, 6, 5, 1, 1, 3, 1, 5, 6, 1, 9, 7, 3, 3, 2, 1, 4, 3, 8, 21, 5, 16, 4, 5, 2, 10, 11, 11, 3, 6, 3, 2, 9, 3, 6, 13, + 1, 2, 1, 1, 1, 1, 11, 12, 6, 6, 1, 4, 2, 6, 5, 2, 1, 1, 3, 3, 6, 13, 3, 1, 1, 5, 1, 2, 3, 3, 14, 2, 1, 2, 2, 2, + 5, 1, 9, 5, 1, 1, 6, 12, 3, 12, 3, 4, 13, 2, 14, 2, 8, 1, 17, 5, 1, 16, 4, 2, 2, 21, 8, 9, 6, 23, 20, 12, 25, 19, 9, 38, + 8, 3, 21, 40, 25, 33, 13, 4, 3, 1, 4, 1, 2, 4, 1, 2, 5, 26, 2, 1, 1, 2, 1, 3, 6, 2, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, + 1, 9, 2, 3, 1, 1, 1, 3, 6, 3, 2, 1, 1, 6, 6, 1, 8, 2, 2, 2, 1, 4, 1, 2, 3, 2, 7, 3, 2, 4, 1, 2, 1, 2, 2, 1, + 1, 1, 1, 1, 3, 1, 2, 5, 4, 10, 9, 4, 9, 1, 1, 1, 1, 1, 1, 5, 3, 2, 1, 6, 4, 9, 6, 1, 10, 2, 31, 17, 8, 3, 7, 5, + 40, 1, 7, 7, 1, 6, 5, 2, 10, 7, 8, 4, 15, 39, 25, 6, 28, 47, 18, 10, 7, 1, 3, 1, 1, 2, 1, 1, 1, 3, 3, 3, 1, 1, 1, 3, + 4, 2, 1, 4, 1, 3, 6, 10, 7, 8, 6, 2, 2, 1, 3, 3, 2, 5, 8, 7, 9, 12, 2, 15, 1, 1, 4, 1, 2, 1, 1, 1, 3, 2, 1, 3, + 3, 5, 6, 2, 3, 2, 10, 1, 4, 2, 8, 1, 1, 1, 11, 6, 1, 21, 4, 16, 3, 1, 3, 1, 4, 2, 3, 6, 5, 1, 3, 1, 1, 3, 3, 4, + 6, 1, 1, 10, 4, 2, 7, 10, 4, 7, 4, 2, 9, 4, 3, 1, 1, 1, 4, 1, 8, 3, 4, 1, 3, 1, 6, 1, 4, 2, 1, 4, 7, 2, 1, 8, + 1, 4, 5, 1, 1, 2, 2, 4, 6, 2, 7, 1, 10, 1, 1, 3, 4, 11, 10, 8, 21, 4, 6, 1, 3, 5, 2, 1, 2, 28, 5, 5, 2, 3, 13, 1, + 2, 3, 1, 4, 2, 1, 5, 20, 3, 8, 11, 1, 3, 3, 3, 1, 8, 10, 9, 2, 10, 9, 2, 3, 1, 1, 2, 4, 1, 8, 3, 6, 1, 7, 8, 6, + 11, 1, 4, 29, 8, 4, 3, 1, 2, 7, 13, 1, 4, 1, 6, 2, 6, 12, 12, 2, 20, 3, 2, 3, 6, 4, 8, 9, 2, 7, 34, 5, 1, 18, 6, 1, + 1, 4, 4, 5, 7, 9, 1, 2, 2, 4, 3, 4, 1, 7, 2, 2, 2, 6, 2, 3, 25, 5, 3, 6, 1, 4, 6, 7, 4, 2, 1, 4, 2, 13, 6, 4, + 4, 3, 1, 5, 3, 4, 4, 3, 2, 1, 1, 4, 1, 2, 1, 1, 3, 1, 11, 1, 6, 3, 1, 7, 3, 6, 2, 8, 8, 6, 9, 3, 4, 11, 3, 2, + 10, 12, 2, 5, 11, 1, 6, 4, 5, 3, 1, 8, 5, 4, 6, 6, 3, 5, 1, 1, 3, 2, 1, 2, 2, 6, 17, 12, 1, 10, 1, 6, 12, 1, 6, 6, + 19, 9, 6, 16, 1, 13, 4, 4, 15, 7, 17, 6, 11, 9, 15, 12, 6, 7, 2, 1, 2, 2, 15, 9, 3, 21, 4, 6, 49, 18, 7, 3, 2, 3, 1, 6, + 8, 2, 2, 6, 2, 9, 1, 3, 6, 4, 4, 1, 2, 16, 2, 5, 2, 1, 6, 2, 3, 5, 3, 1, 2, 5, 1, 2, 1, 9, 3, 1, 8, 6, 4, 8, + 11, 3, 1, 1, 1, 1, 3, 1, 13, 8, 4, 1, 3, 2, 2, 1, 4, 1, 11, 1, 5, 2, 1, 5, 2, 5, 8, 6, 1, 1, 7, 4, 3, 8, 3, 2, + 7, 2, 1, 5, 1, 5, 2, 4, 7, 6, 2, 8, 5, 1, 11, 4, 5, 3, 6, 18, 1, 2, 13, 3, 3, 1, 21, 1, 1, 4, 1, 4, 1, 1, 1, 8, + 1, 2, 2, 7, 1, 2, 4, 2, 2, 9, 2, 1, 1, 1, 4, 3, 6, 3, 12, 5, 1, 1, 1, 5, 6, 3, 2, 4, 8, 2, 2, 4, 2, 7, 1, 8, + 9, 5, 2, 3, 2, 1, 3, 2, 13, 7, 14, 6, 5, 1, 1, 2, 1, 4, 2, 23, 2, 1, 1, 6, 3, 1, 4, 1, 15, 3, 1, 7, 3, 9, 14, 1, + 3, 1, 4, 1, 1, 5, 8, 1, 3, 8, 3, 8, 15, 11, 4, 14, 4, 4, 2, 5, 5, 1, 7, 1, 6, 14, 7, 7, 8, 5, 15, 4, 8, 6, 5, 6, + 2, 1, 13, 1, 20, 15, 11, 9, 2, 5, 6, 2, 11, 2, 6, 2, 5, 1, 5, 8, 4, 13, 19, 25, 4, 1, 1, 11, 1, 34, 2, 5, 9, 14, 6, 2, + 2, 6, 1, 1, 14, 1, 3, 14, 13, 1, 6, 12, 21, 14, 14, 6, 32, 17, 8, 32, 9, 28, 1, 2, 4, 11, 8, 3, 1, 14, 2, 5, 15, 1, 1, 1, + 1, 3, 6, 4, 1, 3, 4, 11, 3, 1, 1, 11, 30, 1, 5, 1, 4, 1, 5, 8, 1, 1, 3, 2, 4, 3, 17, 35, 2, 6, 12, 17, 3, 1, 6, 2, + 1, 1, 12, 2, 7, 3, 3, 2, 1, 16, 2, 8, 3, 6, 5, 4, 7, 3, 3, 8, 1, 9, 8, 5, 1, 2, 1, 3, 2, 8, 1, 2, 9, 12, 1, 1, + 2, 3, 8, 3, 24, 12, 4, 3, 7, 5, 8, 3, 3, 3, 3, 3, 3, 1, 23, 10, 3, 1, 2, 2, 6, 3, 1, 16, 1, 16, 22, 3, 10, 4, 11, 6, + 9, 7, 7, 3, 6, 2, 2, 2, 4, 10, 2, 1, 1, 2, 8, 7, 1, 6, 4, 1, 3, 3, 3, 5, 10, 12, 12, 2, 3, 12, 8, 15, 1, 1, 16, 6, + 6, 1, 5, 9, 11, 4, 11, 4, 2, 6, 12, 1, 17, 5, 13, 1, 4, 9, 5, 1, 11, 2, 1, 8, 1, 5, 7, 28, 8, 3, 5, 10, 2, 17, 3, 38, + 22, 1, 2, 18, 12, 10, 4, 38, 18, 1, 4, 44, 19, 4, 1, 8, 4, 1, 12, 1, 4, 31, 12, 1, 14, 7, 75, 7, 5, 10, 6, 6, 13, 3, 2, 11, + 11, 3, 2, 5, 28, 15, 6, 18, 18, 5, 6, 4, 3, 16, 1, 7, 18, 7, 36, 3, 5, 3, 1, 7, 1, 9, 1, 10, 7, 2, 4, 2, 6, 2, 9, 7, + 4, 3, 32, 12, 3, 7, 10, 2, 23, 16, 3, 1, 12, 3, 31, 4, 11, 1, 3, 8, 9, 5, 1, 30, 15, 6, 12, 3, 2, 2, 11, 19, 9, 14, 2, 6, + 2, 3, 19, 13, 17, 5, 3, 3, 25, 3, 14, 1, 1, 1, 36, 1, 3, 2, 19, 3, 13, 36, 9, 13, 31, 6, 4, 16, 34, 2, 5, 4, 2, 3, 3, 5, + 1, 1, 1, 4, 3, 1, 17, 3, 2, 3, 5, 3, 1, 3, 2, 3, 5, 6, 3, 12, 11, 1, 3, 1, 2, 26, 7, 12, 7, 2, 14, 3, 3, 7, 7, 11, + 25, 25, 28, 16, 4, 36, 1, 2, 1, 6, 2, 1, 9, 3, 27, 17, 4, 3, 4, 13, 4, 1, 3, 2, 2, 1, 10, 4, 2, 4, 6, 3, 8, 2, 1, 18, + 1, 1, 24, 2, 2, 4, 33, 2, 3, 63, 7, 1, 6, 40, 7, 3, 4, 4, 2, 4, 15, 18, 1, 16, 1, 1, 11, 2, 41, 14, 1, 3, 18, 13, 3, 2, + 4, 16, 2, 17, 7, 15, 24, 7, 18, 13, 44, 2, 2, 3, 6, 1, 1, 7, 5, 1, 7, 1, 4, 3, 3, 5, 10, 8, 2, 3, 1, 8, 1, 1, 27, 4, + 2, 1, 12, 1, 2, 1, 10, 6, 1, 6, 7, 5, 2, 3, 7, 11, 5, 11, 3, 6, 6, 2, 3, 15, 4, 9, 1, 1, 2, 1, 2, 11, 2, 8, 12, 8, + 5, 4, 2, 3, 1, 5, 2, 2, 1, 14, 1, 12, 11, 4, 1, 11, 17, 17, 4, 3, 2, 5, 5, 7, 3, 1, 5, 9, 9, 8, 2, 5, 6, 6, 13, 13, + 2, 1, 2, 6, 1, 2, 2, 49, 4, 9, 1, 2, 10, 16, 7, 8, 4, 3, 2, 23, 4, 58, 3, 29, 1, 14, 19, 19, 11, 11, 2, 7, 5, 1, 3, 4, + 6, 2, 18, 5, 12, 12, 17, 17, 3, 3, 2, 4, 1, 6, 2, 3, 4, 3, 1, 1, 1, 1, 5, 1, 1, 9, 1, 3, 1, 3, 6, 1, 8, 1, 1, 2, + 6, 4, 14, 3, 1, 4, 11, 4, 1, 3, 32, 1, 2, 4, 13, 4, 1, 2, 4, 2, 1, 3, 1, 11, 1, 4, 2, 1, 4, 4, 6, 3, 5, 1, 6, 5, + 7, 6, 3, 23, 3, 5, 3, 5, 3, 3, 13, 3, 9, 10, 1, 12, 10, 2, 3, 18, 13, 7, 160, 52, 4, 2, 2, 3, 2, 14, 5, 4, 12, 4, 6, 4, + 1, 20, 4, 11, 6, 2, 12, 27, 1, 4, 1, 2, 2, 7, 4, 5, 2, 28, 3, 7, 25, 8, 3, 19, 3, 6, 10, 2, 2, 1, 10, 2, 5, 4, 1, 3, + 4, 1, 5, 3, 2, 6, 9, 3, 6, 2, 16, 3, 3, 16, 4, 5, 5, 3, 2, 1, 2, 16, 15, 8, 2, 6, 21, 2, 4, 1, 22, 5, 8, 1, 1, 21, + 11, 2, 1, 11, 11, 19, 13, 12, 4, 2, 3, 2, 3, 6, 1, 8, 11, 1, 4, 2, 9, 5, 2, 1, 11, 2, 9, 1, 1, 2, 14, 31, 9, 3, 4, 21, + 14, 4, 8, 1, 7, 2, 2, 2, 5, 1, 4, 20, 3, 3, 4, 10, 1, 11, 9, 8, 2, 1, 4, 5, 14, 12, 14, 2, 17, 9, 6, 31, 4, 14, 1, 20, + 13, 26, 5, 2, 7, 3, 6, 13, 2, 4, 2, 19, 6, 2, 2, 18, 9, 3, 5, 12, 12, 14, 4, 6, 2, 3, 6, 9, 5, 22, 4, 5, 25, 6, 4, 8, + 5, 2, 6, 27, 2, 35, 2, 16, 3, 7, 8, 8, 6, 6, 5, 9, 17, 2, 20, 6, 19, 2, 13, 3, 1, 1, 1, 4, 17, 12, 2, 14, 7, 1, 4, 18, + 12, 38, 33, 2, 10, 1, 1, 2, 13, 14, 17, 11, 50, 6, 33, 20, 26, 74, 16, 23, 45, 50, 13, 38, 33, 6, 6, 7, 4, 4, 2, 1, 3, 2, 5, 8, + 7, 8, 9, 3, 11, 21, 9, 13, 1, 3, 10, 6, 7, 1, 2, 2, 18, 5, 5, 1, 9, 9, 2, 68, 9, 19, 13, 2, 5, 1, 4, 4, 7, 4, 13, 3, + 9, 10, 21, 17, 3, 26, 2, 1, 5, 2, 4, 5, 4, 1, 7, 4, 7, 3, 4, 2, 1, 6, 1, 1, 20, 4, 1, 9, 2, 2, 1, 3, 3, 2, 3, 2, + 1, 1, 1, 20, 2, 3, 1, 6, 2, 3, 6, 2, 4, 8, 1, 3, 2, 10, 3, 5, 3, 4, 4, 3, 4, 16, 1, 6, 1, 10, 2, 4, 2, 1, 1, 2, + 10, 11, 2, 2, 3, 1, 24, 31, 4, 10, 10, 2, 5, 12, 16, 164, 15, 4, 16, 7, 9, 15, 19, 17, 1, 2, 1, 1, 5, 1, 1, 1, 1, 1, 3, 1, + 4, 3, 1, 3, 1, 3, 1, 2, 1, 1, 3, 3, 7, 2, 8, 1, 2, 2, 2, 1, 3, 4, 3, 7, 8, 12, 92, 2, 10, 3, 1, 3, 14, 5, 25, 16, + 42, 4, 7, 7, 4, 2, 21, 5, 27, 26, 27, 21, 25, 30, 31, 2, 1, 5, 13, 3, 22, 5, 6, 6, 11, 9, 12, 1, 5, 9, 7, 5, 5, 22, 60, 3, + 5, 13, 1, 1, 8, 1, 1, 3, 3, 2, 1, 9, 3, 3, 18, 4, 1, 2, 3, 7, 6, 3, 1, 2, 3, 9, 1, 3, 1, 3, 2, 1, 3, 1, 1, 1, + 2, 1, 11, 3, 1, 6, 9, 1, 3, 2, 3, 1, 2, 1, 5, 1, 1, 4, 3, 4, 1, 2, 2, 4, 4, 1, 7, 2, 1, 2, 2, 3, 5, 13, 18, 3, + 4, 14, 9, 9, 4, 16, 3, 7, 5, 8, 2, 6, 48, 28, 3, 1, 1, 4, 2, 14, 8, 2, 9, 2, 1, 15, 2, 4, 3, 2, 10, 16, 12, 8, 7, 1, + 1, 3, 1, 1, 1, 2, 7, 4, 1, 6, 4, 38, 39, 16, 23, 7, 15, 15, 3, 2, 12, 7, 21, 37, 27, 6, 5, 4, 8, 2, 10, 8, 8, 6, 5, 1, + 2, 1, 3, 24, 1, 16, 17, 9, 23, 10, 17, 6, 1, 51, 55, 44, 13, 294, 9, 3, 6, 2, 4, 2, 2, 15, 1, 1, 1, 13, 21, 17, 68, 14, 8, 9, + 4, 1, 4, 9, 3, 11, 7, 1, 1, 1, 5, 6, 3, 2, 1, 1, 1, 2, 3, 8, 1, 2, 2, 4, 1, 5, 5, 2, 1, 4, 3, 7, 13, 4, 1, 4, + 1, 3, 1, 1, 1, 5, 5, 10, 1, 6, 1, 5, 2, 1, 5, 2, 4, 1, 4, 5, 7, 3, 18, 2, 9, 11, 32, 4, 3, 3, 2, 4, 7, 11, 16, 9, + 11, 8, 13, 38, 32, 8, 4, 2, 1, 1, 2, 1, 2, 4, 4, 1, 1, 1, 4, 1, 21, 3, 11, 1, 16, 1, 1, 6, 1, 3, 2, 4, 9, 8, 57, 7, + 44, 1, 3, 3, 13, 3, 10, 1, 1, 7, 5, 2, 7, 21, 47, 63, 3, 15, 4, 7, 1, 16, 1, 1, 2, 8, 2, 3, 42, 15, 4, 1, 29, 7, 22, 10, + 3, 78, 16, 12, 20, 18, 4, 67, 11, 5, 1, 3, 15, 6, 21, 31, 32, 27, 18, 13, 71, 35, 5, 142, 4, 10, 1, 2, 50, 19, 33, 16, 35, 37, 16, 19, + 27, 7, 1, 133, 19, 1, 4, 8, 7, 20, 1, 4, 4, 1, 10, 3, 1, 6, 1, 2, 51, 5, 40, 15, 24, 43, 22928, 11, 1, 13, 154, 70, 3, 1, 1, 7, + 4, 10, 1, 2, 1, 1, 2, 1, 2, 1, 2, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 1, 1, 3, 2, 1, 1, 1, 1, 2, 1, 1, + }; + static ImWchar base_ranges[] = // not zero-terminated + { + 0x0020, + 0x00FF, // Basic Latin + Latin Supplement + 0x3000, + 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana + 0x31F0, + 0x31FF, // Katakana Phonetic Extensions + 0xFF00, + 0xFFEF, // Half-width characters + 0xFFFD, + 0xFFFD // Invalid + }; + static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00) * 2 + 1] = {0}; + if (!full_ranges[0]) { + memcpy(full_ranges, base_ranges, sizeof(base_ranges)); + UnpackAccumulativeOffsetsIntoRanges(0x4E00, + accumulative_offsets_from_0x4E00, + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00), + full_ranges + IM_ARRAYSIZE(base_ranges)); + } + return &full_ranges[0]; +} + +const ImWchar *ImFontAtlas::GetGlyphRangesCyrillic() +{ + static const ImWchar ranges[] = { + 0x0020, + 0x00FF, // Basic Latin + Latin Supplement + 0x0400, + 0x052F, // Cyrillic + Cyrillic Supplement + 0x2DE0, + 0x2DFF, // Cyrillic Extended-A + 0xA640, + 0xA69F, // Cyrillic Extended-B + 0, + }; + return &ranges[0]; +} + +const ImWchar *ImFontAtlas::GetGlyphRangesThai() +{ + static const ImWchar ranges[] = { + 0x0020, + 0x00FF, // Basic Latin + 0x2010, + 0x205E, // Punctuations + 0x0E00, + 0x0E7F, // Thai + 0, + }; + return &ranges[0]; +} + +const ImWchar *ImFontAtlas::GetGlyphRangesVietnamese() +{ + static const ImWchar ranges[] = { + 0x0020, + 0x00FF, // Basic Latin + 0x0102, + 0x0103, + 0x0110, + 0x0111, + 0x0128, + 0x0129, + 0x0168, + 0x0169, + 0x01A0, + 0x01A1, + 0x01AF, + 0x01B0, + 0x1EA0, + 0x1EF9, + 0, + }; + return &ranges[0]; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImFontGlyphRangesBuilder +//----------------------------------------------------------------------------- + +void ImFontGlyphRangesBuilder::AddText(const char *text, const char *text_end) +{ + while (text_end ? (text < text_end) : *text) { + unsigned int c = 0; + int c_len = ImTextCharFromUtf8(&c, text, text_end); + text += c_len; + if (c_len == 0) + break; + 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; + for (int n = 0; n <= max_codepoint; n++) + if (GetBit(n)) { + out_ranges->push_back((ImWchar)n); + while (n < max_codepoint && GetBit(n + 1)) + n++; + out_ranges->push_back((ImWchar)n); + } + out_ranges->push_back(0); +} + +//----------------------------------------------------------------------------- +// [SECTION] ImFont +//----------------------------------------------------------------------------- + +ImFont::ImFont() +{ + FontSize = 0.0f; + FallbackAdvanceX = 0.0f; + FallbackChar = (ImWchar)-1; + EllipsisChar = (ImWchar)-1; + EllipsisWidth = EllipsisCharStep = 0.0f; + EllipsisCharCount = 0; + FallbackGlyph = NULL; + ContainerAtlas = NULL; + ConfigData = NULL; + ConfigDataCount = 0; + DirtyLookupTables = false; + Scale = 1.0f; + Ascent = Descent = 0.0f; + MetricsTotalSurface = 0; + memset(Used4kPagesMap, 0, sizeof(Used4kPagesMap)); +} + +ImFont::~ImFont() +{ + ClearOutputData(); +} + +void ImFont::ClearOutputData() +{ + FontSize = 0.0f; + FallbackAdvanceX = 0.0f; + Glyphs.clear(); + IndexAdvanceX.clear(); + IndexLookup.clear(); + FallbackGlyph = NULL; + ContainerAtlas = NULL; + DirtyLookupTables = true; + Ascent = Descent = 0.0f; + MetricsTotalSurface = 0; +} + +static ImWchar FindFirstExistingGlyph(ImFont *font, const ImWchar *candidate_chars, int candidate_chars_count) +{ + for (int n = 0; n < candidate_chars_count; n++) + if (font->FindGlyphNoFallback(candidate_chars[n]) != NULL) + return candidate_chars[n]; + return (ImWchar)-1; +} + +void ImFont::BuildLookupTable() +{ + 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 < 0xFFFF); // -1 is reserved + IndexAdvanceX.clear(); + IndexLookup.clear(); + DirtyLookupTables = false; + memset(Used4kPagesMap, 0, sizeof(Used4kPagesMap)); + 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] = (ImWchar)i; + + // Mark 4K page as used + const int page_n = codepoint / 4096; + Used4kPagesMap[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] = (ImWchar)(Glyphs.Size - 1); + } + + // Mark special glyphs as not visible (note that AddGlyph already mark as non-visible glyphs with zero-size polygons) + SetGlyphVisible((ImWchar)' ', false); + SetGlyphVisible((ImWchar)'\t', 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[] = {(ImWchar)0x2026, (ImWchar)0x0085}; + const ImWchar dots_chars[] = {(ImWchar)'.', (ImWchar)0xFF0E}; + if (EllipsisChar == (ImWchar)-1) + EllipsisChar = FindFirstExistingGlyph(this, ellipsis_chars, IM_ARRAYSIZE(ellipsis_chars)); + const ImWchar dot_char = FindFirstExistingGlyph(this, dots_chars, IM_ARRAYSIZE(dots_chars)); + if (EllipsisChar != (ImWchar)-1) { + EllipsisCharCount = 1; + EllipsisWidth = EllipsisCharStep = FindGlyph(EllipsisChar)->X1; + } else if (dot_char != (ImWchar)-1) { + const ImFontGlyph *glyph = FindGlyph(dot_char); + EllipsisChar = dot_char; + EllipsisCharCount = 3; + EllipsisCharStep = (glyph->X1 - glyph->X0) + 1.0f; + EllipsisWidth = EllipsisCharStep * 3.0f - 1.0f; + } +} + +// API is designed this way to avoid exposing the 4K page size +// e.g. use with IsGlyphRangeUnused(0, 255) +bool ImFont::IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last) +{ + unsigned int page_begin = (c_begin / 4096); + unsigned int page_last = (c_last / 4096); + for (unsigned int page_n = page_begin; page_n <= page_last; page_n++) + if ((page_n >> 3) < sizeof(Used4kPagesMap)) + if (Used4kPagesMap[page_n >> 3] & (1 << (page_n & 7))) + return false; + return true; +} + +void ImFont::SetGlyphVisible(ImWchar c, bool visible) +{ + if (ImFontGlyph *glyph = (ImFontGlyph *)(void *)FindGlyph((ImWchar)c)) + glyph->Visible = visible ? 1 : 0; +} + +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, (ImWchar)-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). +// 'cfg' is not necessarily == 'this->ConfigData' because multiple source fonts+configs can be used to build one target font. +void ImFont::AddGlyph(const ImFontConfig *cfg, + ImWchar codepoint, + float x0, + float y0, + float x1, + float y1, + float u0, + float v0, + float u1, + float v1, + float advance_x) +{ + if (cfg != NULL) { + // Clamp & recenter if needed + const float advance_x_original = advance_x; + advance_x = ImClamp(advance_x, cfg->GlyphMinAdvanceX, cfg->GlyphMaxAdvanceX); + if (advance_x != advance_x_original) { + float char_off_x = cfg->PixelSnapH ? ImFloor((advance_x - advance_x_original) * 0.5f) : (advance_x - advance_x_original) * 0.5f; + x0 += char_off_x; + x1 += char_off_x; + } + + // Snap to pixel + if (cfg->PixelSnapH) + advance_x = IM_ROUND(advance_x); + + // Bake spacing + advance_x += cfg->GlyphExtraSpacing.x; + } + + Glyphs.resize(Glyphs.Size + 1); + ImFontGlyph &glyph = Glyphs.back(); + 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; + + // 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); +} + +void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) +{ + 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] == (ImWchar)-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] : (ImWchar)-1; + IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f; +} + +const ImFontGlyph *ImFont::FindGlyph(ImWchar c) const +{ + if (c >= (size_t)IndexLookup.Size) + return FallbackGlyph; + const ImWchar i = IndexLookup.Data[c]; + if (i == (ImWchar)-1) + return FallbackGlyph; + return &Glyphs.Data[i]; +} + +const ImFontGlyph *ImFont::FindGlyphNoFallback(ImWchar c) const +{ + if (c >= (size_t)IndexLookup.Size) + return NULL; + const ImWchar i = IndexLookup.Data[c]; + if (i == (ImWchar)-1) + return NULL; + return &Glyphs.Data[i]; +} + +// Wrapping skips upcoming blanks +static inline const char *CalcWordWrapNextLineStartA(const char *text, const char *text_end) +{ + while (text < text_end && ImCharIsBlankA(*text)) + text++; + if (*text == '\n') + text++; + return text; +} + +// 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 +{ + // 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" + 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) { + unsigned int c = (unsigned int)*s; + const char *next_s; + if (c < 0x80) + next_s = s + 1; + else + next_s = s + ImTextCharFromUtf8(&c, s, text_end); + + if (c < 32) { + if (c == '\n') { + line_width = word_width = blank_width = 0.0f; + inside_word = true; + s = next_s; + continue; + } + if (c == '\r') { + s = next_s; + continue; + } + } + + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX); + if (ImCharIsBlankW(c)) { + if (inside_word) { + line_width += blank_width; + blank_width = 0.0f; + word_end = s; + } + blank_width += char_width; + inside_word = false; + } else { + word_width += char_width; + if (inside_word) { + word_end = next_s; + } else { + prev_word_end = word_end; + line_width += word_width + blank_width; + word_width = blank_width = 0.0f; + } + + // Allow wrapping after punctuation. + inside_word = (c != '.' && c != ',' && c != ';' && c != '!' && c != '?' && c != '\"'); + } + + // We ignore blank width at the end of the line (they can be skipped) + if (line_width + word_width > wrap_width) { + // Words that cannot possibly fit within an entire line will be cut anywhere. + if (word_width < wrap_width) + 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; +} + +ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char *text_begin, const char *text_end, const char **remaining) const +{ + if (!text_end) + text_end = text_begin + strlen(text_begin); // FIXME-OPT: Need to avoid this. + + const float line_height = size; + const float scale = size / FontSize; + + 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) { + 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 - line_width); + + if (s >= word_wrap_eol) { + if (text_size.x < line_width) + text_size.x = line_width; + text_size.y += line_height; + line_width = 0.0f; + word_wrap_eol = NULL; + s = CalcWordWrapNextLineStartA(s, text_end); // Wrapping skips upcoming blanks + continue; + } + } + + // Decode and advance source + const char *prev_s = s; + unsigned int c = (unsigned int)*s; + if (c < 0x80) + s += 1; + else + s += ImTextCharFromUtf8(&c, s, text_end); + + if (c < 32) { + if (c == '\n') { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + continue; + } + if (c == '\r') + continue; + } + + const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX) * 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) const +{ + const ImFontGlyph *glyph = 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 x = IM_FLOOR(pos.x); + float y = IM_FLOOR(pos.y); + 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); +} + +// 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) const +{ + if (!text_end) + text_end = text_begin + strlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls. + + // Align to be pixel perfect + float x = IM_FLOOR(pos.x); + float y = IM_FLOOR(pos.y); + if (y > clip_rect.w) + return; + + const float start_x = x; + const float scale = size / FontSize; + const float line_height = FontSize * scale; + 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) + while (y + line_height < clip_rect.y && s < text_end) { + const char *line_end = (const char *)memchr(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. + // However it is still better than nothing performing the fast-forward! + s = CalcWordWrapPositionA(scale, s, line_end ? line_end : text_end, wrap_width); + s = CalcWordWrapNextLineStartA(s, text_end); + } else { + s = line_end ? line_end + 1 : text_end; + } + 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) { + const char *s_end = s; + float y_end = y; + while (y_end < clip_rect.w && s_end < text_end) { + s_end = (const char *)memchr(s_end, '\n', text_end - s_end); + s_end = s_end ? s_end + 1 : text_end; + y_end += line_height; + } + text_end = s_end; + } + 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; + const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max; + draw_list->PrimReserve(idx_count_max, vtx_count_max); + ImDrawVert *vtx_write = draw_list->_VtxWritePtr; + ImDrawIdx *idx_write = draw_list->_IdxWritePtr; + unsigned int vtx_index = draw_list->_VtxCurrentIdx; + + 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 - start_x)); + + if (s >= word_wrap_eol) { + x = start_x; + y += line_height; + word_wrap_eol = NULL; + s = CalcWordWrapNextLineStartA(s, text_end); // Wrapping skips upcoming blanks + 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') { + x = start_x; + y += line_height; + if (y > clip_rect.w) + break; // break out of main loop + continue; + } + if (c == '\r') + continue; + } + + const ImFontGlyph *glyph = FindGlyph((ImWchar)c); + if (glyph == NULL) + continue; + + float char_width = glyph->AdvanceX * scale; + if (glyph->Visible) { + // We don't do a second finer clipping test on the Y axis as we've already skipped anything before clip_rect.y and exit once we pass clip_rect.w + float x1 = x + glyph->X0 * scale; + float x2 = x + glyph->X1 * scale; + float y1 = y + glyph->Y0 * scale; + float y2 = y + glyph->Y1 * scale; + if (x1 <= clip_rect.z && x2 >= clip_rect.x) { + // Render a character + float u1 = glyph->U0; + 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) { + if (x1 < clip_rect.x) { + u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1); + x1 = clip_rect.x; + } + if (y1 < clip_rect.y) { + v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1); + y1 = clip_rect.y; + } + if (x2 > clip_rect.z) { + u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1); + x2 = clip_rect.z; + } + if (y2 > clip_rect.w) { + v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1); + y2 = clip_rect.w; + } + if (y1 >= y2) { + x += char_width; + 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; + vtx_write[1].pos.x = x2; + vtx_write[1].pos.y = y1; + vtx_write[1].col = glyph_col; + vtx_write[1].uv.x = u2; + vtx_write[1].uv.y = v1; + vtx_write[2].pos.x = x2; + vtx_write[2].pos.y = y2; + vtx_write[2].col = glyph_col; + vtx_write[2].uv.x = u2; + vtx_write[2].uv.y = v2; + vtx_write[3].pos.x = x1; + vtx_write[3].pos.y = y2; + vtx_write[3].col = glyph_col; + vtx_write[3].uv.x = u1; + vtx_write[3].uv.y = v2; + idx_write[0] = (ImDrawIdx)(vtx_index); + idx_write[1] = (ImDrawIdx)(vtx_index + 1); + idx_write[2] = (ImDrawIdx)(vtx_index + 2); + idx_write[3] = (ImDrawIdx)(vtx_index); + idx_write[4] = (ImDrawIdx)(vtx_index + 2); + idx_write[5] = (ImDrawIdx)(vtx_index + 3); + vtx_write += 4; + vtx_index += 4; + idx_write += 6; + } + } + } + x += char_width; + } + + // 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); + draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ElemCount -= (idx_expected_size - draw_list->IdxBuffer.Size); + draw_list->_VtxWritePtr = vtx_write; + draw_list->_IdxWritePtr = idx_write; + draw_list->_VtxCurrentIdx = vtx_index; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImGui Internal Render Helpers +//----------------------------------------------------------------------------- +// Vaguely redesigned to stop accessing ImGui global state: +// - RenderArrow() +// - RenderBullet() +// - RenderCheckMark() +// - RenderArrowPointingAt() +// - RenderRectFilledRangeH() +// - RenderRectFilledWithHole() +//----------------------------------------------------------------------------- +// 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) { + case ImGuiDir_Up: + case ImGuiDir_Down: + if (dir == ImGuiDir_Up) + r = -r; + a = ImVec2(+0.000f, +0.750f) * r; + b = ImVec2(-0.866f, -0.750f) * r; + c = ImVec2(+0.866f, -0.750f) * r; + break; + case ImGuiDir_Left: + case ImGuiDir_Right: + if (dir == ImGuiDir_Left) + r = -r; + a = ImVec2(+0.750f, +0.000f) * r; + b = ImVec2(-0.750f, +0.866f) * r; + c = ImVec2(-0.750f, -0.866f) * r; + break; + case ImGuiDir_None: + case ImGuiDir_COUNT: + IM_ASSERT(0); + break; + } + 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; + draw_list->PathLineTo(ImVec2(bx - third, by - third)); + draw_list->PathLineTo(ImVec2(bx, by)); + 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) +{ + switch (direction) { + case ImGuiDir_Left: + draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), pos, col); + return; + case ImGuiDir_Right: + draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), pos, col); + return; + case ImGuiDir_Up: + draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), pos, col); + return; + case ImGuiDir_Down: + draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), pos, col); + return; + case ImGuiDir_None: + case ImGuiDir_COUNT: + break; // Fix warnings + } +} + +static inline float ImAcos01(float x) +{ + if (x <= 0.0f) + return IM_PI * 0.5f; + if (x >= 1.0f) + return 0.0f; + 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) +{ + if (x_end_norm == x_start_norm) + 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) { + 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); + const float arc0_e = ImAcos01(1.0f - (p1.x - rect.Min.x) * inv_rounding); + const float half_pi = IM_PI * 0.5f; // We will == compare to this because we know this is the exact value ImAcos01 can return. + const float x0 = ImMax(p0.x, rect.Min.x + rounding); + if (arc0_b == arc0_e) { + draw_list->PathLineTo(ImVec2(x0, p1.y)); + draw_list->PathLineTo(ImVec2(x0, p0.y)); + } else if (arc0_b == 0.0f && arc0_e == half_pi) { + draw_list->PathArcToFast(ImVec2(x0, p1.y - rounding), rounding, 3, 6); // BL + draw_list->PathArcToFast(ImVec2(x0, p0.y + rounding), rounding, 6, 9); // TR + } else { + draw_list->PathArcTo(ImVec2(x0, p1.y - rounding), rounding, IM_PI - arc0_e, IM_PI - arc0_b, 3); // BL + draw_list->PathArcTo(ImVec2(x0, p0.y + rounding), rounding, IM_PI + arc0_b, IM_PI + arc0_e, 3); // TR + } + if (p1.x > rect.Min.x + rounding) { + const float arc1_b = ImAcos01(1.0f - (rect.Max.x - p1.x) * inv_rounding); + const float arc1_e = ImAcos01(1.0f - (rect.Max.x - p0.x) * inv_rounding); + const float x1 = ImMin(p1.x, rect.Max.x - rounding); + if (arc1_b == arc1_e) { + draw_list->PathLineTo(ImVec2(x1, p0.y)); + draw_list->PathLineTo(ImVec2(x1, p1.y)); + } else if (arc1_b == 0.0f && arc1_e == half_pi) { + draw_list->PathArcToFast(ImVec2(x1, p0.y + rounding), rounding, 9, 12); // TR + draw_list->PathArcToFast(ImVec2(x1, p1.y - rounding), rounding, 0, 3); // BR + } else { + draw_list->PathArcTo(ImVec2(x1, p0.y + rounding), rounding, -arc1_e, -arc1_b, 3); // TR + draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e, 3); // BR + } + } + 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); + const bool fill_R = (inner.Max.x < outer.Max.x); + const bool fill_U = (inner.Min.y > outer.Min.y); + const bool fill_D = (inner.Max.y < outer.Max.y); + if (fill_L) + draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Min.y), + ImVec2(inner.Min.x, inner.Max.y), + col, + rounding, + ImDrawFlags_RoundCornersNone | (fill_U ? 0 : ImDrawFlags_RoundCornersTopLeft) + | (fill_D ? 0 : ImDrawFlags_RoundCornersBottomLeft)); + if (fill_R) + draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Min.y), + ImVec2(outer.Max.x, inner.Max.y), + col, + rounding, + ImDrawFlags_RoundCornersNone | (fill_U ? 0 : ImDrawFlags_RoundCornersTopRight) + | (fill_D ? 0 : ImDrawFlags_RoundCornersBottomRight)); + if (fill_U) + draw_list->AddRectFilled(ImVec2(inner.Min.x, outer.Min.y), + ImVec2(inner.Max.x, inner.Min.y), + col, + rounding, + ImDrawFlags_RoundCornersNone | (fill_L ? 0 : ImDrawFlags_RoundCornersTopLeft) + | (fill_R ? 0 : ImDrawFlags_RoundCornersTopRight)); + if (fill_D) + draw_list->AddRectFilled(ImVec2(inner.Min.x, inner.Max.y), + ImVec2(inner.Max.x, outer.Max.y), + col, + rounding, + ImDrawFlags_RoundCornersNone | (fill_L ? 0 : ImDrawFlags_RoundCornersBottomLeft) + | (fill_R ? 0 : ImDrawFlags_RoundCornersBottomRight)); + if (fill_L && fill_U) + draw_list->AddRectFilled(ImVec2(outer.Min.x, outer.Min.y), ImVec2(inner.Min.x, inner.Min.y), col, rounding, ImDrawFlags_RoundCornersTopLeft); + if (fill_R && fill_U) + draw_list->AddRectFilled(ImVec2(inner.Max.x, outer.Min.y), ImVec2(outer.Max.x, inner.Min.y), col, rounding, ImDrawFlags_RoundCornersTopRight); + 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); +} + +// 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. +// FIXME: uses ImGui::GetColorU32 +void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList *draw_list, + ImVec2 p_min, + ImVec2 p_max, + ImU32 col, + float grid_step, + ImVec2 grid_off, + float rounding, + ImDrawFlags flags) +{ + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags = ImDrawFlags_RoundCornersDefault_; + if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF) { + 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++) { + float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y); + if (y2 <= y1) + continue; + for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f) { + float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x); + if (x2 <= x1) + continue; + 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); + } + } + } else { + draw_list->AddRectFilled(p_min, p_max, col, rounding, flags); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] Decompression code +//----------------------------------------------------------------------------- +// Compressed with stb_compress() then converted to a C array and encoded as base85. +// Use the program in misc/fonts/binary_to_compressed_c.cpp to create the array from a TTF file. +// 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; +static void stb__match(const unsigned char *data, unsigned int length) +{ + // INVERSE of memmove... write each byte before copying the next... + IM_ASSERT(stb__dout + length <= stb__barrier_out_e); + if (stb__dout + length > stb__barrier_out_e) { + stb__dout += length; + return; + } + 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); + if (stb__dout + length > stb__barrier_out_e) { + stb__dout += length; + return; + } + if (data < stb__barrier_in_b) { + stb__dout = stb__barrier_out_e + 1; + return; + } + 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 + if (*i >= 0x80) + stb__match(stb__dout - i[1] - 1, i[0] - 0x80 + 1), i += 2; + else if (*i >= 0x40) + stb__match(stb__dout - (stb__in2(0) - 0x4000 + 1), i[2] + 1), i += 3; + else /* *i >= 0x20 */ + stb__lit(i + 1, i[0] - 0x20 + 1), i += 1 + (i[0] - 0x20 + 1); + } else { // more ifs for cases that expand large, since overhead is amortized + if (*i >= 0x18) + stb__match(stb__dout - (stb__in3(0) - 0x180000 + 1), i[3] + 1), i += 4; + else if (*i >= 0x10) + stb__match(stb__dout - (stb__in3(0) - 0x100000 + 1), stb__in2(3) + 1), i += 5; + else if (*i >= 0x08) + stb__lit(i + 2, stb__in2(0) - 0x0800 + 1), i += 2 + (stb__in2(0) - 0x0800 + 1); + else if (*i == 0x07) + stb__lit(i + 3, stb__in2(1) + 1), i += 3 + (stb__in2(1) + 1); + else if (*i == 0x06) + stb__match(stb__dout - (stb__in3(1) + 1), i[4] + 1), i += 5; + else if (*i == 0x04) + stb__match(stb__dout - (stb__in3(1) + 1), stb__in2(4) + 1), i += 6; + } + 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) { + s1 += buffer[0], s2 += s1; + s1 += buffer[1], s2 += s1; + s1 += buffer[2], s2 += s1; + s1 += buffer[3], s2 += s1; + s1 += buffer[4], s2 += s1; + 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; + if (stb__in4(4) != 0) + return 0; // error! stream is > 4GB + const unsigned int olen = stb_decompress_length(i); + stb__barrier_in_b = i; + stb__barrier_out_e = output + olen; + stb__barrier_out_b = output; + i += 16; + + stb__dout = output; + for (;;) { + const unsigned char *old_i = i; + i = stb_decompress_token(i); + if (i == old_i) { + if (*i == 0x05 && i[1] == 0xfa) { + IM_ASSERT(stb__dout == output + olen); + if (stb__dout != output + olen) + return 0; + if (stb_adler32(1, output, olen) != (unsigned int)stb__in4(2)) + return 0; + return olen; + } else { + IM_ASSERT(0); /* NOTREACHED */ + return 0; + } + } + IM_ASSERT(stb__dout <= output + olen); + if (stb__dout > output + olen) + return 0; + } +} + +//----------------------------------------------------------------------------- +// [SECTION] Default font data (ProggyClean.ttf) +//----------------------------------------------------------------------------- +// ProggyClean.ttf +// Copyright (c) 2004, 2005 Tristan Grimmer +// MIT license (see License.txt in http://www.upperbounds.net/download/ProggyClean.ttf.zip) +// Download and more information at http://upperbounds.net +//----------------------------------------------------------------------------- +// File: 'ProggyClean.ttf' (41208 bytes) +// Exported using misc/fonts/binary_to_compressed_c.cpp (with compression + base85 string encoding). +// The purpose of encoding as base85 instead of "0x00,0x01,..." style is only save on _source code_ size. +//----------------------------------------------------------------------------- +static const char proggy_clean_ttf_compressed_data_base85[11980 + 1] = + "7])#######hV0qs'/###[),##/l:$#Q6>##5[n42>c-TH`->>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCp#-i>.r$<$6pD>Lb';9Crc6tgXmKVeU2cD4Eo3R/" + "2*>]b(MC;$jPfY.;h^`IWM9Qo#t'X#(v#Y9w0#1D$CIf;W'#pWUPXOuxXuU(H9M(1=Ke$$'5F%)]0^#0X@U.a$FBjVQTSDgEKnIS7EM9>ZY9w0#L;>>#Mx&4Mvt//L[MkA#W@lK.N'[0#7RL_&#w+F%HtG9M#XL`N&.,GM4Pg;--VsM.M0rJfLH2eTM`*oJMHRC`N" + "kfimM2J,W-jXS:)r0wK#@Fge$U>`w'N7G#$#fB#$E^$#:9:hk+eOe--6x)F7*E%?76%^GMHePW-Z5l'&GiF#$956:rS?dA#fiK:)Yr+`�j@'DbG&#^$PG.Ll+DNa&VZ>1i%h1S9u5o@YaaW$e+bROPOpxTO7Stwi1::iB1q)C_=dV26J;2,]7op$]uQr@_V7$q^%lQwtuHY]=DX,n3L#0PHDO4f9>dC@O>HBuKPpP*E,N+b3L#lpR/MrTEH.IAQk.a>D[.e;mc." + "x]Ip.PH^'/aqUO/$1WxLoW0[iLAw=4h(9.`G" + "CRUxHPeR`5Mjol(dUWxZa(>STrPkrJiWx`5U7F#.g*jrohGg`cg:lSTvEY/EV_7H4Q9[Z%cnv;JQYZ5q.l7Zeas:HOIZOB?Ggv:[7MI2k).'2($5FNP&EQ(,)" + "U]W]+fh18.vsai00);D3@4ku5P?DP8aJt+;qUM]=+b'8@;mViBKx0DE[-auGl8:PJ&Dj+M6OC]O^((##]`0i)drT;-7X`=-H3[igUnPG-NZlo.#k@h#=Ork$m>a>$-?Tm$UV(?#P6YY#" + "'/###xe7q.73rI3*pP/$1>s9)W,JrM7SN]'/4C#v$U`0#V.[0>xQsH$fEmPMgY2u7Kh(G%siIfLSoS+MK2eTM$=5,M8p`A.;_R%#u[K#$x4AG8.kK/HSB==-'Ie/QTtG?-.*^N-4B/ZM" + "_3YlQC7(p7q)&](`6_c)$/*JL(L-^(]$wIM`dPtOdGA,U3:w2M-0+WomX2u7lqM2iEumMTcsF?-aT=Z-97UEnXglEn1K-bnEO`gu" + "Ft(c%=;Am_Qs@jLooI&NX;]0#j4#F14;gl8-GQpgwhrq8'=l_f-b49'UOqkLu7-##oDY2L(te+Mch&gLYtJ,MEtJfLh'x'M=$CS-ZZ%P]8bZ>#S?YY#%Q&q'3^Fw&?D)UDNrocM3A76/" + "/oL?#h7gl85[qW/NDOk%16ij;+:1a'iNIdb-ou8.P*w,v5#EI$TWS>Pot-R*H'-SEpA:g)f+O$%%`kA#G=8RMmG1&O`>to8bC]T&$,n.LoO>29sp3dt-52U%VM#q7'DHpg+#Z9%H[Ket`e;)f#Km8&+DC$I46>#Kr]]u-[=99tts1.qb#q72g1WJO81q+eN'03'eM>&1XxY-caEnO" + "j%2n8)),?ILR5^.Ibn<-X-Mq7[a82Lq:F&#ce+S9wsCK*x`569E8ew'He]h:sI[2LM$[guka3ZRd6:t%IG:;$%YiJ:Nq=?eAw;/:nnDq0(CYcMpG)qLN4$##&J-XTt,%OVU4)S1+R-#dg0/Nn?Ku1^0f$B*P:Rowwm-`0PKjYDDM'3]d39VZHEl4,.j']Pk-M.h^&:0FACm$maq-&sgw0t7/6(^xtk%" + "LuH88Fj-ekm>GA#_>568x6(OFRl-IZp`&b,_P'$MhLbxfc$mj`,O;&%W2m`Zh:/)Uetw:aJ%]K9h:TcF]u_-Sj9,VK3M.*'&0D[Ca]J9gp8,kAW]" + "%(?A%R$f<->Zts'^kn=-^@c4%-pY6qI%J%1IGxfLU9CP8cbPlXv);C=b),<2mOvP8up,UVf3839acAWAW-W?#ao/^#%KYo8fRULNd2.>%m]UK:n%r$'sw]J;5pAoO_#2mO3n,'=H5(et" + "Hg*`+RLgv>=4U8guD$I%D:W>-r5V*%j*W:Kvej.Lp$'?;++O'>()jLR-^u68PHm8ZFWe+ej8h:9r6L*0//c&iH&R8pRbA#Kjm%upV1g:" + "a_#Ur7FuA#(tRh#.Y5K+@?3<-8m0$PEn;J:rh6?I6uG<-`wMU'ircp0LaE_OtlMb&1#6T.#FDKu#1Lw%u%+GM+X'e?YLfjM[VO0MbuFp7;>Q&#WIo)0@F%q7c#4XAXN-U&VBpqB>0ie&jhZ[?iLR@@_AvA-iQC(=ksRZRVp7`.=+NpBC%rh&3]R:8XDmE5^V8O(x<-+k?'(^](H.aREZSi,#1:[IXaZFOm<-ui#qUq2$##Ri;u75OK#(RtaW-K-F`S+cF]uN`-KMQ%rP/Xri.LRcB##=YL3BgM/3M" + "D?@f&1'BW-)Ju#bmmWCMkk&#TR`C,5d>g)F;t,4:@_l8G/5h4vUd%&%950:VXD'QdWoY-F$BtUwmfe$YqL'8(PWX(" + "P?^@Po3$##`MSs?DWBZ/S>+4%>fX,VWv/w'KD`LP5IbH;rTV>n3cEK8U#bX]l-/V+^lj3;vlMb&[5YQ8#pekX9JP3XUC72L,,?+Ni&co7ApnO*5NK,((W-i:$,kp'UDAO(G0Sq7MVjJs" + "bIu)'Z,*[>br5fX^:FPAWr-m2KgLQ_nN6'8uTGT5g)uLv:873UpTLgH+#FgpH'_o1780Ph8KmxQJ8#H72L4@768@Tm&Q" + "h4CB/5OvmA&,Q&QbUoi$a_%3M01H)4x7I^&KQVgtFnV+;[Pc>[m4k//,]1?#`VY[Jr*3&&slRfLiVZJ:]?=K3Sw=[$=uRB?3xk48@aege0jT6'N#(q%.O=?2S]u*(m<-" + "V8J'(1)G][68hW$5'q[GC&5j`TE?m'esFGNRM)j,ffZ?-qx8;->g4t*:CIP/[Qap7/9'#(1sao7w-.qNUdkJ)tCF&#B^;xGvn2r9FEPFFFcL@.iFNkTve$m%#QvQS8U@)2Z+3K:AKM5i" + "sZ88+dKQ)W6>J%CL`.d*(B`-n8D9oK-XV1q['-5k'cAZ69e;D_?$ZPP&s^+7])$*$#@QYi9,5P r+$%CE=68>K8r0=dSC%%(@p7" + ".m7jilQ02'0-VWAgTlGW'b)Tq7VT9q^*^$$.:&N@@" + "$&)WHtPm*5_rO0&e%K&#-30j(E4#'Zb.o/(Tpm$>K'f@[PvFl,hfINTNU6u'0pao7%XUp9]5.>%h`8_=VYbxuel.NTSsJfLacFu3B'lQSu/m6-Oqem8T+oE--$0a/k]uj9EwsG>%veR*" + "hv^BFpQj:K'#SJ,sB-'#](j.Lg92rTw-*n%@/;39rrJF,l#qV%OrtBeC6/,;qB3ebNW[?,Hqj2L.1NP&GjUR=1D8QaS3Up&@*9wP?+lo7b?@%'k4`p0Z$22%K3+iCZj?XJN4Nm&+YF]u" + "@-W$U%VEQ/,,>>#)D#%8cY#YZ?=,`Wdxu/ae&#" + "w6)R89tI#6@s'(6Bf7a&?S=^ZI_kS&ai`&=tE72L_D,;^R)7[$so8lKN%5/$(vdfq7+ebA#" + "u1p]ovUKW&Y%q]'>$1@-[xfn$7ZTp7mM,G,Ko7a&Gu%G[RMxJs[0MM%wci.LFDK)(%:_i2B5CsR8&9Z&#=mPEnm0f`<&c)QL5uJ#%u%lJj+D-r;BoFDoS97h5g)E#o:&S4weDF,9^Hoe`h*L+_a*NrLW-1pG_&2UdB8" + "6e%B/:=>)N4xeW.*wft-;$'58-ESqr#U`'6AQ]m&6/`Z>#S?YY#Vc;r7U2&326d=w&H####?TZ`*4?&.MK?LP8Vxg>$[QXc%QJv92.(Db*B)gb*BM9dM*hJMAo*c&#" + "b0v=Pjer]$gG&JXDf->'StvU7505l9$AFvgYRI^&<^b68?j#q9QX4SM'RO#&sL1IM.rJfLUAj221]d##DW=m83u5;'bYx,*Sl0hL(W;;$doB&O/TQ:(Z^xBdLjLV#*8U_72Lh+2Q8Cj0i:6hp&$C/:p(HK>T8Y[gHQ4`4)'$Ab(Nof%V'8hL&#SfD07&6D@M.*J:;$-rv29'M]8qMv-tLp,'886iaC=Hb*YJoKJ,(j%K=H`K.v9HggqBIiZu'QvBT.#=)0ukruV&.)3=(^1`o*Pj4<-#MJ+gLq9-##@HuZPN0]u:h7.T..G:;$/Usj(T7`Q8tT72LnYl<-qx8;-HV7Q-&Xdx%1a,hC=0u+HlsV>nuIQL-5" + "_>@kXQtMacfD.m-VAb8;IReM3$wf0''hra*so568'Ip&vRs849'MRYSp%:t:h5qSgwpEr$B>Q,;s(C#$)`svQuF$##-D,##,g68@2[T;.XSdN9Qe)rpt._K-#5wF)sP'##p#C0c%-Gb%" + "hd+<-j'Ai*x&&HMkT]C'OSl##5RG[JXaHN;d'uA#x._U;.`PU@(Z3dt4r152@:v,'R.Sj'w#0<-;kPI)FfJ&#AYJ&#//)>-k=m=*XnK$>=)72L]0I%>.G690a:$##<,);?;72#?x9+d;" + "^V'9;jY@;)br#q^YQpx:X#Te$Z^'=-=bGhLf:D6&bNwZ9-ZD#n^9HhLMr5G;']d&6'wYmTFmLq9wI>P(9mI[>kC-ekLC/R&CH+s'B;K-M6$EB%is00:" + "+A4[7xks.LrNk0&E)wILYF@2L'0Nb$+pv<(2.768/FrY&h$^3i&@+G%JT'<-,v`3;_)I9M^AE]CN?Cl2AZg+%4iTpT3$U4O]GKx'm9)b@p7YsvK3w^YR-" + "CdQ*:Ir<($u&)#(&?L9Rg3H)4fiEp^iI9O8KnTj,]H?D*r7'M;PwZ9K0E^k&-cpI;.p/6_vwoFMV<->#%Xi.LxVnrU(4&8/P+:hLSKj$#U%]49t'I:rgMi'FL@a:0Y-uA[39',(vbma*" + "hU%<-SRF`Tt:542R_VV$p@[p8DV[A,?1839FWdFTi1O*H&#(AL8[_P%.M>v^-))qOT*F5Cq0`Ye%+$B6i:7@0IXSsDiWP,##P`%/L-" + "S(qw%sf/@%#B6;/U7K]uZbi^Oc^2n%t<)'mEVE''n`WnJra$^TKvX5B>;_aSEK',(hwa0:i4G?.Bci.(X[?b*($,=-n<.Q%`(X=?+@Am*Js0&=3bh8K]mL69=Lb,OcZV/);TTm8VI;?%OtJ<(b4mq7M6:u?KRdFl*:xP?Yb.5)%w_I?7uk5JC+FS(m#i'k.'a0i)9<7b'fs'59hq$*5Uhv##pi^8+hIEBF`nvo`;'l0.^S1<-wUK2/Coh58KKhLj" + "M=SO*rfO`+qC`W-On.=AJ56>>i2@2LH6A:&5q`?9I3@@'04&p2/LVa*T-4<-i3;M9UvZd+N7>b*eIwg:CC)c<>nO&#$(>.Z-I&J(Q0Hd5Q%7Co-b`-cP)hI;*_F]u`Rb[.j8_Q/<&>uu+VsH$sM9TA%?)(vmJ80),P7E>)tjD%2L=-t#fK[%`v=Q8WlA2);Sa" + ">gXm8YB`1d@K#n]76-a$U,mF%Ul:#/'xoFM9QX-$.QN'>" + "[%$Z$uF6pA6Ki2O5:8w*vP1<-1`[G,)-m#>0`P&#eb#.3i)rtB61(o'$?X3B2Qft^ae_5tKL9MUe9b*sLEQ95C&`=G?@Mj=wh*'3E>=-<)Gt*Iw)'QG:`@I" + "wOf7&]1i'S01B+Ev/Nac#9S;=;YQpg_6U`*kVY39xK,[/6Aj7:'1Bm-_1EYfa1+o&o4hp7KN_Q(OlIo@S%;jVdn0'1h19w,WQhLI)3S#f$2(eb,jr*b;3Vw]*7NH%$c4Vs,eD9>XW8?N]o+(*pgC%/72LV-uW%iewS8W6m2rtCpo'RS1R84=@paTKt)>=%&1[)*vp'u+x,VrwN;&]kuO9JDbg=pO$J*.jVe;u'm0dr9l,<*wMK*Oe=g8lV_KEBFkO'oU]^=[-792#ok,)" + "i]lR8qQ2oA8wcRCZ^7w/Njh;?.stX?Q1>S1q4Bn$)K1<-rGdO'$Wr.Lc.CG)$/*JL4tNR/,SVO3,aUw'DJN:)Ss;wGn9A32ijw%FL+Z0Fn.U9;reSq)bmI32U==5ALuG&#Vf1398/pVo" + "1*c-(aY168o<`JsSbk-,1N;$>0:OUas(3:8Z972LSfF8eb=c-;>SPw7.6hn3m`9^Xkn(r.qS[0;T%&Qc=+STRxX'q1BNk3&*eu2;&8q$&x>Q#Q7^Tf+6<(d%ZVmj2bDi%.3L2n+4W'$P" + "iDDG)g,r%+?,$@?uou5tSe2aN_AQU*'IAO" + "URQ##V^Fv-XFbGM7Fl(N<3DhLGF%q.1rC$#:T__&Pi68%0xi_&[qFJ(77j_&JWoF.V735&T,[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&n`kr-#GJcM6X;uM6X;uM(.a..^2TkL%oR(#" + ";u.T%fAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFY>>^8p,KKF.W]L29uLkLlu/+4T" + "w$)F./^n3+rlo+DB;5sIYGNk+i1t-69Jg--0pao7Sm#K)pdHW&;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4^Rh%Sflr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4" + "A1OY5EI0;6Ibgr6M$HS7Q<)58C5w,;WoA*#[%T*#`1g*#d=#+#hI5+#lUG+#pbY+#tnl+#x$),#&1;,#*=M,#.I`,#2Ur,#6b.-#;w[H#iQtA#m^0B#qjBB#uvTB##-hB#'9$C#+E6C#" + "/QHC#3^ZC#7jmC#;v)D#?,)4kMYD4lVu`4m`:&5niUA5@(A5BA1]PBB:xlBCC=2CDLXMCEUtiCf&0g2'tN?PGT4CPGT4CPGT4CPGT4CPGT4CPGT4CPGT4CP" + "GT4CPGT4CPGT4CPGT4CPGT4CPGT4CP-qekC`.9kEg^+F$kwViFJTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5o,^<-28ZI'O?;xp" + "O?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xp;7q-#lLYI:xvD=#"; + +static const char *GetDefaultCompressedFontDataTTFBase85() +{ + return proggy_clean_ttf_compressed_data_base85; +} + +#endif // #ifndef IMGUI_DISABLE diff --git a/extern/imgui/src/imgui_internal.h b/extern/imgui/src/imgui_internal.h new file mode 100644 index 0000000..f43b46b --- /dev/null +++ b/extern/imgui/src/imgui_internal.h @@ -0,0 +1,4642 @@ +// dear imgui, v1.89.9 +// (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. +// To implement maths operators for ImVec2 (disabled by default to not conflict with using IM_VEC2_CLASS_EXTRA with your own math types+operators), use: +/* +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui_internal.h" +*/ + +/* + +Index of this file: + +// [SECTION] Header mess +// [SECTION] Forward declarations +// [SECTION] Context pointer +// [SECTION] STB libraries includes +// [SECTION] Macros +// [SECTION] Generic helpers +// [SECTION] ImDrawList support +// [SECTION] Widgets support: flags, enums, data structures +// [SECTION] Inputs support +// [SECTION] Clipper support +// [SECTION] Navigation support +// [SECTION] Columns support +// [SECTION] Multi-select support +// [SECTION] Docking support +// [SECTION] Viewport support +// [SECTION] Settings support +// [SECTION] Localization support +// [SECTION] Metrics, Debug tools +// [SECTION] Generic context hooks +// [SECTION] ImGuiContext (main imgui context) +// [SECTION] ImGuiWindowTempData, ImGuiWindow +// [SECTION] Tab bar, Tab item support +// [SECTION] Table support +// [SECTION] ImGui internal API +// [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 // INT_MIN, INT_MAX +#include // sqrtf, fabsf, fmodf, powf, floorf, ceilf, cosf, sinf +#include // FILE*, sscanf +#include // NULL, malloc, free, qsort, atoi, atof + +// 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 +#include +#endif + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning( \ + disable : 4251) // class 'xxx' needs to have dll-interface to be used by clients of struct 'xxx' // when IMGUI_API is set to__declspec(dllexport) +#pragma warning(disable : 26812) // The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). [MSVC Static Analyzer) +#pragma warning(disable : 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6). +#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later +#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 +#if __has_warning("-Wunknown-warning-option") +#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored \ + "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants ok, for ImFloorSigned() +#pragma clang diagnostic ignored "-Wunused-function" // for stb_textedit.h +#pragma clang diagnostic ignored "-Wmissing-prototypes" // for stb_textedit.h +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#pragma clang diagnostic ignored "-Wdouble-promotion" +#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#pragma clang diagnostic ignored "-Wmissing-noreturn" // warning: function 'xxx' could be declared with attribute 'noreturn' +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#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 + +// 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 +#endif +#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 +//----------------------------------------------------------------------------- + +struct ImBitVector; // Store 1-bit per value +struct ImRect; // An axis-aligned rectangle (2 points) +struct ImDrawDataBuilder; // Helper to build a ImDrawData instance +struct ImDrawListSharedData; // Data shared between all ImDrawList instances +struct ImGuiColorMod; // Stacked color modifier, backup of modified data so we can restore it +struct ImGuiContext; // Main Dear ImGui context +struct ImGuiContextHook; // Hook for extensions like ImGuiTestEngine +struct ImGuiDataVarInfo; // Variable information (e.g. to avoid style variables from an enum) +struct ImGuiDataTypeInfo; // Type information associated to a ImGuiDataType enum +struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup() +struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box +struct ImGuiInputTextDeactivateData; // Short term storage to backup text of a deactivating InputText() while another is stealing active id +struct ImGuiLastItemData; // Status storage for last submitted items +struct ImGuiLocEntry; // A localization entry. +struct ImGuiMenuColumns; // Simple column measurement, currently used for MenuItem() only +struct ImGuiNavItemData; // Result of a gamepad/keyboard directional navigation move query result +struct ImGuiNavTreeNodeData; // Temporary storage for last TreeNode() being a Left arrow landing candidate. +struct ImGuiMetricsConfig; // Storage for ShowMetricsWindow() and DebugNodeXXX() functions +struct ImGuiNextWindowData; // Storage for SetNextWindow** functions +struct ImGuiNextItemData; // Storage for SetNextItem** functions +struct ImGuiOldColumnData; // Storage data for a single column for legacy Columns() api +struct ImGuiOldColumns; // Storage data for a columns set for legacy Columns() api +struct ImGuiPopupData; // Storage for current popup stack +struct ImGuiSettingsHandler; // Storage for one type registered in the .ini file +struct ImGuiStackSizes; // Storage of stack sizes for debugging/asserting +struct ImGuiStyleMod; // Stacked style modifier, backup of modified data so we can restore it +struct ImGuiTabBar; // Storage for a tab bar +struct ImGuiTabItem; // Storage for a tab item (within a tab bar) +struct ImGuiTable; // Storage for a table +struct ImGuiTableColumn; // Storage for one column of a table +struct ImGuiTableInstanceData; // Storage for one instance of a same table +struct ImGuiTableTempData; // Temporary storage for one table (one per table in the stack), shared between tables. +struct ImGuiTableSettings; // Storage for a table .ini settings +struct ImGuiTableColumnsSettings; // Storage for a column .ini settings +struct ImGuiWindow; // Storage for one window +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 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 +typedef int ImGuiFocusRequestFlags; // -> enum ImGuiFocusRequestFlags_ // Flags: for FocusWindow(); +typedef int ImGuiInputFlags; // -> enum ImGuiInputFlags_ // Flags: for IsKeyPressed(), IsMouseClicked(), SetKeyOwner(), SetItemKeyOwner() etc. +typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag(), g.LastItemData.InFlags +typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for g.LastItemData.StatusFlags +typedef int ImGuiOldColumnFlags; // -> enum ImGuiOldColumnFlags_ // Flags: for BeginColumns() +typedef int ImGuiNavHighlightFlags; // -> enum ImGuiNavHighlightFlags_ // Flags: for RenderNavHighlight() +typedef int ImGuiNavMoveFlags; // -> enum ImGuiNavMoveFlags_ // Flags: for navigation requests +typedef int ImGuiNextItemDataFlags; // -> enum ImGuiNextItemDataFlags_ // Flags: for SetNextItemXXX() functions +typedef int ImGuiNextWindowDataFlags; // -> enum ImGuiNextWindowDataFlags_// Flags: for SetNextWindowXXX() functions +typedef int ImGuiScrollFlags; // -> enum ImGuiScrollFlags_ // Flags: for ScrollToItem() and navigation requests +typedef int ImGuiSeparatorFlags; // -> enum ImGuiSeparatorFlags_ // Flags: for SeparatorEx() +typedef int ImGuiTextFlags; // -> enum ImGuiTextFlags_ // Flags: for TextEx() +typedef int ImGuiTooltipFlags; // -> enum ImGuiTooltipFlags_ // Flags: for BeginTooltipEx() + +typedef void (*ImGuiErrorLogCallback)(void *user_data, const char *fmt, ...); + +//----------------------------------------------------------------------------- +// [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] STB libraries includes +//------------------------------------------------------------------------- + +namespace ImStb +{ + +#undef STB_TEXTEDIT_STRING +#undef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_STRING ImGuiInputTextState +#define STB_TEXTEDIT_CHARTYPE ImWchar +#define STB_TEXTEDIT_GETWIDTH_NEWLINE (-1.0f) +#define STB_TEXTEDIT_UNDOSTATECOUNT 99 +#define STB_TEXTEDIT_UNDOCHARCOUNT 999 +#include + +} // namespace ImStb + +//----------------------------------------------------------------------------- +// [SECTION] Macros +//----------------------------------------------------------------------------- + +// 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 +#ifndef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS +#define IMGUI_DEBUG_PRINTF(_FMT, ...) printf(_FMT, __VA_ARGS__) +#else +#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. +#ifndef IMGUI_DISABLE_DEBUG_TOOLS +#define IMGUI_DEBUG_LOG(...) ImGui::DebugLog(__VA_ARGS__) +#else +#define IMGUI_DEBUG_LOG(...) ((void)0) +#endif +#define IMGUI_DEBUG_LOG_ACTIVEID(...) \ + do { \ + if (g.DebugLogFlags & ImGuiDebugLogFlags_EventActiveId) \ + IMGUI_DEBUG_LOG(__VA_ARGS__); \ + } while (0) +#define IMGUI_DEBUG_LOG_FOCUS(...) \ + do { \ + if (g.DebugLogFlags & ImGuiDebugLogFlags_EventFocus) \ + IMGUI_DEBUG_LOG(__VA_ARGS__); \ + } while (0) +#define IMGUI_DEBUG_LOG_POPUP(...) \ + do { \ + if (g.DebugLogFlags & ImGuiDebugLogFlags_EventPopup) \ + IMGUI_DEBUG_LOG(__VA_ARGS__); \ + } while (0) +#define IMGUI_DEBUG_LOG_NAV(...) \ + do { \ + if (g.DebugLogFlags & ImGuiDebugLogFlags_EventNav) \ + IMGUI_DEBUG_LOG(__VA_ARGS__); \ + } while (0) +#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) + +// 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 +#ifdef IMGUI_DEBUG_PARANOID +#define IM_ASSERT_PARANOID(_EXPR) IM_ASSERT(_EXPR) +#else +#define IM_ASSERT_PARANOID(_EXPR) +#endif + +// Error handling +// Down the line in some frameworks/languages we would like to have a way to redirect those to the programmer and recover from more faults. +#ifndef IM_ASSERT_USER_ERROR +#define IM_ASSERT_USER_ERROR(_EXP, _MSG) IM_ASSERT((_EXP) && _MSG) // Recoverable User Error +#endif + +// Misc Macros +#define IM_PI 3.14159265358979323846f +#ifdef _WIN32 +#define IM_NEWLINE "\r\n" // Play it nice with Windows users (Update: since 2018-05, Notepad finally appears to support Unix-style carriage returns!) +#else +#define IM_NEWLINE "\n" +#endif +#ifndef IM_TABSIZE // Until we move this to runtime and/or add proper tab support, at least allow users to compile-time override +#define IM_TABSIZE (4) +#endif +#define IM_MEMALIGN(_OFF, _ALIGN) \ + (((_OFF) + ((_ALIGN)-1)) & ~((_ALIGN)-1)) // Memory align e.g. IM_ALIGN(0,4)=0, IM_ALIGN(1,4)=4, IM_ALIGN(4,4)=4, IM_ALIGN(5,4)=8 +#define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL) >= 0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose +#define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 +#define IM_FLOOR(_VAL) ((float)(int)(_VAL)) // ImFloor() is not inlined in MSVC debug builds +#define IM_ROUND(_VAL) ((float)(int)((_VAL) + 0.5f)) // +#define IM_STRINGIFY_HELPER(_X) #_X +#define IM_STRINGIFY(_X) IM_STRINGIFY_HELPER(_X) // Preprocessor idiom to stringify e.g. an integer. + +// 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. +#ifndef IM_DEBUG_BREAK +#if defined(_MSC_VER) +#define IM_DEBUG_BREAK() __debugbreak() +#elif defined(__clang__) +#define IM_DEBUG_BREAK() __builtin_debugtrap() +#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +#define IM_DEBUG_BREAK() __asm__ volatile("int $0x03") +#elif defined(__GNUC__) && defined(__thumb__) +#define IM_DEBUG_BREAK() __asm__ volatile(".inst 0xde01") +#elif defined(__GNUC__) && defined(__arm__) && !defined(__thumb__) +#define IM_DEBUG_BREAK() __asm__ volatile(".inst 0xe7f001f0"); +#else +#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 + +//----------------------------------------------------------------------------- +// [SECTION] Generic helpers +// Note that the ImXXX helpers functions are lower-level than ImGui functions. +// ImGui functions or the ImGui context are never called/used from other ImXXX functions. +//----------------------------------------------------------------------------- +// - Helpers: Hashing +// - Helpers: Sorting +// - Helpers: Bit manipulation +// - Helpers: String +// - Helpers: Formatting +// - Helpers: UTF-8 <> wchar conversions +// - Helpers: ImVec2/ImVec4 operators +// - Helpers: Maths +// - Helpers: Geometry +// - Helper: ImVec1 +// - Helper: ImVec2ih +// - Helper: ImRect +// - Helper: ImBitArray +// - Helper: ImBitVector +// - Helper: ImSpan<>, ImSpanAllocator<> +// - Helper: ImPool<> +// - Helper: ImChunkStream<> +// - Helper: ImGuiTextIndex +//----------------------------------------------------------------------------- + +// 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); +} +#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; +} + +// Helpers: String +IMGUI_API int ImStricmp(const char *str1, const char *str2); +IMGUI_API int ImStrnicmp(const char *str1, const char *str2, size_t count); +IMGUI_API void ImStrncpy(char *dst, const char *src, size_t count); +IMGUI_API char *ImStrdup(const char *str); +IMGUI_API char *ImStrdupcpy(char *dst, size_t *p_dst_size, const char *str); +IMGUI_API const char *ImStrchrRange(const char *str_begin, const char *str_end, char c); +IMGUI_API int ImStrlenW(const ImWchar *str); +IMGUI_API const char *ImStreolRange(const char *str, const char *str_end); // End end-of-line +IMGUI_API const ImWchar *ImStrbolW(const ImWchar *buf_mid_line, const ImWchar *buf_begin); // Find beginning-of-line +IMGUI_API const char *ImStristr(const char *haystack, const char *haystack_end, const char *needle, const char *needle_end); +IMGUI_API void ImStrTrimBlanks(char *str); +IMGUI_API const char *ImStrSkipBlank(const char *str); +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; +} +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); +IMGUI_API void ImFormatStringToTempBuffer(const char **out_buf, const char **out_buf_end, const char *fmt, ...) IM_FMTARGS(3); +IMGUI_API void ImFormatStringToTempBufferV(const char **out_buf, const char **out_buf_end, const char *fmt, va_list args) IM_FMTLIST(3); +IMGUI_API const char *ImParseFormatFindStart(const char *format); +IMGUI_API const char *ImParseFormatFindEnd(const char *format); +IMGUI_API const char *ImParseFormatTrimDecorations(const char *format, char *buf, size_t buf_size); +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 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 +IMGUI_API int ImTextCountCharsFromUtf8(const char *in_text, const char *in_text_end); // return number of UTF-8 code-points (NOT bytes count) +IMGUI_API int ImTextCountUtf8BytesFromChar(const char *in_text, const char *in_text_end); // return number of bytes to express one char in UTF-8 +IMGUI_API int ImTextCountUtf8BytesFromStr(const ImWchar *in_text, const ImWchar *in_text_end); // return number of bytes to express string in UTF-8 + +// 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; +} +#endif +#ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS +typedef FILE *ImFileHandle; +IMGUI_API ImFileHandle ImFileOpen(const char *filename, const char *mode); +IMGUI_API bool ImFileClose(ImFileHandle file); +IMGUI_API ImU64 ImFileGetSize(ImFileHandle file); +IMGUI_API ImU64 ImFileRead(void *data, ImU64 size, ImU64 count, ImFileHandle file); +IMGUI_API ImU64 ImFileWrite(const void *data, ImU64 size, ImU64 count, ImFileHandle file); +#else +#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) +#ifndef IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS +#define ImFabs(X) fabsf(X) +#define ImSqrt(X) sqrtf(X) +#define ImFmod(X, Y) fmodf((X), (Y)) +#define ImCos(X) cosf(X) +#define ImSin(X) sinf(X) +#define ImAcos(X) acosf(X) +#define ImAtan2(Y, X) atan2f((Y), (X)) +#define ImAtof(STR) atof(STR) +// #define ImFloorStd(X) floorf(X) // We use our own, see ImFloor() and ImFloorSigned() +#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; +} +#ifdef IMGUI_ENABLE_SSE +static 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); +} +#endif +static 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; +} +// - 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, 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 ImFloor(float f) +{ + return (float)(int)(f); +} +static inline float ImFloorSigned(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((float)(int)(v.x), (float)(int)(v.y)); +} +static inline ImVec2 ImFloorSigned(const ImVec2 &v) +{ + return ImVec2(ImFloorSigned(v.x), ImFloorSigned(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 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; +} +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 +IMGUI_API ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2 &p1, + const ImVec2 &p2, + const ImVec2 &p3, + const ImVec2 &p4, + const ImVec2 &p, + float tess_tol); // For auto-tessellated curves you can use tess_tol = style.CurveTessellationTol +IMGUI_API ImVec2 ImBezierQuadraticCalc(const ImVec2 &p1, const ImVec2 &p2, const ImVec2 &p3, float t); +IMGUI_API ImVec2 ImLineClosestPoint(const ImVec2 &a, const ImVec2 &b, const ImVec2 &p); +IMGUI_API bool ImTriangleContainsPoint(const ImVec2 &a, const ImVec2 &b, const ImVec2 &c, const ImVec2 &p); +IMGUI_API ImVec2 ImTriangleClosestPoint(const ImVec2 &a, const ImVec2 &b, const ImVec2 &c, const ImVec2 &p); +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; +} + +// 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 +struct ImVec1 { + float x; + constexpr ImVec1() + : x(0.0f) + { + } + constexpr ImVec1(float _x) + : x(_x) + { + } +}; + +// Helper: ImVec2ih (2D vector, half-size integer, for long-term packed storage) +struct ImVec2ih { + short x, y; + constexpr ImVec2ih() + : x(0) + , y(0) + { + } + 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; + } + float GetHeight() const + { + return Max.y - Min.y; + } + float GetArea() const + { + return (Max.x - Min.x) * (Max.y - Min.y); + } + ImVec2 GetTL() const + { + return Min; + } // Top-left + ImVec2 GetTR() const + { + return ImVec2(Max.x, Min.y); + } // Top-right + ImVec2 GetBL() const + { + return ImVec2(Min.x, Max.y); + } // Bottom-left + ImVec2 GetBR() const + { + return Max; + } // Bottom-right + bool Contains(const ImVec2 &p) const + { + return p.x >= Min.x && p.y >= Min.y && p.x < Max.x && p.y < Max.y; + } + bool Contains(const ImRect &r) const + { + return r.Min.x >= Min.x && r.Min.y >= Min.y && r.Max.x <= Max.x && r.Max.y <= Max.y; + } + bool Overlaps(const ImRect &r) const + { + return r.Min.y < Max.y && r.Max.y > Min.y && r.Min.x < Max.x && r.Max.x > Min.x; + } + void Add(const ImVec2 &p) + { + if (Min.x > p.x) + Min.x = p.x; + if (Min.y > p.y) + Min.y = p.y; + if (Max.x < p.x) + Max.x = p.x; + if (Max.y < p.y) + Max.y = p.y; + } + void Add(const ImRect &r) + { + if (Min.x > r.Min.x) + Min.x = r.Min.x; + if (Min.y > r.Min.y) + Min.y = r.Min.y; + if (Max.x < r.Max.x) + Max.x = r.Max.x; + if (Max.y < r.Max.y) + Max.y = r.Max.y; + } + void Expand(const float amount) + { + Min.x -= amount; + Min.y -= amount; + Max.x += amount; + Max.y += amount; + } + void Expand(const ImVec2 &amount) + { + Min.x -= amount.x; + Min.y -= amount.y; + Max.x += amount.x; + Max.y += amount.y; + } + void Translate(const ImVec2 &d) + { + Min.x += d.x; + Min.y += d.y; + Max.x += d.x; + Max.y += d.y; + } + void TranslateX(float dx) + { + Min.x += dx; + Max.x += dx; + } + void TranslateY(float dy) + { + Min.y += dy; + Max.y += dy; + } + void ClipWith(const ImRect &r) + { + Min = ImMax(Min, r.Min); + Max = ImMin(Max, r.Max); + } // Simple version, may lead to an inverted rectangle, which is fine for Contains/Overlaps test but not for display. + void ClipWithFull(const ImRect &r) + { + Min = ImClamp(Min, r.Min, r.Max); + Max = ImClamp(Max, r.Min, r.Max); + } // Full version, ensure both points are fully clipped. + void Floor() + { + Min.x = IM_FLOOR(Min.x); + Min.y = IM_FLOOR(Min.y); + Max.x = IM_FLOOR(Max.x); + Max.y = IM_FLOOR(Max.y); + } + 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! +inline size_t ImBitArrayGetStorageSizeInBytes(int bitcount) +{ + return (size_t)((bitcount + 31) >> 5) << 2; +} +inline void ImBitArrayClearAllBits(ImU32 *arr, int bitcount) +{ + memset(arr, 0, ImBitArrayGetStorageSizeInBytes(bitcount)); +} +inline bool ImBitArrayTestBit(const ImU32 *arr, int n) +{ + ImU32 mask = (ImU32)1 << (n & 31); + return (arr[n >> 5] & mask) != 0; +} +inline void ImBitArrayClearBit(ImU32 *arr, int n) +{ + ImU32 mask = (ImU32)1 << (n & 31); + arr[n >> 5] &= ~mask; +} +inline void ImBitArraySetBit(ImU32 *arr, int n) +{ + ImU32 mask = (ImU32)1 << (n & 31); + arr[n >> 5] |= mask; +} +inline void ImBitArraySetBitRange(ImU32 *arr, int n, int n2) // Works on range [n..n2) +{ + n2--; + while (n <= n2) { + int a_mod = (n & 31); + int b_mod = (n2 > (n | 31) ? 31 : (n2 & 31)) + 1; + ImU32 mask = (ImU32)(((ImU64)1 << b_mod) - 1) & ~(ImU32)(((ImU64)1 << a_mod) - 1); + arr[n >> 5] |= mask; + 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 +struct ImBitArray { + ImU32 Storage[(BITCOUNT + 31) >> 5]; + ImBitArray() + { + ClearAllBits(); + } + void ClearAllBits() + { + memset(Storage, 0, sizeof(Storage)); + } + void SetAllBits() + { + memset(Storage, 255, sizeof(Storage)); + } + bool TestBit(int n) const + { + n += OFFSET; + IM_ASSERT(n >= 0 && n < BITCOUNT); + return IM_BITARRAY_TESTBIT(Storage, n); + } + void SetBit(int n) + { + n += OFFSET; + IM_ASSERT(n >= 0 && n < BITCOUNT); + ImBitArraySetBit(Storage, n); + } + void ClearBit(int n) + { + n += OFFSET; + IM_ASSERT(n >= 0 && n < BITCOUNT); + ImBitArrayClearBit(Storage, n); + } + 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 { + ImVector Storage; + void Create(int sz) + { + Storage.resize((sz + 31) >> 5); + memset(Storage.Data, 0, (size_t)Storage.Size * sizeof(Storage.Data[0])); + } + void Clear() + { + Storage.clear(); + } + bool TestBit(int n) const + { + IM_ASSERT(n < (Storage.Size << 5)); + return IM_BITARRAY_TESTBIT(Storage.Data, n); + } + void SetBit(int n) + { + IM_ASSERT(n < (Storage.Size << 5)); + ImBitArraySetBit(Storage.Data, n); + } + 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 +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. +template +struct ImSpanAllocator { + char *BasePtr; + int CurrOff; + 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; + } + inline void SetArenaBasePtr(void *base_ptr) + { + BasePtr = (char *)base_ptr; + } + inline void *GetSpanPtrBegin(int n) + { + IM_ASSERT(n >= 0 && n < CHUNKS && CurrIdx == CHUNKS); + return (void *)(BasePtr + Offsets[n]); + } + inline void *GetSpanPtrEnd(int n) + { + IM_ASSERT(n >= 0 && n < CHUNKS && CurrIdx == CHUNKS); + return (void *)(BasePtr + Offsets[n] + Sizes[n]); + } + template + inline void GetSpan(int n, ImSpan *span) + { + span->set((T *)GetSpanPtrBegin(n), (T *)GetSpanPtrEnd(n)); + } +}; + +// 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. +typedef int ImPoolIdx; +template +struct ImPool { + ImVector Buf; // Contiguous data + 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; + } + T *GetByIndex(ImPoolIdx n) + { + return &Buf[n]; + } + ImPoolIdx GetIndex(const T *p) const + { + IM_ASSERT(p >= Buf.Data && p < Buf.Data + Buf.Size); + return (ImPoolIdx)(p - Buf.Data); + } + T *GetOrAddByKey(ImGuiID key) + { + int *p_idx = Map.GetIntRef(key, -1); + if (*p_idx != -1) + return &Buf[*p_idx]; + *p_idx = FreeIdx; + return Add(); + } + bool Contains(const T *p) const + { + return (p >= Buf.Data && p < Buf.Data + Buf.Size); + } + void Clear() + { + for (int n = 0; n < Map.Data.Size; n++) { + int idx = Map.Data[n].val_i; + if (idx != -1) + Buf[idx].~T(); + } + Map.Clear(); + Buf.clear(); + FreeIdx = AliveCount = 0; + } + T *Add() + { + int idx = FreeIdx; + if (idx == Buf.Size) { + Buf.resize(Buf.Size + 1); + FreeIdx++; + } else { + FreeIdx = *(int *)&Buf[idx]; + } + IM_PLACEMENT_NEW(&Buf[idx]) T(); + AliveCount++; + return &Buf[idx]; + } + 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) + int GetBufSize() const + { + return Buf.Size; + } + 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); + } +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + int GetSize() + { + return GetMapSize(); + } // For ImPlot: should use GetMapSize() from (IMGUI_VERSION_NUM >= 18304) +#endif +}; + +// 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. +// We store the chunk size first, and align the final size on 4 bytes boundaries. +// The tedious/zealous amount of casting is to avoid -Wcast-align warnings. +template +struct ImChunkStream { + ImVector Buf; + + void clear() + { + Buf.clear(); + } + bool empty() const + { + return Buf.Size == 0; + } + int size() const + { + return Buf.Size; + } + T *alloc_chunk(size_t sz) + { + size_t HDR_SZ = 4; + sz = IM_MEMALIGN(HDR_SZ + sz, 4u); + int off = Buf.Size; + Buf.resize(off + (int)sz); + ((int *)(void *)(Buf.Data + off))[0] = (int)sz; + return (T *)(void *)(Buf.Data + off + (int)HDR_SZ); + } + T *begin() + { + size_t HDR_SZ = 4; + if (!Buf.Data) + return NULL; + return (T *)(void *)(Buf.Data + HDR_SZ); + } + T *next_chunk(T *p) + { + size_t HDR_SZ = 4; + IM_ASSERT(p >= begin() && p < end()); + p = (T *)(void *)((char *)(void *)p + chunk_size(p)); + if (p == (T *)(void *)((char *)end() + HDR_SZ)) + return (T *)0; + IM_ASSERT(p < end()); + return p; + } + int chunk_size(const T *p) + { + return ((const int *)p)[-1]; + } + T *end() + { + return (T *)(void *)(Buf.Data + Buf.Size); + } + int offset_from_ptr(const T *p) + { + IM_ASSERT(p >= begin() && p < end()); + const ptrdiff_t off = (const char *)p - Buf.Data; + return (int)off; + } + 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); +}; + +//----------------------------------------------------------------------------- +// [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: +// N = ceil ( pi / acos(1 - error / r) ) where r > 0, error <= r +// Our equation is significantly simpler that one in the post thanks for choosing segment that is +// perpendicular to X axis. Follow steps in the article from this starting condition and you will +// will get this result. +// +// Rendering circles with an odd number of segments, while mathematically correct will produce +// asymmetrical results on the raster grid. Therefore we're rounding N to next even number (7->8, 8->8, 9->10 etc.) +#define IM_ROUNDUP_TO_EVEN(_V) ((((_V) + 1) / 2) * 2) +#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 +// You may want to create your own instance of this if you want to use 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 + ImFont *Font; // Current/default font (optional, for simplified AddText overload) + float FontSize; // Current/default font size (optional, for simplified AddText overload) + float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() + float CircleSegmentMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc + ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() + ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) + + // [Internal] Temp write buffer + ImVector TempBuffer; + + // [Internal] 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) + const ImVec4 *TexUvLines; // UV of anti-aliased lines in the atlas + + 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)); + } +}; + +//----------------------------------------------------------------------------- +// [SECTION] Widgets support: flags, enums, data structures +//----------------------------------------------------------------------------- + +// Flags used by upcoming items +// - input: PushItemFlag() manipulates g.CurrentItemFlags, ItemAdd() calls may add extra flags. +// - output: stored in g.LastItemData.InFlags +// Current window shared by all windows. +// This is going to be exposed in imgui.h when stabilized enough. +enum ImGuiItemFlags_ { + // Controlled by user + ImGuiItemFlags_None = 0, + ImGuiItemFlags_NoTabStop = 1 << 0, // false // Disable keyboard tabbing. This is a "lighter" version of ImGuiItemFlags_NoNav. + ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings. + ImGuiItemFlags_Disabled = + 1 << 2, // false // Disable interactions but doesn't affect visuals. See BeginDisabled()/EndDisabled(). See github.com/ocornut/imgui/issues/211 + ImGuiItemFlags_NoNav = 1 << 3, // false // Disable any form of focusing (keyboard/gamepad directional navigation and SetKeyboardFocusHere() calls) + ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false // Disable item being a candidate for default focus (e.g. used by title bar items) + ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // Disable MenuItem/Selectable() automatically closing their popup window + ImGuiItemFlags_MixedValue = 1 << 6, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently + // only supported by Checkbox() (later should support all sorts of widgets) + ImGuiItemFlags_ReadOnly = 1 << 7, // false // [ALPHA] Allow hovering interactions but underlying value is not changed. + ImGuiItemFlags_NoWindowHoverableCheck = 1 << 8, // false // Disable hoverable check in ItemHoverable() + ImGuiItemFlags_AllowOverlap = 1 << 9, // false // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame. + + // Controlled by widget code + ImGuiItemFlags_Inputable = 1 << 10, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before + // it becomes a generic feature. +}; + +// Status flags for an already submitted item +// - output: stored in g.LastItemData.StatusFlags +enum ImGuiItemStatusFlags_ { + ImGuiItemStatusFlags_None = 0, + ImGuiItemStatusFlags_HoveredRect = 1 << 0, // Mouse position is within item rectangle (does NOT mean that the window is in correct z-order and can be + // hovered!, this is only one part of the most-common IsItemHovered test) + ImGuiItemStatusFlags_HasDisplayRect = 1 << 1, // g.LastItemData.DisplayRect is valid + ImGuiItemStatusFlags_Edited = 1 << 2, // Value exposed by item was edited in the current frame (should match the bool return value of most widgets) + ImGuiItemStatusFlags_ToggledSelection = 1 << 3, // Set when Selectable(), TreeNode() reports toggling a selection. We can't report "Selected", only state + // changes, in order to easily handle clipping with less issues. + ImGuiItemStatusFlags_ToggledOpen = 1 << 4, // Set when TreeNode() reports toggling their open state. + ImGuiItemStatusFlags_HasDeactivated = 1 << 5, // Set if the widget/group is able to provide data for the ImGuiItemStatusFlags_Deactivated flag. + ImGuiItemStatusFlags_Deactivated = 1 << 6, // Only valid if ImGuiItemStatusFlags_HasDeactivated is set. + ImGuiItemStatusFlags_HoveredWindow = 1 << 7, // Override the HoveredWindow test to allow cross-window hover testing. + ImGuiItemStatusFlags_FocusedByTabbing = 1 << 8, // Set when the Focusable item just got focused by Tabbing (FIXME: to be removed soon) + ImGuiItemStatusFlags_Visible = 1 << 9, // [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()). + +// Additional status + semantic for ImGuiTestEngine +#ifdef IMGUI_ENABLE_TEST_ENGINE + ImGuiItemStatusFlags_Openable = 1 << 20, // Item is an openable (e.g. TreeNode) + ImGuiItemStatusFlags_Opened = 1 << 21, // Opened status + ImGuiItemStatusFlags_Checkable = 1 << 22, // Item is a checkable (e.g. CheckBox, MenuItem) + ImGuiItemStatusFlags_Checked = 1 << 23, // Checked status + ImGuiItemStatusFlags_Inputable = 1 << 24, // Item is a text-inputable (e.g. InputText, SliderXXX, DragXXX) +#endif +}; + +// Extend ImGuiHoveredFlags_ +enum ImGuiHoveredFlagsPrivate_ { + ImGuiHoveredFlags_DelayMask_ = ImGuiHoveredFlags_DelayNone | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay, + ImGuiHoveredFlags_AllowedMaskForIsWindowHovered = ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_AnyWindow + | ImGuiHoveredFlags_NoPopupHierarchy | 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_ { + // [Internal] + ImGuiInputTextFlags_Multiline = 1 << 26, // For internal use by InputTextMultiline() + ImGuiInputTextFlags_NoMarkEdited = 1 << 27, // For internal use by functions using InputText() before reformatting data + ImGuiInputTextFlags_MergedItem = 1 << 28, // For internal use by TempInputText(), will skip calling ItemAdd(). Require bounding-box to strictly match. +}; + +// Extend ImGuiButtonFlags_ +enum ImGuiButtonFlagsPrivate_ { + ImGuiButtonFlags_PressedOnClick = 1 << 4, // return true on click (mouse down event) + ImGuiButtonFlags_PressedOnClickRelease = 1 << 5, // [Default] return true on click + release on same item <-- this is what the majority of Button are using + ImGuiButtonFlags_PressedOnClickReleaseAnywhere = 1 << 6, // return true on click + release even if the release event is not done while hovering the item + ImGuiButtonFlags_PressedOnRelease = 1 << 7, // return true on release (default requires click+release) + ImGuiButtonFlags_PressedOnDoubleClick = 1 << 8, // return true on double-click (default requires click+release) + ImGuiButtonFlags_PressedOnDragDropHold = + 1 << 9, // return true when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers) + ImGuiButtonFlags_Repeat = 1 << 10, // hold to repeat + ImGuiButtonFlags_FlattenChildren = 1 << 11, // allow interactions even if a child window is overlapping + ImGuiButtonFlags_AllowOverlap = 1 << 12, // require previous frame HoveredId to either match id or be null before being usable. + ImGuiButtonFlags_DontClosePopups = 1 << 13, // disable automatically closing parent popup on press // [UNUSED] + // ImGuiButtonFlags_Disabled = 1 << 14, // disable interactions -> use BeginDisabled() or ImGuiItemFlags_Disabled + ImGuiButtonFlags_AlignTextBaseLine = 1 << 15, // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by + // SmallButton(), not possible currently because of DC.CursorPosPrevLine + ImGuiButtonFlags_NoKeyModifiers = 1 << 16, // disable mouse interaction if a key modifier is held + ImGuiButtonFlags_NoHoldingActiveId = 1 << 17, // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only) + ImGuiButtonFlags_NoNavFocus = + 1 << 18, // don't override navigation focus when activated (FIXME: this is essentially used everytime an item uses ImGuiItemFlags_NoNav, but because + // legacy specs don't requires LastItemData to be set ButtonBehavior(), we can't poll g.LastItemData.InFlags) + 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_PressedOnMask_ = ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere + | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick | ImGuiButtonFlags_PressedOnDragDropHold, + ImGuiButtonFlags_PressedOnDefault_ = ImGuiButtonFlags_PressedOnClickRelease, +}; + +// 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_ { + // NB: need to be in sync with last value of ImGuiSelectableFlags_ + ImGuiSelectableFlags_NoHoldingActiveID = 1 << 20, + ImGuiSelectableFlags_SelectOnNav = 1 << 21, // (WIP) Auto-select when moved into. This is not exposed in public API as to handle multi-select and modifiers + // we will need user to explicitly control focus scope. May be replaced with a BeginSelection() API. + ImGuiSelectableFlags_SelectOnClick = 1 << 22, // Override button behavior to react on Click (default is Click+Release) + ImGuiSelectableFlags_SelectOnRelease = 1 << 23, // Override button behavior to react on Release (default is Click+Release) + ImGuiSelectableFlags_SpanAvailWidth = 1 + << 24, // Span all avail width even if we declared less for layout purpose. FIXME: We may be able to remove this (added in 6251d379, 2bcafc86 for menus) + ImGuiSelectableFlags_SetNavIdOnHover = 1 << 25, // Set Nav/Focus ID on mouse hover (used by MenuItem) + 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_ClipLabelForTrailingButton = 1 << 20, + ImGuiTreeNodeFlags_UpsideDownArrow = 1 << 21, // (FIXME-WIP) Turn Down arrow into an Up arrow, but reversed trees (#6517) +}; + +enum ImGuiSeparatorFlags_ { + ImGuiSeparatorFlags_None = 0, + ImGuiSeparatorFlags_Horizontal = 1 << 0, // Axis default to current layout type, so generally Horizontal unless e.g. in a menu bar + 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. +enum ImGuiFocusRequestFlags_ { + ImGuiFocusRequestFlags_None = 0, + 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_ { ImGuiLayoutType_Horizontal = 0, ImGuiLayoutType_Vertical = 1 }; + +enum ImGuiLogType { + ImGuiLogType_None = 0, + ImGuiLogType_TTY, + ImGuiLogType_File, + ImGuiLogType_Buffer, + ImGuiLogType_Clipboard, +}; + +// X/Y enums are fixed to 0/1 so they may be used to index ImVec2 +enum ImGuiAxis { ImGuiAxis_None = -1, ImGuiAxis_X = 0, ImGuiAxis_Y = 1 }; + +enum ImGuiPlotType { + ImGuiPlotType_Lines, + ImGuiPlotType_Histogram, +}; + +enum ImGuiPopupPositionPolicy { + ImGuiPopupPositionPolicy_Default, + ImGuiPopupPositionPolicy_ComboBox, + ImGuiPopupPositionPolicy_Tooltip, +}; + +struct ImGuiDataVarInfo { + ImGuiDataType Type; + ImU32 Count; // 1+ + ImU32 Offset; // Offset in parent structure + void *GetVarPtr(void *parent) const + { + return (void *)((unsigned char *)parent + Offset); + } +}; + +struct ImGuiDataTypeTempStorage { + ImU8 Data[8]; // Can fit any data up to ImGuiDataType_COUNT +}; + +// Type information associated to one ImGuiDataType. Retrieve with DataTypeGetInfo(). +struct ImGuiDataTypeInfo { + size_t Size; // Size in bytes + const char *Name; // Short descriptive name for the type, for debugging + const char *PrintFmt; // Default printf format for the type + const char *ScanFmt; // Default scanf format for the type +}; + +// Extend ImGuiDataType_ +enum ImGuiDataTypePrivate_ { + ImGuiDataType_String = ImGuiDataType_COUNT + 1, + ImGuiDataType_Pointer, + ImGuiDataType_ID, +}; + +// 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 { + ImGuiStyleVar VarIdx; + union { + int BackupInt[2]; + float BackupFloat[2]; + }; + ImGuiStyleMod(ImGuiStyleVar idx, int v) + { + VarIdx = idx; + BackupInt[0] = v; + } + 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; + } +}; + +// Storage data for BeginComboPreview()/EndComboPreview() +struct IMGUI_API ImGuiComboPreviewData { + ImRect PreviewRect; + ImVec2 BackupCursorPos; + ImVec2 BackupCursorMaxPos; + ImVec2 BackupCursorPosPrevLine; + float BackupPrevLineTextBaseOffset; + ImGuiLayoutType BackupLayout; + + ImGuiComboPreviewData() + { + memset(this, 0, sizeof(*this)); + } +}; + +// Stacked storage data for BeginGroup()/EndGroup() +struct IMGUI_API ImGuiGroupData { + ImGuiID WindowID; + ImVec2 BackupCursorPos; + ImVec2 BackupCursorMaxPos; + ImVec1 BackupIndent; + ImVec1 BackupGroupOffset; + ImVec2 BackupCurrLineSize; + float BackupCurrLineTextBaseOffset; + ImGuiID BackupActiveIdIsAlive; + bool BackupActiveIdPreviousFrameIsAlive; + bool BackupHoveredIdIsAlive; + 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 { + ImU32 TotalWidth; + ImU32 NextTotalWidth; + ImU16 Spacing; + ImU16 OffsetIcon; // Always zero for now + ImU16 OffsetLabel; // Offsets are locked in Update() + 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(); + } +}; +// Internal state of the currently focused/edited text input box +// For a given item ID, access with ImGui::GetInputTextState() +struct IMGUI_API ImGuiInputTextState { + ImGuiContext *Ctx; // parent UI context (needs to be set explicitly by parent). + ImGuiID ID; // widget id owning the text state + int CurLenW, CurLenA; // we need to maintain our buffer length in both UTF-8 and wchar format. UTF-8 length is valid even if TextA is not. + ImVector TextW; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. + ImVector TextA; // temporary UTF8 buffer for callbacks and other operations. this is not updated in every code-path! size=capacity. + ImVector InitialTextA; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) + bool TextAIsValid; // temporary UTF8 buffer is not initially valid before we make the widget active (until then we pull the data from user argument) + int BufCapacityA; // end-user buffer capacity + float ScrollX; // horizontal scrolling/offset + ImStb::STB_TexteditState Stb; // state for stb_textedit.h + float CursorAnim; // timer for cursor blink, reset on every user action so the cursor reappears immediately + bool CursorFollow; // set when we want scrolling to follow the current cursor position (not always!) + bool SelectedAllMouseLock; // after a double-click to select all, we ignore further mouse drags to update selection + bool Edited; // edited this frame + ImGuiInputTextFlags Flags; // copy of InputText() flags. may be used to check if e.g. ImGuiInputTextFlags_Password is set. + + ImGuiInputTextState() + { + memset(this, 0, sizeof(*this)); + } + void ClearText() + { + CurLenW = CurLenA = 0; + TextW[0] = 0; + TextA[0] = 0; + CursorClamp(); + } + void ClearFreeMemory() + { + TextW.clear(); + TextA.clear(); + InitialTextA.clear(); + } + int GetUndoAvailCount() const + { + return Stb.undostate.undo_point; + } + int GetRedoAvailCount() const + { + return STB_TEXTEDIT_UNDOSTATECOUNT - Stb.undostate.redo_point; + } + void OnKeyPressed(int key); // Cannot be inline because we call in code in stb_textedit.h implementation + + // Cursor & Selection + void CursorAnimReset() + { + CursorAnim = -0.30f; + } // After a user-input the cursor stays on for a while without blinking + void CursorClamp() + { + Stb.cursor = ImMin(Stb.cursor, CurLenW); + Stb.select_start = ImMin(Stb.select_start, CurLenW); + Stb.select_end = ImMin(Stb.select_end, CurLenW); + } + bool HasSelection() const + { + return Stb.select_start != Stb.select_end; + } + void ClearSelection() + { + Stb.select_start = Stb.select_end = Stb.cursor; + } + int GetCursorPos() const + { + return Stb.cursor; + } + int GetSelectionStart() const + { + return Stb.select_start; + } + int GetSelectionEnd() const + { + return Stb.select_end; + } + void SelectAll() + { + Stb.select_start = 0; + Stb.cursor = Stb.select_end = CurLenW; + Stb.has_preferred_x = 0; + } +}; + +// Storage for current popup stack +struct ImGuiPopupData { + ImGuiID PopupId; // Set on OpenPopup() + ImGuiWindow *Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() + ImGuiWindow *BackupNavWindow; // Set on OpenPopup(), a NavWindow that will be restored on popup close + int ParentNavLayer; // Resolved on BeginPopup(). Actually a ImGuiNavLayer type (declared down below), initialized to -1 which is not part of an enum, but + // serves well-enough as "not any of layers" value + int OpenFrameCount; // Set on OpenPopup() + 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; + } +}; + +enum ImGuiNextWindowDataFlags_ { + ImGuiNextWindowDataFlags_None = 0, + ImGuiNextWindowDataFlags_HasPos = 1 << 0, + ImGuiNextWindowDataFlags_HasSize = 1 << 1, + ImGuiNextWindowDataFlags_HasContentSize = 1 << 2, + ImGuiNextWindowDataFlags_HasCollapsed = 1 << 3, + ImGuiNextWindowDataFlags_HasSizeConstraint = 1 << 4, + ImGuiNextWindowDataFlags_HasFocus = 1 << 5, + ImGuiNextWindowDataFlags_HasBgAlpha = 1 << 6, + ImGuiNextWindowDataFlags_HasScroll = 1 << 7, +}; + +// Storage for SetNexWindow** functions +struct ImGuiNextWindowData { + ImGuiNextWindowDataFlags Flags; + ImGuiCond PosCond; + ImGuiCond SizeCond; + ImGuiCond CollapsedCond; + ImVec2 PosVal; + ImVec2 PosPivotVal; + ImVec2 SizeVal; + ImVec2 ContentSizeVal; + ImVec2 ScrollVal; + bool CollapsedVal; + ImRect SizeConstraintRect; + ImGuiSizeCallback SizeCallback; + void *SizeCallbackUserData; + float BgAlphaVal; // Override background alpha + 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?) + + ImGuiNextWindowData() + { + memset(this, 0, sizeof(*this)); + } + inline void ClearFlags() + { + Flags = ImGuiNextWindowDataFlags_None; + } +}; + +enum ImGuiNextItemDataFlags_ { + ImGuiNextItemDataFlags_None = 0, + ImGuiNextItemDataFlags_HasWidth = 1 << 0, + ImGuiNextItemDataFlags_HasOpen = 1 << 1, +}; + +struct ImGuiNextItemData { + ImGuiNextItemDataFlags Flags; + ImGuiItemFlags ItemFlags; // Currently only tested/used for ImGuiItemFlags_AllowOverlap. + float Width; // Set by SetNextItemWidth() + ImGuiID FocusScopeId; // Set by SetNextItemMultiSelectData() (!= 0 signify value has been set, so it's an alternate version of HasSelectionData, we don't + // use Flags for this because they are cleared too early. This is mostly used for debugging) + ImGuiCond OpenCond; + bool OpenVal; // Set by SetNextItemOpen() + + ImGuiNextItemData() + { + memset(this, 0, sizeof(*this)); + } + inline void ClearFlags() + { + Flags = ImGuiNextItemDataFlags_None; + ItemFlags = ImGuiItemFlags_None; + } // Also cleared manually by ItemAdd()! +}; + +// Status storage for the last submitted item +struct ImGuiLastItemData { + ImGuiID ID; + ImGuiItemFlags InFlags; // See ImGuiItemFlags_ + ImGuiItemStatusFlags StatusFlags; // See ImGuiItemStatusFlags_ + ImRect Rect; // Full rectangle + ImRect NavRect; // Navigation scoring rectangle (not displayed) + ImRect DisplayRect; // Display rectangle (only if ImGuiItemStatusFlags_HasDisplayRect is set) + + ImGuiLastItemData() + { + memset(this, 0, sizeof(*this)); + } +}; + +// Store data emitted by TreeNode() for usage by TreePop() to implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere. +// This is the minimum amount of data that we need to perform the equivalent of NavApplyItemToResult() and which we can't infer in TreePop() +// Only stored when the node is a potential candidate for landing on a Left arrow jump. +struct ImGuiNavTreeNodeData { + ImGuiID ID; + ImGuiItemFlags InFlags; + ImRect NavRect; +}; + +struct IMGUI_API ImGuiStackSizes { + short SizeOfIDStack; + short SizeOfColorStack; + short SizeOfStyleVarStack; + short SizeOfFontStack; + short SizeOfFocusScopeStack; + short SizeOfGroupStack; + short SizeOfItemFlagsStack; + short SizeOfBeginPopupStack; + short SizeOfDisabledStack; + + ImGuiStackSizes() + { + memset(this, 0, sizeof(*this)); + } + void SetToContextState(ImGuiContext *ctx); + void CompareWithContextState(ImGuiContext *ctx); +}; + +// Data saved for each window pushed into the stack +struct ImGuiWindowStackData { + ImGuiWindow *Window; + ImGuiLastItemData ParentLastItemDataBackup; + ImGuiStackSizes StackSizesOnBegin; // Store size of various stacks for asserting +}; + +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; + } +}; + +//----------------------------------------------------------------------------- +// [SECTION] Inputs support +//----------------------------------------------------------------------------- + +// Bit array for named keys +typedef ImBitArray ImBitArrayForNamedKeys; + +// [Internal] Key ranges +#define ImGuiKey_LegacyNativeKey_BEGIN 0 +#define ImGuiKey_LegacyNativeKey_END 512 +#define ImGuiKey_Keyboard_BEGIN (ImGuiKey_NamedKey_BEGIN) +#define ImGuiKey_Keyboard_END (ImGuiKey_GamepadStart) +#define ImGuiKey_Gamepad_BEGIN (ImGuiKey_GamepadStart) +#define ImGuiKey_Gamepad_END (ImGuiKey_GamepadRStickDown + 1) +#define ImGuiKey_Mouse_BEGIN (ImGuiKey_MouseLeft) +#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 +#define ImGuiKey_NavGamepadTweakSlow ImGuiKey_GamepadL1 +#define ImGuiKey_NavGamepadTweakFast ImGuiKey_GamepadR1 +#define ImGuiKey_NavGamepadActivate ImGuiKey_GamepadFaceDown +#define ImGuiKey_NavGamepadCancel ImGuiKey_GamepadFaceRight +#define ImGuiKey_NavGamepadMenu ImGuiKey_GamepadFaceLeft +#define ImGuiKey_NavGamepadInput ImGuiKey_GamepadFaceUp + +enum ImGuiInputEventType { + ImGuiInputEventType_None = 0, + ImGuiInputEventType_MousePos, + ImGuiInputEventType_MouseWheel, + ImGuiInputEventType_MouseButton, + ImGuiInputEventType_Key, + ImGuiInputEventType_Text, + ImGuiInputEventType_Focus, + ImGuiInputEventType_COUNT +}; + +enum ImGuiInputSource { + ImGuiInputSource_None = 0, + ImGuiInputSource_Mouse, // Note: may be Mouse or TouchScreen or Pen. See io.MouseSource to distinguish them. + ImGuiInputSource_Keyboard, + ImGuiInputSource_Gamepad, + ImGuiInputSource_Clipboard, // Currently only used by InputText() + 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; +}; +struct ImGuiInputEventMouseWheel { + float WheelX, WheelY; + ImGuiMouseSource MouseSource; +}; +struct ImGuiInputEventMouseButton { + int Button; + bool Down; + ImGuiMouseSource MouseSource; +}; +struct ImGuiInputEventKey { + ImGuiKey Key; + bool Down; + float AnalogValue; +}; +struct ImGuiInputEventText { + unsigned int Char; +}; +struct ImGuiInputEventAppFocused { + bool Focused; +}; + +struct ImGuiInputEvent { + ImGuiInputEventType Type; + ImGuiInputSource Source; + ImU32 EventId; // Unique, sequential increasing integer to identify an event (if you need to correlate them to other data). + union { + ImGuiInputEventMousePos MousePos; // if Type == ImGuiInputEventType_MousePos + ImGuiInputEventMouseWheel MouseWheel; // if Type == ImGuiInputEventType_MouseWheel + ImGuiInputEventMouseButton MouseButton; // if Type == ImGuiInputEventType_MouseButton + ImGuiInputEventKey Key; // if Type == ImGuiInputEventType_Key + ImGuiInputEventText Text; // if Type == ImGuiInputEventType_Text + 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_None ((ImGuiID)-1) // Require key to have no owner. + +typedef ImS16 ImGuiKeyRoutingIndex; + +// Routing table entry (sizeof() == 16 bytes) +struct ImGuiKeyRoutingData { + ImGuiKeyRoutingIndex NextEntryIndex; + ImU16 Mods; // Technically we'd only need 4-bits but for simplify we store ImGuiMod_ values which need 16-bits. ImGuiMod_Shortcut is already translated to + // Ctrl/Super. + ImU8 RoutingNextScore; // Lower is better (0: perfect score) + ImGuiID RoutingCurr; + ImGuiID RoutingNext; + + ImGuiKeyRoutingData() + { + NextEntryIndex = -1; + Mods = 0; + RoutingNextScore = 255; + RoutingCurr = RoutingNext = ImGuiKeyOwner_None; + } +}; + +// 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 { + 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 { + ImGuiID OwnerCurr; + 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_None; + LockThisFrame = LockUntilRelease = false; + } +}; + +// Flags for extended versions of IsKeyPressed(), IsMouseClicked(), Shortcut(), SetKeyOwner(), SetItemKeyOwner() +// Don't mistake with ImGuiInputTextFlags! (for ImGui::InputText() function) +enum ImGuiInputFlags_ { + // Flags for IsKeyPressed(), IsMouseClicked(), Shortcut() + ImGuiInputFlags_None = 0, + ImGuiInputFlags_Repeat = + 1 << 0, // Return true on successive repeats. Default for legacy IsKeyPressed(). NOT Default for legacy IsMouseClicked(). MUST BE == 1. + ImGuiInputFlags_RepeatRateDefault = 1 << 1, // Repeat rate: Regular (default) + ImGuiInputFlags_RepeatRateNavMove = 1 << 2, // Repeat rate: Fast + ImGuiInputFlags_RepeatRateNavTweak = 1 << 3, // Repeat rate: Faster + ImGuiInputFlags_RepeatRateMask_ = ImGuiInputFlags_RepeatRateDefault | ImGuiInputFlags_RepeatRateNavMove | ImGuiInputFlags_RepeatRateNavTweak, + + // Flags for SetItemKeyOwner() + ImGuiInputFlags_CondHovered = 1 << 4, // Only set if item is hovered (default to both) + ImGuiInputFlags_CondActive = 1 << 5, // Only set if item is active (default to both) + ImGuiInputFlags_CondDefault_ = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive, + ImGuiInputFlags_CondMask_ = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive, + + // Flags for SetKeyOwner(), SetItemKeyOwner() + ImGuiInputFlags_LockThisFrame = 1 << 6, // Access to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared at + // end of frame. This is useful to make input-owner-aware code steal keys from non-input-owner-aware code. + ImGuiInputFlags_LockUntilRelease = + 1 << 7, // Access 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. This is useful to make input-owner-aware code steal keys from non-input-owner-aware code. + + // Routing policies for Shortcut() + low-level SetShortcutRouting() + // - The general idea is that several callers register interest in a shortcut, and only one owner gets it. + // - When a policy (other than _RouteAlways) is set, Shortcut() will register itself with SetShortcutRouting(), + // allowing the system to decide where to route the input among other route-aware calls. + // - Shortcut() uses ImGuiInputFlags_RouteFocused by default: meaning that a simple Shortcut() poll + // will register a route and only succeed when parent window is in the focus stack and if no-one + // with a higher priority is claiming the shortcut. + // - Using ImGuiInputFlags_RouteAlways is roughly equivalent to doing e.g. IsKeyPressed(key) + testing mods. + // - Priorities: GlobalHigh > Focused (when owner is active item) > Global > Focused (when focused window) > GlobalLow. + // - Can select only 1 policy among all available. + ImGuiInputFlags_RouteFocused = 1 << 8, // (Default) Register focused route: Accept inputs if window is in focus stack. Deep-most focused window takes + // inputs. ActiveId takes inputs over deep-most focused window. + ImGuiInputFlags_RouteGlobalLow = + 1 << 9, // Register route globally (lowest priority: unless a focused window or active item registered the route) -> recommended Global priority. + ImGuiInputFlags_RouteGlobal = + 1 << 10, // Register route globally (medium priority: unless an active item registered the route, e.g. CTRL+A registered by InputText). + ImGuiInputFlags_RouteGlobalHigh = + 1 << 11, // Register route globally (highest priority: unlikely you need to use that: will interfere with every active items) + ImGuiInputFlags_RouteMask_ = ImGuiInputFlags_RouteFocused | ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteGlobalLow + | ImGuiInputFlags_RouteGlobalHigh, // _Always not part of this! + ImGuiInputFlags_RouteAlways = 1 << 12, // Do not register route, poll keys directly. + ImGuiInputFlags_RouteUnlessBgFocused = 1 << 13, // Global routes will not be applied if underlying background/void is focused (== no Dear ImGui windows are + // focused). Useful for overlay applications. + ImGuiInputFlags_RouteExtraMask_ = ImGuiInputFlags_RouteAlways | ImGuiInputFlags_RouteUnlessBgFocused, + + // [Internal] Mask of which function support which flags + ImGuiInputFlags_SupportedByIsKeyPressed = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_, + ImGuiInputFlags_SupportedByShortcut = + ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_ | ImGuiInputFlags_RouteMask_ | ImGuiInputFlags_RouteExtraMask_, + 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 { + int Min; + int Max; + 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 { + ImGuiListClipper *ListClipper; + float LossynessOffset; + 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, + ImGuiActivateFlags_PreferInput = 1 << 0, // Favor activation that requires keyboard text input (e.g. for Slider/Drag). Default for Enter key. + ImGuiActivateFlags_PreferTweak = + 1 << 1, // Favor activation for tweaking with arrows or gamepad (e.g. for Slider/Drag). Default for Space key and if keyboard is not used. + ImGuiActivateFlags_TryToPreserveState = 1 << 2, // Request widget to preserve state if it can (e.g. InputText will try to preserve cursor/selection) +}; + +// Early work-in-progress API for ScrollToItem() +enum ImGuiScrollFlags_ { + ImGuiScrollFlags_None = 0, + ImGuiScrollFlags_KeepVisibleEdgeX = + 1 << 0, // If item is not visible: scroll as little as possible on X axis to bring item back into view [default for X axis] + ImGuiScrollFlags_KeepVisibleEdgeY = 1 << 1, // If item is not visible: scroll as little as possible on Y axis to bring item back into view [default for Y + // axis for windows that are already visible] + ImGuiScrollFlags_KeepVisibleCenterX = 1 << 2, // If item is not visible: scroll to make the item centered on X axis [rarely used] + ImGuiScrollFlags_KeepVisibleCenterY = 1 << 3, // If item is not visible: scroll to make the item centered on Y axis + ImGuiScrollFlags_AlwaysCenterX = 1 << 4, // Always center the result item on X axis [rarely used] + ImGuiScrollFlags_AlwaysCenterY = 1 << 5, // Always center the result item on Y axis [default for Y axis for appearing window) + ImGuiScrollFlags_NoScrollParent = + 1 << 6, // Disable forwarding scrolling to parent window if required to keep item/rect visible (only scroll window the function was applied to). + ImGuiScrollFlags_MaskX_ = ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleCenterX | ImGuiScrollFlags_AlwaysCenterX, + ImGuiScrollFlags_MaskY_ = ImGuiScrollFlags_KeepVisibleEdgeY | ImGuiScrollFlags_KeepVisibleCenterY | ImGuiScrollFlags_AlwaysCenterY, +}; + +enum ImGuiNavHighlightFlags_ { + ImGuiNavHighlightFlags_None = 0, + ImGuiNavHighlightFlags_TypeDefault = 1 << 0, + ImGuiNavHighlightFlags_TypeThin = 1 << 1, + ImGuiNavHighlightFlags_AlwaysDraw = 1 << 2, // Draw rectangular highlight if (g.NavId == id) _even_ when using the mouse. + ImGuiNavHighlightFlags_NoRounding = 1 << 3, +}; + +enum ImGuiNavMoveFlags_ { + ImGuiNavMoveFlags_None = 0, + ImGuiNavMoveFlags_LoopX = 1 << 0, // On failed request, restart from opposite side + ImGuiNavMoveFlags_LoopY = 1 << 1, + ImGuiNavMoveFlags_WrapX = 1 << 2, // On failed request, request from opposite side one line down (when NavDir==right) or one line up (when NavDir==left) + ImGuiNavMoveFlags_WrapY = 1 << 3, // This is not super useful but provided for completeness + ImGuiNavMoveFlags_WrapMask_ = ImGuiNavMoveFlags_LoopX | ImGuiNavMoveFlags_LoopY | ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_WrapY, + ImGuiNavMoveFlags_AllowCurrentNavId = + 1 << 4, // Allow scoring and considering the current NavId as a move target candidate. This is used when the move source is offset (e.g. pressing + // PageDown actually needs to send a Up move request, if we are pressing PageDown from the bottom-most item we need to stay in place) + ImGuiNavMoveFlags_AlsoScoreVisibleSet = + 1 << 5, // Store alternate result in NavMoveResultLocalVisible that only comprise elements that are already fully visible (used by PageUp/PageDown) + ImGuiNavMoveFlags_ScrollToEdgeY = 1 << 6, // Force scrolling to min/max (used by Home/End) // FIXME-NAV: Aim to remove or reword, probably unnecessary + ImGuiNavMoveFlags_Forwarded = 1 << 7, + ImGuiNavMoveFlags_DebugNoResult = 1 << 8, // Dummy scoring for debug purpose, don't apply result + ImGuiNavMoveFlags_FocusApi = 1 << 9, // Requests from focus API can land/focus/activate items even if they are marked with _NoTabStop (see + // NavProcessItemForTabbingRequest() for details) + ImGuiNavMoveFlags_IsTabbing = 1 << 10, // == Focus + Activate if item is Inputable + DontChangeNavHighlight + ImGuiNavMoveFlags_IsPageMove = 1 << 11, // Identify a PageDown/PageUp request. + ImGuiNavMoveFlags_Activate = 1 << 12, // Activate/select target item. + ImGuiNavMoveFlags_NoSelect = 1 << 13, // Don't trigger selection by not setting g.NavJustMovedTo + ImGuiNavMoveFlags_NoSetNavHighlight = 1 << 14, // Do not alter the visible state of keyboard vs mouse nav highlight +}; + +enum ImGuiNavLayer { + ImGuiNavLayer_Main = 0, // Main scrolling layer + ImGuiNavLayer_Menu = 1, // Menu layer (access with Alt) + ImGuiNavLayer_COUNT +}; + +struct ImGuiNavItemData { + ImGuiWindow *Window; // Init,Move // Best candidate window (result->ItemWindow->RootWindowForNav == request->Window) + ImGuiID ID; // Init,Move // Best candidate item ID + ImGuiID FocusScopeId; // Init,Move // Best candidate focus scope ID + ImRect RectRel; // Init,Move // Best candidate bounding box in window relative space + ImGuiItemFlags InFlags; // ????,Move // Best candidate item flags + float DistBox; // Move // Best candidate box distance to current NavId + float DistCenter; // Move // Best candidate center distance to current NavId + float DistAxial; // Move // Best candidate axial distance to current NavId + + ImGuiNavItemData() + { + Clear(); + } + void Clear() + { + Window = NULL; + ID = FocusScopeId = 0; + InFlags = 0; + DistBox = DistCenter = DistAxial = FLT_MAX; + } +}; + +//----------------------------------------------------------------------------- +// [SECTION] Columns support +//----------------------------------------------------------------------------- + +// Flags for internal's BeginColumns(). Prefix using BeginTable() nowadays! +enum ImGuiOldColumnFlags_ { + ImGuiOldColumnFlags_None = 0, + ImGuiOldColumnFlags_NoBorder = 1 << 0, // Disable column dividers + ImGuiOldColumnFlags_NoResize = 1 << 1, // Disable resizing columns when clicking on the dividers + 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, // (WIP) 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, + ImGuiColumnsFlags_NoBorder = ImGuiOldColumnFlags_NoBorder, + ImGuiColumnsFlags_NoResize = ImGuiOldColumnFlags_NoResize, + ImGuiColumnsFlags_NoPreserveWidths = ImGuiOldColumnFlags_NoPreserveWidths, + ImGuiColumnsFlags_NoForceWithinWindow = ImGuiOldColumnFlags_NoForceWithinWindow, + 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; + ImGuiOldColumnFlags Flags; + bool IsFirstFrame; + bool IsBeingResized; + int Current; + int Count; + float OffMinX, OffMaxX; // Offsets from HostWorkRect.Min.x + float LineMinY, LineMaxY; + float HostCursorPosY; // Backup of CursorPos at the time of BeginColumns() + float HostCursorMaxPosX; // Backup of CursorMaxPos at the time of BeginColumns() + ImRect HostInitialClipRect; // Backup of ClipRect at the time of BeginColumns() + ImRect HostBackupClipRect; // Backup of ClipRect during PushColumnsBackground()/PopColumnsBackground() + ImRect HostBackupParentWorkRect; // Backup of WorkRect at the time of BeginColumns() + ImVector Columns; + ImDrawListSplitter Splitter; + + ImGuiOldColumns() + { + memset(this, 0, sizeof(*this)); + } +}; + +//----------------------------------------------------------------------------- +// [SECTION] Multi-select support +//----------------------------------------------------------------------------- + +#ifdef IMGUI_HAS_MULTI_SELECT +// +#endif // #ifdef IMGUI_HAS_MULTI_SELECT + +//----------------------------------------------------------------------------- +// [SECTION] Docking support +//----------------------------------------------------------------------------- + +#ifdef IMGUI_HAS_DOCK +// +#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 { + int BgFgDrawListsLastFrame[2]; // Last frame number the background (0) and foreground (1) draw lists were used + ImDrawList *BgFgDrawLists[2]; // Convenience background (0) and foreground (1) draw lists. We use them to draw software mouser cursor when + // io.MouseDrawCursor is set and to draw most debug overlays. + ImDrawData DrawDataP; + ImDrawDataBuilder DrawDataBuilder; // Temporary data while building final ImDrawData + ImVec2 WorkOffsetMin; // Work Area: Offset from Pos to top-left corner of Work Area. Generally (0,0) or (0,+main_menu_bar_height). Work Area is Full Area + // but without menu-bars/status-bars (so WorkArea always fit inside Pos/Size!) + ImVec2 WorkOffsetMax; // Work Area: Offset from Pos+Size to bottom-right corner of Work Area. Generally (0,0) or (0,-status_bar_height). + ImVec2 BuildWorkOffsetMin; // Work Area: Offset being built during current frame. Generally >= 0.0f. + ImVec2 BuildWorkOffsetMax; // Work Area: Offset being built during current frame. Generally <= 0.0f. + + ImGuiViewportP() + { + BgFgDrawListsLastFrame[0] = BgFgDrawListsLastFrame[1] = -1; + BgFgDrawLists[0] = BgFgDrawLists[1] = NULL; + } + ~ImGuiViewportP() + { + if (BgFgDrawLists[0]) + IM_DELETE(BgFgDrawLists[0]); + if (BgFgDrawLists[1]) + IM_DELETE(BgFgDrawLists[1]); + } + + // 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 &off_min) const + { + return ImVec2(Pos.x + off_min.x, Pos.y + off_min.y); + } + ImVec2 CalcWorkRectSize(const ImVec2 &off_min, const ImVec2 &off_max) const + { + return ImVec2(ImMax(0.0f, Size.x - off_min.x + off_max.x), ImMax(0.0f, Size.y - off_min.y + off_max.y)); + } + void UpdateWorkRect() + { + WorkPos = CalcWorkRectPos(WorkOffsetMin); + WorkSize = CalcWorkRectSize(WorkOffsetMin, WorkOffsetMax); + } // 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(BuildWorkOffsetMin); + ImVec2 size = CalcWorkRectSize(BuildWorkOffsetMin, BuildWorkOffsetMax); + 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) +struct ImGuiWindowSettings { + ImGuiID ID; + ImVec2ih Pos; + ImVec2ih Size; + bool Collapsed; + 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)); + } + char *GetName() + { + return (char *)(this + 1); + } +}; + +struct ImGuiSettingsHandler { + const char *TypeName; // Short description stored in .ini file. Disallowed characters: '[' ']' + ImGuiID TypeHash; // == ImHashStr(TypeName) + void (*ClearAllFn)(ImGuiContext *ctx, ImGuiSettingsHandler *handler); // Clear all settings data + void (*ReadInitFn)(ImGuiContext *ctx, ImGuiSettingsHandler *handler); // Read: Called before reading (in registration order) + void *(*ReadOpenFn)(ImGuiContext *ctx, + ImGuiSettingsHandler *handler, + const char *name); // Read: Called when entering into a new ini entry e.g. "[Window][Name]" + void (*ReadLineFn)(ImGuiContext *ctx, + ImGuiSettingsHandler *handler, + void *entry, + const char *line); // Read: Called for every line of text within an ini entry + 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 { + ImGuiLocKey_VersionStr, + ImGuiLocKey_TableSizeOne, + ImGuiLocKey_TableSizeAllFit, + ImGuiLocKey_TableSizeAllDefault, + ImGuiLocKey_TableResetOrder, + ImGuiLocKey_WindowingMainMenuBar, + ImGuiLocKey_WindowingPopup, + ImGuiLocKey_WindowingUntitled, + ImGuiLocKey_COUNT +}; + +struct ImGuiLocEntry { + ImGuiLocKey Key; + const char *Text; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Metrics, Debug Tools +//----------------------------------------------------------------------------- + +enum ImGuiDebugLogFlags_ { + // Event types + ImGuiDebugLogFlags_None = 0, + ImGuiDebugLogFlags_EventActiveId = 1 << 0, + ImGuiDebugLogFlags_EventFocus = 1 << 1, + ImGuiDebugLogFlags_EventPopup = 1 << 2, + ImGuiDebugLogFlags_EventNav = 1 << 3, + ImGuiDebugLogFlags_EventClipper = 1 << 4, + ImGuiDebugLogFlags_EventSelection = 1 << 5, + ImGuiDebugLogFlags_EventIO = 1 << 6, + ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup + | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO, + ImGuiDebugLogFlags_OutputToTTY = 1 << 10, // Also send output to TTY +}; + +struct ImGuiMetricsConfig { + bool ShowDebugLog = false; + bool ShowStackTool = false; + bool ShowWindowsRects = false; + bool ShowWindowsBeginOrder = false; + bool ShowTablesRects = false; + bool ShowDrawCmdMesh = true; + bool ShowDrawCmdBoundingBoxes = true; + bool ShowAtlasTintedWithTextColor = false; + int ShowWindowsRectsType = -1; + int ShowTablesRectsType = -1; +}; + +struct ImGuiStackLevelInfo { + ImGuiID ID; + ImS8 QueryFrameCount; // >= 1: Query in progress + 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 Stack tool queries +struct ImGuiStackTool { + int LastActiveFrame; + int StackLevel; // -1: query stack and resize Results, >= 0: individual stack level + ImGuiID QueryId; // ID to query details for + ImVector Results; + bool CopyToClipboardOnCtrlC; + float CopyToClipboardLastTime; + + ImGuiStackTool() + { + 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() + ImGuiContextHookType Type; + 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; + ImGuiStyle Style; + 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. + ImDrawListSharedData DrawListSharedData; + double Time; + int FrameCount; + int FrameCountEnded; + int FrameCountRendered; + bool WithinFrameScope; // Set by NewFrame(), cleared by EndFrame() + bool WithinFrameScopeWithImplicitWindow; // Set by NewFrame(), cleared by EndFrame() when the implicit debug window has been pushed + bool WithinEndChild; // Set within EndChild() + bool GcCompactAll; // Request full GC + bool TestEngineHookItems; // Will call test engine hooks: ImGuiTestEngineHook_ItemAdd(), ImGuiTestEngineHook_ItemInfo(), ImGuiTestEngineHook_Log() + void *TestEngine; // Test engine user data + + // 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. + ImVector WindowsTempSortBuffer; // Temporary buffer used in EndFrame() to reorder windows so parents are kept before their child + ImVector CurrentWindowStack; + ImGuiStorage WindowsById; // Map window's ImGuiID to ImGuiWindow* + int WindowsActiveCount; // Number of unique windows submitted by frame + ImVec2 WindowsHoverPadding; // Padding around resizable windows for which hovering on counts as hovering the window == ImMax(style.TouchExtraPadding, + // WINDOWS_HOVER_PADDING) + ImGuiWindow *CurrentWindow; // Window being drawn into + ImGuiWindow *HoveredWindow; // Window the mouse is hovering. Will typically catch mouse inputs. + ImGuiWindow *HoveredWindowUnderMovingWindow; // Hovered window ignoring MovingWindow. Only set if MovingWindow is set. + ImGuiWindow + *MovingWindow; // Track the window we clicked on (in order to preserve focus). The actual window that is moved is generally MovingWindow->RootWindow. + ImGuiWindow *WheelingWindow; // Track the window we started mouse-wheeling on. Until a timer elapse or mouse has moved, generally keep scrolling the same + // window even if during the course of scrolling the mouse ends up hovering a child window. + ImVec2 WheelingWindowRefMousePos; + int WheelingWindowStartFrame; // This may be set one frame before WheelingWindow is != NULL + float WheelingWindowReleaseTimer; + ImVec2 WheelingWindowWheelRemainder; + ImVec2 WheelingAxisAvg; + + // Item/widgets state and tracking information + ImGuiID DebugHookIdInfo; // Will call core hooks: DebugHookIdInfo() from GetID functions, used by Stack Tool [next HoveredId/ActiveId to not pull in an + // extra cache-line] + ImGuiID HoveredId; // Hovered widget, filled during the frame + ImGuiID HoveredIdPreviousFrame; + bool HoveredIdAllowOverlap; + bool HoveredIdDisabled; // At least one widget passed the rect test, but has been discarded by disabled flag or popup inhibit. May be true even if HoveredId + // == 0. + float HoveredIdTimer; // Measure contiguous hovering time + float HoveredIdNotActiveTimer; // Measure contiguous hovering time where the item has not been active + ImGuiID ActiveId; // Active widget + ImGuiID ActiveIdIsAlive; // Active widget has been seen this frame (we can't use a bool as the ActiveId may change within the frame) + float ActiveIdTimer; + bool ActiveIdIsJustActivated; // Set at the time of activation for one frame + bool ActiveIdAllowOverlap; // Active widget allows another widget to steal active id (generally for overlapping widgets, but not always) + bool ActiveIdNoClearOnFocusLoss; // Disable losing active id if the active id window gets unfocused. + bool ActiveIdHasBeenPressedBefore; // Track whether the active id led to a press (this is to allow changing between PressOnClick and PressOnRelease without + // pressing twice). Used by range_select branch. + bool ActiveIdHasBeenEditedBefore; // Was the value associated to the widget Edited over the course of the Active state. + bool ActiveIdHasBeenEditedThisFrame; + ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) + ImGuiWindow *ActiveIdWindow; + ImGuiInputSource ActiveIdSource; // Activating source: ImGuiInputSource_Mouse OR ImGuiInputSource_Keyboard OR ImGuiInputSource_Gamepad + int ActiveIdMouseButton; + ImGuiID ActiveIdPreviousFrame; + bool ActiveIdPreviousFrameIsAlive; + bool ActiveIdPreviousFrameHasBeenEditedBefore; + ImGuiWindow *ActiveIdPreviousFrameWindow; + 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. + + // [EXPERIMENTAL] 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_None (== -1) or a custom ID. + // - Routing is requested ahead of time for a given chord (Key + Mods) and granted in NewFrame(). + ImGuiKeyOwnerData KeysOwnerData[ImGuiKey_NamedKey_COUNT]; + ImGuiKeyRoutingTable KeysRoutingTable; + ImU32 ActiveIdUsingNavDirMask; // Active widget will want to read those nav move requests (e.g. can activate a button and move away from it) + bool ActiveIdUsingAllKeyboardKeys; // Active widget will want to read all keyboard keys inputs. (FIXME: This is a shortcut for not taking ownership of 100+ + // keys but perhaps best to not have the inconsistency) +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + ImU32 ActiveIdUsingNavInputMask; // If you used this. Since (IMGUI_VERSION_NUM >= 18804) : 'g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);' + // becomes 'SetKeyOwner(ImGuiKey_Escape, g.ActiveId) and/or SetKeyOwner(ImGuiKey_NavGamepadCancel, g.ActiveId);' +#endif + + // Next window/item data + ImGuiID CurrentFocusScopeId; // == g.FocusScopeStack.back() + ImGuiItemFlags CurrentItemFlags; // == g.ItemFlagsStack.back() + ImGuiID DebugLocateId; // Storage for DebugLocateItemOnHover() feature: this is read by ItemAdd() so we keep it in a hot/cached location + ImGuiNextItemData NextItemData; // Storage for SetNextItem** functions + ImGuiLastItemData LastItemData; // Storage for last submitted item (setup by ItemAdd) + ImGuiNextWindowData NextWindowData; // Storage for SetNextWindow** functions + + // Shared stacks + 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 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) + ImVector NavTreeNodeStack; // Stack for TreeNode() when a NavLeft requested is emitted. + + int BeginMenuCount; + + // Viewports + ImVector Viewports; // Active viewports (Size==1 in 'master' branch). Each viewports hold their copy of ImDrawData. + + // Gamepad/keyboard Navigation + ImGuiWindow *NavWindow; // Focused window for navigation. Could be called 'FocusedWindow' + ImGuiID NavId; // Focused item for navigation + ImGuiID NavFocusScopeId; // Identify a selection scope (selection code often wants to "clear other items" when landing on an item of the selection set) + ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? + // NavId : 0, also set when calling ActivateItem() + 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; + ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest). + ImGuiID NavJustMovedToFocusScopeId; // Just navigated to this focus scope id (result of a successfully MoveRequest). + ImGuiKeyChord NavJustMovedToKeyMods; + ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame. + ImGuiActivateFlags NavNextActivateFlags; + ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource_Keyboard or ImGuiInputSource_Mouse + ImGuiNavLayer NavLayer; // Layer we are navigating on. For now the system is hard-coded for 0=main contents and 1=menu/title bar, may expose layers later. + bool NavIdIsAlive; // Nav widget has been seen this frame ~~ NavRectRel is valid + bool NavMousePosDirty; // When set we will update mouse position if (io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) if set (NB: this not enabled by + // default) + bool NavDisableHighlight; // When user starts using mouse, we hide gamepad/keyboard highlight (NB: but they are still available, which is why + // NavDisableHighlight isn't always != NavDisableMouseHover) + bool NavDisableMouseHover; // When user starts using gamepad/keyboard, we hide mouse hovering highlight until mouse is touched again. + + // 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 + bool NavInitRequestFromMove; + ImGuiNavItemData NavInitResult; // Init request result (first item of the window, or one for which SetItemDefaultFocus() was called) + bool NavMoveSubmitted; // Move request submitted, will process result on next NewFrame() + bool NavMoveScoringItems; // Move request submitted, still scoring incoming items + bool NavMoveForwardToNextFrame; + ImGuiNavMoveFlags NavMoveFlags; + ImGuiScrollFlags NavMoveScrollFlags; + ImGuiKeyChord NavMoveKeyMods; + ImGuiDir NavMoveDir; // Direction of the move request (left/right/up/down) + ImGuiDir NavMoveDirForDebug; + ImGuiDir NavMoveClipDir; // FIXME-NAV: Describe the purpose of this better. Might want to rename? + ImRect NavScoringRect; // Rectangle used for scoring, in screen space. Based of window->NavRectRel[], modified for directional navigation scoring. + ImRect NavScoringNoClipRect; // Some nav operations (such as PageUp/PageDown) enforce a region which clipper will attempt to always keep submitted + int NavScoringDebugCount; // Metrics for debugging + int NavTabbingDir; // Generally -1 or +1, 0 when tabbing without a nav id + int NavTabbingCounter; // >0 when counting items for tabbing + ImGuiNavItemData NavMoveResultLocal; // Best move request candidate within NavWindow + 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: Windowing (CTRL+TAB for list, or Menu button + keys or directional pads to move/resize) + ImGuiKeyChord ConfigNavWindowingKeyNext; // = ImGuiMod_Ctrl | ImGuiKey_Tab, for reconfiguration (see #4828) + ImGuiKeyChord ConfigNavWindowingKeyPrev; // = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab + ImGuiWindow *NavWindowingTarget; // Target window when doing CTRL+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most! + ImGuiWindow *NavWindowingTargetAnim; // Record of last valid NavWindowingTarget until DimBgRatio and NavWindowingHighlightAlpha becomes 0.0f, so the + // fade-out can stay on it. + ImGuiWindow *NavWindowingListWindow; // Internal window actually listing the CTRL+Tab contents + float NavWindowingTimer; + float NavWindowingHighlightAlpha; + bool NavWindowingToggleLayer; + 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. + bool DragDropWithinTarget; // Set when within a BeginDragDropXXX/EndDragDropXXX block for a drag target. + ImGuiDragDropFlags DragDropSourceFlags; + int DragDropSourceFrameCount; + int DragDropMouseButton; + ImGuiPayload DragDropPayload; + ImRect DragDropTargetRect; // Store rectangle of current target candidate (we favor small targets when overlapping) + ImGuiID DragDropTargetId; + ImGuiDragDropFlags DragDropAcceptFlags; + float DragDropAcceptIdCurrRectSurface; // Target item surface (we resolve overlapping targets by prioritizing the smaller surface) + ImGuiID DragDropAcceptIdCurr; // Target item id (set at the time of accepting the payload) + ImGuiID DragDropAcceptIdPrev; // Target item id from previous frame (we need to store this to allow for overlapping drag and drop targets) + int DragDropAcceptFrameCount; // Last time a target expressed a desire to accept the source + 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; + int TablesTempDataStacked; // Temporary table data size (because we leave previous instances undestructed, we generally don't use TablesTempData.Size) + ImVector TablesTempData; // Temporary table data (buffers reused/shared across instances, support nesting) + 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; + + // Hover Delay system + ImGuiID HoverItemDelayId; + ImGuiID HoverItemDelayIdPreviousFrame; + float HoverItemDelayTimer; // Currently used by IsItemHovered() + 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; + ImGuiID TempInputId; // Temporary text input when CTRL+clicking on a slider, etc. + ImGuiColorEditFlags ColorEditOptions; // Store user options for color edit widgets + ImGuiID ColorEditCurrentID; // Set temporarily while inside of the parent-most ColorEdit4/ColorPicker4 (because they call each others). + ImGuiID ColorEditSavedID; // ID we are saving/restoring HS for + float ColorEditSavedHue; // Backup of last Hue associated to LastColor, so we can restore Hue in lossy RGB<>HSV round trips + float ColorEditSavedSat; // Backup of last Saturation associated to LastColor, so we can restore Saturation in lossy RGB<>HSV round trips + ImU32 ColorEditSavedColor; // RGB value with alpha set to 0. + ImVec4 ColorPickerRef; // Initial/reference color at the time of opening the color picker. + ImGuiComboPreviewData ComboPreviewData; + float SliderGrabClickOffset; + float SliderCurrentAccum; // Accumulated slider delta when using navigation controls. + bool SliderCurrentAccumDirty; // Has the accumulated slider delta changed since last time we tried to apply it? + bool DragCurrentAccumDirty; + float DragCurrentAccum; // Accumulator for dragging modification. Always high-precision, not rounded by end-user precision settings + float DragSpeedDefaultRatio; // If speed == 0.0f, uses (max-min) * DragSpeedDefaultRatio + float ScrollbarClickDeltaToGrabCenter; // Distance between mouse and center of grab box, normalized in parent space. Use storage? + float DisabledAlphaBackup; // Backup for style.Alpha for BeginDisabled() + short DisabledStackSize; + short LockMarkEdited; + short TooltipOverrideCount; + ImVector ClipboardHandlerData; // If no custom clipboard handler is defined + ImVector MenusIdSubmittedThisFrame; // A list of menu IDs that were rendered at least once + + // Platform support + ImGuiPlatformImeData PlatformImeData; // Data updated by current frame + ImGuiPlatformImeData PlatformImeDataPrev; // Previous frame data (when changing we will call io.SetPlatformImeDataFn + + // Settings + bool SettingsLoaded; + float SettingsDirtyTimer; // Save .ini Settings to memory when time reaches zero + ImGuiTextBuffer SettingsIniData; // In memory .ini settings + ImVector SettingsHandlers; // List of .ini settings handlers + ImChunkStream SettingsWindows; // ImGuiWindow .ini settings entries + 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 + ImGuiLogType LogType; // Capture target + ImFileHandle LogFile; // If != NULL log to stdout/ file + ImGuiTextBuffer LogBuffer; // Accumulation buffer when log to clipboard. This is pointer so our GImGui static constructor doesn't call heap allocators. + const char *LogNextPrefix; + const char *LogNextSuffix; + float LogLinePosY; + bool LogLineFirstItem; + int LogDepthRef; + int LogDepthToExpand; + int LogDepthToExpandDefault; // Default/stored value for LogDepthMaxExpand if not specified in the LogXXX function call. + + // Debug Tools + ImGuiDebugLogFlags DebugLogFlags; + ImGuiTextBuffer DebugLogBuf; + ImGuiTextIndex DebugLogIndex; + ImU8 DebugLogClipperAutoDisableFrames; + ImU8 DebugLocateFrames; // For DebugLocateItemOnHover(). This is used together with DebugLocateId which is in a hot/cached spot above. + ImS8 DebugBeginReturnValueCullDepth; // Cycle between 0..9 then wrap around. + bool DebugItemPickerActive; // Item picker is active (started with DebugStartItemPicker()) + ImU8 DebugItemPickerMouseButton; + ImGuiID DebugItemPickerBreakId; // Will call IM_DEBUG_BREAK() when encountering this ID + ImGuiMetricsConfig DebugMetricsConfig; + ImGuiStackTool DebugStackTool; + + // Misc + float FramerateSecPerFrame[60]; // Calculate estimate of framerate for user over the last 60 frames.. + int FramerateSecPerFrameIdx; + int FramerateSecPerFrameCount; + float FramerateSecPerFrameAccum; + int WantCaptureMouseNextFrame; // Explicit capture override via SetNextFrameWantCaptureMouse()/SetNextFrameWantCaptureKeyboard(). Default to -1. + int WantCaptureKeyboardNextFrame; // " + int WantTextInputNextFrame; + ImVector TempBuffer; // Temporary text buffer + + ImGuiContext(ImFontAtlas *shared_font_atlas) + { + IO.Ctx = this; + InputTextState.Ctx = this; + + Initialized = false; + FontAtlasOwnedByContext = shared_font_atlas ? false : true; + Font = NULL; + FontSize = FontBaseSize = 0.0f; + IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); + Time = 0.0f; + FrameCount = 0; + FrameCountEnded = FrameCountRendered = -1; + WithinFrameScope = WithinFrameScopeWithImplicitWindow = WithinEndChild = false; + GcCompactAll = false; + TestEngineHookItems = false; + TestEngine = NULL; + + InputEventsNextMouseSource = ImGuiMouseSource_Mouse; + InputEventsNextEventId = 1; + + WindowsActiveCount = 0; + CurrentWindow = NULL; + HoveredWindow = NULL; + HoveredWindowUnderMovingWindow = NULL; + MovingWindow = NULL; + WheelingWindow = NULL; + WheelingWindowStartFrame = -1; + WheelingWindowReleaseTimer = 0.0f; + + DebugHookIdInfo = 0; + HoveredId = HoveredIdPreviousFrame = 0; + HoveredIdAllowOverlap = false; + HoveredIdDisabled = false; + HoveredIdTimer = HoveredIdNotActiveTimer = 0.0f; + ActiveId = 0; + ActiveIdIsAlive = 0; + ActiveIdTimer = 0.0f; + ActiveIdIsJustActivated = false; + ActiveIdAllowOverlap = false; + ActiveIdNoClearOnFocusLoss = false; + ActiveIdHasBeenPressedBefore = false; + ActiveIdHasBeenEditedBefore = false; + ActiveIdHasBeenEditedThisFrame = false; + ActiveIdClickOffset = ImVec2(-1, -1); + ActiveIdWindow = NULL; + ActiveIdSource = ImGuiInputSource_None; + ActiveIdMouseButton = -1; + ActiveIdPreviousFrame = 0; + ActiveIdPreviousFrameIsAlive = false; + ActiveIdPreviousFrameHasBeenEditedBefore = false; + ActiveIdPreviousFrameWindow = NULL; + LastActiveId = 0; + LastActiveIdTimer = 0.0f; + + ActiveIdUsingNavDirMask = 0x00; + ActiveIdUsingAllKeyboardKeys = false; +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + ActiveIdUsingNavInputMask = 0x00; +#endif + + CurrentFocusScopeId = 0; + CurrentItemFlags = ImGuiItemFlags_None; + BeginMenuCount = 0; + + NavWindow = NULL; + NavId = NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = 0; + NavJustMovedToId = NavJustMovedToFocusScopeId = NavNextActivateId = 0; + NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None; + NavJustMovedToKeyMods = ImGuiMod_None; + NavInputSource = ImGuiInputSource_Keyboard; + NavLayer = ImGuiNavLayer_Main; + NavIdIsAlive = false; + NavMousePosDirty = false; + NavDisableHighlight = true; + NavDisableMouseHover = false; + NavAnyRequest = false; + NavInitRequest = false; + NavInitRequestFromMove = false; + NavMoveSubmitted = false; + NavMoveScoringItems = false; + NavMoveForwardToNextFrame = false; + NavMoveFlags = ImGuiNavMoveFlags_None; + NavMoveScrollFlags = ImGuiScrollFlags_None; + NavMoveKeyMods = ImGuiMod_None; + NavMoveDir = NavMoveDirForDebug = NavMoveClipDir = ImGuiDir_None; + NavScoringDebugCount = 0; + NavTabbingDir = 0; + NavTabbingCounter = 0; + + ConfigNavWindowingKeyNext = ImGuiMod_Ctrl | ImGuiKey_Tab; + ConfigNavWindowingKeyPrev = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab; + NavWindowingTarget = NavWindowingTargetAnim = NavWindowingListWindow = NULL; + NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f; + NavWindowingToggleLayer = false; + + DimBgRatio = 0.0f; + + DragDropActive = DragDropWithinSource = DragDropWithinTarget = false; + DragDropSourceFlags = ImGuiDragDropFlags_None; + DragDropSourceFrameCount = -1; + DragDropMouseButton = -1; + DragDropTargetId = 0; + DragDropAcceptFlags = ImGuiDragDropFlags_None; + DragDropAcceptIdCurrRectSurface = 0.0f; + DragDropAcceptIdPrev = DragDropAcceptIdCurr = 0; + DragDropAcceptFrameCount = -1; + DragDropHoldJustPressedId = 0; + memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal)); + + ClipperTempDataStacked = 0; + + CurrentTable = NULL; + TablesTempDataStacked = 0; + CurrentTabBar = NULL; + + HoverItemDelayId = HoverItemDelayIdPreviousFrame = HoverItemUnlockedStationaryId = HoverWindowUnlockedStationaryId = 0; + HoverItemDelayTimer = HoverItemDelayClearTimer = 0.0f; + + MouseCursor = ImGuiMouseCursor_Arrow; + MouseStationaryTimer = 0.0f; + + TempInputId = 0; + ColorEditOptions = ImGuiColorEditFlags_DefaultOptions_; + ColorEditCurrentID = ColorEditSavedID = 0; + ColorEditSavedHue = ColorEditSavedSat = 0.0f; + ColorEditSavedColor = 0; + SliderGrabClickOffset = 0.0f; + SliderCurrentAccum = 0.0f; + SliderCurrentAccumDirty = false; + DragCurrentAccumDirty = false; + DragCurrentAccum = 0.0f; + DragSpeedDefaultRatio = 1.0f / 100.0f; + ScrollbarClickDeltaToGrabCenter = 0.0f; + DisabledAlphaBackup = 0.0f; + DisabledStackSize = 0; + LockMarkEdited = 0; + TooltipOverrideCount = 0; + + PlatformImeData.InputPos = ImVec2(0.0f, 0.0f); + PlatformImeDataPrev.InputPos = ImVec2(-1.0f, -1.0f); // Different to ensure initial submission + + SettingsLoaded = false; + SettingsDirtyTimer = 0.0f; + HookIdNext = 0; + + memset(LocalizationTable, 0, sizeof(LocalizationTable)); + + LogEnabled = false; + LogType = ImGuiLogType_None; + LogNextPrefix = LogNextSuffix = NULL; + LogFile = NULL; + LogLinePosY = FLT_MAX; + LogLineFirstItem = false; + LogDepthRef = 0; + LogDepthToExpand = LogDepthToExpandDefault = 2; + + DebugLogFlags = ImGuiDebugLogFlags_OutputToTTY; + DebugLocateId = 0; + DebugLogClipperAutoDisableFrames = 0; + DebugLocateFrames = 0; + DebugBeginReturnValueCullDepth = -1; + DebugItemPickerActive = false; + DebugItemPickerMouseButton = ImGuiMouseButton_Left; + DebugItemPickerBreakId = 0; + + memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame)); + FramerateSecPerFrameIdx = FramerateSecPerFrameCount = 0; + FramerateSecPerFrameAccum = 0.0f; + WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; + } +}; + +//----------------------------------------------------------------------------- +// [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) +struct IMGUI_API ImGuiWindowTempData { + // Layout + ImVec2 CursorPos; // Current emitting position, in absolute coordinates. + ImVec2 CursorPosPrevLine; + ImVec2 CursorStartPos; // Initial position after Begin(), generally ~ window position + WindowPadding. + ImVec2 CursorMaxPos; // Used to implicitly calculate ContentSize at the beginning of next frame, for scrolling range and auto-resize. Always growing during + // the frame. + ImVec2 IdealMaxPos; // Used to implicitly calculate ContentSizeIdeal at the beginning of next frame, for auto-resize only. Always growing during the frame. + ImVec2 CurrLineSize; + ImVec2 PrevLineSize; + float CurrLineTextBaseOffset; // Baseline offset (0.0f by default on a new line, generally == style.FramePadding.y when a framed item has been added). + float PrevLineTextBaseOffset; + bool IsSameLine; + bool IsSetPos; + ImVec1 Indent; // Indentation / start position from left of window (increased by TreePush/TreePop, etc.) + 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) + short NavLayersActiveMaskNext; // Which layers have been written to (accumulator for current frame) + 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 TreeJumpToParentOnPopMask; // Store a copy of !g.NavIdIsAlive for TreeDepth 0..31.. Could be turned into a ImU64 if necessary. + ImVector ChildWindows; + ImGuiStorage *StateStorage; // Current persistent per-window storage (store e.g. tree node open/close state) + ImGuiOldColumns *CurrentColumns; // Current columns set + int CurrentTableIdx; // Current table index (into g.Tables) + ImGuiLayoutType LayoutType; + ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() + + // 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). + float TextWrapPos; // Current text wrap pos. + 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 { + ImGuiContext *Ctx; // Parent UI context (needs to be set explicitly by parent). + char *Name; // Window name, owned by the window. + ImGuiID ID; // == ImHashStr(Name) + ImGuiWindowFlags Flags; // See enum ImGuiWindowFlags_ + ImGuiViewportP *Viewport; // Always set in Begin(). Inactive windows may have a NULL value here if their viewport was discarded. + ImVec2 Pos; // Position (always rounded-up to nearest pixel) + ImVec2 Size; // Current size (==SizeFull or collapsed title bar size) + ImVec2 SizeFull; // Size when non collapsed + ImVec2 ContentSize; // Size of contents/scrollable client area (calculated from the extents reach of the cursor) from previous frame. Does not include + // window decoration or window padding. + ImVec2 ContentSizeIdeal; + ImVec2 ContentSizeExplicit; // Size of contents/scrollable client area explicitly request by the user via SetNextWindowContentSize(). + ImVec2 WindowPadding; // Window padding at the time of Begin(). + float WindowRounding; // Window rounding at the time of Begin(). May be clamped lower to avoid rendering artifacts with title bar, menu bar etc. + float WindowBorderSize; // Window border size at the time of Begin(). + float DecoOuterSizeX1, DecoOuterSizeY1; // Left/Up offsets. Sum of non-scrolling outer decorations (X1 generally == 0.0f. Y1 generally = TitleBarHeight + + // MenuBarHeight). Locked during Begin(). + float DecoOuterSizeX2, DecoOuterSizeY2; // Right/Down offsets (X2 generally == ScrollbarSize.x, Y2 == ScrollbarSizes.y). + float DecoInnerSizeX1, DecoInnerSizeY1; // Applied AFTER/OVER InnerRect. Specialized for Tables as they use specialized form of clipping and frozen + // rows/columns are inside InnerRect (and not part of regular decoration sizes). + int NameBufLen; // Size of buffer storing Name. May be larger than strlen(Name)! + ImGuiID MoveId; // == window->GetID("#MOVE") + ImGuiID ChildId; // ID of corresponding item in parent window (for navigation to return from child window to parent window) + ImVec2 Scroll; + ImVec2 ScrollMax; + ImVec2 ScrollTarget; // target scroll position. stored as cursor position with scrolling canceled out, so the highest point is always 0.0f. (FLT_MAX for no + // change) + ImVec2 ScrollTargetCenterRatio; // 0.0f = scroll so that target position is at top, 0.5f = scroll so that target position is centered + ImVec2 ScrollTargetEdgeSnapDist; // 0.0f = no snapping, >0.0f snapping threshold + ImVec2 ScrollbarSizes; // Size taken by each scrollbars on their smaller axis. Pay attention! ScrollbarSizes.x == width of the vertical scrollbar, + // ScrollbarSizes.y = height of the horizontal scrollbar. + bool ScrollbarX, ScrollbarY; // Are scrollbars visible? + bool Active; // Set to true on Begin(), unless Collapsed + bool WasActive; + bool WriteAccessed; // Set to true when any widget access the current window + bool Collapsed; // Set when collapsing window to become only title-bar + bool WantCollapseToggle; + bool SkipItems; // Set when items can safely be all clipped (e.g. window not visible or collapsed) + bool Appearing; // Set during the frame where the window is appearing (or re-appearing) + bool Hidden; // Do not display (== HiddenFrames*** > 0) + bool IsFallbackWindow; // Set on the "Debug##Default" window. + bool IsExplicitChild; // Set when passed _ChildWindow, left to false by BeginDocked() + bool HasCloseButton; // Set when the window has a close button (p_open != NULL) + signed char ResizeBorderHeld; // Current border being held for resize (-1: none, otherwise 0-3) + short BeginCount; // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple Begin/End pairs) + short BeginCountPreviousFrame; // Number of Begin() during the previous frame + short BeginOrderWithinParent; // Begin() order within immediate parent window, if we are a child window. Otherwise 0. + short BeginOrderWithinContext; // Begin() order within entire imgui context. This is mostly used for debugging submission order related issues. + short FocusOrder; // Order within WindowsFocusOrder[], altered when windows are focused. + ImGuiID PopupId; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling) + ImS8 AutoFitFramesX, AutoFitFramesY; + ImS8 AutoFitChildAxises; + bool AutoFitOnlyGrows; + ImGuiDir AutoPosLastDirection; + ImS8 HiddenFramesCanSkipItems; // Hide the window for N frames + ImS8 HiddenFramesCannotSkipItems; // Hide the window for N frames while allowing items to be submitted so we can measure their size + ImS8 HiddenFramesForRenderOnly; // Hide the window until frame N at Render() time only + ImS8 DisableInputsFrames; // Disable window interactions for N frames + ImGuiCond SetWindowPosAllowFlags : 8; // store acceptable condition flags for SetNextWindowPos() use. + ImGuiCond SetWindowSizeAllowFlags : 8; // store acceptable condition flags for SetNextWindowSize() use. + ImGuiCond SetWindowCollapsedAllowFlags : 8; // store acceptable condition flags for SetNextWindowCollapsed() 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. + ImRect InnerRect; // Inner rectangle (omit title bar, menu bar, scroll bar) + ImRect InnerClipRect; // == InnerRect shrunk by WindowPadding*0.5f on each side, clipped within viewport or parent clip rect. + ImRect WorkRect; // Initially covers the whole scrolling region. Reduced by containers e.g columns/tables when active. Shrunk by WindowPadding*1.0f on each + // side. This is meant to replace ContentRegionRect over time (from 1.71+ onward). + ImRect ParentWorkRect; // Backup of WorkRect before entering a container such as columns/tables. Used by e.g. SpanAllColumns functions to easily access. + // Stacked containers are responsible for maintaining this. // FIXME-WORKRECT: Could be a stack? + ImRect ClipRect; // Current clipping/scissoring rectangle, evolve as we are using PushClipRect(), etc. == DrawList->clip_rect_stack.back(). + 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. + float LastTimeActive; // Last timestamp the window was Active (using float as we don't need high precision there) + float ItemWidthDefault; + ImGuiStorage StateStorage; + ImVector ColumnsStorage; + float FontWindowScale; // User scale multiplier per-window, via SetWindowFontScale() + 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. + ImGuiWindow *ParentWindowInBeginStack; + ImGuiWindow *RootWindow; // Point to ourself or first ancestor that is not a child window. Doesn't cross through popups/dock nodes. + ImGuiWindow *RootWindowPopupTree; // Point to ourself or first ancestor that is not a child window. Cross through popups parent<>child. + 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 *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 + +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 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; + float scale = g.FontBaseSize * FontWindowScale; + if (ParentWindow) + scale *= ParentWindow->FontWindowScale; + return scale; + } + float TitleBarHeight() const + { + ImGuiContext &g = *Ctx; + return (Flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : CalcFontSize() + g.Style.FramePadding.y * 2.0f; + } + ImRect TitleBarRect() const + { + return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight())); + } + float MenuBarHeight() const + { + ImGuiContext &g = *Ctx; + return (Flags & ImGuiWindowFlags_MenuBar) ? DC.MenuBarOffset.y + CalcFontSize() + g.Style.FramePadding.y * 2.0f : 0.0f; + } + ImRect MenuBarRect() const + { + float y1 = Pos.y + TitleBarHeight(); + return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight()); + } +}; + +//----------------------------------------------------------------------------- +// [SECTION] Tab bar, Tab item support +//----------------------------------------------------------------------------- + +// Extend ImGuiTabBarFlags_ +enum ImGuiTabBarFlagsPrivate_ { + ImGuiTabBarFlags_DockNode = 1 << 20, // Part of a dock node [we don't use this in the master branch but it facilitate branch syncing to keep this around] + 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_ { + ImGuiTabItemFlags_SectionMask_ = ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing, + ImGuiTabItemFlags_NoCloseButton = + 1 << 20, // Track whether p_open was set or not (we'll need this info on the next frame to recompute ContentWidth during layout) + ImGuiTabItemFlags_Button = 1 << 21, // Used by TabItemButton, change the tab item behavior to mimic a button +}; + +// Storage for one active tab item (sizeof() 40 bytes) +struct ImGuiTabItem { + ImGuiID ID; + ImGuiTabItemFlags Flags; + int LastFrameVisible; + 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 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() 152 bytes) +struct IMGUI_API ImGuiTabBar { + ImVector Tabs; + ImGuiTabBarFlags Flags; + ImGuiID ID; // Zero for tab-bars used by docking + ImGuiID SelectedTabId; // Selected tab/window + ImGuiID NextSelectedTabId; // Next selected tab/window. Will also trigger a scrolling animation + ImGuiID VisibleTabId; // Can occasionally be != SelectedTabId (e.g. when previewing contents for CTRL+TAB preview) + int CurrFrameVisible; + int PrevFrameVisible; + ImRect BarRect; + float CurrTabsContentsHeight; + float PrevTabsContentsHeight; // Record the height of contents submitted below the tab bar + float WidthAllTabs; // Actual width of all tabs (locked during layout) + float WidthAllTabsIdeal; // Ideal width if all tabs were visible and not clipped + float ScrollingAnim; + float ScrollingTarget; + float ScrollingTargetDistToVisibility; + float ScrollingSpeed; + float ScrollingRectMinX; + float ScrollingRectMaxX; + ImGuiID ReorderRequestTabId; + ImS16 ReorderRequestOffset; + ImS8 BeginCount; + 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 + 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; + +// [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. +// This is in contrast with some user-facing api such as IsItemVisible() / IsRectVisible() which use "Visible" to mean "not clipped". +struct ImGuiTableColumn { + ImGuiTableColumnFlags Flags; // Flags after some patching (not directly same as provided by user). See ImGuiTableColumnFlags_ + float WidthGiven; // Final/actual width visible == (MaxX - MinX), locked in TableUpdateLayout(). May be > WidthRequest to honor minimum width, may be < + // WidthRequest to honor shrinking columns down in tight space. + float MinX; // Absolute positions + float MaxX; + float WidthRequest; // Master width absolute value when !(Flags & _WidthStretch). When Stretch this is derived every frame from StretchWeight in + // TableUpdateLayout() + float WidthAuto; // Automatic width + float StretchWeight; // Master width weight when (Flags & _WidthStretch). Often around ~1.0f initially. + float InitStretchWeightOrWidth; // Value passed to TableSetupColumn(). For Width it is a content width (_without padding_). + ImRect ClipRect; // Clipping rectangle for the column + ImGuiID UserID; // Optional, value passed to TableSetupColumn() + float WorkMinX; // Contents region min ~(MinX + CellPaddingX + CellSpacingX1) == cursor start position when entering column + float WorkMaxX; // Contents region max ~(MaxX - CellPaddingX - CellSpacingX2) + float ItemWidth; // Current item width for the column, preserved across rows + float ContentMaxXFrozen; // Contents maximum position for frozen rows (apart from headers), from which we can infer content width. + float ContentMaxXUnfrozen; + float ContentMaxXHeadersUsed; // Contents maximum position for headers rows (regardless of freezing). TableHeader() automatically softclip itself + report + // ideal desired size, to avoid creating extraneous draw calls + float ContentMaxXHeadersIdeal; + ImS16 NameOffset; // Offset into parent ColumnsNames[] + ImGuiTableColumnIdx DisplayOrder; // Index within Table's IndexToDisplayOrder[] (column may be reordered by users) + ImGuiTableColumnIdx IndexWithinEnabledSet; // Index within enabled/visible set (<= IndexToDisplayOrder) + ImGuiTableColumnIdx PrevEnabledColumn; // Index of prev enabled/visible column within Columns[], -1 if first enabled/visible column + ImGuiTableColumnIdx NextEnabledColumn; // Index of next enabled/visible column within Columns[], -1 if last enabled/visible column + ImGuiTableColumnIdx SortOrder; // Index of this column within sort specs, -1 if not sorting on this column, 0 for single-sort, may be >0 on multi-sort + ImGuiTableDrawChannelIdx DrawChannelCurrent; // Index within DrawSplitter.Channels[] + ImGuiTableDrawChannelIdx DrawChannelFrozen; // Draw channels for frozen rows (often headers) + ImGuiTableDrawChannelIdx DrawChannelUnfrozen; // Draw channels for unfrozen rows + bool IsEnabled; // IsUserEnabled && (Flags & ImGuiTableColumnFlags_Disabled) == 0 + bool IsUserEnabled; // Is the column not marked Hidden by the user? (unrelated to being off view, e.g. clipped by scrolling). + bool IsUserEnabledNextFrame; + bool IsVisibleX; // Is actually in view (e.g. overlapping the host window clipping rectangle, not scrolled). + bool IsVisibleY; + bool IsRequestOutput; // Return value for TableSetColumnIndex() / TableNextColumn(): whether we request user to output contents or not. + bool IsSkipItems; // Do we want item submissions to this column to be completely ignored (no layout will happen). + bool IsPreserveWidthAuto; + ImS8 NavLayerCurrent; // ImGuiNavLayer in 1 byte + ImU8 AutoFitQueue; // Queue of 8 values for the next 8 frames to request auto-fit + ImU8 CannotSkipItemsQueue; // Queue of 8 values for the next 8 frames to disable Clipped/SkipItem + ImU8 SortDirection : 2; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending + 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)); + StretchWeight = WidthRequest = -1.0f; + NameOffset = -1; + DisplayOrder = IndexWithinEnabledSet = -1; + PrevEnabledColumn = NextEnabledColumn = -1; + SortOrder = -1; + SortDirection = ImGuiSortDirection_None; + DrawChannelCurrent = DrawChannelFrozen = DrawChannelUnfrozen = (ImU8)-1; + } +}; + +// Transient cell data stored per row. +// sizeof() ~ 6 +struct ImGuiTableCellData { + ImU32 BgColor; // Actual color + ImGuiTableColumnIdx Column; // Column number +}; + +// 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?) +struct ImGuiTableInstanceData { + ImGuiID TableInstanceID; + float LastOuterHeight; // Outer height from last frame + float LastFirstRowHeight; // Height of first row from last frame (FIXME: this is used as "header height" and may be reworked) + 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 = LastFirstRowHeight = LastFrozenHeight = 0.0f; + HoveredRowLast = HoveredRowNext = -1; + } +}; + +// FIXME-TABLE: more transient data could be stored in a stacked ImGuiTableTempData: e.g. SortSpecs, incoming RowData +// sizeof() ~ 580 bytes + heap allocs described in TableBeginInitMemory() +struct IMGUI_API ImGuiTable { + ImGuiID ID; + ImGuiTableFlags Flags; + void *RawData; // Single allocation to hold Columns[], DisplayOrderToIndex[] and RowCellData[] + ImGuiTableTempData *TempData; // Transient data while table is active. Point within g.CurrentTableStack[] + ImSpan Columns; // Point within RawData[] + ImSpan DisplayOrderToIndex; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1) + ImSpan RowCellData; // Point within RawData[]. Store cells background requests for current row. + ImBitArrayPtr EnabledMaskByDisplayOrder; // Column DisplayOrder -> IsEnabled map + ImBitArrayPtr + EnabledMaskByIndex; // Column Index -> IsEnabled map (== not hidden by user/api) in a format adequate for iterating column without touching cold data + ImBitArrayPtr VisibleMaskByIndex; // Column Index -> IsVisibleX|IsVisibleY map (== not hidden by user/api && not hidden by scrolling/cliprect) + ImGuiTableFlags SettingsLoadedFlags; // Which data were loaded from the .ini file (e.g. when order is not altered we won't save order) + int SettingsOffset; // Offset in g.SettingsTables + int LastFrameActive; + int ColumnsCount; // Number of columns declared in BeginTable() + int CurrentRow; + int CurrentColumn; + ImS16 InstanceCurrent; // Count of BeginTable() calls with same ID in the same frame (generally 0). This is a little bit similar to BeginCount for a window, + // but multiple table with same ID look are multiple tables, they are just synched. + ImS16 InstanceInteracted; // Mark which instance (generally 0) of the same ID is being interacted with + float RowPosY1; + float RowPosY2; + float RowMinHeight; // Height submitted to TableNextRow() + float RowCellPaddingY; // Top and bottom padding. Reloaded during row change. + float RowTextBaseline; + float RowIndentOffsetX; + ImGuiTableRowFlags RowFlags : 16; // Current row flags, see ImGuiTableRowFlags_ + ImGuiTableRowFlags LastRowFlags : 16; + int RowBgColorCounter; // Counter for alternating background colors (can be fast-forwarded by e.g clipper), not same as CurrentRow because header rows + // typically don't increase this. + ImU32 RowBgColor[2]; // Background color override for current row. + ImU32 BorderColorStrong; + ImU32 BorderColorLight; + float BorderX1; + float BorderX2; + float HostIndentX; + float MinColumnWidth; + float OuterPaddingX; + float CellPaddingX; // Padding from each borders. Locked in BeginTable()/Layout. + float CellSpacingX1; // Spacing between non-bordered cells. Locked in BeginTable()/Layout. + float CellSpacingX2; + float InnerWidth; // User value passed to BeginTable(), see comments at the top of BeginTable() for details. + float ColumnsGivenWidth; // Sum of current column width + float ColumnsAutoFitWidth; // Sum of ideal column width in order nothing to be clipped, used for auto-fitting and content width submission in outer window + float ColumnsStretchSumWeights; // Sum of weight of all enabled stretching columns + float ResizedColumnNextWidth; + float ResizeLockMinContentsX2; // Lock minimum contents width while resizing down in order to not create feedback loops. But we allow growing the table. + float RefScale; // Reference scale to be able to rescale columns on font/dpi changes. + ImRect OuterRect; // Note: for non-scrolling table, OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable(). + ImRect InnerRect; // InnerRect but without decoration. As with OuterRect, for non-scrolling tables, InnerRect.Max.y is + ImRect WorkRect; + ImRect InnerClipRect; + ImRect BgClipRect; // We use this to cpu-clip cell background color fill, evolve during the frame as we cross frozen rows boundaries + ImRect Bg0ClipRectForDrawCmd; // Actual ImDrawCmd clip rect for BG0/1 channel. This tends to be == OuterWindow->ClipRect at BeginTable() because output in + // BG0/BG1 is cpu-clipped + ImRect Bg2ClipRectForDrawCmd; // Actual ImDrawCmd clip rect for BG2 channel. This tends to be a correct, tight-fit, because output to BG2 are done by + // widgets relying on regular ClipRect. + ImRect HostClipRect; // This is used to check if we can eventually merge our columns draw calls into the current draw call of the current window. + ImRect HostBackupInnerClipRect; // Backup of InnerWindow->ClipRect during PushTableBackground()/PopTableBackground() + ImGuiWindow *OuterWindow; // Parent window for the table + ImGuiWindow *InnerWindow; // Window holding the table data (== OuterWindow or a child window) + ImGuiTextBuffer ColumnsNames; // Contiguous buffer holding columns names + ImDrawListSplitter + *DrawSplitter; // Shortcut to TempData->DrawSplitter while in table. Isolate draw commands per columns to avoid switching clip rect constantly + ImGuiTableInstanceData InstanceDataFirst; + ImVector InstanceDataExtra; // FIXME-OPT: Using a small-vector pattern would be good. + ImGuiTableColumnSortSpecs SortSpecsSingle; + ImVector SortSpecsMulti; // FIXME-OPT: Using a small-vector pattern would be good. + ImGuiTableSortSpecs SortSpecs; // Public facing sorts specs, this is what we return in TableGetSortSpecs() + ImGuiTableColumnIdx SortSpecsCount; + ImGuiTableColumnIdx ColumnsEnabledCount; // Number of enabled columns (<= ColumnsCount) + ImGuiTableColumnIdx ColumnsEnabledFixedCount; // Number of enabled columns (<= ColumnsCount) + ImGuiTableColumnIdx DeclColumnsCount; // Count calls to TableSetupColumn() + ImGuiTableColumnIdx HoveredColumnBody; // Index of column whose visible region is being hovered. Important: == ColumnsCount when hovering empty region after + // the right-most column! + ImGuiTableColumnIdx HoveredColumnBorder; // Index of column whose right-border is being hovered (for resizing). + ImGuiTableColumnIdx AutoFitSingleColumn; // Index of single column requesting auto-fit. + ImGuiTableColumnIdx ResizedColumn; // Index of column being resized. Reset when InstanceCurrent==0. + ImGuiTableColumnIdx LastResizedColumn; // Index of column being resized from previous frame. + ImGuiTableColumnIdx HeldHeaderColumn; // Index of column header being held. + ImGuiTableColumnIdx ReorderColumn; // Index of column being reordered. (not cleared) + ImGuiTableColumnIdx ReorderColumnDir; // -1 or +1 + ImGuiTableColumnIdx LeftMostEnabledColumn; // Index of left-most non-hidden column. + ImGuiTableColumnIdx RightMostEnabledColumn; // Index of right-most non-hidden column. + ImGuiTableColumnIdx LeftMostStretchedColumn; // Index of left-most stretched column. + ImGuiTableColumnIdx RightMostStretchedColumn; // Index of right-most stretched column. + ImGuiTableColumnIdx ContextPopupColumn; // Column right-clicked on, of -1 if opening context menu from a neutral/empty spot + ImGuiTableColumnIdx FreezeRowsRequest; // Requested frozen rows count + ImGuiTableColumnIdx FreezeRowsCount; // Actual frozen row count (== FreezeRowsRequest, or == 0 when no scrolling offset) + ImGuiTableColumnIdx FreezeColumnsRequest; // Requested frozen columns count + ImGuiTableColumnIdx FreezeColumnsCount; // Actual frozen columns count (== FreezeColumnsRequest, or == 0 when no scrolling offset) + ImGuiTableColumnIdx RowCellDataCurrent; // Index of current RowCellData[] entry in current row + ImGuiTableDrawChannelIdx DummyDrawChannel; // Redirect non-visible columns here. + ImGuiTableDrawChannelIdx + Bg2DrawChannelCurrent; // For Selectable() and other widgets drawing across columns after the freezing line. Index within DrawSplitter.Channels[] + ImGuiTableDrawChannelIdx Bg2DrawChannelUnfrozen; + bool IsLayoutLocked; // Set by TableUpdateLayout() which is called when beginning the first row. + bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow(). + bool IsInitializing; + 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 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) + bool IsResetAllRequest; + bool IsResetDisplayOrderRequest; + bool IsUnfrozenRows; // Set when we got past the frozen row. + bool IsDefaultSizingPolicy; // Set if user didn't explicitly set a sizing policy in BeginTable() + bool HasScrollbarYCurr; // Whether ANY instance of this table had a vertical scrollbar during the current frame. + 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. +// sizeof() ~ 112 bytes. +struct IMGUI_API ImGuiTableTempData { + int TableIndex; // Index in g.Tables.Buf[] pool + float LastTimeActive; // Last timestamp this structure was used + + 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() + ImVec2 HostBackupCurrLineSize; // Backup of InnerWindow->DC.CurrLineSize at the end of BeginTable() + ImVec2 HostBackupCursorMaxPos; // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable() + 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() ~ 12 +struct ImGuiTableColumnSettings { + float WidthOrWeight; + ImGuiID UserID; + ImGuiTableColumnIdx Index; + ImGuiTableColumnIdx DisplayOrder; + ImGuiTableColumnIdx SortOrder; + ImU8 SortDirection : 2; + ImU8 IsEnabled : 1; // "Visible" in ini file + ImU8 IsStretch : 1; + + ImGuiTableColumnSettings() + { + WidthOrWeight = 0.0f; + UserID = 0; + Index = -1; + DisplayOrder = SortOrder = -1; + SortDirection = ImGuiSortDirection_None; + IsEnabled = 1; + IsStretch = 0; + } +}; + +// This is designed to be stored in a single ImChunkStream (1 header followed by N ImGuiTableColumnSettings, etc.) +struct ImGuiTableSettings { + ImGuiID ID; // Set to 0 to invalidate/delete the setting + ImGuiTableFlags SaveFlags; // Indicate data we want to save using the Resizable/Reorderable/Sortable/Hideable flags (could be using its own flags..) + float RefScale; // Reference scale to be able to rescale columns on font/dpi changes. + 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 +// We should always have a CurrentWindow in the stack (there is an implicit "Debug" window) +// If this ever crash because g.CurrentWindow is NULL it means that either +// - ImGui::NewFrame() has never been called, which is illegal. +// - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal. +inline ImGuiWindow *GetCurrentWindowRead() +{ + ImGuiContext &g = *GImGui; + return g.CurrentWindow; +} +inline ImGuiWindow *GetCurrentWindow() +{ + ImGuiContext &g = *GImGui; + g.CurrentWindow->WriteAccessed = true; + return g.CurrentWindow; +} +IMGUI_API ImGuiWindow *FindWindowByID(ImGuiID id); +IMGUI_API ImGuiWindow *FindWindowByName(const char *name); +IMGUI_API void UpdateWindowParentAndRootLinks(ImGuiWindow *window, ImGuiWindowFlags flags, ImGuiWindow *parent_window); +IMGUI_API ImVec2 CalcWindowNextAutoFitSize(ImGuiWindow *window); +IMGUI_API bool IsWindowChildOf(ImGuiWindow *window, ImGuiWindow *potential_parent, bool popup_hierarchy); +IMGUI_API bool IsWindowWithinBeginStackOf(ImGuiWindow *window, ImGuiWindow *potential_parent); +IMGUI_API bool IsWindowAbove(ImGuiWindow *potential_above, ImGuiWindow *potential_below); +IMGUI_API bool IsWindowNavFocusable(ImGuiWindow *window); +IMGUI_API void SetWindowPos(ImGuiWindow *window, const ImVec2 &pos, ImGuiCond cond = 0); +IMGUI_API void SetWindowSize(ImGuiWindow *window, const ImVec2 &size, ImGuiCond cond = 0); +IMGUI_API void SetWindowCollapsed(ImGuiWindow *window, bool collapsed, ImGuiCond cond = 0); +IMGUI_API void SetWindowHitTestHole(ImGuiWindow *window, const ImVec2 &pos, const ImVec2 &size); +IMGUI_API void SetWindowHiddendAndSkipItemsForCurrentFrame(ImGuiWindow *window); +inline ImRect WindowRectAbsToRel(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 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 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); +IMGUI_API void BringWindowToFocusFront(ImGuiWindow *window); +IMGUI_API void BringWindowToDisplayFront(ImGuiWindow *window); +IMGUI_API void BringWindowToDisplayBack(ImGuiWindow *window); +IMGUI_API void BringWindowToDisplayBehind(ImGuiWindow *window, ImGuiWindow *above_window); +IMGUI_API int FindWindowDisplayIndex(ImGuiWindow *window); +IMGUI_API ImGuiWindow *FindBottomMostVisibleWindowWithinBeginStack(ImGuiWindow *window); + +// 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]; +} +inline ImDrawList *GetForegroundDrawList(ImGuiWindow *window) +{ + IM_UNUSED(window); + return GetForegroundDrawList(); +} // This seemingly unnecessary wrapper simplifies compatibility between the 'master' and 'docking' branches. +IMGUI_API ImDrawList *GetBackgroundDrawList(ImGuiViewport *viewport); // get background draw list for the given viewport. 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); // get foreground draw list for the given viewport. this draw list will be the last + // rendered one. Useful to quickly draw shapes/text over dear imgui contents. +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 StartMouseMovingWindow(ImGuiWindow *window); +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 SetWindowViewport(ImGuiWindow *window, ImGuiViewportP *viewport); + +// Settings +IMGUI_API void MarkIniSettingsDirty(); +IMGUI_API void MarkIniSettingsDirty(ImGuiWindow *window); +IMGUI_API void ClearIniSettings(); +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); +IMGUI_API ImVec2 ScrollToRectEx(ImGuiWindow *window, const ImRect &rect, ImGuiScrollFlags flags = 0); +// #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.InFlags; +} +inline ImGuiID GetActiveID() +{ + ImGuiContext &g = *GImGui; + return g.ActiveId; +} +inline ImGuiID GetFocusID() +{ + ImGuiContext &g = *GImGui; + return g.NavId; +} +IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow *window); +IMGUI_API void SetFocusID(ImGuiID id, ImGuiWindow *window); +IMGUI_API void ClearActiveID(); +IMGUI_API ImGuiID GetHoveredID(); +IMGUI_API void SetHoveredID(ImGuiID id); +IMGUI_API void KeepAliveID(ImGuiID id); +IMGUI_API void MarkItemEdited(ImGuiID id); // Mark data associated to given item as "edited", used by IsItemDeactivatedAfterEdit() function. +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. +IMGUI_API bool ItemAdd(const ImRect &bb, ImGuiID id, const ImRect *nav_bb = NULL, ImGuiItemFlags extra_flags = 0); +IMGUI_API bool ItemHoverable(const ImRect &bb, ImGuiID id, ImGuiItemFlags item_flags); +IMGUI_API bool IsWindowContentHoverable(ImGuiWindow *window, ImGuiHoveredFlags flags = 0); +IMGUI_API bool IsClippedEx(const ImRect &bb, ImGuiID id); +IMGUI_API void SetLastItemData(ImGuiID item_id, ImGuiItemFlags in_flags, ImGuiItemStatusFlags status_flags, const ImRect &item_rect); +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 bool IsItemToggledSelection(); // Was the last item selection toggled? (after Selectable(), TreeNode() etc. We only returns toggle _event_ in order to + // handle clipping correctly) +IMGUI_API ImVec2 GetContentRegionMaxAbs(); +IMGUI_API void ShrinkWidths(ImGuiShrinkWidthItem *items, int count, float width_excess); + +// Parameter stacks (shared) +IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); +IMGUI_API void PopItemFlag(); +IMGUI_API const ImGuiDataVarInfo *GetStyleVarInfo(ImGuiStyleVar idx); + +// Logging/Capture +IMGUI_API void LogBegin(ImGuiLogType type, 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); + +// Popups, Modals, Tooltips +IMGUI_API bool BeginChildEx(const char *name, ImGuiID id, const ImVec2 &size_arg, bool border, ImGuiWindowFlags flags); +IMGUI_API void OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags = ImGuiPopupFlags_None); +IMGUI_API void ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup); +IMGUI_API void ClosePopupsOverWindow(ImGuiWindow *ref_window, bool restore_focus_to_window_under_popup); +IMGUI_API void ClosePopupsExceptModals(); +IMGUI_API bool IsPopupOpen(ImGuiID id, ImGuiPopupFlags popup_flags); +IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags); +IMGUI_API bool BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags extra_window_flags); +IMGUI_API ImRect GetPopupAllowedExtentRect(ImGuiWindow *window); +IMGUI_API ImGuiWindow *GetTopMostPopupModal(); +IMGUI_API ImGuiWindow *GetTopMostAndVisiblePopupModal(); +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); + +// 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(); + +// Gamepad/Keyboard Navigation +IMGUI_API void NavInitWindow(ImGuiWindow *window, bool force_reinit); +IMGUI_API void NavInitRequestApplyResult(); +IMGUI_API bool NavMoveRequestButNoResultYet(); +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, ImGuiNavTreeNodeData *tree_node_data); +IMGUI_API void NavMoveRequestCancel(); +IMGUI_API void NavMoveRequestApplyResult(); +IMGUI_API void NavMoveRequestTryWrapping(ImGuiWindow *window, ImGuiNavMoveFlags move_flags); +IMGUI_API void NavClearPreferredPosForAxis(ImGuiAxis axis); +IMGUI_API void NavUpdateCurrentWindowIsScrollPushableX(); +IMGUI_API void SetNavWindow(ImGuiWindow *window); +IMGUI_API void SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect &rect_rel); + +// 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. + +// 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; +} +inline bool IsNamedKeyOrModKey(ImGuiKey key) +{ + return (key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END) || key == ImGuiMod_Ctrl || key == ImGuiMod_Shift || key == ImGuiMod_Alt + || key == ImGuiMod_Super || key == ImGuiMod_Shortcut; +} +inline bool IsLegacyKey(ImGuiKey key) +{ + return key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_LegacyNativeKey_END; +} +inline bool IsKeyboardKey(ImGuiKey key) +{ + return key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END; +} +inline bool IsGamepadKey(ImGuiKey key) +{ + return key >= ImGuiKey_Gamepad_BEGIN && key < ImGuiKey_Gamepad_END; +} +inline bool IsMouseKey(ImGuiKey key) +{ + return key >= ImGuiKey_Mouse_BEGIN && key < ImGuiKey_Mouse_END; +} +inline bool IsAliasKey(ImGuiKey key) +{ + return key >= ImGuiKey_Aliases_BEGIN && key < ImGuiKey_Aliases_END; +} +inline ImGuiKeyChord ConvertShortcutMod(ImGuiKeyChord key_chord) +{ + ImGuiContext &g = *GImGui; + IM_ASSERT_PARANOID(key_chord & ImGuiMod_Shortcut); + return (key_chord & ~ImGuiMod_Shortcut) | (g.IO.ConfigMacOSXBehaviors ? ImGuiMod_Super : ImGuiMod_Ctrl); +} +inline ImGuiKey ConvertSingleModFlagToKey(ImGuiContext *ctx, ImGuiKey key) +{ + ImGuiContext &g = *ctx; + if (key == ImGuiMod_Ctrl) + return ImGuiKey_ReservedForModCtrl; + if (key == ImGuiMod_Shift) + return ImGuiKey_ReservedForModShift; + if (key == ImGuiMod_Alt) + return ImGuiKey_ReservedForModAlt; + if (key == ImGuiMod_Super) + return ImGuiKey_ReservedForModSuper; + if (key == ImGuiMod_Shortcut) + return (g.IO.ConfigMacOSXBehaviors ? ImGuiKey_ReservedForModSuper : ImGuiKey_ReservedForModCtrl); + return key; +} + +IMGUI_API ImGuiKeyData *GetKeyData(ImGuiContext *ctx, ImGuiKey key); +inline ImGuiKeyData *GetKeyData(ImGuiKey key) +{ + ImGuiContext &g = *GImGui; + return GetKeyData(&g, key); +} +IMGUI_API void GetKeyChordName(ImGuiKeyChord key_chord, char *out_buf, int out_buf_size); +inline ImGuiKey MouseButtonToKey(ImGuiMouseButton button) +{ + IM_ASSERT(button >= 0 && button < ImGuiMouseButton_COUNT); + return (ImGuiKey)(ImGuiKey_MouseLeft + button); +} +IMGUI_API bool IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_threshold = -1.0f); +IMGUI_API ImVec2 GetKeyMagnitude2d(ImGuiKey key_left, ImGuiKey key_right, ImGuiKey key_up, ImGuiKey key_down); +IMGUI_API float GetNavTweakPressedAmount(ImGuiAxis axis); +IMGUI_API int CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, float repeat_rate); +IMGUI_API void GetTypematicRepeatRate(ImGuiInputFlags flags, float *repeat_delay, float *repeat_rate); +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). +// - Input queries can then read input by specifying ImGuiKeyOwner_Any (== 0), ImGuiKeyOwner_None (== -1) or a custom ID. +// - Legacy input queries (without specifying an owner or _Any or _None) are equivalent to using ImGuiKeyOwner_Any (== 0). +// - Input ownership is automatically released on the frame after a key is released. Therefore: +// - for ownership registration happening as a result of a down/press event, the SetKeyOwner() call may be done once (common case). +// - for ownership registration happening ahead of a down/press event, the SetKeyOwner() call needs to be made every frame (happens if e.g. claiming ownership +// on hover). +// - SetItemKeyOwner() is a shortcut for common simple case. A custom widget will probably want to call SetKeyOwner() multiple times directly based on its +// interaction state. +// - This is marked experimental because not all widgets are fully honoring the Set/Test idioms. We will need to move forward step by step. +// Please open a GitHub Issue to submit your usage scenario or if there's a use case you need solved. +IMGUI_API ImGuiID GetKeyOwner(ImGuiKey key); +IMGUI_API void SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0); +IMGUI_API void SetKeyOwnersForKeyChord(ImGuiKeyChord key, ImGuiID owner_id, ImGuiInputFlags flags = 0); +IMGUI_API void SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags = 0); // 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(ctx, 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) +// - Specifying a value for 'ImGuiID owner' will test that EITHER the key is NOT owned (UNLESS locked), EITHER the key is owned by 'owner'. +// Legacy functions use ImGuiKeyOwner_Any meaning that they typically ignore ownership, unless a call to SetKeyOwner() explicitly used +// ImGuiInputFlags_LockThisFrame or ImGuiInputFlags_LockUntilRelease. +// - Binding generators may want to ignore those for now, or suffix them with Ex() until we decide if this gets moved into public API. +IMGUI_API bool IsKeyDown(ImGuiKey key, ImGuiID owner_id); +IMGUI_API bool IsKeyPressed(ImGuiKey key, + ImGuiID owner_id, + ImGuiInputFlags flags = 0); // Important: when transitioning from old to new IsKeyPressed(): old API has "bool repeat = true", so + // would default to repeat. New API requiress explicit ImGuiInputFlags_Repeat. +IMGUI_API bool IsKeyReleased(ImGuiKey key, ImGuiID owner_id); +IMGUI_API bool IsMouseDown(ImGuiMouseButton button, ImGuiID owner_id); +IMGUI_API bool IsMouseClicked(ImGuiMouseButton button, ImGuiID owner_id, ImGuiInputFlags flags = 0); +IMGUI_API bool IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id); + +// [EXPERIMENTAL] Shortcut Routing +// - ImGuiKeyChord = a ImGuiKey optionally OR-red with ImGuiMod_Alt/ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Super. +// ImGuiKey_C (accepted by functions taking ImGuiKey or ImGuiKeyChord) +// ImGuiKey_C | ImGuiMod_Ctrl (accepted by functions taking ImGuiKeyChord) +// ONLY ImGuiMod_XXX values are legal to 'OR' with an ImGuiKey. You CANNOT 'OR' two ImGuiKey values. +// - When using one of the routing flags (e.g. ImGuiInputFlags_RouteFocused): routes requested ahead of time given a chord (key + modifiers) and a routing +// policy. +// - Routes are resolved during NewFrame(): if keyboard modifiers are matching current ones: SetKeyOwner() is called + route is granted for the frame. +// - Route is granted to a single owner. When multiple requests are made we have policies to select the winning route. +// - Multiple read sites may use the same owner id and will all get the granted route. +// - For routing: when owner_id is 0 we use the current Focus Scope ID as a default owner in order to identify our location. +IMGUI_API bool Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0); +IMGUI_API bool SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0); +IMGUI_API bool TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id); +IMGUI_API ImGuiKeyRoutingData *GetShortcutRoutingData(ImGuiKeyChord key_chord); + +// [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: +// - Selection patterns generally need to react (e.g. clear a selection) when landing on one item of the set. +// So in order to identify a set multiple lists in same window may each need a focus scope. +// If you imagine an hypothetical BeginSelectionGroup()/EndSelectionGroup() api, it would likely call PushFocusScope()/EndFocusScope() +// - Shortcut routing also use focus scope as a default location identifier if an owner is not provided. +// We don't use the ID Stack for this as it is common to want them separate. +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); + +// 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(). +IMGUI_API void EndColumns(); // close columns +IMGUI_API void PushColumnClipRect(int column_index); +IMGUI_API void PushColumnsBackground(); +IMGUI_API void PopColumnsBackground(); +IMGUI_API ImGuiID GetColumnsID(const char *str_id, int count); +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); +IMGUI_API void TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs); +IMGUI_API int TableGetHoveredColumn(); // May use (TableGetColumnFlags() & ImGuiTableColumnFlags_IsHovered) instead. 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. +IMGUI_API int +TableGetHoveredRow(); // Retrieve *PREVIOUS FRAME* hovered row. This difference with TableGetHoveredColumn() is the reason why this is not public yet. +IMGUI_API float TableGetHeaderRowHeight(); +IMGUI_API void TablePushBackgroundChannel(); +IMGUI_API void TablePopBackgroundChannel(); + +// Tables: Internals +inline ImGuiTable *GetCurrentTable() +{ + ImGuiContext &g = *GImGui; + return g.CurrentTable; +} +IMGUI_API ImGuiTable *TableFindByID(ImGuiID id); +IMGUI_API bool +BeginTableEx(const char *name, ImGuiID id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2 &outer_size = ImVec2(0, 0), float inner_width = 0.0f); +IMGUI_API void TableBeginInitMemory(ImGuiTable *table, int columns_count); +IMGUI_API void TableBeginApplyRequests(ImGuiTable *table); +IMGUI_API void TableSetupDrawChannels(ImGuiTable *table); +IMGUI_API void TableUpdateLayout(ImGuiTable *table); +IMGUI_API void TableUpdateBorders(ImGuiTable *table); +IMGUI_API void TableUpdateColumnsWeightFromWidth(ImGuiTable *table); +IMGUI_API void TableDrawBorders(ImGuiTable *table); +IMGUI_API void TableDrawContextMenu(ImGuiTable *table); +IMGUI_API bool TableBeginContextMenuPopup(ImGuiTable *table); +IMGUI_API void TableMergeDrawChannels(ImGuiTable *table); +inline ImGuiTableInstanceData *TableGetInstanceData(ImGuiTable *table, int instance_no) +{ + if (instance_no == 0) + return &table->InstanceDataFirst; + return &table->InstanceDataExtra[instance_no - 1]; +} +inline ImGuiID TableGetInstanceID(ImGuiTable *table, int instance_no) +{ + return TableGetInstanceData(table, instance_no)->TableInstanceID; +} +IMGUI_API void TableSortSpecsSanitize(ImGuiTable *table); +IMGUI_API void TableSortSpecsBuild(ImGuiTable *table); +IMGUI_API ImGuiSortDirection TableGetColumnNextSortDirection(ImGuiTableColumn *column); +IMGUI_API void TableFixColumnSortDirection(ImGuiTable *table, ImGuiTableColumn *column); +IMGUI_API float TableGetColumnWidthAuto(ImGuiTable *table, ImGuiTableColumn *column); +IMGUI_API void TableBeginRow(ImGuiTable *table); +IMGUI_API void TableEndRow(ImGuiTable *table); +IMGUI_API void TableBeginCell(ImGuiTable *table, int column_n); +IMGUI_API void TableEndCell(ImGuiTable *table); +IMGUI_API ImRect TableGetCellBgRect(const ImGuiTable *table, int column_n); +IMGUI_API const char *TableGetColumnName(const ImGuiTable *table, int column_n); +IMGUI_API ImGuiID TableGetColumnResizeID(ImGuiTable *table, int column_n, int instance_no = 0); +IMGUI_API float TableGetMaxColumnWidth(const ImGuiTable *table, int column_n); +IMGUI_API void TableSetColumnWidthAutoSingle(ImGuiTable *table, int column_n); +IMGUI_API void TableSetColumnWidthAutoAll(ImGuiTable *table); +IMGUI_API void TableRemove(ImGuiTable *table); +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); +IMGUI_API void TableResetSettings(ImGuiTable *table); +IMGUI_API ImGuiTableSettings *TableGetBoundSettings(ImGuiTable *table); +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); +IMGUI_API ImGuiTabItem *TabBarFindTabByID(ImGuiTabBar *tab_bar, ImGuiID tab_id); +IMGUI_API ImGuiTabItem *TabBarFindTabByOrder(ImGuiTabBar *tab_bar, int order); +IMGUI_API ImGuiTabItem *TabBarGetCurrentTab(ImGuiTabBar *tab_bar); +inline int TabBarGetTabOrder(ImGuiTabBar *tab_bar, ImGuiTabItem *tab) +{ + return tab_bar->Tabs.index_from_ptr(tab); +} +IMGUI_API const char *TabBarGetTabName(ImGuiTabBar *tab_bar, ImGuiTabItem *tab); +IMGUI_API void TabBarRemoveTab(ImGuiTabBar *tab_bar, ImGuiID tab_id); +IMGUI_API void TabBarCloseTab(ImGuiTabBar *tab_bar, ImGuiTabItem *tab); +IMGUI_API void TabBarQueueFocus(ImGuiTabBar *tab_bar, ImGuiTabItem *tab); +IMGUI_API void TabBarQueueReorder(ImGuiTabBar *tab_bar, ImGuiTabItem *tab, int offset); +IMGUI_API void TabBarQueueReorderFromMousePos(ImGuiTabBar *tab_bar, ImGuiTabItem *tab, ImVec2 mouse_pos); +IMGUI_API bool TabBarProcessReorder(ImGuiTabBar *tab_bar); +IMGUI_API bool TabItemEx(ImGuiTabBar *tab_bar, const char *label, bool *p_open, ImGuiTabItemFlags flags, ImGuiWindow *docked_window); +IMGUI_API ImVec2 TabItemCalcSize(const char *label, bool has_close_button_or_unsaved_marker); +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) +IMGUI_API void RenderText(ImVec2 pos, const char *text, const char *text_end = NULL, bool hide_text_after_hash = true); +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 RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = 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); +IMGUI_API void RenderNavHighlight(const ImRect &bb, ImGuiID id, ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_TypeDefault); // Navigation highlight +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); +IMGUI_API void RenderCheckMark(ImDrawList *draw_list, ImVec2 pos, ImU32 col, float sz); +IMGUI_API void RenderArrowPointingAt(ImDrawList *draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col); +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); + +// Widgets +IMGUI_API void TextEx(const char *text, const char *text_end = NULL, ImGuiTextFlags flags = 0); +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 texture_id, + const ImVec2 &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); +IMGUI_API void Scrollbar(ImGuiAxis axis); +IMGUI_API bool ScrollbarEx(const ImRect &bb, ImGuiID id, ImGuiAxis axis, ImS64 *p_scroll_v, ImS64 avail_v, ImS64 contents_v, ImDrawFlags flags); +IMGUI_API ImRect GetWindowScrollbarRect(ImGuiWindow *window, ImGuiAxis axis); +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); +IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char *label, const char *label_end = NULL); +IMGUI_API void TreePushOverrideID(ImGuiID id); +IMGUI_API void TreeNodeSetOpen(ImGuiID id, bool open); +IMGUI_API bool +TreeNodeUpdateNextOpen(ImGuiID 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); " +template +IMGUI_API float +ScaleRatioFromValueT(ImGuiDataType data_type, T v, T v_min, T v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_size); +template +IMGUI_API T +ScaleValueFromRatioT(ImGuiDataType data_type, float t, T v_min, T v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_size); +template +IMGUI_API bool DragBehaviorT(ImGuiDataType data_type, T *v, float v_speed, T v_min, T v_max, const char *format, ImGuiSliderFlags flags); +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); +IMGUI_API void DataTypeApplyOp(ImGuiDataType data_type, int op, void *output, const void *arg_1, const void *arg_2); +IMGUI_API bool DataTypeApplyFromText(const char *buf, ImGuiDataType data_type, void *p_data, const char *format); +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); + +// 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); +IMGUI_API bool TempInputText(const ImRect &bb, ImGuiID id, const char *label, char *buf, int buf_size, ImGuiInputTextFlags flags); +IMGUI_API bool TempInputScalar(const ImRect &bb, + ImGuiID id, + const char *label, + ImGuiDataType data_type, + void *p_data, + const char *format, + const void *p_clamp_min = NULL, + const void *p_clamp_max = NULL); +inline bool TempInputIsActive(ImGuiID id) +{ + ImGuiContext &g = *GImGui; + return (g.ActiveId == id && g.TempInputId == id); +} +inline ImGuiInputTextState *GetInputTextState(ImGuiID id) +{ + ImGuiContext &g = *GImGui; + return (id != 0 && g.InputTextState.ID == id) ? &g.InputTextState : NULL; +} // Get input text state if active + +// 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); + +// Garbage collection +IMGUI_API void GcCompactTransientMiscBuffers(); +IMGUI_API void GcCompactTransientWindowBuffers(ImGuiWindow *window); +IMGUI_API void GcAwakeTransientWindowBuffers(ImGuiWindow *window); + +// Debug Log +IMGUI_API void DebugLog(const char *fmt, ...) IM_FMTARGS(1); +IMGUI_API void DebugLogV(const char *fmt, va_list args) IM_FMTLIST(1); + +// Debug Tools +IMGUI_API void ErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback, void *user_data = NULL); +IMGUI_API void ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, void *user_data = NULL); +IMGUI_API void ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); +IMGUI_API void DebugDrawCursorPos(ImU32 col = IM_COL32(255, 0, 0, 255)); +IMGUI_API void DebugDrawLineExtents(ImU32 col = IM_COL32(255, 0, 0, 255)); +IMGUI_API void DebugDrawItemRect(ImU32 col = IM_COL32(255, 0, 0, 255)); +IMGUI_API void DebugLocateItem(ImGuiID target_id); // Call sparingly: only 1 at the same time! +IMGUI_API void DebugLocateItemOnHover(ImGuiID target_id); // Only call on reaction to a mouse Hover: because only 1 at the same time! +IMGUI_API void DebugLocateItemResolveWithLastItem(); +inline void DebugStartItemPicker() +{ + ImGuiContext &g = *GImGui; + g.DebugItemPickerActive = true; +} +IMGUI_API void ShowFontAtlas(ImFontAtlas *atlas); +IMGUI_API void DebugHookIdInfo(ImGuiID id, ImGuiDataType data_type, const void *data_id, const void *data_id_end); +IMGUI_API void DebugNodeColumns(ImGuiOldColumns *columns); +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 DebugNodeFontGlyph(ImFont *font, const ImFontGlyph *glyph); +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); +IMGUI_API void DebugNodeTableSettings(ImGuiTableSettings *settings); +IMGUI_API void DebugNodeInputTextState(ImGuiInputTextState *state); +IMGUI_API void DebugNodeWindow(ImGuiWindow *window, const char *label); +IMGUI_API void DebugNodeWindowSettings(ImGuiWindowSettings *settings); +IMGUI_API void DebugNodeWindowsList(ImVector *windows, const char *label); +IMGUI_API void DebugNodeWindowsListByBeginStackParent(ImGuiWindow **windows, int windows_size, ImGuiWindow *parent_in_begin_stack); +IMGUI_API void DebugNodeViewport(ImGuiViewportP *viewport); +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 + +// Refactored focus/nav/tabbing system in 1.82 and 1.84. If you have old/custom copy-and-pasted widgets that 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 = (GetItemStatusFlags() & +// ImGuiItemStatusFlags_Focused) != 0' (New) IMGUI_VERSION_NUM >= 18413: using 'ItemAdd(..., ImGuiItemFlags_Inputable)' and 'bool tab_focused = +// (GetItemStatusFlags() & ImGuiItemStatusFlags_FocusedTabbing) != 0 || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput))' (WIP) +// Widget code are simplified as there's no need to call FocusableItemUnregister() while managing the transition from regular widget to TempInputText() +inline bool FocusableItemRegister(ImGuiWindow *window, ImGuiID id) +{ + IM_ASSERT(0); + IM_UNUSED(window); + IM_UNUSED(id); + return false; +} // -> pass ImGuiItemAddFlags_Inputable flag to ItemAdd() +inline void FocusableItemUnregister(ImGuiWindow *window) +{ + IM_ASSERT(0); + IM_UNUSED(window); +} // -> unnecessary: TempInputText() uses ImGuiInputTextFlags_MergedItem +#endif +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO +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! +#endif + +} // namespace ImGui + +//----------------------------------------------------------------------------- +// [SECTION] ImFontAtlas internal API +//----------------------------------------------------------------------------- + +// This structure is likely to evolve as we add support for incremental atlas updates +struct ImFontBuilderIO { + bool (*FontBuilder_Build)(ImFontAtlas *atlas); +}; + +// Helper for font builder +#ifdef IMGUI_ENABLE_STB_TRUETYPE +IMGUI_API const ImFontBuilderIO *ImFontAtlasGetBuilderForStbTruetype(); +#endif +IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas *atlas); +IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas *atlas, ImFont *font, ImFontConfig *font_config, 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); + +//----------------------------------------------------------------------------- +// [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) +#define IMGUI_TEST_ENGINE_LOG(_FMT, ...) \ + if (g.TestEngineHookItems) \ + ImGuiTestEngineHook_Log(&g, _FMT, __VA_ARGS__) // Custom log entry from user land into test log +#else +#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/extern/imgui/src/imgui_tables.cpp b/extern/imgui/src/imgui_tables.cpp new file mode 100644 index 0000000..bd0306f --- /dev/null +++ b/extern/imgui/src/imgui_tables.cpp @@ -0,0 +1,4178 @@ +// dear imgui, v1.89.9 +// (tables and columns code) + +/* + +Index of this file: + +// [SECTION] Commentary +// [SECTION] Header mess +// [SECTION] Tables: Main code +// [SECTION] Tables: Simple accessors +// [SECTION] Tables: Row changes +// [SECTION] Tables: Columns changes +// [SECTION] Tables: Columns width management +// [SECTION] Tables: Drawing +// [SECTION] Tables: Sorting +// [SECTION] Tables: Headers +// [SECTION] Tables: Context Menu +// [SECTION] Tables: Settings (.ini data) +// [SECTION] Tables: Garbage Collection +// [SECTION] Tables: Debugging +// [SECTION] Columns, BeginColumns, EndColumns, etc. + +*/ + +// Navigating this file: +// - In Visual Studio IDE: CTRL+comma ("Edit.GoToAll") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// - With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. + +//----------------------------------------------------------------------------- +// [SECTION] Commentary +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Typical tables call flow: (root level is generally public API): +//----------------------------------------------------------------------------- +// - BeginTable() user begin into a table +// | BeginChild() - (if ScrollX/ScrollY is set) +// | TableBeginInitMemory() - first time table is used +// | TableResetSettings() - on settings reset +// | TableLoadSettings() - on settings load +// | TableBeginApplyRequests() - apply queued resizing/reordering/hiding requests +// | - TableSetColumnWidth() - apply resizing width (for mouse resize, often requested by previous frame) +// | - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of stretch columns) from their respective width +// - TableSetupColumn() user submit columns details (optional) +// - TableSetupScrollFreeze() user submit scroll freeze information (optional) +//----------------------------------------------------------------------------- +// - TableUpdateLayout() [Internal] followup to BeginTable(): setup everything: widths, columns positions, clipping rectangles. Automatically called +// by the FIRST call to TableNextRow() or TableHeadersRow(). +// | TableSetupDrawChannels() - setup ImDrawList channels +// | TableUpdateBorders() - detect hovering columns for resize, ahead of contents submission +// | TableDrawContextMenu() - draw right-click context menu +//----------------------------------------------------------------------------- +// - TableHeadersRow() or TableHeader() user submit a headers row (optional) +// | TableSortSpecsClickColumn() - when left-clicked: alter sort order and sort direction +// | TableOpenContextMenu() - when right-clicked: trigger opening of the default context menu +// - TableGetSortSpecs() user queries updated sort specs (optional, generally after submitting headers) +// - TableNextRow() user begin into a new row (also automatically called by TableHeadersRow()) +// | TableEndRow() - finish existing row +// | TableBeginRow() - add a new row +// - TableSetColumnIndex() / TableNextColumn() user begin into a cell +// | TableEndCell() - close existing column/cell +// | TableBeginCell() - enter into current column/cell +// - [...] user emit contents +//----------------------------------------------------------------------------- +// - EndTable() user ends the table +// | TableDrawBorders() - draw outer borders, inner vertical borders +// | TableMergeDrawChannels() - merge draw channels if clipping isn't required +// | EndChild() - (if ScrollX/ScrollY is set) +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// TABLE SIZING +//----------------------------------------------------------------------------- +// (Read carefully because this is subtle but it does make sense!) +//----------------------------------------------------------------------------- +// About 'outer_size': +// Its meaning needs to differ slightly depending on if we are using ScrollX/ScrollY flags. +// Default value is ImVec2(0.0f, 0.0f). +// X +// - outer_size.x <= 0.0f -> Right-align from window/work-rect right-most edge. With -FLT_MIN or 0.0f will align exactly on right-most edge. +// - outer_size.x > 0.0f -> Set Fixed width. +// Y with ScrollX/ScrollY disabled: we output table directly in current window +// - outer_size.y < 0.0f -> Bottom-align (but will auto extend, unless _NoHostExtendY is set). Not meaningful if parent window can vertically scroll. +// - outer_size.y = 0.0f -> No minimum height (but will auto extend, unless _NoHostExtendY is set) +// - outer_size.y > 0.0f -> Set Minimum height (but will auto extend, unless _NoHostExtendY is set) +// Y with ScrollX/ScrollY enabled: using a child window for scrolling +// - outer_size.y < 0.0f -> Bottom-align. Not meaningful if parent window can vertically scroll. +// - outer_size.y = 0.0f -> Bottom-align, consistent with BeginChild(). Not recommended unless table is last item in parent window. +// - outer_size.y > 0.0f -> Set Exact height. Recommended when using Scrolling on any axis. +//----------------------------------------------------------------------------- +// Outer size is also affected by the NoHostExtendX/NoHostExtendY flags. +// Important to note how the two flags have slightly different behaviors! +// - ImGuiTableFlags_NoHostExtendX -> Make outer width auto-fit to columns (overriding outer_size.x value). Only available when ScrollX/ScrollY are disabled +// and Stretch columns are not used. +// - ImGuiTableFlags_NoHostExtendY -> Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit). Only available when +// ScrollX/ScrollY is disabled. Data below the limit will be clipped and not visible. +// In theory ImGuiTableFlags_NoHostExtendY could be the default and any non-scrolling tables with outer_size.y != 0.0f would use exact height. +// This would be consistent but perhaps less useful and more confusing (as vertically clipped items are not useful and not easily noticeable). +//----------------------------------------------------------------------------- +// About 'inner_width': +// With ScrollX disabled: +// - inner_width -> *ignored* +// With ScrollX enabled: +// - inner_width < 0.0f -> *illegal* fit in known width (right align from outer_size.x) <-- weird +// - inner_width = 0.0f -> fit in outer_width: Fixed size columns will take space they need (if avail, otherwise shrink down), Stretch columns becomes +// Fixed columns. +// - inner_width > 0.0f -> override scrolling width, generally to be larger than outer_size.x. Fixed column take space they need (if avail, otherwise +// shrink down), Stretch columns share remaining space! +//----------------------------------------------------------------------------- +// Details: +// - If you want to use Stretch columns with ScrollX, you generally need to specify 'inner_width' otherwise the concept +// of "available space" doesn't make sense. +// - 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) +//----------------------------------------------------------------------------- +// About overriding column sizing policy and width/weight with TableSetupColumn(): +// We use a default parameter of -1 for 'init_width'/'init_weight'. +// - with ImGuiTableColumnFlags_WidthFixed, init_width <= 0 (default) --> width is automatic +// - with ImGuiTableColumnFlags_WidthFixed, init_width > 0 (explicit) --> width is custom +// - with ImGuiTableColumnFlags_WidthStretch, init_weight <= 0 (default) --> weight is 1.0f +// - with ImGuiTableColumnFlags_WidthStretch, init_weight > 0 (explicit) --> weight is custom +// Widths are specified _without_ CellPadding. If you specify a width of 100.0f, the column will be cover (100.0f + Padding * 2.0f) +// and you can fit a 100.0f wide item in it without clipping and with padding honored. +//----------------------------------------------------------------------------- +// About default sizing policy (if you don't specify a ImGuiTableColumnFlags_WidthXXXX flag) +// - with Table policy ImGuiTableFlags_SizingFixedFit --> default Column policy is ImGuiTableColumnFlags_WidthFixed, default Width is equal to contents +// width +// - with Table policy ImGuiTableFlags_SizingFixedSame --> default Column policy is ImGuiTableColumnFlags_WidthFixed, default Width is max of all contents +// width +// - with Table policy ImGuiTableFlags_SizingStretchSame --> default Column policy is ImGuiTableColumnFlags_WidthStretch, default Weight is 1.0f +// - with Table policy ImGuiTableFlags_SizingStretchWeight --> default Column policy is ImGuiTableColumnFlags_WidthStretch, default Weight is proportional to +// contents +// Default Width and default Weight can be overridden when calling TableSetupColumn(). +//----------------------------------------------------------------------------- +// About mixing Fixed/Auto and Stretch columns together: +// - the typical use of mixing sizing policies is: any number of LEADING Fixed columns, followed by one or two TRAILING Stretch columns. +// - using mixed policies with ScrollX does not make much sense, as using Stretch columns with ScrollX does not make much sense in the first place! +// that is, unless 'inner_width' is passed to BeginTable() to explicitly provide a total width to layout columns in. +// - when using ImGuiTableFlags_SizingFixedSame with mixed columns, only the Fixed/Auto columns will match their widths to the width of the maximum contents. +// - when using ImGuiTableFlags_SizingStretchSame with mixed columns, only the Stretch columns will match their weights/widths. +//----------------------------------------------------------------------------- +// About using column width: +// If a column is manually resizable or has a width specified with TableSetupColumn(): +// - you may use GetContentRegionAvail().x to query the width available in a given column. +// - right-side alignment features such as SetNextItemWidth(-x) or PushItemWidth(-x) will rely on this width. +// If the column is not resizable and has no width specified with TableSetupColumn(): +// - its width will be automatic and be set to the max of items submitted. +// - 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 +//----------------------------------------------------------------------------- +// About clipping/culling of Rows in Tables: +// - For large numbers of rows, it is recommended you use ImGuiListClipper to submit only visible rows. +// ImGuiListClipper is reliant on the fact that rows are of equal height. +// See 'Demo->Tables->Vertical Scrolling' or 'Demo->Tables->Advanced' for a demo of using the clipper. +// - Note that auto-resizing columns don't play well with using the clipper. +// By default a table with _ScrollX but without _Resizable will have column auto-resize. +// So, if you want to use the clipper, make sure to either enable _Resizable, either setup columns width explicitly with _WidthFixed. +//----------------------------------------------------------------------------- +// About clipping/culling of Columns in Tables: +// - Both TableSetColumnIndex() and TableNextColumn() return true when the column is visible or performing +// width measurements. Otherwise, you may skip submitting the contents of a cell/column, BUT ONLY if you know +// it is not going to contribute to row height. +// In many situations, you may skip submitting contents for every column but one (e.g. the first one). +// - Case A: column is not hidden by user, and at least partially in sight (most common case). +// - Case B: column is clipped / out of sight (because of scrolling or parent ClipRect): TableNextColumn() return false as a hint but we still allow layout +// output. +// - Case C: column is hidden explicitly by the user (e.g. via the context menu, or _DefaultHide column flag, etc.). +// +// [A] [B] [C] +// TableNextColumn(): true false false -> [userland] when TableNextColumn() / TableSetColumnIndex() returns false, user can skip +// submitting items but only if the column doesn't contribute to row height. +// SkipItems: false false true -> [internal] when SkipItems is true, most widgets will early out if submitted, resulting is no +// layout output. +// ClipRect: normal zero-width zero-width -> [internal] when ClipRect is zero, ItemAdd() will return false and most widgets will early out +// mid-way. +// ImDrawList output: normal dummy dummy -> [internal] when using the dummy channel, ImDrawList submissions (if any) will be wasted +// (because cliprect is zero-width anyway). +// +// - We need to distinguish those cases because non-hidden columns that are clipped outside of scrolling bounds should still contribute their height to the row. +// However, in the majority of cases, the contribution to row height is the same for all columns, or the tallest cells are known by the programmer. +//----------------------------------------------------------------------------- +// 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 +#pragma warning(disable : 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later +#pragma warning(disable : 5054) // operator '|': deprecated between enumerations of different types +#endif +#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") +#pragma clang diagnostic ignored \ + "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to + // be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored \ + "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to vsnformat(). yes, + // user passing incorrect format strings can crash the code. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#pragma clang diagnostic ignored \ + "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using + // printf() is a misery with this as C++ va_arg ellipsis changes float to double. +#pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') +#pragma clang diagnostic ignored \ + "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#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 +//----------------------------------------------------------------------------- +// - TableFixFlags() [Internal] +// - TableFindByID() [Internal] +// - BeginTable() +// - BeginTableEx() [Internal] +// - TableBeginInitMemory() [Internal] +// - TableBeginApplyRequests() [Internal] +// - TableSetupColumnFlags() [Internal] +// - TableUpdateLayout() [Internal] +// - TableUpdateBorders() [Internal] +// - EndTable() +// - TableSetupColumn() +// - TableSetupScrollFreeze() +//----------------------------------------------------------------------------- + +// Configuration +static const int TABLE_DRAW_CHANNEL_BG0 = 0; +static const int TABLE_DRAW_CHANNEL_BG2_FROZEN = 1; +static const int TABLE_DRAW_CHANNEL_NOCLIP = 2; // When using ImGuiTableFlags_NoClip (this becomes the last visible channel) +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. + const bool use_child_window = (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0; + const ImVec2 avail_size = GetContentRegionAvail(); + ImVec2 actual_outer_size = CalcItemSize(outer_size, ImMax(avail_size.x, 1.0f), use_child_window ? ImMax(avail_size.y, 1.0f) : 0.0f); + ImRect outer_rect(outer_window->DC.CursorPos, outer_window->DC.CursorPos + actual_outer_size); + if (use_child_window && IsClippedEx(outer_rect, 0)) { + ItemSize(outer_rect); + return false; + } + + // Acquire storage for the table + ImGuiTable *table = g.Tables.GetOrAddByKey(id); + const ImGuiTableFlags table_last_flags = table->Flags; + + // Acquire temporary buffers + const int table_idx = g.Tables.GetIndex(table); + if (++g.TablesTempDataStacked > g.TablesTempData.Size) + g.TablesTempData.resize(g.TablesTempDataStacked, ImGuiTableTempData()); + ImGuiTableTempData *temp_data = table->TempData = &g.TablesTempData[g.TablesTempDataStacked - 1]; + 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 instance_no = (table->LastFrameActive != g.FrameCount) ? 0 : table->InstanceCurrent + 1; + table->ID = id; + table->Flags = flags; + table->LastFrameActive = g.FrameCount; + table->OuterWindow = table->InnerWindow = outer_window; + table->ColumnsCount = columns_count; + table->IsLayoutLocked = false; + table->InnerWidth = inner_width; + temp_data->UserOuterSize = outer_size; + + // Instance data (for instance 0, TableID == TableInstanceID) + ImGuiID instance_id; + table->InstanceCurrent = (ImS16)instance_no; + if (instance_no > 0) { + IM_ASSERT(table->ColumnsCount == columns_count && "BeginTable(): Cannot change columns count mid-frame while preserving same ID"); + if (table->InstanceDataExtra.Size < instance_no) + table->InstanceDataExtra.push_back(ImGuiTableInstanceData()); + instance_id = GetIDWithSeed(instance_no, GetIDWithSeed("##Instances", NULL, id)); // Push "##Instances" followed by (int)instance_no in ID stack. + } else { + instance_id = id; + } + 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) { + // Ensure no vertical scrollbar appears if we only want horizontal one, to make flag consistent + // (we have no other way to disable vertical scrollbar of a window while keeping the horizontal one showing) + 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 ((table_last_flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) == 0) + SetNextWindowScroll(ImVec2(0.0f, 0.0f)); + + // Create scrolling region (without border and zero window padding) + ImGuiWindowFlags child_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; + BeginChildEx(name, instance_id, outer_rect.GetSize(), false, child_flags); + table->InnerWindow = g.CurrentWindow; + table->WorkRect = table->InnerWindow->WorkRect; + 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); + + // 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) { + table->HasScrollbarYPrev = table->HasScrollbarYCurr; + table->HasScrollbarYCurr = false; + } + table->HasScrollbarYCurr |= table->InnerWindow->ScrollbarY; + } else { + // For non-scrolling tables, WorkRect == OuterRect == InnerRect. + // 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; + } + + // 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; + table->HostClipRect = inner_window->ClipRect; + table->HostSkipItems = inner_window->SkipItems; + temp_data->HostBackupWorkRect = inner_window->WorkRect; + temp_data->HostBackupParentWorkRect = inner_window->ParentWorkRect; + temp_data->HostBackupColumnsOffset = outer_window->DC.ColumnsOffset; + temp_data->HostBackupPrevLineSize = inner_window->DC.PrevLineSize; + temp_data->HostBackupCurrLineSize = inner_window->DC.CurrLineSize; + temp_data->HostBackupCursorMaxPos = inner_window->DC.CursorMaxPos; + 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); + + // Padding and Spacing + // - None ........Content..... Pad .....Content........ + // - PadOuter | Pad ..Content..... Pad .....Content.. Pad | + // - PadInner ........Content.. Pad | Pad ..Content........ + // - PadOuter+PadInner | Pad ..Content.. Pad | Pad ..Content.. Pad | + const bool pad_outer_x = (flags & ImGuiTableFlags_NoPadOuterX) ? false + : (flags & ImGuiTableFlags_PadOuterX) ? true + : (flags & ImGuiTableFlags_BordersOuterV) != 0; + const bool pad_inner_x = (flags & ImGuiTableFlags_NoPadInnerX) ? false : true; + const float inner_spacing_for_border = (flags & ImGuiTableFlags_BordersInnerV) ? TABLE_BORDER_SIZE : 0.0f; + const float inner_spacing_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) == 0) ? g.Style.CellPadding.x : 0.0f; + const float inner_padding_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) != 0) ? g.Style.CellPadding.x : 0.0f; + 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; + table->LastRowFlags = ImGuiTableRowFlags_None; + table->InnerClipRect = (inner_window == outer_window) ? table->WorkRect : inner_window->ClipRect; + 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) : inner_window->ClipRect.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; + table->FreezeRowsRequest = table->FreezeRowsCount = 0; // This will be setup by TableSetupScrollFreeze(), if any + table->FreezeColumnsRequest = table->FreezeColumnsCount = 0; + table->IsUnfrozenRows = true; + table->DeclColumnsCount = 0; + + // 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(); + 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 ((table_last_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; + const int old_columns_count = table->Columns.size(); + if (old_columns_count != 0 && old_columns_count != columns_count) { + // Attempt to preserve width on column count change (#4046) + old_columns_to_preserve = table->Columns.Data; + old_columns_raw_data = table->RawData; + table->RawData = NULL; + } + if (table->RawData == NULL) { + TableBeginInitMemory(table, columns_count); + table->IsInitializing = table->IsSettingsRequestLoad = true; + } + if (table->IsResetAllRequest) + TableResetSettings(table); + if (table->IsInitializing) { + // Initialize + table->SettingsOffset = -1; + table->IsSortSpecsDirty = true; + table->InstanceInteracted = -1; + table->ContextPopupColumn = -1; + table->ReorderColumn = table->ResizedColumn = table->LastResizedColumn = -1; + table->AutoFitSingleColumn = -1; + table->HoveredColumnBody = table->HoveredColumnBorder = -1; + for (int n = 0; n < columns_count; n++) { + ImGuiTableColumn *column = &table->Columns[n]; + if (old_columns_to_preserve && n < old_columns_count) { + // FIXME: We don't attempt to preserve column order in this path. + *column = old_columns_to_preserve[n]; + } else { + float width_auto = column->WidthAuto; + *column = ImGuiTableColumn(); + column->WidthAuto = width_auto; + column->IsPreserveWidthAuto = + true; // Preserve WidthAuto when reinitializing a live table: not technically necessary but remove a visible flicker + column->IsEnabled = column->IsUserEnabled = column->IsUserEnabledNextFrame = true; + } + column->DisplayOrder = table->DisplayOrderToIndex[n] = (ImGuiTableColumnIdx)n; + } + } + 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. + // FIXME-DPI: Provide consistent standards for reference size. Perhaps using g.CurrentDpiScale would be more self explanatory. + // This is will lead us to non-rounded WidthRequest in columns, which should work but is a poorly tested path. + const float new_ref_scale_unit = g.FontSize; // g.Font->GetCharAdvance('A') ? + if (table->RefScale != 0.0f && table->RefScale != new_ref_scale_unit) { + const float scale_factor = new_ref_scale_unit / table->RefScale; + // IMGUI_DEBUG_PRINT("[table] %08X RefScaleUnit %.3f -> %.3f, scaling width by %.3f\n", table->ID, table->RefScaleUnit, new_ref_scale_unit, + // scale_factor); + for (int n = 0; n < columns_count; n++) + 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) +// + 1 (for table->ColumnsNames, if names are used) +// Shared allocations for the maximum number of simultaneously nested tables (generally a very small number) +// + 1 (for table->Splitter._Channels) +// + 2 * active_channels_count (for ImDrawCmd and ImDrawIdx buffers inside channels) +// Where active_channels_count is variable but often == columns_count or == columns_count + 1, see TableSetupDrawChannels() for details. +// Unused channels don't perform their +2 allocations. +void ImGui::TableBeginInitMemory(ImGuiTable *table, int columns_count) +{ + // Allocate single buffer for our arrays + const int columns_bit_array_size = (int)ImBitArrayGetStorageSizeInBytes(columns_count); + ImSpanAllocator<6> span_allocator; + span_allocator.Reserve(0, columns_count * sizeof(ImGuiTableColumn)); + span_allocator.Reserve(1, columns_count * sizeof(ImGuiTableColumnIdx)); + span_allocator.Reserve(2, columns_count * sizeof(ImGuiTableCellData), 4); + for (int n = 3; n < 6; n++) + span_allocator.Reserve(n, columns_bit_array_size); + table->RawData = IM_ALLOC(span_allocator.GetArenaSizeInBytes()); + memset(table->RawData, 0, span_allocator.GetArenaSizeInBytes()); + span_allocator.SetArenaBasePtr(table->RawData); + span_allocator.GetSpan(0, &table->Columns); + span_allocator.GetSpan(1, &table->DisplayOrderToIndex); + span_allocator.GetSpan(2, &table->RowCellData); + table->EnabledMaskByDisplayOrder = (ImU32 *)span_allocator.GetSpanPtrBegin(3); + 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) +{ + // Handle resizing request + // (We process this in the TableBegin() of the first instance of each table) + // FIXME-TABLE: Contains columns if our work area doesn't allow for scrolling? + if (table->InstanceCurrent == 0) { + if (table->ResizedColumn != -1 && table->ResizedColumnNextWidth != FLT_MAX) + TableSetColumnWidth(table->ResizedColumn, table->ResizedColumnNextWidth); + 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) { + TableSetColumnWidth(table->AutoFitSingleColumn, table->Columns[table->AutoFitSingleColumn].WidthAuto); + table->AutoFitSingleColumn = -1; + } + } + + // Handle reordering request + // Note: we don't clear ReorderColumn after handling the request. + if (table->InstanceCurrent == 0) { + if (table->HeldHeaderColumn == -1 && table->ReorderColumn != -1) + table->ReorderColumn = -1; + table->HeldHeaderColumn = -1; + if (table->ReorderColumn != -1 && table->ReorderColumnDir != 0) { + // We need to handle reordering across hidden columns. + // In the configuration below, moving C to the right of E will lead to: + // ... C [D] E ---> ... [D] E C (Column name/index) + // ... 2 3 4 ... 2 3 4 (Display order) + const int reorder_dir = table->ReorderColumnDir; + IM_ASSERT(reorder_dir == -1 || reorder_dir == +1); + IM_ASSERT(table->Flags & ImGuiTableFlags_Reorderable); + ImGuiTableColumn *src_column = &table->Columns[table->ReorderColumn]; + ImGuiTableColumn *dst_column = &table->Columns[(reorder_dir == -1) ? src_column->PrevEnabledColumn : src_column->NextEnabledColumn]; + IM_UNUSED(dst_column); + const int src_order = src_column->DisplayOrder; + const int dst_order = dst_column->DisplayOrder; + src_column->DisplayOrder = (ImGuiTableColumnIdx)dst_order; + 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; + table->ReorderColumnDir = 0; + table->IsSettingsDirty = true; + } + } + + // Handle display order reset request + if (table->IsResetDisplayOrderRequest) { + for (int n = 0; n < table->ColumnsCount; n++) + table->DisplayOrderToIndex[n] = table->Columns[n].DisplayOrder = (ImGuiTableColumnIdx)n; + table->IsResetDisplayOrderRequest = false; + 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) { + const ImGuiTableFlags table_sizing_policy = (table->Flags & ImGuiTableFlags_SizingMask_); + if (table_sizing_policy == ImGuiTableFlags_SizingFixedFit || table_sizing_policy == ImGuiTableFlags_SizingFixedSame) + flags |= ImGuiTableColumnFlags_WidthFixed; + else + flags |= ImGuiTableColumnFlags_WidthStretch; + } else { + 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) { + int count = 0, mask = 0, list = 0; + if ((flags & ImGuiTableColumnFlags_PreferSortAscending) != 0 && (flags & ImGuiTableColumnFlags_NoSortAscending) == 0) { + mask |= 1 << ImGuiSortDirection_Ascending; + list |= ImGuiSortDirection_Ascending << (count << 1); + count++; + } + if ((flags & ImGuiTableColumnFlags_PreferSortDescending) != 0 && (flags & ImGuiTableColumnFlags_NoSortDescending) == 0) { + mask |= 1 << ImGuiSortDirection_Descending; + list |= ImGuiSortDirection_Descending << (count << 1); + count++; + } + if ((flags & ImGuiTableColumnFlags_PreferSortAscending) == 0 && (flags & ImGuiTableColumnFlags_NoSortAscending) == 0) { + mask |= 1 << ImGuiSortDirection_Ascending; + list |= ImGuiSortDirection_Ascending << (count << 1); + count++; + } + if ((flags & ImGuiTableColumnFlags_PreferSortDescending) == 0 && (flags & ImGuiTableColumnFlags_NoSortDescending) == 0) { + mask |= 1 << ImGuiSortDirection_Descending; + list |= ImGuiSortDirection_Descending << (count << 1); + count++; + } + if ((table->Flags & ImGuiTableFlags_SortTristate) || count == 0) { + mask |= 1 << ImGuiSortDirection_None; + count++; + } + column->SortDirectionsAvailList = (ImU8)list; + column->SortDirectionsAvailMask = (ImU8)mask; + column->SortDirectionsAvailCount = (ImU8)count; + 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. +// Increase feedback side-effect with widgets relying on WorkRect.Max.x... Maybe provide a default distribution for _WidthAuto columns? +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; + ImBitArrayClearAllBits(table->EnabledMaskByIndex, table->ColumnsCount); + 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 + int count_stretch = 0; // Number of columns that have stretch sizing policies + int prev_visible_column_idx = -1; + bool has_auto_fit_request = false; + bool has_resizable = false; + float stretch_sum_width_auto = 0.0f; + float fixed_max_width_auto = 0.0f; + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { + const int column_n = table->DisplayOrderToIndex[order_n]; + 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. + if (table->DeclColumnsCount <= column_n) { + TableSetupColumnFlags(table, column, ImGuiTableColumnFlags_None); + column->NameOffset = -1; + 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; + if (column->IsUserEnabled != column->IsUserEnabledNextFrame) { + column->IsUserEnabled = column->IsUserEnabledNextFrame; + 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; + if (prev_visible_column_idx != -1) + table->Columns[prev_visible_column_idx].NextEnabledColumn = (ImGuiTableColumnIdx)column_n; + else + table->LeftMostEnabledColumn = (ImGuiTableColumnIdx)column_n; + column->IndexWithinEnabledSet = table->ColumnsEnabledCount++; + ImBitArraySetBit(table->EnabledMaskByIndex, column_n); + 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) + 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) { + stretch_sum_width_auto += column->WidthAuto; + count_stretch++; + } else { + fixed_max_width_auto = ImMax(fixed_max_width_auto, column->WidthAuto); + count_fixed++; + } + } + if ((table->Flags & ImGuiTableFlags_Sortable) && table->SortSpecsCount == 0 && !(table->Flags & ImGuiTableFlags_SortTristate)) + 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). + // FIXME-TABLE: for always auto-resizing columns may not want to do that all the time. + if (has_auto_fit_request && table->OuterWindow != table->InnerWindow) + 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. + table->LeftMostStretchedColumn = table->RightMostStretchedColumn = -1; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { + 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) { + // Apply same widths policy + 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). + // This is merely making the side-effect less extreme, but doesn't properly fixes it. + // FIXME: Move this to ->WidthGiven to avoid temporary lossyless? + // FIXME: This break IsPreserveWidthAuto from not flickering if the stored WidthAuto was smaller. + if (column->AutoFitQueue > 0x01 && table->IsInitializing && !column->IsPreserveWidthAuto) + column->WidthRequest = ImMax(column->WidthRequest, table->MinColumnWidth * 4.0f); // FIXME-TABLE: Another constant/scale? + sum_width_requests += column->WidthRequest; + } else { + // Initialize stretch weight + if (column->AutoFitQueue != 0x00 || column->StretchWeight < 0.0f || !column_is_resizable) { + if (column->InitStretchWeightOrWidth > 0.0f) + column->StretchWeight = column->InitStretchWeightOrWidth; + else if (table_sizing_policy == ImGuiTableFlags_SizingStretchProp) + column->StretchWeight = (column->WidthAuto / stretch_sum_width_auto) * count_stretch; + 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; + if (table->RightMostStretchedColumn == -1 || table->Columns[table->RightMostStretchedColumn].DisplayOrder < column->DisplayOrder) + table->RightMostStretchedColumn = (ImGuiTableColumnIdx)column_n; + } + column->IsPreserveWidthAuto = false; + sum_width_requests += table->CellPaddingX * 2.0f; + } + 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); + const float width_removed = (table->HasScrollbarYPrev && !table->InnerWindow->ScrollbarY) + ? g.Style.ScrollbarSize + : 0.0f; // To synchronize decoration width of synched tables with mismatching scrollbar state (#5920) + const float width_avail = ImMax( + 1.0f, + (((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) ? table->InnerClipRect.GetWidth() : work_rect.GetWidth()) - width_removed); + const float width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests; + float width_remaining_for_stretched_columns = width_avail_for_stretched_columns; + table->ColumnsGivenWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { + 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) { + float weight_ratio = column->StretchWeight / stretch_sum_weights; + column->WidthRequest = IM_FLOOR(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 = ImFloor(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)) + for (int order_n = table->ColumnsCount - 1; stretch_sum_weights > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--) { + if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n)) + continue; + ImGuiTableColumn *column = &table->Columns[table->DisplayOrderToIndex[order_n]]; + if (!(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + continue; + column->WidthRequest += 1.0f; + 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 + // clear ActiveId, which is equivalent to the change provided by _AllowWhenBLockedByActiveItem). + // - This allows columns to be marked as hovered when e.g. clicking a button inside the column, or using drag and drop. + ImGuiTableInstanceData *table_instance = TableGetInstanceData(table, table->InstanceCurrent); + table_instance->HoveredRowLast = table_instance->HoveredRowNext; + table_instance->HoveredRowNext = -1; + table->HoveredColumnBody = -1; + table->HoveredColumnBorder = -1; + const ImRect mouse_hit_rect(table->OuterRect.Min.x, + table->OuterRect.Min.y, + table->OuterRect.Max.x, + ImMax(table->OuterRect.Max.y, table->OuterRect.Min.y + table_instance->LastOuterHeight)); + const ImGuiID backup_active_id = g.ActiveId; + g.ActiveId = 0; + const bool is_hovering_table = ItemHoverable(mouse_hit_rect, 0, ImGuiItemFlags_None); + g.ActiveId = backup_active_id; + + // [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; + bool offset_x_frozen = (table->FreezeColumnsCount > 0); + float offset_x = ((table->FreezeColumnsCount > 0) ? table->OuterRect.Min.x : work_rect.Min.x) + table->OuterPaddingX - table->CellSpacingX1; + ImRect host_clip_rect = table->InnerClipRect; + // host_clip_rect.Max.x += table->CellPaddingX + table->CellSpacingX2; + ImBitArrayClearAllBits(table->VisibleMaskByIndex, table->ColumnsCount); + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { + const int column_n = table->DisplayOrderToIndex[order_n]; + ImGuiTableColumn *column = &table->Columns[column_n]; + + column->NavLayerCurrent = + (ImS8)(table->FreezeRowsCount > 0 ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main); // Use Count NOT request so Header line changes layer when frozen + + 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. + // We set a zero-width clip rect but set Min.y/Max.y properly to not interfere with the clipper. + column->MinX = column->MaxX = column->WorkMinX = column->ClipRect.Min.x = column->ClipRect.Max.x = offset_x; + column->WidthGiven = 0.0f; + column->ClipRect.Min.y = work_rect.Min.y; + column->ClipRect.Max.y = FLT_MAX; + column->ClipRect.ClipWithFull(host_clip_rect); + column->IsVisibleX = column->IsVisibleY = column->IsRequestOutput = false; + column->IsSkipItems = true; + column->ItemWidth = 1.0f; + continue; + } + + // Detect hovered column + if (is_hovering_table && g.IO.MousePos.x >= column->ClipRect.Min.x && g.IO.MousePos.x < column->ClipRect.Max.x) + table->HoveredColumnBody = (ImGuiTableColumnIdx)column_n; + + // Lock start position + column->MinX = offset_x; + + // Lock width based on start position and minimum/maximum width for this position + float max_width = TableGetMaxColumnWidth(table, column_n); + column->WidthGiven = ImMin(column->WidthGiven, max_width); + 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. + // - ClipRect.Max.x: using MaxX makes it easier for header to receive hover highlight with no discontinuity and display sorting arrow. + // - FIXME-TABLE: We want equal width columns to have equal (ClipRect.Max.x - WorkMinX) width, which means ClipRect.max.x cannot stray off + // host_clip_rect.Max.x else right-most column may appear shorter. + column->WorkMinX = column->MinX + table->CellPaddingX + table->CellSpacingX1; + column->WorkMaxX = column->MaxX - table->CellPaddingX - table->CellSpacingX2; // Expected max + column->ItemWidth = ImFloor(column->WidthGiven * 0.65f); + column->ClipRect.Min.x = column->MinX; + column->ClipRect.Min.y = work_rect.Min.y; + 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. Taking advantage of LastOuterHeight would yield good results there... + // FIXME-TABLE: Y clipping is disabled because it effectively means not submitting will reduce contents width which is fed to + // outer_window->DC.CursorMaxPos.x, and this may be used (e.g. typically by outer_window using AlwaysAutoResize or outer_window's horizontal scrollbar, + // but could be something else). Possible solution to preserve last known content width for clipped column. Test 'table_reported_size' fails when + // enabling Y clipping and window is resized small. + column->IsVisibleX = (column->ClipRect.Max.x > column->ClipRect.Min.x); + column->IsVisibleY = true; // (column->ClipRect.Max.y > column->ClipRect.Min.y); + 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) + column->IsSkipItems = !column->IsEnabled || table->HostSkipItems; + if (column->IsSkipItems) + IM_ASSERT(!is_visible); + + // Update status flags + column->Flags |= ImGuiTableColumnFlags_IsEnabled; + if (is_visible) + column->Flags |= ImGuiTableColumnFlags_IsVisible; + if (column->SortOrder != -1) + column->Flags |= ImGuiTableColumnFlags_IsSorted; + if (table->HoveredColumnBody == column_n) + column->Flags |= ImGuiTableColumnFlags_IsHovered; + + // 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 + // visible rows, but nav/programmatic scroll would have visible artifacts.) + // if (column->Flags & ImGuiTableColumnFlags_AlignRight) + // 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 + column->ContentMaxXFrozen = column->ContentMaxXUnfrozen = column->WorkMinX; + column->ContentMaxXHeadersUsed = column->ContentMaxXHeadersIdeal = column->WorkMinX; + + // Don't decrement auto-fit counters until container window got a chance to submit its items + if (table->HostSkipItems == false) { + 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++; + } + + // [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. + const float unused_x1 = ImMax(table->WorkRect.Min.x, table->Columns[table->RightMostEnabledColumn].ClipRect.Max.x); + if (is_hovering_table && table->HoveredColumnBody == -1) { + if (g.IO.MousePos.x >= unused_x1) + table->HoveredColumnBody = (ImGuiTableColumnIdx)table->ColumnsCount; + } + if (has_resizable == false && (table->Flags & ImGuiTableFlags_Resizable)) + table->Flags &= ~ImGuiTableFlags_Resizable; + + // [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. + if (table->RightMostStretchedColumn != -1) + table->Flags &= ~ImGuiTableFlags_NoHostExtendX; + if (table->Flags & ImGuiTableFlags_NoHostExtendX) { + table->OuterRect.Max.x = table->WorkRect.Max.x = unused_x1; + table->InnerClipRect.Max.x = ImMin(table->InnerClipRect.Max.x, unused_x1); + } + table->InnerWindow->ParentWorkRect = table->WorkRect; + table->BorderX1 = table->InnerClipRect.Min.x; // +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : -1.0f); + table->BorderX2 = table->InnerClipRect.Max.x; // +((table->Flags & ImGuiTableFlags_BordersOuter) ? 0.0f : +1.0f); + + // 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) + window_content_max_y = table->OuterRect.Max.y; + 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->LastFirstRowHeight = 0.0f; + table->IsLayoutLocked = true; + table->IsUsingHeaders = false; + + // [Part 11] Context menu + if (TableBeginContextMenuPopup(table)) { + TableDrawContextMenu(table); + 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) + table->DrawSplitter->SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP); + else + inner_window->DrawList->PushClipRect(inner_window->ClipRect.Min, inner_window->ClipRect.Max, false); +} + +// 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). + // Actual columns highlight/render will be performed in EndTable() and not be affected. + ImGuiTableInstanceData *table_instance = TableGetInstanceData(table, table->InstanceCurrent); + const float hit_half_width = TABLE_RESIZE_SEPARATOR_HALF_THICKNESS; + const float hit_y1 = table->OuterRect.Min.y; + const float hit_y2_body = ImMax(table->OuterRect.Max.y, hit_y1 + table_instance->LastOuterHeight); + const float hit_y2_head = hit_y1 + table_instance->LastFirstRowHeight; + + 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)) { + TableSetColumnWidthAutoSingle(table, column_n); + ClearActiveID(); + held = hovered = false; + } + if (held) { + if (table->LastResizedColumn == -1) + table->ResizeLockMinContentsX2 = table->RightMostEnabledColumn != -1 ? table->Columns[table->RightMostEnabledColumn].MaxX : -FLT_MAX; + table->ResizedColumn = (ImGuiTableColumnIdx)column_n; + table->InstanceInteracted = table->InstanceCurrent; + } + if ((hovered && g.HoveredIdTimer > TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER) || held) { + table->HoveredColumnBorder = (ImGuiTableColumnIdx)column_n; + SetMouseCursor(ImGuiMouseCursor_ResizeEW); + } + } +} + +void ImGui::EndTable() +{ + ImGuiContext &g = *GImGui; + ImGuiTable *table = g.CurrentTable; + IM_ASSERT(table != NULL && "Only call EndTable() if BeginTable() returns true!"); + + // 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; + inner_window->DC.CurrLineSize = temp_data->HostBackupCurrLineSize; + inner_window->DC.CursorMaxPos = temp_data->HostBackupCursorMaxPos; + const float inner_content_max_y = table->RowPosY2; + IM_ASSERT(table->RowPosY2 == inner_window->DC.CursorPos.y); + if (inner_window != outer_window) + inner_window->DC.CursorMaxPos.y = inner_content_max_y; + else if (!(flags & ImGuiTableFlags_NoHostExtendY)) + 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 axises together. + if (table->Flags & ImGuiTableFlags_ScrollX) { + const float outer_padding_for_border = (table->Flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f; + float max_pos_x = table->InnerWindow->DC.CursorMaxPos.x; + if (table->RightMostEnabledColumn != -1) + max_pos_x = ImMax(max_pos_x, + table->Columns[table->RightMostEnabledColumn].WorkMaxX + table->CellPaddingX + table->OuterPaddingX - outer_padding_for_border); + if (table->ResizedColumn != -1) + max_pos_x = ImMax(max_pos_x, table->ResizeLockMinContentsX2); + table->InnerWindow->DC.CursorMaxPos.x = max_pos_x; + } + + // 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) + // Pros: remove draw calls which will have no effect. since they'll have zero-size cliprect they may be early out anyway. + // Cons: making it harder for users watching metrics/debugger to spot the wasted vertices. + if (table->DummyDrawChannel != (ImGuiTableColumnIdx)-1) + { + ImDrawChannel* dummy_channel = &table->DrawSplitter._Channels[table->DummyDrawChannel]; + dummy_channel->_CmdBuffer.resize(0); + 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; + float auto_fit_width_for_stretched_min = 0.0f; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + if (IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n)) { + ImGuiTableColumn *column = &table->Columns[column_n]; + float column_width_request = ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !(column->Flags & ImGuiTableColumnFlags_NoResize)) + ? column->WidthRequest + : TableGetColumnWidthAuto(table, column); + if (column->Flags & ImGuiTableColumnFlags_WidthFixed) + auto_fit_width_for_fixed += column_width_request; + else + auto_fit_width_for_stretched += column_width_request; + if ((column->Flags & ImGuiTableColumnFlags_WidthStretch) && (column->Flags & ImGuiTableColumnFlags_NoResize) != 0) + auto_fit_width_for_stretched_min = + ImMax(auto_fit_width_for_stretched_min, column_width_request / (column->StretchWeight / table->ColumnsStretchSumWeights)); + } + 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) { + inner_window->Scroll.x = 0.0f; + } else if (table->LastResizedColumn != -1 && table->ResizedColumn == -1 && inner_window->ScrollbarX + && table->InstanceInteracted == table->InstanceCurrent) { + // When releasing a column being resized, scroll to keep the resulting column in sight + const float neighbor_width_to_keep_visible = table->MinColumnWidth + table->CellPaddingX * 2.0f; + ImGuiTableColumn *column = &table->Columns[table->LastResizedColumn]; + if (column->MaxX < table->InnerClipRect.Min.x) + SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x - neighbor_width_to_keep_visible, 1.0f); + 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) { + ImGuiTableColumn *column = &table->Columns[table->ResizedColumn]; + const float new_x2 = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + TABLE_RESIZE_SEPARATOR_HALF_THICKNESS); + const float new_width = ImFloor(new_x2 - column->MinX - table->CellSpacingX1 - table->CellPaddingX * 2.0f); + table->ResizedColumnNextWidth = new_width; + } + + // 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; + inner_window->ParentWorkRect = temp_data->HostBackupParentWorkRect; + inner_window->SkipItems = table->HostSkipItems; + outer_window->DC.CursorPos = table->OuterRect.Min; + 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) + if (inner_window != outer_window) { + EndChild(); + } else { + 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) { + // FIXME-TABLE: Could we remove this section? + // ColumnsAutoFitWidth may be one frame ahead here since for Fixed+NoResize is calculated from latest contents + IM_ASSERT((table->Flags & ImGuiTableFlags_ScrollX) == 0); + outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth); + } else if (temp_data->UserOuterSize.x <= 0.0f) { + const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollX) ? inner_window->ScrollbarSizes.x : 0.0f; + outer_window->DC.IdealMaxPos.x = + ImMax(outer_window->DC.IdealMaxPos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth + decoration_size - temp_data->UserOuterSize.x); + outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table->OuterRect.Max.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth)); + } else { + outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table->OuterRect.Max.x); + } + if (temp_data->UserOuterSize.y <= 0.0f) { + const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollY) ? inner_window->ScrollbarSizes.y : 0.0f; + outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, inner_content_max_y + decoration_size - temp_data->UserOuterSize.y); + outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table->OuterRect.Max.y, inner_content_max_y)); + } else { + // 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); + temp_data = (--g.TablesTempDataStacked > 0) ? &g.TablesTempData[g.TablesTempDataStacked - 1] : NULL; + g.CurrentTable = temp_data ? g.Tables.GetByIndex(temp_data->TableIndex) : NULL; + if (g.CurrentTable) { + g.CurrentTable->TempData = temp_data; + g.CurrentTable->DrawSplitter = &temp_data->DrawSplitter; + } + outer_window->DC.CurrentTableIdx = g.CurrentTable ? g.Tables.GetIndex(g.CurrentTable) : -1; + NavUpdateCurrentWindowIsScrollPushableX(); +} + +// See "COLUMN 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) +{ + ImGuiContext &g = *GImGui; + ImGuiTable *table = g.CurrentTable; + IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); + IM_ASSERT(table->IsLayoutLocked == false && "Need to call call TableSetupColumn() before first row!"); + IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()"); + if (table->DeclColumnsCount >= table->ColumnsCount) { + 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) + if ((table->Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedFit + || (table->Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedSame) + flags |= ImGuiTableColumnFlags_WidthFixed; + + TableSetupColumnFlags(table, column, flags); + column->UserID = user_id; + flags = column->Flags; + + // Initialize defaults + column->InitStretchWeightOrWidth = init_width_or_weight; + if (table->IsInitializing) { + // Init width or weight + if (column->WidthRequest < 0.0f && column->StretchWeight < 0.0f) { + if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f) + column->WidthRequest = init_width_or_weight; + if (flags & ImGuiTableColumnFlags_WidthStretch) + column->StretchWeight = (init_width_or_weight > 0.0f) ? init_width_or_weight : -1.0f; + + // Disable auto-fit if an explicit width/weight has been specified + if (init_width_or_weight > 0.0f) + column->AutoFitQueue = 0x00; + } + + // Init default visibility/sort state + if ((flags & ImGuiTableColumnFlags_DefaultHide) && (table->SettingsLoadedFlags & ImGuiTableFlags_Hideable) == 0) + column->IsUserEnabled = column->IsUserEnabledNextFrame = false; + if (flags & ImGuiTableColumnFlags_DefaultSort && (table->SettingsLoadedFlags & ImGuiTableFlags_Sortable) == 0) { + column->SortOrder = 0; // Multiple columns using _DefaultSort will be reassigned unique SortOrder values when building the sort specs. + column->SortDirection = + (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending); + } + } + + // Store name (append with zero-terminator in contiguous buffer) + column->NameOffset = -1; + if (label != NULL && label[0] != 0) { + column->NameOffset = (ImS16)table->ColumnsNames.size(); + table->ColumnsNames.append(label, label + strlen(label) + 1); + } +} + +// [Public] +void ImGui::TableSetupScrollFreeze(int columns, int rows) +{ + ImGuiContext &g = *GImGui; + ImGuiTable *table = g.CurrentTable; + IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); + 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++) { + int order_n = table->DisplayOrderToIndex[column_n]; + if (order_n != column_n && order_n >= table->FreezeColumnsRequest) { + ImSwap(table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder, table->Columns[table->DisplayOrderToIndex[column_n]].DisplayOrder); + ImSwap(table->DisplayOrderToIndex[order_n], table->DisplayOrderToIndex[column_n]); + } + } +} + +//----------------------------------------------------------------------------- +// [SECTION] Tables: Simple accessors +//----------------------------------------------------------------------------- +// - TableGetColumnCount() +// - TableGetColumnName() +// - TableGetColumnName() [Internal] +// - TableSetColumnEnabled() +// - TableGetColumnFlags() +// - TableGetCellBgRect() [Internal] +// - TableGetColumnResizeID() [Internal] +// - TableGetHoveredColumn() [Internal] +// - 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; + ImGuiTable *table = g.CurrentTable; + if (!table) + return NULL; + if (column_n < 0) + 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) + return ""; // NameOffset is invalid at this point + const ImGuiTableColumn *column = &table->Columns[column_n]; + if (column->NameOffset == -1) + 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. +// - Request will be applied during next layout, which happens on the first call to TableNextRow() after BeginTable(). +// - For the getter you can test (TableGetColumnFlags() & ImGuiTableColumnFlags_IsEnabled) != 0. +// - Alternative: the ImGuiTableColumnFlags_Disabled is an overriding/master disable flag which will also hide the column from context menu. +void ImGui::TableSetColumnEnabled(int column_n, bool enabled) +{ + ImGuiContext &g = *GImGui; + ImGuiTable *table = g.CurrentTable; + IM_ASSERT(table != NULL); + if (!table) + return; + IM_ASSERT(table->Flags & ImGuiTableFlags_Hideable); // See comments above + if (column_n < 0) + column_n = table->CurrentColumn; + IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); + 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) +{ + ImGuiContext &g = *GImGui; + ImGuiTable *table = g.CurrentTable; + if (!table) + return ImGuiTableColumnFlags_None; + if (column_n < 0) + column_n = table->CurrentColumn; + if (column_n == table->ColumnsCount) + 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. +// - Important: if ImGuiTableFlags_PadOuterX is set but ImGuiTableFlags_PadInnerX is not set, the outer-most left and right +// columns report a small offset so their CellBgRect can extend up to the outer border. +// FIXME: But the rendering code in TableEndRow() nullifies that with clamping required for scrolling. +ImRect ImGui::TableGetCellBgRect(const ImGuiTable *table, int column_n) +{ + const ImGuiTableColumn *column = &table->Columns[column_n]; + float x1 = column->MinX; + float x2 = column->MaxX; + // if (column->PrevEnabledColumn == -1) + // x1 -= table->OuterPaddingX; + // if (column->NextEnabledColumn == -1) + // x2 += table->OuterPaddingX; + x1 = ImMax(x1, table->WorkRect.Min.x); + 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) +{ + IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); + 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() +{ + ImGuiContext &g = *GImGui; + ImGuiTable *table = g.CurrentTable; + if (!table) + 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. +int ImGui::TableGetHoveredRow() +{ + ImGuiContext &g = *GImGui; + ImGuiTable *table = g.CurrentTable; + if (!table) + return -1; + 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 (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) { + case ImGuiTableBgTarget_CellBg: { + if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard + return; + if (column_n == -1) + column_n = table->CurrentColumn; + if (!IM_BITARRAY_TESTBIT(table->VisibleMaskByIndex, column_n)) + return; + if (table->RowCellDataCurrent < 0 || table->RowCellData[table->RowCellDataCurrent].Column != column_n) + table->RowCellDataCurrent++; + ImGuiTableCellData *cell_data = &table->RowCellData[table->RowCellDataCurrent]; + cell_data->BgColor = color; + cell_data->Column = (ImGuiTableColumnIdx)column_n; + break; + } + case ImGuiTableBgTarget_RowBg0: + case ImGuiTableBgTarget_RowBg1: { + if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard + return; + IM_ASSERT(column_n == -1); + int bg_idx = (target == ImGuiTableBgTarget_RowBg1) ? 1 : 0; + table->RowBgColor[bg_idx] = color; + break; + } + default: + IM_ASSERT(0); + } +} + +//------------------------------------------------------------------------- +// [SECTION] Tables: Row changes +//------------------------------------------------------------------------- +// - TableGetRowIndex() +// - TableNextRow() +// - TableBeginRow() [Internal] +// - TableEndRow() [Internal] +//------------------------------------------------------------------------- + +// [Public] Note: for row coloring we use ->RowBgColorCounter which is the same value without counting header rows +int ImGui::TableGetRowIndex() +{ + ImGuiContext &g = *GImGui; + ImGuiTable *table = g.CurrentTable; + if (!table) + 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) { + TableSetBgColor(ImGuiTableBgTarget_RowBg0, GetColorU32(ImGuiCol_TableHeaderBg)); + if (table->CurrentRow == 0) + table->IsUsingHeaders = true; + } +} + +// [Internal] Called by TableNextRow() +void ImGui::TableEndRow(ImGuiTable *table) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + IM_ASSERT(window == table->InnerWindow); + IM_ASSERT(table->IsInsideRow); + + if (table->CurrentColumn != -1) + TableEndCell(table); + + // 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; + const bool unfreeze_rows_actual = (table->CurrentRow + 1 == table->FreezeRowsCount); + const bool unfreeze_rows_request = (table->CurrentRow + 1 == table->FreezeRowsRequest); + if (table->CurrentRow == 0) + TableGetInstanceData(table, table->InstanceCurrent)->LastFirstRowHeight = 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) + TableGetInstanceData(table, table->InstanceCurrent)->HoveredRowNext = table->CurrentRow; + + // Decide of background color for the row + ImU32 bg_col0 = 0; + ImU32 bg_col1 = 0; + if (table->RowBgColor[0] != IM_COL32_DISABLE) + bg_col0 = table->RowBgColor[0]; + else if (table->Flags & ImGuiTableFlags_RowBg) + 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 border_col = 0; + const float border_size = TABLE_BORDER_SIZE; + if (table->CurrentRow > 0 || table->InnerWindow == table->OuterWindow) + if (table->Flags & ImGuiTableFlags_BordersInnerH) + 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 | border_col) != 0 || draw_strong_bottom_border || draw_cell_bg_color) { + // In theory we could call SetWindowClipRectBeforeSetChannel() but since we know TableEndRow() is + // always followed by a change of clipping rectangle we perform the smallest overwrite possible here. + if ((table->Flags & ImGuiTableFlags_NoClip) == 0) + 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) { + ImRect row_rect(table->WorkRect.Min.x, bg_y1, table->WorkRect.Max.x, bg_y2); + row_rect.ClipWith(table->BgClipRect); + if (bg_col0 != 0 && row_rect.Min.y < row_rect.Max.y) + window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col0); + 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) { + ImGuiTableCellData *cell_data_end = &table->RowCellData[table->RowCellDataCurrent]; + for (ImGuiTableCellData *cell_data = &table->RowCellData[0]; cell_data <= cell_data_end; cell_data++) { + // As we render the BG here we need to clip things (for layout we would not) + // FIXME: This cancels the OuterPadding addition done by TableGetCellBgRect(), need to keep it while rendering correctly while scrolling. + const ImGuiTableColumn *column = &table->Columns[cell_data->Column]; + ImRect cell_bg_rect = TableGetCellBgRect(table, cell_data->Column); + cell_bg_rect.ClipWith(table->BgClipRect); + cell_bg_rect.Min.x = ImMax(cell_bg_rect.Min.x, column->ClipRect.Min.x); // So that first column after frozen one gets clipped when scrolling + cell_bg_rect.Max.x = ImMin(cell_bg_rect.Max.x, column->MaxX); + window->DrawList->AddRectFilled(cell_bg_rect.Min, cell_bg_rect.Max, cell_data->BgColor); + } + } + + // Draw top border + if (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), 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. + if (unfreeze_rows_request) + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + table->Columns[column_n].NavLayerCurrent = ImGuiNavLayer_Main; + if (unfreeze_rows_actual) { + IM_ASSERT(table->IsUnfrozenRows == false); + const float y0 = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y); + table->IsUnfrozenRows = true; + TableGetInstanceData(table, table->InstanceCurrent)->LastFrozenHeight = y0 - table->OuterRect.Min.y; + + // BgClipRect starts as table->InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect + table->BgClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y = ImMin(y0, window->InnerClipRect.Max.y); + table->BgClipRect.Max.y = table->Bg2ClipRectForDrawCmd.Max.y = window->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; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { + ImGuiTableColumn *column = &table->Columns[column_n]; + 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 +//------------------------------------------------------------------------- +// - TableGetColumnIndex() +// - TableSetColumnIndex() +// - TableNextColumn() +// - TableBeginCell() [Internal] +// - TableEndCell() [Internal] +//------------------------------------------------------------------------- + +int ImGui::TableGetColumnIndex() +{ + ImGuiContext &g = *GImGui; + ImGuiTable *table = g.CurrentTable; + if (!table) + return 0; + return table->CurrentColumn; +} + +// [Public] Append into a specific column +bool ImGui::TableSetColumnIndex(int column_n) +{ + ImGuiContext &g = *GImGui; + ImGuiTable *table = g.CurrentTable; + if (!table) + return false; + + if (table->CurrentColumn != column_n) { + if (table->CurrentColumn != -1) + TableEndCell(table); + IM_ASSERT(column_n >= 0 && table->ColumnsCount); + 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() +{ + ImGuiContext &g = *GImGui; + ImGuiTable *table = g.CurrentTable; + if (!table) + return false; + + if (table->IsInsideRow && table->CurrentColumn + 1 < table->ColumnsCount) { + if (table->CurrentColumn != -1) + TableEndCell(table); + TableBeginCell(table, table->CurrentColumn + 1); + } else { + 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. +void ImGui::TableBeginCell(ImGuiTable *table, int column_n) +{ + ImGuiContext &g = *GImGui; + 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; + window->DC.ColumnsOffset.x = start_x - window->Pos.x - window->DC.Indent.x; // FIXME-WORKRECT + 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; + } + + 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. + table->DrawSplitter->SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP); + // IM_ASSERT(table->DrawSplitter._Current == TABLE_DRAW_CHANNEL_NOCLIP); + } else { + // FIXME-TABLE: Could avoid this if draw channel is dummy channel? + SetWindowClipRectBeforeSetChannel(window, column->ClipRect); + table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); + } + + // Logging + if (g.LogEnabled && !column->IsSkipItems) { + LogRenderedText(&window->DC.CursorPos, "|"); + 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) + p_max_pos_x = &column->ContentMaxXHeadersUsed; // Useful in case user submit contents in header row that is not a TableHeader() call + else + p_max_pos_x = table->IsUnfrozenRows ? &column->ContentMaxXUnfrozen : &column->ContentMaxXFrozen; + *p_max_pos_x = ImMax(*p_max_pos_x, window->DC.CursorMaxPos.x); + 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 +//------------------------------------------------------------------------- +// - TableGetMaxColumnWidth() [Internal] +// - TableGetColumnWidthAuto() [Internal] +// - TableSetColumnWidth() +// - TableSetColumnWidthAutoSingle() [Internal] +// - TableSetColumnWidthAutoAll() [Internal] +// - TableUpdateColumnsWeightFromWidth() [Internal] +//------------------------------------------------------------------------- + +// Maximum column content width given current layout. Use column->MinX so this value on a per-column basis. +float ImGui::TableGetMaxColumnWidth(const ImGuiTable *table, int column_n) +{ + const ImGuiTableColumn *column = &table->Columns[column_n]; + float max_width = FLT_MAX; + const float min_column_distance = table->MinColumnWidth + table->CellPaddingX * 2.0f + table->CellSpacingX1 + table->CellSpacingX2; + if (table->Flags & ImGuiTableFlags_ScrollX) { + // Frozen columns can't reach beyond visible width else scrolling will naturally break. + // (we use DisplayOrder as within a set of multiple frozen column reordering is possible) + if (column->DisplayOrder < table->FreezeColumnsRequest) { + max_width = (table->InnerClipRect.Max.x - (table->FreezeColumnsRequest - column->DisplayOrder) * min_column_distance) - column->MinX; + max_width = max_width - table->OuterPaddingX - table->CellPaddingX - table->CellSpacingX2; + } + } else if ((table->Flags & ImGuiTableFlags_NoKeepColumnsVisible) == 0) { + // If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make + // sure they are all visible. Because of this we also know that all of the columns will always fit in + // table->WorkRect and therefore in table->InnerRect (because ScrollX is off) + // FIXME-TABLE: This is solved incorrectly but also quite a difficult problem to fix as we also want ClipRect width to match. + // See "table_width_distrib" and "table_width_keep_visible" tests + max_width = table->WorkRect.Max.x - (table->ColumnsEnabledCount - column->IndexWithinEnabledSet - 1) * min_column_distance - column->MinX; + // max_width -= table->CellSpacingX1; + max_width -= table->CellSpacingX2; + max_width -= table->CellPaddingX * 2.0f; + max_width -= table->OuterPaddingX; + } + 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) +{ + const float content_width_body = ImMax(column->ContentMaxXFrozen, column->ContentMaxXUnfrozen) - column->WorkMinX; + const float content_width_headers = column->ContentMaxXHeadersIdeal - column->WorkMinX; + 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) +{ + ImGuiContext &g = *GImGui; + ImGuiTable *table = g.CurrentTable; + IM_ASSERT(table != NULL && table->IsLayoutLocked == false); + 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); + const float min_width = table->MinColumnWidth; + const float max_width = ImMax(min_width, TableGetMaxColumnWidth(table, column_n)); + 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: + // - F1 F2 F3 resize from F1| or F2| --> ok: alter ->WidthRequested of Fixed column. Subsequent columns will be offset. + // - F1 F2 F3 resize from F3| --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered. + // - F1 F2 W3 resize from F1| or F2| --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered, but it doesn't make much + // sense as the Stretch column will always be minimal size. + // - F1 F2 W3 resize from W3| --> ok: no-op (disabled by Resize Rule 1) + // - W1 W2 W3 resize from W1| or W2| --> ok + // - W1 W2 W3 resize from W3| --> ok: no-op (disabled by Resize Rule 1) + // - W1 F2 F3 resize from F3| --> ok: no-op (disabled by Resize Rule 1) + // - W1 F2 resize from F2| --> ok: no-op (disabled by Resize Rule 1) + // - W1 W2 F3 resize from W1| or W2| --> ok + // - W1 F2 W3 resize from W1| or F2| --> ok + // - F1 W2 F3 resize from W2| --> ok + // - F1 W3 F2 resize from W3| --> ok + // - 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) + if (!column_1 || table->LeftMostStretchedColumn == -1 || table->Columns[table->LeftMostStretchedColumn].DisplayOrder >= column_0->DisplayOrder) { + column_0->WidthRequest = column_0_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); + column_0_width = column_0->WidthRequest + column_1->WidthRequest - column_1_width; + IM_ASSERT(column_0_width > 0.0f && column_1_width > 0.0f); + column_0->WidthRequest = column_0_width; + column_1->WidthRequest = column_1_width; + if ((column_0->Flags | column_1->Flags) & ImGuiTableColumnFlags_WidthStretch) + 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) +{ + // Single auto width uses auto-fit + ImGuiTableColumn *column = &table->Columns[column_n]; + if (!column->IsEnabled) + return; + 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++) { + ImGuiTableColumn *column = &table->Columns[column_n]; + if (!column->IsEnabled && !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) // Cannot reset weight of hidden stretch column + continue; + column->CannotSkipItemsQueue = (1 << 0); + 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; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { + ImGuiTableColumn *column = &table->Columns[column_n]; + if (!column->IsEnabled || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + continue; + IM_ASSERT(column->StretchWeight > 0.0f); + visible_weight += column->StretchWeight; + 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++) { + ImGuiTableColumn *column = &table->Columns[column_n]; + if (!column->IsEnabled || !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) + continue; + column->StretchWeight = (column->WidthRequest / visible_width) * visible_weight; + IM_ASSERT(column->StretchWeight > 0.0f); + } +} + +//------------------------------------------------------------------------- +// [SECTION] Tables: Drawing +//------------------------------------------------------------------------- +// - TablePushBackgroundChannel() [Internal] +// - TablePopBackgroundChannel() [Internal] +// - TableSetupDrawChannels() [Internal] +// - TableMergeDrawChannels() [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() +{ + 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, 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. +// - We isolate headers draw commands in their own channels instead of just altering clip rects. +// This is in order to facilitate merging of draw commands. +// - After crossing FreezeRowsCount, all columns see their current draw channel changed to a second set of channels. +// - We only use the dummy draw channel so we can push a null clipping rectangle into it without affecting other +// channels, while simplifying per-row/per-cell overhead. It will be empty and discarded when merged. +// - We allocate 1 or 2 background draw channels. This is because we know TablePushBackgroundChannel() is only used for +// horizontal spanning. If we allowed vertical spanning we'd need one background draw channel per merge group (1-4). +// Draw channel allocation (before merging): +// - NoClip --> 2+D+1 channels: bg0/1 + bg2 + foreground (same clip rect == always 1 draw call) +// - Clip --> 2+D+N channels +// - FreezeRows --> 2+D+N*2 (unless scrolling value is zero) +// - FreezeRows || FreezeColunns --> 3+D+N*2 (unless scrolling value is zero) +// Where D is 1 if any column is clipped or hidden (dummy channel) otherwise 0. +void ImGui::TableSetupDrawChannels(ImGuiTable *table) +{ + const int freeze_row_multiplier = (table->FreezeRowsCount > 0) ? 2 : 1; + const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClip) ? 1 : table->ColumnsEnabledCount; + const int channels_for_bg = 1 + 1 * freeze_row_multiplier; + const int channels_for_dummy = + (table->ColumnsEnabledCount < table->ColumnsCount + || (memcmp(table->VisibleMaskByIndex, table->EnabledMaskByIndex, ImBitArrayGetStorageSizeInBytes(table->ColumnsCount)) != 0)) + ? +1 + : 0; + const int channels_total = channels_for_bg + (channels_for_row * freeze_row_multiplier) + channels_for_dummy; + table->DrawSplitter->Split(table->InnerWindow->DrawList, channels_total); + 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++) { + ImGuiTableColumn *column = &table->Columns[column_n]; + if (column->IsVisibleX && column->IsVisibleY) { + column->DrawChannelFrozen = (ImGuiTableDrawChannelIdx)(draw_channel_current); + column->DrawChannelUnfrozen = (ImGuiTableDrawChannelIdx)(draw_channel_current + (table->FreezeRowsCount > 0 ? channels_for_row + 1 : 0)); + if (!(table->Flags & ImGuiTableFlags_NoClip)) + draw_channel_current++; + } else { + column->DrawChannelFrozen = column->DrawChannelUnfrozen = table->DummyDrawChannel; + } + 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) + table->BgClipRect = table->InnerClipRect; + table->Bg0ClipRectForDrawCmd = table->OuterWindow->ClipRect; + 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(). +// +// Columns where the contents didn't stray off their local clip rectangle can be merged. To achieve +// this we merge their clip rect and make them contiguous in the channel list, so they can be merged +// by the call to DrawSplitter.Merge() following to the call to this function. +// We reorder draw commands by arranging them into a maximum of 4 distinct groups: +// +// 1 group: 2 groups: 2 groups: 4 groups: +// [ 0. ] no freeze [ 0. ] row freeze [ 01 ] col freeze [ 01 ] row+col freeze +// [ .. ] or no scroll [ 2. ] and v-scroll [ .. ] and h-scroll [ 23 ] and v+h-scroll +// +// Each column itself can use 1 channel (row freeze disabled) or 2 channels (row freeze enabled). +// When the contents of a column didn't stray off its limit, we move its channels into the corresponding group +// based on its position (within frozen rows/columns groups or not). +// At the end of the operation our 1-4 groups will each have a ImDrawCmd using the same ClipRect. +// This function assume that each column are pointing to a distinct draw channel, +// otherwise merge_group->ChannelsCount will not match set bit count of merge_group->ChannelsMask. +// +// Column channels will not be merged into one of the 1-4 groups in the following cases: +// - The contents stray off its clipping rectangle (we only compare the MaxX value, not the MinX value). +// Direct ImDrawList calls won't be taken into account by default, if you use them make sure the ImGui:: bounds +// matches, by e.g. calling SetCursorScreenPos(). +// - The channel uses more than one draw command itself. We drop all our attempt at merging stuff here.. +// we could do better but it's going to be rare and probably not worth the hassle. +// Columns for which the draw channel(s) haven't been merged with other will use their own ImDrawCmd. +// +// This function is particularly tricky to understand.. take a breath. +void ImGui::TableMergeDrawChannels(ImGuiTable *table) +{ + ImGuiContext &g = *GImGui; + ImDrawListSplitter *splitter = table->DrawSplitter; + 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 { + ImRect ClipRect; + int ChannelsCount = 0; + ImBitArrayPtr ChannelsMask = NULL; + }; + 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); + g.TempBuffer.reserve(size_for_masks_bitarrays_one * 5); + memset(g.TempBuffer.Data, 0, size_for_masks_bitarrays_one * 5); + 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)) { + float content_max_x; + if (!has_freeze_v) + content_max_x = ImMax(column->ContentMaxXUnfrozen, column->ContentMaxXHeadersUsed); // No row freeze + else if (merge_group_sub_n == 0) + content_max_x = ImMax(column->ContentMaxXFrozen, column->ContentMaxXHeadersUsed); // Row freeze: use width before freeze + else + content_max_x = column->ContentMaxXUnfrozen; // Row freeze: use width after freeze + 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]; + if (merge_group->ChannelsCount == 0) + merge_group->ClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); + ImBitArraySetBit(merge_group->ChannelsMask, channel_no); + merge_group->ChannelsCount++; + 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) + for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++) + { + MergeGroup* merge_group = &merge_groups[merge_group_n]; + if (merge_group->ChannelsCount == 0) + continue; + char buf[32]; + ImFormatString(buf, 32, "MG%d:%d", merge_group_n, merge_group->ChannelsCount); + ImVec2 text_pos = merge_group->ClipRect.Min + ImVec2(4, 4); + ImVec2 text_size = CalcTextSize(buf, NULL); + GetForegroundDrawList()->AddRectFilled(text_pos, text_pos + text_size, IM_COL32(0, 0, 0, 255)); + GetForegroundDrawList()->AddText(text_pos, IM_COL32(255, 255, 0, 255), buf, NULL); + 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) { + // We skip channel 0 (Bg0/Bg1) and 1 (Bg2 frozen) from the shuffling since they won't move - see channels allocation in TableSetupDrawChannels(). + const int LEADING_DRAW_CHANNELS = 2; + g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - LEADING_DRAW_CHANNELS); // Use shared temporary storage so the allocation gets amortized + ImDrawChannel *dst_tmp = g.DrawChannelsTempMergeBuffer.Data; + ImBitArraySetBitRange(remaining_mask, LEADING_DRAW_CHANNELS, splitter->_Count); + ImBitArrayClearBit(remaining_mask, table->Bg2DrawChannelUnfrozen); + IM_ASSERT(has_freeze_v == false || table->Bg2DrawChannelUnfrozen != TABLE_DRAW_CHANNEL_BG2_FROZEN); + int remaining_count = splitter->_Count - (has_freeze_v ? LEADING_DRAW_CHANNELS + 1 : LEADING_DRAW_CHANNELS); + // ImRect host_rect = (table->InnerWindow == table->OuterWindow) ? table->InnerClipRect : table->HostClipRect; + ImRect host_rect = table->HostClipRect; + for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++) { + if (int merge_channels_count = merge_groups[merge_group_n].ChannelsCount) { + 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: + // - On a same-window table (not scrolling = single group), all fitting columns ClipRect -> will extend and match host ClipRect -> will merge + // - Columns can use padding and have left-most ClipRect.Min.x and right-most ClipRect.Max.x != from host ClipRect -> will extend and match host + // ClipRect -> will merge + // FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column doesn't fit + // within host clip rect, solely because of the half-padding difference between window->WorkRect and window->InnerClipRect. + if ((merge_group_n & 1) == 0 || !has_freeze_h) + merge_clip_rect.Min.x = ImMin(merge_clip_rect.Min.x, host_rect.Min.x); + if ((merge_group_n & 2) == 0 || !has_freeze_v) + merge_clip_rect.Min.y = ImMin(merge_clip_rect.Min.y, host_rect.Min.y); + if ((merge_group_n & 1) != 0) + merge_clip_rect.Max.x = ImMax(merge_clip_rect.Max.x, host_rect.Max.x); + if ((merge_group_n & 2) != 0 && (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0) + merge_clip_rect.Max.y = ImMax(merge_clip_rect.Max.y, host_rect.Max.y); + // GetForegroundDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0f, 0, 1.0f); // [DEBUG] + // GetForegroundDrawList()->AddLine(merge_group->ClipRect.Min, merge_clip_rect.Min, IM_COL32(255, 100, 0, 200)); + // GetForegroundDrawList()->AddLine(merge_group->ClipRect.Max, merge_clip_rect.Max, IM_COL32(255, 100, 0, 200)); + remaining_count -= merge_group->ChannelsCount; + for (int n = 0; n < (size_for_masks_bitarrays_one >> 2); n++) + remaining_mask[n] &= ~merge_group->ChannelsMask[n]; + for (int n = 0; n < splitter->_Count && merge_channels_count != 0; n++) { + // Copy + overwrite new clip rect + if (!IM_BITARRAY_TESTBIT(merge_group->ChannelsMask, n)) + 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++) { + if (!IM_BITARRAY_TESTBIT(remaining_mask, n)) + continue; + ImDrawChannel *channel = &splitter->_Channels[n]; + memcpy(dst_tmp++, channel, sizeof(ImDrawChannel)); + remaining_count--; + } + IM_ASSERT(dst_tmp == g.DrawChannelsTempMergeBuffer.Data + g.DrawChannelsTempMergeBuffer.Size); + memcpy(splitter->_Channels.Data + LEADING_DRAW_CHANNELS, + g.DrawChannelsTempMergeBuffer.Data, + (splitter->_Count - LEADING_DRAW_CHANNELS) * sizeof(ImDrawChannel)); + } +} + +// 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; + const float draw_y1 = table->InnerRect.Min.y; + const float draw_y2_body = table->InnerRect.Max.y; + const float draw_y2_head = table->IsUsingHeaders + ? ImMin(table->InnerRect.Max.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table_instance->LastFirstRowHeight) + : draw_y1; + if (table->Flags & ImGuiTableFlags_BordersInnerV) { + 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]; + const bool is_hovered = (table->HoveredColumnBorder == column_n); + const bool is_resized = (table->ResizedColumn == column_n) && (table->InstanceInteracted == table->InstanceCurrent); + const bool is_resizable = (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) == 0; + 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. + ImU32 col; + float draw_y2; + if (is_hovered || is_resized || is_frozen_separator) { + draw_y2 = draw_y2_body; + col = is_resized ? GetColorU32(ImGuiCol_SeparatorActive) : is_hovered ? GetColorU32(ImGuiCol_SeparatorHovered) : table->BorderColorStrong; + } else { + draw_y2 = (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) ? draw_y2_head : draw_y2_body; + col = (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) ? table->BorderColorStrong + : table->BorderColorLight; + } + + if (draw_y2 > draw_y1) + inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), col, border_size); + } + } + + // Draw outer border + // FIXME: could use AddRect or explicit VLine/HLine helper? + if (table->Flags & ImGuiTableFlags_BordersOuter) { + // Display outer border offset by 1 which is a simple way to display it without adding an extra draw call + // (Without the offset, in outer_window it would be rendered behind cells, because child windows are above their + // parent. In inner_window, it won't reach out over scrollbars. Another weird solution would be to display part + // of it in inner window, and the part that's over scrollbars in the outer window..) + // Either solution currently won't allow us to use a larger border size: the border would clipped. + const ImRect outer_border = table->OuterRect; + const ImU32 outer_col = table->BorderColorStrong; + if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter) { + inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, 0, border_size); + } else if (table->Flags & ImGuiTableFlags_BordersOuterV) { + inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Min.x, outer_border.Max.y), outer_col, border_size); + inner_drawlist->AddLine(ImVec2(outer_border.Max.x, outer_border.Min.y), outer_border.Max, outer_col, border_size); + } else if (table->Flags & ImGuiTableFlags_BordersOuterH) { + inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Max.x, outer_border.Min.y), outer_col, border_size); + inner_drawlist->AddLine(ImVec2(outer_border.Min.x, outer_border.Max.y), outer_border.Max, outer_col, border_size); + } + } + if ((table->Flags & ImGuiTableFlags_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y) { + // Draw bottom-most row border + const float border_y = table->RowPosY2; + 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 +//------------------------------------------------------------------------- +// - TableGetSortSpecs() +// - TableFixColumnSortDirection() [Internal] +// - TableGetColumnNextSortDirection() [Internal] +// - TableSetColumnSortDirection() [Internal] +// - 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, +// else you may wastefully sort your data every frame! +// Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()! +ImGuiTableSortSpecs *ImGui::TableGetSortSpecs() +{ + ImGuiContext &g = *GImGui; + ImGuiTable *table = g.CurrentTable; + IM_ASSERT(table != NULL); + + if (!(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 (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) +{ + if (column->SortOrder == -1 || (column->SortDirectionsAvailMask & (1 << column->SortDirection)) != 0) + return; + 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. +IM_STATIC_ASSERT(ImGuiSortDirection_None == 0 && ImGuiSortDirection_Ascending == 1 && ImGuiSortDirection_Descending == 2); +ImGuiSortDirection ImGui::TableGetColumnNextSortDirection(ImGuiTableColumn *column) +{ + IM_ASSERT(column->SortDirectionsAvailCount > 0); + if (column->SortOrder == -1) + return TableGetColumnAvailSortDirection(column, 0); + for (int n = 0; n < 3; n++) + if (column->SortDirection == TableGetColumnAvailSortDirection(column, n)) + return TableGetColumnAvailSortDirection(column, (n + 1) % column->SortDirectionsAvailCount); + 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]; + if (other_column != column && !append_to_sort_specs) + other_column->SortOrder = -1; + TableFixColumnSortDirection(table, other_column); + } + 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; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { + ImGuiTableColumn *column = &table->Columns[column_n]; + if (column->SortOrder != -1 && !column->IsEnabled) + column->SortOrder = -1; + if (column->SortOrder == -1) + continue; + sort_order_count++; + 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) { + ImU64 fixed_mask = 0x00; + for (int sort_n = 0; sort_n < sort_order_count; sort_n++) { + // Fix: Rewrite sort order fields if needed so they have no gap or duplicate. + // (e.g. SortOrder 0 disappeared, SortOrder 1..2 exists --> rewrite then as SortOrder 0..1) + int column_with_smallest_sort_order = -1; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + if ((fixed_mask & ((ImU64)1 << (ImU64)column_n)) == 0 && table->Columns[column_n].SortOrder != -1) + if (column_with_smallest_sort_order == -1 || table->Columns[column_n].SortOrder < table->Columns[column_with_smallest_sort_order].SortOrder) + column_with_smallest_sort_order = column_n; + 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) { + sort_order_count = 1; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + if (column_n != column_with_smallest_sort_order) + table->Columns[column_n].SortOrder = -1; + break; + } + } + } + + // 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++) { + ImGuiTableColumn *column = &table->Columns[column_n]; + if (column->IsEnabled && !(column->Flags & ImGuiTableColumnFlags_NoSort)) { + sort_order_count = 1; + column->SortOrder = 0; + column->SortDirection = (ImU8)TableGetColumnAvailSortDirection(column, 0); + break; + } + } + + table->SortSpecsCount = (ImGuiTableColumnIdx)sort_order_count; +} + +void ImGui::TableSortSpecsBuild(ImGuiTable *table) +{ + bool dirty = table->IsSortSpecsDirty; + if (dirty) { + TableSortSpecsSanitize(table); + table->SortSpecsMulti.resize(table->SortSpecsCount <= 1 ? 0 : table->SortSpecsCount); + table->SortSpecs.SpecsDirty = true; // Mark as dirty for user + table->IsSortSpecsDirty = false; // Mark as not dirty for us + } + + // Write output + ImGuiTableColumnSortSpecs *sort_specs = (table->SortSpecsCount == 0) ? NULL + : (table->SortSpecsCount == 1) ? &table->SortSpecsSingle + : table->SortSpecsMulti.Data; + if (dirty && sort_specs != NULL) + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) { + ImGuiTableColumn *column = &table->Columns[column_n]; + if (column->SortOrder == -1) + continue; + IM_ASSERT(column->SortOrder < table->SortSpecsCount); + ImGuiTableColumnSortSpecs *sort_spec = &sort_specs[column->SortOrder]; + sort_spec->ColumnUserID = column->UserID; + sort_spec->ColumnIndex = (ImGuiTableColumnIdx)column_n; + sort_spec->SortOrder = (ImGuiTableColumnIdx)column->SortOrder; + sort_spec->SortDirection = column->SortDirection; + } + + table->SortSpecs.Specs = sort_specs; + table->SortSpecs.SpecsCount = table->SortSpecsCount; +} + +//------------------------------------------------------------------------- +// [SECTION] Tables: Headers +//------------------------------------------------------------------------- +// - TableGetHeaderRowHeight() [Internal] +// - TableHeadersRow() +// - TableHeader() +//------------------------------------------------------------------------- + +float ImGui::TableGetHeaderRowHeight() +{ + // Caring for a minor edge case: + // Calculate row height, for the unlikely case that some labels may be taller than others. + // If we didn't do that, uneven header height would highlight but smaller one before the tallest wouldn't catch input for all height. + // In your custom header row you may omit this all together and just call TableNextRow() without a height... + float row_height = GetTextLineHeight(); + int columns_count = TableGetColumnCount(); + for (int column_n = 0; column_n < columns_count; column_n++) { + ImGuiTableColumnFlags flags = TableGetColumnFlags(column_n); + if ((flags & ImGuiTableColumnFlags_IsEnabled) && !(flags & ImGuiTableColumnFlags_NoHeaderLabel)) + row_height = ImMax(row_height, CalcTextSize(TableGetColumnName(column_n)).y); + } + row_height += GetStyle().CellPadding.y * 2.0f; + return row_height; +} + +// [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 preceeded by Checkbox() or other custom widgets. +// See 'Demo->Tables->Custom headers' for a demonstration of implementing a custom version of this. +// This code is constructed to not make much use of internal functions, as it is intended to be a template to copy. +// FIXME-TABLE: TableOpenContextMenu() and TableGetHeaderRowHeight() are not public. +void ImGui::TableHeadersRow() +{ + ImGuiContext &g = *GImGui; + ImGuiTable *table = g.CurrentTable; + IM_ASSERT(table != NULL && "Need to call TableHeadersRow() after BeginTable()!"); + + // Layout if not already done (this is automatically done by TableNextRow, we do it here solely to facilitate stepping in debugger as it is frequent to step + // in TableUpdateLayout) + if (!table->IsLayoutLocked) + TableUpdateLayout(table); + + // Open row + const float row_y1 = GetCursorScreenPos().y; + const float row_height = TableGetHeaderRowHeight(); + TableNextRow(ImGuiTableRowFlags_Headers, row_height); + 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 unnamed labels (generally accidental, but let's behave nicely with them) + // In your own code you may omit the PushID/PopID all-together, provided you know they won't collide. + 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(-1); // 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() +void ImGui::TableHeader(const char *label) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + if (window->SkipItems) + return; + + ImGuiTable *table = g.CurrentTable; + IM_ASSERT(table != NULL && "Need to call TableHeader() after BeginTable()!"); + 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; + char sort_order_suf[4] = ""; + const float ARROW_SCALE = 0.65f; + if ((table->Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort)) { + w_arrow = ImFloor(g.FontSize * ARROW_SCALE + g.Style.FramePadding.x); + if (column->SortOrder > 0) { + ImFormatString(sort_order_suf, IM_ARRAYSIZE(sort_order_suf), "%d", column->SortOrder + 1); + 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 considering for merging. + float max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow; + column->ContentMaxXHeadersUsed = ImMax(column->ContentMaxXHeadersUsed, column->WorkMaxX); + column->ContentMaxXHeadersIdeal = ImMax(column->ContentMaxXHeadersIdeal, max_pos_x); + + // Keep header highlighted when context menu is open. + const bool selected = (table->IsContextPopupOpen && table->ContextPopupColumn == column_n && table->InstanceInteracted == table->InstanceCurrent); + 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. + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_AllowOverlap); + if (held || hovered || selected) { + const ImU32 col = GetColorU32(held ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + // RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + TableSetBgColor(ImGuiTableBgTarget_CellBg, col, table->CurrentColumn); + } else { + // Submit single cell bg color in the case we didn't submit a full header row + if ((table->RowFlags & ImGuiTableRowFlags_Headers) == 0) + TableSetBgColor(ImGuiTableBgTarget_CellBg, GetColorU32(ImGuiCol_TableHeaderBg), table->CurrentColumn); + } + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); + 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) { + // 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) + if (!((column->Flags | prev_column->Flags) & ImGuiTableColumnFlags_NoReorder)) + if ((column->IndexWithinEnabledSet < table->FreezeColumnsRequest) == (prev_column->IndexWithinEnabledSet < table->FreezeColumnsRequest)) + table->ReorderColumnDir = -1; + if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > cell_r.Max.x) + if (ImGuiTableColumn *next_column = (column->NextEnabledColumn != -1) ? &table->Columns[column->NextEnabledColumn] : NULL) + if (!((column->Flags | next_column->Flags) & ImGuiTableColumnFlags_NoReorder)) + 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)) { + if (column->SortOrder != -1) { + float x = ImMax(cell_r.Min.x, cell_r.Max.x - w_arrow - w_sort_text); + float y = label_pos.y; + if (column->SortOrder > 0) { + PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_Text, 0.70f)); + RenderText(ImVec2(x + g.Style.ItemInnerSpacing.x, y), sort_order_suf); + PopStyleColor(); + x += w_sort_text; + } + 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) { + ImGuiSortDirection sort_direction = TableGetColumnNextSortDirection(column); + 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); + + 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); +} + +//------------------------------------------------------------------------- +// [SECTION] Tables: Context Menu +//------------------------------------------------------------------------- +// - TableOpenContextMenu() [Internal] +// - TableDrawContextMenu() [Internal] +//------------------------------------------------------------------------- + +// Use -1 to open menu not specific to a given column. +void ImGui::TableOpenContextMenu(int column_n) +{ + ImGuiContext &g = *GImGui; + ImGuiTable *table = g.CurrentTable; + if (column_n == -1 && table->CurrentColumn != -1) // When called within a column automatically use this one (for consistency) + column_n = table->CurrentColumn; + if (column_n == table->ColumnsCount) // To facilitate using with TableGetHoveredColumn() + column_n = -1; + IM_ASSERT(column_n >= -1 && column_n < table->ColumnsCount); + if (table->Flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable)) { + table->IsContextPopupOpen = true; + table->ContextPopupColumn = (ImGuiTableColumnIdx)column_n; + table->InstanceInteracted = table->InstanceCurrent; + const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID); + OpenPopupEx(context_menu_id, ImGuiPopupFlags_None); + } +} + +bool ImGui::TableBeginContextMenuPopup(ImGuiTable *table) +{ + if (!table->IsContextPopupOpen || table->InstanceCurrent != table->InstanceInteracted) + return false; + const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID); + if (BeginPopupEx(context_menu_id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings)) + return true; + 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? +void ImGui::TableDrawContextMenu(ImGuiTable *table) +{ + ImGuiContext &g = *GImGui; + 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 (table->Flags & ImGuiTableFlags_Resizable) { + if (column != NULL) { + const bool can_resize = !(column->Flags & ImGuiTableColumnFlags_NoResize) && column->IsEnabled; + 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 + else + size_all_desc = LocalizeGetMsg(ImGuiLocKey_TableSizeAllDefault); // "###SizeAll" All stretch or mixed + if (MenuItem(size_all_desc, NULL)) + TableSetColumnWidthAutoAll(table); + want_separator = true; + } + + // Ordering + if (table->Flags & ImGuiTableFlags_Reorderable) { + if (MenuItem(LocalizeGetMsg(ImGuiLocKey_TableResetOrder), NULL, false, !table->IsDefaultDisplayOrder)) + 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 + if ((table->Flags & ImGuiTableFlags_Sortable) && column != NULL && (column->Flags & ImGuiTableColumnFlags_NoSort) == 0) + { + 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); + if (MenuItem("Sort in Descending Order", NULL, column->SortOrder != -1 && column->SortDirection == ImGuiSortDirection_Descending, (column->Flags & ImGuiTableColumnFlags_NoSortDescending) == 0)) + TableSetColumnSortDirection(table, column_n, ImGuiSortDirection_Descending, append_to_sort_specs); + } +#endif + + // Hiding / Visibility + if (table->Flags & ImGuiTableFlags_Hideable) { + if (want_separator) + Separator(); + want_separator = true; + + PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true); + 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) + menu_item_active = false; + if (MenuItem(name, NULL, other_column->IsUserEnabled, menu_item_active)) + other_column->IsUserEnabledNextFrame = !other_column->IsUserEnabled; + } + PopItemFlag(); + } +} + +//------------------------------------------------------------------------- +// [SECTION] Tables: Settings (.ini data) +//------------------------------------------------------------------------- +// FIXME: The binding/finding/creating flow are too confusing. +//------------------------------------------------------------------------- +// - TableSettingsInit() [Internal] +// - TableSettingsCalcChunkSize() [Internal] +// - TableSettingsCreate() [Internal] +// - TableSettingsFindByID() [Internal] +// - TableGetBoundSettings() [Internal] +// - TableResetSettings() +// - TableSaveSettings() [Internal] +// - TableLoadSettings() [Internal] +// - TableSettingsHandler_ClearAll() [Internal] +// - TableSettingsHandler_ApplyAll() [Internal] +// - TableSettingsHandler_ReadOpen() [Internal] +// - TableSettingsHandler_ReadLine() [Internal] +// - TableSettingsHandler_WriteAll() [Internal] +// - TableSettingsInstallHandler() [Internal] +//------------------------------------------------------------------------- +// [Init] 1: TableSettingsHandler_ReadXXXX() Load and parse .ini file into TableSettings. +// [Main] 2: TableLoadSettings() When table is created, bind Table to TableSettings, serialize TableSettings data into Table. +// [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) +{ + IM_PLACEMENT_NEW(settings) ImGuiTableSettings(); + ImGuiTableColumnSettings *settings_column = settings->GetColumnSettings(); + for (int n = 0; n < columns_count_max; n++, settings_column++) + IM_PLACEMENT_NEW(settings_column) ImGuiTableColumnSettings(); + settings->ID = id; + settings->ColumnsCount = (ImGuiTableColumnIdx)columns_count; + 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; + ImGuiTableSettings *settings = g.SettingsTables.alloc_chunk(TableSettingsCalcChunkSize(columns_count)); + TableSettingsInit(settings, id, columns_count, columns_count); + return settings; +} + +// Find existing settings +ImGuiTableSettings *ImGui::TableSettingsFindByID(ImGuiID id) +{ + // FIXME-OPT: Might want to store a lookup map for this? + ImGuiContext &g = *GImGui; + for (ImGuiTableSettings *settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + if (settings->ID == id) + return settings; + return NULL; +} + +// Get settings for a given table, NULL if none +ImGuiTableSettings *ImGui::TableGetBoundSettings(ImGuiTable *table) +{ + if (table->SettingsOffset != -1) { + ImGuiContext &g = *GImGui; + ImGuiTableSettings *settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset); + IM_ASSERT(settings->ID == table->ID); + if (settings->ColumnsCountMax >= table->ColumnsCount) + return settings; // OK + settings->ID = 0; // Invalidate storage, we won't fit because of a count change + } + return NULL; +} + +// Restore initial state of table (with or without saved settings) +void ImGui::TableResetSettings(ImGuiTable *table) +{ + table->IsInitializing = table->IsSettingsDirty = true; + table->IsResetAllRequest = false; + 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); + if (settings == NULL) { + settings = TableSettingsCreate(table->ID, table->ColumnsCount); + 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++) { + const float width_or_weight = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? column->StretchWeight : column->WidthRequest; + column_settings->WidthOrWeight = width_or_weight; + column_settings->Index = (ImGuiTableColumnIdx)n; + column_settings->DisplayOrder = column->DisplayOrder; + column_settings->SortOrder = column->SortOrder; + column_settings->SortDirection = column->SortDirection; + column_settings->IsEnabled = column->IsUserEnabled; + 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. + if (width_or_weight != column->InitStretchWeightOrWidth) + settings->SaveFlags |= ImGuiTableFlags_Resizable; + if (column->DisplayOrder != n) + settings->SaveFlags |= ImGuiTableFlags_Reorderable; + if (column->SortOrder != -1) + settings->SaveFlags |= ImGuiTableFlags_Sortable; + if (column->IsUserEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0)) + settings->SaveFlags |= ImGuiTableFlags_Hideable; + } + 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) { + settings = TableSettingsFindByID(table->ID); + if (settings == NULL) + return; + if (settings->ColumnsCount != table->ColumnsCount) // Allow settings if columns count changed. We could otherwise decide to return... + table->IsSettingsDirty = true; + table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings); + } else { + settings = TableGetBoundSettings(table); + } + + table->SettingsLoadedFlags = settings->SaveFlags; + table->RefScale = settings->RefScale; + + // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn + ImGuiTableColumnSettings *column_settings = settings->GetColumnSettings(); + ImU64 display_order_mask = 0; + for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++) { + 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) { + if (column_settings->IsStretch) + column->StretchWeight = column_settings->WidthOrWeight; + else + column->WidthRequest = column_settings->WidthOrWeight; + column->AutoFitQueue = 0x00; + } + if (settings->SaveFlags & ImGuiTableFlags_Reorderable) + column->DisplayOrder = column_settings->DisplayOrder; + else + column->DisplayOrder = (ImGuiTableColumnIdx)column_n; + display_order_mask |= (ImU64)1 << column->DisplayOrder; + column->IsUserEnabled = column->IsUserEnabledNextFrame = column_settings->IsEnabled; + 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; + for (int i = 0; i != g.Tables.GetMapSize(); i++) + if (ImGuiTable *table = g.Tables.TryGetMapData(i)) + table->SettingsOffset = -1; + g.SettingsTables.clear(); +} + +// Apply to existing windows (if any) +static void TableSettingsHandler_ApplyAll(ImGuiContext *ctx, ImGuiSettingsHandler *) +{ + ImGuiContext &g = *ctx; + for (int i = 0; i != g.Tables.GetMapSize(); i++) + if (ImGuiTable *table = g.Tables.TryGetMapData(i)) { + table->IsSettingsRequestLoad = true; + 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) { + TableSettingsInit(settings, id, columns_count, settings->ColumnsCountMax); // Recycle + return settings; + } + settings->ID = 0; // Invalidate storage, we won't fit because of a count change + } + 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) + return; + line = ImStrSkipBlank(line + r); + char c = 0; + ImGuiTableColumnSettings *column = settings->GetColumnSettings() + column_n; + column->Index = (ImGuiTableColumnIdx)column_n; + if (sscanf(line, "UserID=0x%08X%n", (ImU32 *)&n, &r) == 1) { + line = ImStrSkipBlank(line + r); + column->UserID = (ImGuiID)n; + } + if (sscanf(line, "Width=%d%n", &n, &r) == 1) { + line = ImStrSkipBlank(line + r); + column->WidthOrWeight = (float)n; + column->IsStretch = 0; + settings->SaveFlags |= ImGuiTableFlags_Resizable; + } + if (sscanf(line, "Weight=%f%n", &f, &r) == 1) { + line = ImStrSkipBlank(line + r); + column->WidthOrWeight = f; + column->IsStretch = 1; + settings->SaveFlags |= ImGuiTableFlags_Resizable; + } + if (sscanf(line, "Visible=%d%n", &n, &r) == 1) { + line = ImStrSkipBlank(line + r); + column->IsEnabled = (ImU8)n; + settings->SaveFlags |= ImGuiTableFlags_Hideable; + } + if (sscanf(line, "Order=%d%n", &n, &r) == 1) { + line = ImStrSkipBlank(line + r); + column->DisplayOrder = (ImGuiTableColumnIdx)n; + settings->SaveFlags |= ImGuiTableFlags_Reorderable; + } + 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; + for (ImGuiTableSettings *settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) { + 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; + const bool save_visible = (settings->SaveFlags & ImGuiTableFlags_Hideable) != 0; + const bool save_order = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0; + const bool save_sort = (settings->SaveFlags & ImGuiTableFlags_Sortable) != 0; + if (!save_size && !save_visible && !save_order && !save_sort) + continue; + + 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) + buf->appendf("RefScale=%g\n", settings->RefScale); + ImGuiTableColumnSettings *column = settings->GetColumnSettings(); + for (int column_n = 0; column_n < settings->ColumnsCount; column_n++, column++) { + // "Column 0 UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v" + bool save_column = column->UserID != 0 || save_size || save_visible || save_order || (save_sort && column->SortOrder != -1); + if (!save_column) + continue; + buf->appendf("Column %-2d", column_n); + if (column->UserID != 0) { + buf->appendf(" UserID=%08X", column->UserID); + } + if (save_size && column->IsStretch) { + buf->appendf(" Weight=%.4f", column->WidthOrWeight); + } + if (save_size && !column->IsStretch) { + buf->appendf(" Width=%d", (int)column->WidthOrWeight); + } + if (save_visible) { + buf->appendf(" Visible=%d", column->IsEnabled); + } + if (save_order) { + buf->appendf(" Order=%d", column->DisplayOrder); + } + if (save_sort && column->SortOrder != -1) { + buf->appendf(" Sort=%d%c", column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? 'v' : '^'); + } + buf->append("\n"); + } + buf->append("\n"); + } +} + +void ImGui::TableSettingsAddSettingsHandler() +{ + ImGuiSettingsHandler ini_handler; + ini_handler.TypeName = "Table"; + ini_handler.TypeHash = ImHashStr("Table"); + ini_handler.ClearAllFn = TableSettingsHandler_ClearAll; + ini_handler.ReadOpenFn = TableSettingsHandler_ReadOpen; + ini_handler.ReadLineFn = TableSettingsHandler_ReadLine; + ini_handler.ApplyAllFn = TableSettingsHandler_ApplyAll; + ini_handler.WriteAllFn = TableSettingsHandler_WriteAll; + AddSettingsHandler(&ini_handler); +} + +//------------------------------------------------------------------------- +// [SECTION] Tables: Garbage Collection +//------------------------------------------------------------------------- +// - TableRemove() [Internal] +// - TableGcCompactTransientBuffers() [Internal] +// - TableGcCompactSettings() [Internal] +//------------------------------------------------------------------------- + +// Remove Table (currently only used by TestEngine) +void ImGui::TableRemove(ImGuiTable *table) +{ + // IMGUI_DEBUG_PRINT("TableRemove() id=0x%08X\n", table->ID); + ImGuiContext &g = *GImGui; + int table_idx = g.Tables.GetIndex(table); + // memset(table->RawData.Data, 0, table->RawData.size_in_bytes()); + // memset(table, 0, sizeof(ImGuiTable)); + 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) +{ + // IMGUI_DEBUG_PRINT("TableGcCompactTransientBuffers() id=0x%08X\n", table->ID); + ImGuiContext &g = *GImGui; + IM_ASSERT(table->MemoryCompacted == false); + table->SortSpecs.Specs = NULL; + table->SortSpecsMulti.clear(); + table->IsSortSpecsDirty = true; // FIXME: In theory shouldn't have to leak into user performing a sort on resume. + table->ColumnsNames.clear(); + table->MemoryCompacted = true; + for (int n = 0; n < table->ColumnsCount; n++) + 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() +{ + ImGuiContext &g = *GImGui; + int required_memory = 0; + for (ImGuiTableSettings *settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + if (settings->ID != 0) + required_memory += (int)TableSettingsCalcChunkSize(settings->ColumnsCount); + if (required_memory == g.SettingsTables.Buf.Size) + return; + ImChunkStream new_chunk_stream; + new_chunk_stream.Buf.reserve(required_memory); + for (ImGuiTableSettings *settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings)) + if (settings->ID != 0) + 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_; + if (sizing_policy == ImGuiTableFlags_SizingFixedFit) { + return "FixedFit"; + } + if (sizing_policy == ImGuiTableFlags_SizingFixedSame) { + return "FixedSame"; + } + if (sizing_policy == ImGuiTableFlags_SizingStretchProp) { + return "StretchProp"; + } + if (sizing_policy == ImGuiTableFlags_SizingStretchSame) { + return "StretchSame"; + } + return "N/A"; +} + +void ImGui::DebugNodeTable(ImGuiTable *table) +{ + const bool is_active = (table->LastFrameActive >= GetFrameCount() - 2); // Note that fully clipped early out scrolling tables will appear as inactive here. + if (!is_active) { + PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); + } + bool open = + TreeNode(table, "Table 0x%08X (%d columns, in '%s')%s", table->ID, table->ColumnsCount, table->OuterWindow->Name, is_active ? "" : " *Inactive*"); + if (!is_active) { + PopStyleColor(); + } + if (IsItemHovered()) + GetForegroundDrawList()->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255)); + if (IsItemVisible() && table->HoveredColumnBody != -1) + GetForegroundDrawList()->AddRect(GetItemRectMin(), GetItemRectMax(), IM_COL32(255, 255, 0, 255)); + if (!open) + return; + if (table->InstanceCurrent > 0) + Text("** %d instances of same table! Some data below will refer to last instance.", table->InstanceCurrent + 1); + 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)" : ""); + BulletText("CellPaddingX: %.1f, CellSpacingX: %.1f/%.1f, OuterPaddingX: %.1f", + table->CellPaddingX, + table->CellSpacingX1, + table->CellSpacingX2, + table->OuterPaddingX); + BulletText("HoveredColumnBody: %d, HoveredColumnBorder: %d", table->HoveredColumnBody, table->HoveredColumnBorder); + BulletText("ResizedColumn: %d, ReorderColumn: %d, HeldHeaderColumn: %d", table->ResizedColumn, table->ReorderColumn, table->HeldHeaderColumn); + for (int n = 0; n < table->InstanceCurrent + 1; n++) { + ImGuiTableInstanceData *table_instance = TableGetInstanceData(table, n); + BulletText("Instance %d: HoveredRow: %d, LastOuterHeight: %.2f", n, table_instance->HoveredRowLast, table_instance->LastOuterHeight); + } + // BulletText("BgDrawChannels: %d/%d", 0, table->BgDrawChannelUnfrozen); + float sum_weights = 0.0f; + for (int n = 0; n < table->ColumnsCount; n++) + if (table->Columns[n].Flags & ImGuiTableColumnFlags_WidthStretch) + sum_weights += table->Columns[n].StretchWeight; + for (int n = 0; n < table->ColumnsCount; n++) { + ImGuiTableColumn *column = &table->Columns[n]; + const char *name = TableGetColumnName(table, n); + char buf[512]; + ImFormatString(buf, + IM_ARRAYSIZE(buf), + "Column %d order %d '%s': offset %+.2f to %+.2f%s\n" + "Enabled: %d, VisibleX/Y: %d/%d, RequestOutput: %d, SkipItems: %d, DrawChannels: %d,%d\n" + "WidthGiven: %.1f, Request/Auto: %.1f/%.1f, StretchWeight: %.3f (%.1f%%)\n" + "MinX: %.1f, MaxX: %.1f (%+.1f), ClipRect: %.1f to %.1f (+%.1f)\n" + "ContentWidth: %.1f,%.1f, HeadersUsed/Ideal %.1f/%.1f\n" + "Sort: %d%s, UserID: 0x%08X, Flags: 0x%04X: %s%s%s..", + n, + column->DisplayOrder, + name, + column->MinX - table->WorkRect.Min.x, + column->MaxX - table->WorkRect.Min.x, + (n < table->FreezeColumnsRequest) ? " (Frozen)" : "", + column->IsEnabled, + column->IsVisibleX, + column->IsVisibleY, + column->IsRequestOutput, + column->IsSkipItems, + column->DrawChannelFrozen, + column->DrawChannelUnfrozen, + column->WidthGiven, + column->WidthRequest, + column->WidthAuto, + column->StretchWeight, + column->StretchWeight > 0.0f ? (column->StretchWeight / sum_weights) * 100.0f : 0.0f, + column->MinX, + column->MaxX, + column->MaxX - column->MinX, + column->ClipRect.Min.x, + column->ClipRect.Max.x, + column->ClipRect.Max.x - column->ClipRect.Min.x, + column->ContentMaxXFrozen - column->WorkMinX, + column->ContentMaxXUnfrozen - column->WorkMinX, + column->ContentMaxXHeadersUsed - column->WorkMinX, + column->ContentMaxXHeadersIdeal - column->WorkMinX, + column->SortOrder, + (column->SortDirection == ImGuiSortDirection_Ascending) ? " (Asc)" + : (column->SortDirection == ImGuiSortDirection_Descending) ? " (Des)" + : "", + column->UserID, + column->Flags, + (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? "WidthStretch " : "", + (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? "WidthFixed " : "", + (column->Flags & ImGuiTableColumnFlags_NoResize) ? "NoResize " : ""); + Bullet(); + Selectable(buf); + if (IsItemHovered()) { + ImRect r(column->MinX, table->OuterRect.Min.y, column->MaxX, table->OuterRect.Max.y); + GetForegroundDrawList()->AddRect(r.Min, r.Max, IM_COL32(255, 255, 0, 255)); + } + } + if (ImGuiTableSettings *settings = TableGetBoundSettings(table)) + DebugNodeTableSettings(settings); + if (clear_settings) + 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)) + return; + BulletText("SaveFlags: 0x%08X", settings->SaveFlags); + BulletText("ColumnsCount: %d (max %d)", settings->ColumnsCount, settings->ColumnsCountMax); + for (int n = 0; n < settings->ColumnsCount; n++) { + ImGuiTableColumnSettings *column_settings = &settings->GetColumnSettings()[n]; + ImGuiSortDirection sort_dir = (column_settings->SortOrder != -1) ? (ImGuiSortDirection)column_settings->SortDirection : ImGuiSortDirection_None; + BulletText("Column %d Order %d SortOrder %d %s Vis %d %s %7.3f UserID 0x%08X", + n, + column_settings->DisplayOrder, + column_settings->SortOrder, + (sort_dir == ImGuiSortDirection_Ascending) ? "Asc" + : (sort_dir == ImGuiSortDirection_Descending) ? "Des" + : "---", + column_settings->IsEnabled, + column_settings->IsStretch ? "Weight" : "Width ", + column_settings->WidthOrWeight, + column_settings->UserID); + } + 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!) +//------------------------------------------------------------------------- +// FIXME: sizing is lossy when columns width is very small (default width may turn negative etc.) +//------------------------------------------------------------------------- +// - SetWindowClipRectBeforeSetChannel() [Internal] +// - GetColumnIndex() +// - GetColumnsCount() +// - GetColumnOffset() +// - GetColumnWidth() +// - SetColumnOffset() +// - SetColumnWidth() +// - PushColumnClipRect() [Internal] +// - PushColumnsBackground() [Internal] +// - PopColumnsBackground() [Internal] +// - FindOrCreateColumns() [Internal] +// - GetColumnsID() [Internal] +// - BeginColumns() +// - NextColumn() +// - 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 +// the subsequent single call to SetCurrentChannel() does it things once. +void ImGui::SetWindowClipRectBeforeSetChannel(ImGuiWindow *window, const ImRect &clip_rect) +{ + ImVec4 clip_rect_vec4 = clip_rect.ToVec4(); + window->ClipRect = clip_rect; + 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_WIDTH = 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 + // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning. + ImGuiContext &g = *GImGui; + 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 + COLUMNS_HIT_RECT_HALF_WIDTH - 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; + else + 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; + ImGuiWindow *window = g.CurrentWindow; + 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() +{ + ImGuiWindow *window = GetCurrentWindowRead(); + 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); + IM_ASSERT(columns->ID == id); + columns->Current = 0; + columns->Count = columns_count; + 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; + const float half_clip_extend_x = ImFloor(ImMax(window->WindowPadding.x * 0.5f, window->WindowBorderSize)); + const float max_1 = window->WorkRect.Max.x + column_padding - ImMax(column_padding - window->WindowPadding.x, 0.0f); + const float max_2 = window->WorkRect.Max.x + half_clip_extend_x; + 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) { + columns->Columns.reserve(columns_count + 1); + for (int n = 0; n < columns_count + 1; n++) { + ImGuiOldColumnData column; + column.OffsetNorm = n / (float)columns_count; + columns->Columns.push_back(column); + } + } + + for (int n = 0; n < columns_count; n++) { + // Compute clipping rectangle + ImGuiOldColumnData *column = &columns->Columns[n]; + float clip_x1 = IM_ROUND(window->Pos.x + GetColumnOffset(n)); + float clip_x2 = IM_ROUND(window->Pos.x + GetColumnOffset(n + 1) - 1.0f); + 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); + float width = offset_1 - offset_0; + PushItemWidth(width * 0.65f); + window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f); + window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); + 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_FLOOR(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) { + // Columns 1+ ignore IndentX (by canceling it out) + // FIXME-COLUMNS: Unnecessary, could be locked? + window->DC.ColumnsOffset.x = GetColumnOffset(columns->Current) - window->DC.Indent.x + column_padding; + } else { + // New row/line: column 0 honor IndentX. + window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f); + window->DC.IsSameLine = false; + columns->LineMinY = columns->LineMaxY; + } + window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); + 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); + float width = offset_1 - offset_0; + 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; + if (!(flags & ImGuiOldColumnFlags_NoBorder) && !window->SkipItems) { + // We clip Y boundaries CPU side because very long triangles are mishandled by some GPU drivers. + const float y1 = ImMax(columns->HostCursorPosY, window->ClipRect.Min.y); + const float y2 = ImMin(window->DC.CursorPos.y, window->ClipRect.Max.y); + int dragging_column = -1; + for (int n = 1; n < columns->Count; n++) { + ImGuiOldColumnData *column = &columns->Columns[n]; + float x = window->Pos.x + GetColumnOffset(n); + const ImGuiID column_id = columns->ID + ImGuiID(n); + const float column_hit_hw = COLUMNS_HIT_RECT_HALF_WIDTH; + 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)) { + ButtonBehavior(column_hit_rect, column_id, &hovered, &held); + if (hovered || held) + g.MouseCursor = ImGuiMouseCursor_ResizeEW; + 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_FLOOR(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) { + if (!columns->IsBeingResized) + for (int n = 0; n < columns->Count + 1; n++) + columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm; + columns->IsBeingResized = is_being_resized = true; + float x = GetDraggedColumnOffset(columns, dragging_column); + SetColumnOffset(dragging_column, x); + } + } + columns->IsBeingResized = is_being_resized; + + window->WorkRect = window->ParentWorkRect; + window->ParentWorkRect = columns->HostBackupParentWorkRect; + window->DC.CurrentColumns = NULL; + window->DC.ColumnsOffset.x = 0.0f; + window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); + NavUpdateCurrentWindowIsScrollPushableX(); +} + +void ImGui::Columns(int columns_count, const char *id, bool border) +{ + ImGuiWindow *window = GetCurrentWindow(); + IM_ASSERT(columns_count >= 1); + + ImGuiOldColumnFlags flags = (border ? 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/extern/imgui/src/imgui_widgets.cpp b/extern/imgui/src/imgui_widgets.cpp new file mode 100644 index 0000000..651f027 --- /dev/null +++ b/extern/imgui/src/imgui_widgets.cpp @@ -0,0 +1,9107 @@ +// dear imgui, v1.89.9 +// (widgets code) + +/* + +Index of this file: + +// [SECTION] Forward Declarations +// [SECTION] Widgets: Text, etc. +// [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.) +// [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.) +// [SECTION] Widgets: ComboBox +// [SECTION] Data Type and Data Formatting Helpers +// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc. +// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc. +// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc. +// [SECTION] Widgets: InputText, InputTextMultiline +// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. +// [SECTION] Widgets: TreeNode, CollapsingHeader, etc. +// [SECTION] Widgets: Selectable +// [SECTION] Widgets: ListBox +// [SECTION] Widgets: PlotLines, PlotHistogram +// [SECTION] Widgets: Value helpers +// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc. +// [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 +#pragma warning(disable : 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later +#pragma warning(disable : 5054) // operator '|': deprecated between enumerations of different types +#endif +#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") +#pragma clang diagnostic ignored \ + "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to + // be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored \ + "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to vsnformat(). yes, + // user passing incorrect format strings can crash the code. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#pragma clang diagnostic ignored \ + "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using + // printf() is a misery with this as C++ va_arg ellipsis changes float to double. +#pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') +#pragma clang diagnostic ignored \ + "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#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 + +//------------------------------------------------------------------------- +// 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; +static const unsigned char IM_U8_MIN = 0; +static const unsigned char IM_U8_MAX = 0xFF; +static const signed short IM_S16_MIN = -32768; +static const signed short IM_S16_MAX = 32767; +static const unsigned short IM_U16_MIN = 0; +static const unsigned short IM_U16_MAX = 0xFFFF; +static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000); +static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF) +static const ImU32 IM_U32_MIN = 0; +static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF) +#ifdef LLONG_MIN +static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll); +static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll); +#else +static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1; +static const ImS64 IM_S64_MAX = 9223372036854775807LL; +#endif +static const ImU64 IM_U64_MIN = 0; +#ifdef ULLONG_MAX +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, + ImGuiInputSource input_source); +static int InputTextCalcTextLenAndLineCount(const char *text_begin, const char **out_text_end); +static ImVec2 InputTextCalcTextSizeW(ImGuiContext *ctx, + const ImWchar *text_begin, + const ImWchar *text_end, + const ImWchar **remaining = NULL, + ImVec2 *out_offset = NULL, + bool stop_on_new_line = false); + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Text, etc. +//------------------------------------------------------------------------- +// - TextEx() [Internal] +// - TextUnformatted() +// - Text() +// - TextV() +// - TextColored() +// - TextColoredV() +// - TextDisabled() +// - TextDisabledV() +// - TextWrapped() +// - TextWrappedV() +// - LabelText() +// - LabelTextV() +// - 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 + strlen(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); + if (text_end - text <= 2000 || wrap_enabled) { + // 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); + } else { + // Long text! + // Perform manual coarse clipping to optimize for long multi-line text + // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled. + // - We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item + // on the line. + // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop. + 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) { + int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height); + if (lines_skippable > 0) { + int lines_skipped = 0; + while (line < text_end && lines_skipped < lines_skippable) { + const char *line_end = (const char *)memchr(line, '\n', text_end - line); + if (!line_end) + line_end = text_end; + if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0) + text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x); + line = line_end + 1; + lines_skipped++; + } + pos.y += lines_skipped * line_height; + } + } + + // Lines to render + if (line < text_end) { + ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height)); + while (line < text_end) { + if (IsClippedEx(line_rect, 0)) + break; + + const char *line_end = (const char *)memchr(line, '\n', text_end - line); + if (!line_end) + line_end = text_end; + text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x); + RenderText(pos, line, line_end, false); + line = line_end + 1; + line_rect.Min.y += line_height; + line_rect.Max.y += line_height; + pos.y += line_height; + } + + // Count remaining lines + int lines_skipped = 0; + while (line < text_end) { + const char *line_end = (const char *)memchr(line, '\n', text_end - line); + if (!line_end) + line_end = text_end; + if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0) + text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x); + line = line_end + 1; + lines_skipped++; + } + 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; + va_start(args, 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; + va_start(args, 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; + va_start(args, fmt); + TextDisabledV(fmt, args); + va_end(args); +} + +void ImGui::TextDisabledV(const char *fmt, va_list args) +{ + ImGuiContext &g = *GImGui; + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + TextV(fmt, args); + PopStyleColor(); +} + +void ImGui::TextWrapped(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextWrappedV(fmt, args); + va_end(args); +} + +void ImGui::TextWrappedV(const char *fmt, va_list args) +{ + ImGuiContext &g = *GImGui; + const bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position if one is already set + if (need_backup) + PushTextWrapPos(0.0f); + TextV(fmt, args); + if (need_backup) + PopTextWrapPos(); +} + +void ImGui::LabelText(const char *label, const char *fmt, ...) +{ + va_list args; + va_start(args, 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; + va_start(args, 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); + const ImVec2 total_size = + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x * 2) : 0.0f), label_size.y); // Empty text doesn't add padding + ImVec2 pos = window->DC.CursorPos; + pos.y += window->DC.CurrLineTextBaseOffset; + ItemSize(total_size, 0.0f); + 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 +//------------------------------------------------------------------------- +// - ButtonBehavior() [Internal] +// - Button() +// - SmallButton() +// - InvisibleButton() +// - ArrowButton() +// - CloseButton() [Internal] +// - CollapseButton() [Internal] +// - GetWindowScrollbarID() [Internal] +// - GetWindowScrollbarRect() [Internal] +// - Scrollbar() [Internal] +// - ScrollbarEx() [Internal] +// - Image() +// - ImageButton() +// - Checkbox() +// - CheckboxFlagsT() [Internal] +// - CheckboxFlags() +// - RadioButton() +// - ProgressBar() +// - Bullet() +//------------------------------------------------------------------------- + +// 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. +// By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior. +// See the series of events below and the corresponding state reported by dear imgui: +//------------------------------------------------------------------------------------------------------------------------------------------------ +// with PressedOnClickRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() +// Frame N+0 (mouse is outside bb) - - - - - - +// Frame N+1 (mouse moves inside bb) - true - - - - +// Frame N+2 (mouse button is down) - true true true - true +// Frame N+3 (mouse button is down) - true true - - - +// Frame N+4 (mouse moves outside bb) - - true - - - +// Frame N+5 (mouse moves inside bb) - true true - - - +// Frame N+6 (mouse button is released) true true - - true - +// Frame N+7 (mouse button is released) - true - - - - +// Frame N+8 (mouse moves outside bb) - - - - - - +//------------------------------------------------------------------------------------------------------------------------------------------------ +// with PressedOnClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() +// Frame N+2 (mouse button is down) true true true true - true +// Frame N+3 (mouse button is down) - true true - - - +// Frame N+6 (mouse button is released) - true - - true - +// Frame N+7 (mouse button is released) - true - - - - +//------------------------------------------------------------------------------------------------------------------------------------------------ +// with PressedOnRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() +// Frame N+2 (mouse button is down) - true - - - true +// Frame N+3 (mouse button is down) - true - - - - +// Frame N+6 (mouse button is released) true true - - - - +// Frame N+7 (mouse button is released) - true - - - - +//------------------------------------------------------------------------------------------------------------------------------------------------ +// with PressedOnDoubleClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() +// Frame N+0 (mouse button is down) - true - - - true +// Frame N+1 (mouse button is down) - true - - - - +// Frame N+2 (mouse button is released) - true - - - - +// Frame N+3 (mouse button is released) - true - - - - +// Frame N+4 (mouse button is down) true true true true - true +// Frame N+5 (mouse button is down) - true true - - - +// Frame N+6 (mouse button is released) - true - - true - +// Frame N+7 (mouse button is released) - true - - - - +//------------------------------------------------------------------------------------------------------------------------------------------------ +// Note that some combinations are supported, +// - PressedOnDragDropHold can generally be associated with any flag. +// - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported. +//------------------------------------------------------------------------------------------------------------------------------------------------ +// The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set: +// Repeat+ Repeat+ Repeat+ Repeat+ +// PressedOnClickRelease PressedOnClick PressedOnRelease PressedOnDoubleClick +//------------------------------------------------------------------------------------------------------------------------------------------------- +// Frame N+0 (mouse button is down) - true - true +// ... - - - - +// Frame N + RepeatDelay true true - true +// ... - - - - +// Frame N + RepeatDelay + RepeatRate*N true true - true +//------------------------------------------------------------------------------------------------------------------------------------------------- + +bool ImGui::ButtonBehavior(const ImRect &bb, ImGuiID id, bool *out_hovered, bool *out_held, ImGuiButtonFlags flags) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = GetCurrentWindow(); + + // Default only reacts to left mouse button + if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0) + flags |= ImGuiButtonFlags_MouseButtonDefault_; + + // Default behavior requires click + release inside bounding box + if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0) + flags |= ImGuiButtonFlags_PressedOnDefault_; + + // 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.InFlags : g.CurrentItemFlags); + if (flags & ImGuiButtonFlags_AllowOverlap) + item_flags |= ImGuiItemFlags_AllowOverlap; + if (flags & ImGuiButtonFlags_Repeat) + item_flags |= ImGuiItemFlags_ButtonRepeat; + + ImGuiWindow *backup_hovered_window = g.HoveredWindow; + const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindow == window; + if (flatten_hovered_children) + g.HoveredWindow = window; + +#ifdef IMGUI_ENABLE_TEST_ENGINE + // Alternate registration spot, for when caller didn't use ItemAdd() + if (id != 0 && 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 + if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) + if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) { + hovered = true; + SetHoveredID(id); + if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER) { + pressed = true; + g.DragDropHoldJustPressedId = id; + 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) { + // 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. + int mouse_button_clicked = -1; + int mouse_button_released = -1; + for (int button = 0; button < 3; button++) + if (flags & (ImGuiButtonFlags_MouseButtonLeft << button)) // Handle ImGuiButtonFlags_MouseButtonRight and ImGuiButtonFlags_MouseButtonMiddle here. + { + if (IsMouseClicked(button, 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 + if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) { + if (mouse_button_clicked != -1 && g.ActiveId != id) { + if (!(flags & ImGuiButtonFlags_NoSetKeyOwner)) + SetKeyOwner(MouseButtonToKey(mouse_button_clicked), id); + if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere)) { + SetActiveID(id, window); + g.ActiveIdMouseButton = mouse_button_clicked; + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + SetFocusID(id, window); + FocusWindow(window); + } + if ((flags & ImGuiButtonFlags_PressedOnClick) + || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2)) { + pressed = true; + if (flags & ImGuiButtonFlags_NoHoldingActiveId) + ClearActiveID(); + else + SetActiveID(id, window); // Hold on ID + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + SetFocusID(id, window); + g.ActiveIdMouseButton = mouse_button_clicked; + FocusWindow(window); + } + } + if (flags & ImGuiButtonFlags_PressedOnRelease) { + if (mouse_button_released != -1) { + const bool has_repeated_at_least_once = (item_flags & ImGuiItemFlags_ButtonRepeat) + && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay; // Repeat mode trumps on release behavior + if (!has_repeated_at_least_once) + pressed = true; + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + SetFocusID(id, window); + 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, test_owner_id, ImGuiInputFlags_Repeat)) + pressed = true; + } + + if (pressed) + g.NavDisableHighlight = true; + } + + // Gamepad/Keyboard navigation + // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse. + if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId)) + if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus)) + hovered = true; + if (g.NavActivateDownId == id) { + bool nav_activated_by_code = (g.NavActivateId == id); + bool nav_activated_by_inputs = (g.NavActivatePressedId == id); + if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat)) { + // Avoid pressing multiple keys from triggering excessive amount of repeat events + const ImGuiKeyData *key1 = GetKeyData(ImGuiKey_Space); + const ImGuiKeyData *key2 = GetKeyData(ImGuiKey_Enter); + const ImGuiKeyData *key3 = GetKeyData(ImGuiKey_NavGamepadActivate); + const float t1 = ImMax(ImMax(key1->DownDuration, key2->DownDuration), key3->DownDuration); + nav_activated_by_inputs = CalcTypematicRepeatAmount(t1 - g.IO.DeltaTime, t1, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0; + } + if (nav_activated_by_code || nav_activated_by_inputs) { + // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button. + pressed = true; + SetActiveID(id, window); + g.ActiveIdSource = g.NavInputSource; + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + SetFocusID(id, window); + } + } + + // Process while held + bool held = false; + if (g.ActiveId == id) { + if (g.ActiveIdSource == ImGuiInputSource_Mouse) { + if (g.ActiveIdIsJustActivated) + g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; + + const int mouse_button = g.ActiveIdMouseButton; + if (mouse_button == -1) { + // Fallback for the rare situation were g.ActiveId was set programmatically or from another widget (e.g. #6304). + ClearActiveID(); + } else if (IsMouseDown(mouse_button, test_owner_id)) { + held = true; + } else { + bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0; + bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0; + if ((release_in || release_anywhere) && !g.DragDropActive) { + // Report as pressed when releasing the mouse (this is the most common path) + bool is_double_click_release = + (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseReleased[mouse_button] && g.IO.MouseClickedLastCount[mouse_button] == 2; + bool is_repeating_already = (item_flags & ImGuiItemFlags_ButtonRepeat) + && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps + bool is_button_avail_or_owned = TestKeyOwner(MouseButtonToKey(mouse_button), test_owner_id); + if (!is_double_click_release && !is_repeating_already && is_button_avail_or_owned) + pressed = true; + } + ClearActiveID(); + } + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + g.NavDisableHighlight = true; + } else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) { + // When activated using Nav, we hold on the ActiveID until activation button is released + if (g.NavActivateDownId != id) + ClearActiveID(); + } + if (pressed) + g.ActiveIdHasBeenPressedBefore = 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); + RenderNavHighlight(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) +{ + ImGuiContext &g = *GImGui; + float backup_padding_y = g.Style.FramePadding.y; + g.Style.FramePadding.y = 0.0f; + bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine); + 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) +{ + ImGuiContext &g = *GImGui; + 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)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + 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); + RenderNavHighlight(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)); + ImRect bb_interact = bb; + const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea(); + if (area_to_visible_ratio < 1.5f) + bb_interact.Expand(ImFloor(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 + // FIXME: Clarify this mess + ImU32 col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); + ImVec2 center = bb.GetCenter(); + if (hovered) + window->DrawList->AddCircleFilled(center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f), col); + + float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; + ImU32 cross_col = GetColorU32(ImGuiCol_Text); + center -= ImVec2(0.5f, 0.5f); + window->DrawList->AddLine(center + ImVec2(+cross_extent, +cross_extent), center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f); + window->DrawList->AddLine(center + ImVec2(+cross_extent, -cross_extent), center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f); + + return pressed; +} + +bool ImGui::CollapseButton(ImGuiID id, const ImVec2 &pos) +{ + 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 + ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + ImU32 text_col = GetColorU32(ImGuiCol_Text); + if (hovered || held) + window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0.0f, -0.5f), g.FontSize * 0.5f + 1.0f, bg_col); + 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)) + StartMouseMovingWindow(window); + + 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) +{ + const ImRect outer_rect = window->Rect(); + const ImRect inner_rect = window->InnerRect; + const float border_size = window->WindowBorderSize; + const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar) + IM_ASSERT(scrollbar_size > 0.0f); + if (axis == ImGuiAxis_X) + return ImRect(inner_rect.Min.x, + ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size), + inner_rect.Max.x - border_size, + outer_rect.Max.y - border_size); + else + return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), + inner_rect.Min.y, + 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; + if (axis == ImGuiAxis_X) { + rounding_corners |= ImDrawFlags_RoundCornersBottomLeft; + if (!window->ScrollbarY) + rounding_corners |= ImDrawFlags_RoundCornersBottomRight; + } else { + if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) + rounding_corners |= ImDrawFlags_RoundCornersTopRight; + if (!window->ScrollbarX) + rounding_corners |= ImDrawFlags_RoundCornersBottomRight; + } + float size_avail = window->InnerRect.Max[axis] - window->InnerRect.Min[axis]; + float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f; + ImS64 scroll = (ImS64)window->Scroll[axis]; + ScrollbarEx(bb, id, axis, &scroll, (ImS64)size_avail, (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) +// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar +// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. +// Still, the code should probably be made simpler.. +bool ImGui::ScrollbarEx(const ImRect &bb_frame, ImGuiID id, ImGuiAxis axis, ImS64 *p_scroll_v, ImS64 size_avail_v, ImS64 size_contents_v, ImDrawFlags flags) +{ + ImGuiContext &g = *GImGui; + 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 < g.FontSize + g.Style.FramePadding.y * 2.0f) + alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.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_FLOOR((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_FLOOR((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_avail_v) + > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers. + const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_avail_v), (ImS64)1); + const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_avail_v / (float)win_size_v), style.GrabMinSize, 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_avail_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 + if (held && allow_interaction && grab_h_norm < 1.0f) { + 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); + SetHoveredID(id); + + bool seek_absolute = false; + if (g.ActiveIdIsJustActivated) { + // On initial click calculate the distance between mouse and the center of the grab + seek_absolute = (clicked_v_norm < grab_v_norm || clicked_v_norm > grab_v_norm + grab_h_norm); + if (seek_absolute) + g.ScrollbarClickDeltaToGrabCenter = 0.0f; + else + g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f; + } + + // 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 + const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm)); + *p_scroll_v = (ImS64)(scroll_v_norm * 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 seeked 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); + window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags); + ImRect grab_rect; + if (axis == ImGuiAxis_X) + grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y); + 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; +} + +void ImGui::Image(ImTextureID user_texture_id, const ImVec2 &size, const ImVec2 &uv0, const ImVec2 &uv1, const ImVec4 &tint_col, const ImVec4 &border_col) +{ + ImGuiWindow *window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + if (border_col.w > 0.0f) + bb.Max += ImVec2(2, 2); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + return; + + if (border_col.w > 0.0f) { + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f); + window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col)); + } else { + window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col)); + } +} + +// ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390) +// We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API. +bool ImGui::ImageButtonEx(ImGuiID id, + ImTextureID texture_id, + const ImVec2 &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 + 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); + RenderNavHighlight(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(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + + return pressed; +} + +bool ImGui::ImageButton(const char *str_id, + ImTextureID user_texture_id, + const ImVec2 &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, size, uv0, uv1, bg_col, tint_col); +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +// Legacy API obsoleted in 1.89. Two differences with new ImageButton() +// - new ImageButton() requires an explicit 'const char* str_id' Old ImageButton() used opaque imTextureId (created issue with: multiple buttons with same +// image, transient texture id values, opaque computation of ID) +// - new ImageButton() always use style.FramePadding Old ImageButton() had an override argument. +// If you need to change padding with new ImageButton() you can use PushStyleVar(ImGuiStyleVar_FramePadding, value), consistent with other Button functions. +bool ImGui::ImageButton(ImTextureID user_texture_id, + const ImVec2 &size, + const ImVec2 &uv0, + const ImVec2 &uv1, + int frame_padding, + const ImVec4 &bg_col, + const ImVec4 &tint_col) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + if (window->SkipItems) + return false; + + // Default to using texture ID as ID. User can still push string/integer prefixes. + PushID((void *)(intptr_t)user_texture_id); + const ImGuiID id = window->GetID("#image"); + PopID(); + + if (frame_padding >= 0) + PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2((float)frame_padding, (float)frame_padding)); + bool ret = ImageButtonEx(id, user_texture_id, size, uv0, uv1, bg_col, tint_col); + if (frame_padding >= 0) + PopStyleVar(); + return ret; +} +#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)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id)) { + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); + return false; + } + + bool hovered, held; + bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) { + *v = !(*v); + MarkItemEdited(id); + } + + const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); + RenderNavHighlight(total_bb, id); + RenderFrame(check_bb.Min, + check_bb.Max, + GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive + : hovered ? ImGuiCol_FrameBgHovered + : ImGuiCol_FrameBg), + true, + style.FrameRounding); + ImU32 check_col = GetColorU32(ImGuiCol_CheckMark); + bool mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0; + if (mixed_value) { + // Undocumented tristate/mixed/indeterminate checkbox (#2644) + // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox) + ImVec2 pad(ImMax(1.0f, IM_FLOOR(square_sz / 3.6f)), ImMax(1.0f, IM_FLOOR(square_sz / 3.6f))); + window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding); + } else if (*v) { + const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f)); + RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f); + } + + ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); + if (g.LogEnabled) + LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]"); + if (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) +{ + bool all_on = (*flags & flags_value) == flags_value; + bool any_on = (*flags & flags_value) != 0; + bool pressed; + if (!all_on && any_on) { + ImGuiContext &g = *GImGui; + g.NextItemData.ItemFlags |= ImGuiItemFlags_MixedValue; + pressed = Checkbox(label, &all_on); + } else { + pressed = Checkbox(label, &all_on); + } + if (pressed) { + if (all_on) + *flags |= flags_value; + else + *flags &= ~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)); + 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)); + 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); + + RenderNavHighlight(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); + if (active) { + const float pad = ImMax(1.0f, IM_FLOOR(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) +{ + const bool pressed = RadioButton(label, *v == v_button); + if (pressed) + *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; + + // Render + fraction = ImSaturate(fraction); + RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); + const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y); + RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding); + + // Default displaying the fraction as percentage string, but user can override it + char overlay_buf[32]; + if (!overlay) { + 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) + RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), + bb.Max, + overlay, + NULL, + &overlay_size, + ImVec2(0.0f, 0.5f), + &bb); +} + +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); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height)); + ItemSize(bb); + if (!ItemAdd(bb, 0)) { + 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); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Low-level Layout helpers +//------------------------------------------------------------------------- +// - Spacing() +// - Dummy() +// - NewLine() +// - AlignTextToFramePadding() +// - SeparatorEx() [Internal] +// - Separator() +// - SplitterBehavior() [Internal] +// - ShrinkWidths() [Internal] +//------------------------------------------------------------------------- + +void ImGui::Spacing() +{ + ImGuiWindow *window = GetCurrentWindow(); + if (window->SkipItems) + 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; + window->DC.IsSameLine = false; + if (window->DC.CurrLineSize.y > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height. + ItemSize(ImVec2(0, 0)); + else + 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. +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). + float y1 = window->DC.CursorPos.y; + float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y; + const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness, y2)); + ItemSize(ImVec2(thickness, 0.0f)); + if (!ItemAdd(bb, 0)) + return; + + // Draw + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator)); + if (g.LogEnabled) + LogText(" |"); + } else if (flags & ImGuiSeparatorFlags_Horizontal) { + // Horizontal Separator + float x1 = window->Pos.x; + float x2 = window->Pos.x + window->Size.x; + + // FIXME-WORKRECT: old hack (#205) until we decide of consistent behavior with WorkRect/Indent and Separator + if (g.GroupStack.Size > 0 && g.GroupStack.back().WindowID == window->ID) + x1 += window->DC.Indent.x; + + // FIXME-WORKRECT: In theory we should simply be using WorkRect.Min.x/Max.x everywhere but it isn't aesthetically what we want, + // need to introduce a variant of WorkRect for that purpose. (#4787) + if (ImGuiTable *table = g.CurrentTable) { + x1 = table->Columns[table->CurrentColumn].MinX; + x2 = table->Columns[table->CurrentColumn].MaxX; + } + + // 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. + ImGuiOldColumns *columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL; + if (columns) + 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) { + PopColumnsBackground(); + columns->LineMinY = window->DC.CursorPos.y; + } + } +} + +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; + flags |= ImGuiSeparatorFlags_SpanAllColumns; // NB: this only applies to legacy Columns() api as they relied on Separator() a lot. + 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)); + const float text_baseline_y = ImFloor((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + + 0.99999f); // ImMax(padding.y, ImFloor((style.SeparatorTextSize - label_size.y) * 0.5f)); + 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 = ImFloor((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) { + const float sep1_x2 = label_pos.x - style.ItemSpacing.x; + const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x; + if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f) + window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep1_x2, seps_y), separator_col, separator_thickness); + if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f) + 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); + } else { + if (g.LogEnabled) + LogText("---"); + if (separator_thickness > 0.0f) + 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) + // - because of this we probably can't turn 'const char* label' into 'const char* fmt, ...' + // Otherwise, we can decide that users wanting to drag this would layout a dedicated drag-item, + // 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. + ImGuiButtonFlags button_flags = ImGuiButtonFlags_FlattenChildren; +#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) { + ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min; + float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x; + + // 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); + if (mouse_delta < -size_1_maximum_delta) + 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) { + if (mouse_delta < 0.0f) + IM_ASSERT(*size1 + mouse_delta >= min_size1); + if (mouse_delta > 0.0f) + IM_ASSERT(*size2 - mouse_delta >= min_size2); + *size1 += mouse_delta; + *size2 -= mouse_delta; + bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta)); + 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; + const ImGuiShrinkWidthItem *b = (const ImGuiShrinkWidthItem *)rhs; + if (int d = (int)(b->Width - a->Width)) + 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) +{ + if (count == 1) { + if (items[0].Width >= 0.0f) + items[0].Width = ImMax(items[0].Width - width_excess, 1.0f); + return; + } + ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer); + int count_same_width = 1; + while (width_excess > 0.0f && 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); + 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); + 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; + } + + // 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; + for (int n = 0; n < count; n++) { + float width_rounded = ImFloor(items[n].Width); + width_excess += items[n].Width - width_rounded; + items[n].Width = width_rounded; + } + while (width_excess > 0.0f) + for (int n = 0; n < count && width_excess > 0.0f; n++) { + float width_to_add = ImMin(items[n].InitialWidth - items[n].Width, 1.0f); + items[n].Width += width_to_add; + width_excess -= width_to_add; + } +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: ComboBox +//------------------------------------------------------------------------- +// - CalcMaxPopupHeightFromItemCount() [Internal] +// - BeginCombo() +// - BeginComboPopup() [Internal] +// - EndCombo() +// - BeginComboPreview() [Internal] +// - EndComboPreview() [Internal] +// - Combo() +//------------------------------------------------------------------------- + +static float CalcMaxPopupHeightFromItemCount(int items_count) +{ + ImGuiContext &g = *GImGui; + if (items_count <= 0) + 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.Flags; + 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 + + const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth(); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); + const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + 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); + const ImGuiID popup_id = ImHashStr("##ComboPopup", 0, id); + bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None); + if (pressed && !popup_open) { + 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); + RenderNavHighlight(bb, id); + if (!(flags & ImGuiComboFlags_NoPreview)) + window->DrawList->AddRectFilled(bb.Min, + ImVec2(value_x2, bb.Max.y), + frame_col, + style.FrameRounding, + (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft); + if (!(flags & ImGuiComboFlags_NoArrowButton)) { + ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + ImU32 text_col = GetColorU32(ImGuiCol_Text); + window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), + bb.Max, + bg_col, + style.FrameRounding, + (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight); + if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x) + 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) { + g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y); + IM_ASSERT(preview_value == NULL || preview_value[0] == 0); + preview_value = NULL; + } + + // Render preview and label + if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview)) { + if (g.LogEnabled) + LogSetNextTextDecoration("{", "}"); + RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL); + } + 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.Flags = 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; + if (!IsPopupOpen(popup_id, ImGuiPopupFlags_None)) { + g.NextWindowData.ClearFlags(); + return false; + } + + // Set popup size + float w = bb.GetWidth(); + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint) { + g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w); + } else { + if ((flags & ImGuiComboFlags_HeightMask_) == 0) + flags |= ImGuiComboFlags_HeightRegular; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one + int popup_max_height_in_items = -1; + if (flags & ImGuiComboFlags_HeightRegular) + popup_max_height_in_items = 8; + else if (flags & ImGuiComboFlags_HeightSmall) + popup_max_height_in_items = 4; + else if (flags & ImGuiComboFlags_HeightLarge) + popup_max_height_in_items = 20; + ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX); + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 + || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size + constraint_min.x = w; + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f) + 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.BeginPopupStack.Size); // 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()? + if (ImGuiWindow *popup_window = FindWindowByName(name)) + if (popup_window->WasActive) { + // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us. + ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window); + popup_window->AutoPosLastDirection = + (flags & ImGuiComboFlags_PopupAlignLeft) ? ImGuiDir_Left : ImGuiDir_Down; // Left = "Below, Toward Left", Down = "Below, Toward Right (default)" + ImRect r_outer = GetPopupAllowedExtentRect(popup_window); + 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; + PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(g.Style.FramePadding.x, g.Style.WindowPadding.y)); // Horizontally align ourselves with the framed text + bool ret = Begin(name, NULL, window_flags); + PopStyleVar(); + if (!ret) { + EndPopup(); + IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above + return false; + } + return true; +} + +void ImGui::EndCombo() +{ + EndPopup(); +} + +// 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() +{ + 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; + preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine; + preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; + preview_data->BackupLayout = window->DC.LayoutType; + window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding; + window->DC.CursorMaxPos = window->DC.CursorPos; + 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) + if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command + { + draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = + draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect; + draw_list->_TryMergeDrawCmds(); + } + PopClipRect(); + window->DC.CursorPos = preview_data->BackupCursorPos; + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos); + window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine; + window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset; + window->DC.LayoutType = preview_data->BackupLayout; + window->DC.IsSameLine = false; + preview_data->PreviewRect = ImRect(); +} + +// Getter for the old Combo() API: const char*[] +static bool Items_ArrayGetter(void *data, int idx, const char **out_text) +{ + const char *const *items = (const char *const *)data; + if (out_text) + *out_text = items[idx]; + return true; +} + +// Getter for the old Combo() API: "item1\0item2\0item3\0" +static bool Items_SingleStringGetter(void *data, int idx, const char **out_text) +{ + // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited. + const char *items_separated_by_zeros = (const char *)data; + int items_count = 0; + const char *p = items_separated_by_zeros; + while (*p) { + if (idx == items_count) + break; + p += strlen(p) + 1; + items_count++; + } + if (!*p) + return false; + if (out_text) + *out_text = p; + return true; +} + +// Old API, prefer using BeginCombo() nowadays if you can. +bool ImGui::Combo(const char *label, + int *current_item, + bool (*items_getter)(void *, int, const char **), + void *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) + items_getter(data, *current_item, &preview_value); + + // 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.Flags & 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 + // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed) + bool value_changed = false; + for (int i = 0; i < items_count; i++) { + PushID(i); + const bool item_selected = (i == *current_item); + const char *item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + if (Selectable(item_text, item_selected) && *current_item != i) { + value_changed = true; + *current_item = i; + } + if (item_selected) + 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) +{ + int items_count = 0; + const char *p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open + while (*p) { + p += strlen(p) + 1; + items_count++; + } + bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void *)items_separated_by_zeros, items_count, height_in_items); + return value_changed; +} + +//------------------------------------------------------------------------- +// [SECTION] Data Type and Data Formatting Helpers [Internal] +//------------------------------------------------------------------------- +// - DataTypeGetInfo() +// - DataTypeFormatString() +// - DataTypeApplyOp() +// - DataTypeApplyOpFromText() +// - DataTypeCompare() +// - DataTypeClamp() +// - GetMinimumStepAtDecimalPrecision +// - RoundScalarWithFormat<>() +//------------------------------------------------------------------------- + +static const ImGuiDataTypeInfo GDataTypeInfo[] = { + {sizeof(char), "S8", "%d", "%d"}, // ImGuiDataType_S8 + {sizeof(unsigned char), "U8", "%u", "%u"}, + {sizeof(short), "S16", "%d", "%d"}, // ImGuiDataType_S16 + {sizeof(unsigned short), "U16", "%u", "%u"}, + {sizeof(int), "S32", "%d", "%d"}, // ImGuiDataType_S32 + {sizeof(unsigned int), "U32", "%u", "%u"}, +#ifdef _MSC_VER + {sizeof(ImS64), "S64", "%I64d", "%I64d"}, // ImGuiDataType_S64 + {sizeof(ImU64), "U64", "%I64u", "%I64u"}, +#else + {sizeof(ImS64), "S64", "%lld", "%lld"}, // ImGuiDataType_S64 + {sizeof(ImU64), "U64", "%llu", "%llu"}, +#endif + {sizeof(float), "float", "%.3f", "%f"}, // ImGuiDataType_Float (float are promoted to double in va_arg) + {sizeof(double), "double", "%f", "%lf"}, // ImGuiDataType_Double +}; +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 + if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32) + return ImFormatString(buf, buf_size, format, *(const ImU32 *)p_data); + if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) + return ImFormatString(buf, buf_size, format, *(const ImU64 *)p_data); + if (data_type == ImGuiDataType_Float) + return ImFormatString(buf, buf_size, format, *(const float *)p_data); + if (data_type == ImGuiDataType_Double) + return ImFormatString(buf, buf_size, format, *(const double *)p_data); + if (data_type == ImGuiDataType_S8) + return ImFormatString(buf, buf_size, format, *(const ImS8 *)p_data); + if (data_type == ImGuiDataType_U8) + return ImFormatString(buf, buf_size, format, *(const ImU8 *)p_data); + if (data_type == ImGuiDataType_S16) + return ImFormatString(buf, buf_size, format, *(const ImS16 *)p_data); + if (data_type == ImGuiDataType_U16) + return ImFormatString(buf, buf_size, format, *(const ImU16 *)p_data); + 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 == '-'); + switch (data_type) { + case ImGuiDataType_S8: + if (op == '+') { + *(ImS8 *)output = ImAddClampOverflow(*(const ImS8 *)arg1, *(const ImS8 *)arg2, IM_S8_MIN, IM_S8_MAX); + } + if (op == '-') { + *(ImS8 *)output = ImSubClampOverflow(*(const ImS8 *)arg1, *(const ImS8 *)arg2, IM_S8_MIN, IM_S8_MAX); + } + return; + case ImGuiDataType_U8: + if (op == '+') { + *(ImU8 *)output = ImAddClampOverflow(*(const ImU8 *)arg1, *(const ImU8 *)arg2, IM_U8_MIN, IM_U8_MAX); + } + if (op == '-') { + *(ImU8 *)output = ImSubClampOverflow(*(const ImU8 *)arg1, *(const ImU8 *)arg2, IM_U8_MIN, IM_U8_MAX); + } + return; + case ImGuiDataType_S16: + if (op == '+') { + *(ImS16 *)output = ImAddClampOverflow(*(const ImS16 *)arg1, *(const ImS16 *)arg2, IM_S16_MIN, IM_S16_MAX); + } + if (op == '-') { + *(ImS16 *)output = ImSubClampOverflow(*(const ImS16 *)arg1, *(const ImS16 *)arg2, IM_S16_MIN, IM_S16_MAX); + } + return; + case ImGuiDataType_U16: + if (op == '+') { + *(ImU16 *)output = ImAddClampOverflow(*(const ImU16 *)arg1, *(const ImU16 *)arg2, IM_U16_MIN, IM_U16_MAX); + } + if (op == '-') { + *(ImU16 *)output = ImSubClampOverflow(*(const ImU16 *)arg1, *(const ImU16 *)arg2, IM_U16_MIN, IM_U16_MAX); + } + return; + case ImGuiDataType_S32: + if (op == '+') { + *(ImS32 *)output = ImAddClampOverflow(*(const ImS32 *)arg1, *(const ImS32 *)arg2, IM_S32_MIN, IM_S32_MAX); + } + if (op == '-') { + *(ImS32 *)output = ImSubClampOverflow(*(const ImS32 *)arg1, *(const ImS32 *)arg2, IM_S32_MIN, IM_S32_MAX); + } + return; + case ImGuiDataType_U32: + if (op == '+') { + *(ImU32 *)output = ImAddClampOverflow(*(const ImU32 *)arg1, *(const ImU32 *)arg2, IM_U32_MIN, IM_U32_MAX); + } + if (op == '-') { + *(ImU32 *)output = ImSubClampOverflow(*(const ImU32 *)arg1, *(const ImU32 *)arg2, IM_U32_MIN, IM_U32_MAX); + } + return; + case ImGuiDataType_S64: + if (op == '+') { + *(ImS64 *)output = ImAddClampOverflow(*(const ImS64 *)arg1, *(const ImS64 *)arg2, IM_S64_MIN, IM_S64_MAX); + } + if (op == '-') { + *(ImS64 *)output = ImSubClampOverflow(*(const ImS64 *)arg1, *(const ImS64 *)arg2, IM_S64_MIN, IM_S64_MAX); + } + return; + case ImGuiDataType_U64: + if (op == '+') { + *(ImU64 *)output = ImAddClampOverflow(*(const ImU64 *)arg1, *(const ImU64 *)arg2, IM_U64_MIN, IM_U64_MAX); + } + if (op == '-') { + *(ImU64 *)output = ImSubClampOverflow(*(const ImU64 *)arg1, *(const ImU64 *)arg2, IM_U64_MIN, IM_U64_MAX); + } + return; + case ImGuiDataType_Float: + if (op == '+') { + *(float *)output = *(const float *)arg1 + *(const float *)arg2; + } + if (op == '-') { + *(float *)output = *(const float *)arg1 - *(const float *)arg2; + } + return; + case ImGuiDataType_Double: + if (op == '+') { + *(double *)output = *(const double *)arg1 + *(const double *)arg2; + } + if (op == '-') { + *(double *)output = *(const double *)arg1 - *(const double *)arg2; + } + return; + case ImGuiDataType_COUNT: + break; + } + 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) +{ + while (ImCharIsBlankA(*buf)) + buf++; + if (!buf[0]) + return false; + + // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all. + const ImGuiDataTypeInfo *type_info = DataTypeGetInfo(data_type); + ImGuiDataTypeTempStorage data_backup; + memcpy(&data_backup, p_data, type_info->Size); + + // 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 %. + char format_sanitized[32]; + if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) + 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) + return false; + if (type_info->Size < 4) { + if (data_type == ImGuiDataType_S8) + *(ImS8 *)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX); + else if (data_type == ImGuiDataType_U8) + *(ImU8 *)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX); + else if (data_type == ImGuiDataType_S16) + *(ImS16 *)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX); + else if (data_type == ImGuiDataType_U16) + *(ImU16 *)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX); + else + IM_ASSERT(0); + } + + return memcmp(&data_backup, p_data, type_info->Size) != 0; +} + +template +static int DataTypeCompareT(const T *lhs, const T *rhs) +{ + if (*lhs < *rhs) + return -1; + if (*lhs > *rhs) + return +1; + return 0; +} + +int ImGui::DataTypeCompare(ImGuiDataType data_type, const void *arg_1, const void *arg_2) +{ + switch (data_type) { + case ImGuiDataType_S8: + return DataTypeCompareT((const ImS8 *)arg_1, (const ImS8 *)arg_2); + case ImGuiDataType_U8: + return DataTypeCompareT((const ImU8 *)arg_1, (const ImU8 *)arg_2); + case ImGuiDataType_S16: + return DataTypeCompareT((const ImS16 *)arg_1, (const ImS16 *)arg_2); + case ImGuiDataType_U16: + return DataTypeCompareT((const ImU16 *)arg_1, (const ImU16 *)arg_2); + case ImGuiDataType_S32: + return DataTypeCompareT((const ImS32 *)arg_1, (const ImS32 *)arg_2); + case ImGuiDataType_U32: + return DataTypeCompareT((const ImU32 *)arg_1, (const ImU32 *)arg_2); + case ImGuiDataType_S64: + return DataTypeCompareT((const ImS64 *)arg_1, (const ImS64 *)arg_2); + case ImGuiDataType_U64: + return DataTypeCompareT((const ImU64 *)arg_1, (const ImU64 *)arg_2); + case ImGuiDataType_Float: + return DataTypeCompareT((const float *)arg_1, (const float *)arg_2); + case ImGuiDataType_Double: + return DataTypeCompareT((const double *)arg_1, (const double *)arg_2); + case ImGuiDataType_COUNT: + break; + } + IM_ASSERT(0); + return 0; +} + +template +static bool DataTypeClampT(T *v, const T *v_min, const T *v_max) +{ + // Clamp, both sides are optional, return true if modified + if (v_min && *v < *v_min) { + *v = *v_min; + return true; + } + 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) { + case ImGuiDataType_S8: + return DataTypeClampT((ImS8 *)p_data, (const ImS8 *)p_min, (const ImS8 *)p_max); + case ImGuiDataType_U8: + return DataTypeClampT((ImU8 *)p_data, (const ImU8 *)p_min, (const ImU8 *)p_max); + case ImGuiDataType_S16: + return DataTypeClampT((ImS16 *)p_data, (const ImS16 *)p_min, (const ImS16 *)p_max); + case ImGuiDataType_U16: + return DataTypeClampT((ImU16 *)p_data, (const ImU16 *)p_min, (const ImU16 *)p_max); + case ImGuiDataType_S32: + return DataTypeClampT((ImS32 *)p_data, (const ImS32 *)p_min, (const ImS32 *)p_max); + case ImGuiDataType_U32: + return DataTypeClampT((ImU32 *)p_data, (const ImU32 *)p_min, (const ImU32 *)p_max); + case ImGuiDataType_S64: + return DataTypeClampT((ImS64 *)p_data, (const ImS64 *)p_min, (const ImS64 *)p_max); + case ImGuiDataType_U64: + return DataTypeClampT((ImU64 *)p_data, (const ImU64 *)p_min, (const ImU64 *)p_max); + case ImGuiDataType_Float: + return DataTypeClampT((float *)p_data, (const float *)p_min, (const float *)p_max); + case ImGuiDataType_Double: + return DataTypeClampT((double *)p_data, (const double *)p_min, (const double *)p_max); + case ImGuiDataType_COUNT: + break; + } + IM_ASSERT(0); + return false; +} + +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}; + if (decimal_precision < 0) + 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) +{ + IM_UNUSED(data_type); + IM_ASSERT(data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double); + 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); + const char *p = v_str; + while (*p == ' ') + p++; + v = (TYPE)ImAtof(p); + + return v; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc. +//------------------------------------------------------------------------- +// - DragBehaviorT<>() [Internal] +// - DragBehavior() [Internal] +// - DragScalar() +// - DragScalarN() +// - DragFloat() +// - DragFloat2() +// - DragFloat3() +// - DragFloat4() +// - DragFloatRange2() +// - DragInt() +// - DragInt2() +// - DragInt3() +// - 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) +{ + ImGuiContext &g = *GImGui; + const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; + const bool is_clamped = (v_min < v_max); + 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_clamped && (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)) { + adjust_delta = g.IO.MouseDelta[axis]; + if (g.IO.KeyAlt) + adjust_delta *= 1.0f / 100.0f; + if (g.IO.KeyShift) + adjust_delta *= 10.0f; + } else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) { + const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0; + const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow); + const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast); + const float tweak_factor = tweak_slow ? 1.0f / 1.0f : tweak_fast ? 10.0f : 1.0f; + adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor; + 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. + bool is_just_activated = g.ActiveIdIsJustActivated; + bool is_already_past_limits_and_pushing_outward = is_clamped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f)); + if (is_just_activated || is_already_past_limits_and_pushing_outward) { + g.DragCurrentAccum = 0.0f; + g.DragCurrentAccumDirty = false; + } else if (adjust_delta != 0.0f) { + 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) { + // 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; + v_cur = ScaleValueFromRatioT(data_type, + v_new_parametric, + v_min, + v_max, + is_logarithmic, + logarithmic_zero_epsilon, + zero_deadzone_halfsize); + v_old_ref_for_accum_remainder = v_old_parametric; + } else { + 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) { + // Convert to parametric space, apply delta, convert back + float v_new_parametric = + ScaleRatioFromValueT(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder); + } else { + g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v); + } + + // Lose zero sign for float/double + if (v_cur == (TYPE)-0) + v_cur = (TYPE)0; + + // Clamp values (+ handle overflow/wrap-around for integer types) + if (*v != v_cur && is_clamped) { + if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point)) + v_cur = v_min; + if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point)) + 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 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); + + ImGuiContext &g = *GImGui; + if (g.ActiveId == id) { + // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation. + if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) + ClearActiveID(); + else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id + && !g.ActiveIdIsJustActivated) + ClearActiveID(); + } + if (g.ActiveId != id) + return false; + if ((g.LastItemData.InFlags & 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; + } + case ImGuiDataType_U8: { + ImU32 v32 = (ImU32) * (ImU8 *)p_v; + bool r = DragBehaviorT(ImGuiDataType_U32, + &v32, + v_speed, + p_min ? *(const ImU8 *)p_min : IM_U8_MIN, + p_max ? *(const ImU8 *)p_max : IM_U8_MAX, + format, + flags); + if (r) + *(ImU8 *)p_v = (ImU8)v32; + return r; + } + case ImGuiDataType_S16: { + ImS32 v32 = (ImS32) * (ImS16 *)p_v; + bool r = DragBehaviorT(ImGuiDataType_S32, + &v32, + v_speed, + p_min ? *(const ImS16 *)p_min : IM_S16_MIN, + p_max ? *(const ImS16 *)p_max : IM_S16_MAX, + format, + flags); + if (r) + *(ImS16 *)p_v = (ImS16)v32; + return r; + } + case ImGuiDataType_U16: { + ImU32 v32 = (ImU32) * (ImU16 *)p_v; + bool r = DragBehaviorT(ImGuiDataType_U32, + &v32, + v_speed, + p_min ? *(const ImU16 *)p_min : IM_U16_MIN, + p_max ? *(const ImU16 *)p_max : IM_U16_MAX, + format, + flags); + if (r) + *(ImU16 *)p_v = (ImU16)v32; + return r; + } + case ImGuiDataType_S32: + return DragBehaviorT(data_type, + (ImS32 *)p_v, + v_speed, + p_min ? *(const ImS32 *)p_min : IM_S32_MIN, + p_max ? *(const ImS32 *)p_max : IM_S32_MAX, + format, + flags); + case ImGuiDataType_U32: + return DragBehaviorT(data_type, + (ImU32 *)p_v, + v_speed, + p_min ? *(const ImU32 *)p_min : IM_U32_MIN, + p_max ? *(const ImU32 *)p_max : IM_U32_MAX, + format, + flags); + case ImGuiDataType_S64: + return DragBehaviorT(data_type, + (ImS64 *)p_v, + v_speed, + p_min ? *(const ImS64 *)p_min : IM_S64_MIN, + p_max ? *(const ImS64 *)p_max : IM_S64_MAX, + format, + flags); + case ImGuiDataType_U64: + return DragBehaviorT(data_type, + (ImU64 *)p_v, + v_speed, + p_min ? *(const ImU64 *)p_min : IM_U64_MIN, + p_max ? *(const ImU64 *)p_max : IM_U64_MAX, + format, + flags); + case ImGuiDataType_Float: + return DragBehaviorT(data_type, + (float *)p_v, + v_speed, + p_min ? *(const float *)p_min : -FLT_MAX, + p_max ? *(const float *)p_max : FLT_MAX, + format, + flags); + case ImGuiDataType_Double: + return DragBehaviorT(data_type, + (double *)p_v, + v_speed, + p_min ? *(const double *)p_min : -DBL_MAX, + p_max ? *(const double *)p_max : DBL_MAX, + format, + flags); + case ImGuiDataType_COUNT: + break; + } + 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) +{ + 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.InFlags); + bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); + if (!temp_input_is_active) { + // Tabbing or CTRL-clicking on Drag turns it into an InputText + const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0; + const bool clicked = hovered && IsMouseClicked(0, id); + const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id)); + const bool make_active = (input_requested_by_tabbing || clicked || double_clicked || g.NavActivateId == id); + if (make_active && (clicked || double_clicked)) + SetKeyOwner(ImGuiKey_MouseLeft, id); + if (make_active && temp_input_allowed) + if (input_requested_by_tabbing || (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)) { + g.NavActivateId = id; + g.NavActivateFlags = ImGuiActivateFlags_PreferInput; + temp_input_is_active = true; + } + + if (make_active && !temp_input_is_active) { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + } + } + + if (temp_input_is_active) { + // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set + const bool is_clamp_input = + (flags & ImGuiSliderFlags_AlwaysClamp) != 0 && (p_min == NULL || p_max == NULL || DataTypeCompare(data_type, p_min, p_max) < 0); + return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL); + } + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavHighlight(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(); + PushID(label); + PushMultiItemsWidths(components, CalcItemWidth()); + size_t type_size = GDataTypeInfo[data_type].Size; + for (int i = 0; i < components; i++) { + PushID(i); + if (i > 0) + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags); + PopID(); + PopItemWidth(); + 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. +//------------------------------------------------------------------------- +// - ScaleRatioFromValueT<> [Internal] +// - ScaleValueFromRatioT<> [Internal] +// - SliderBehaviorT<>() [Internal] +// - SliderBehavior() [Internal] +// - SliderScalar() +// - SliderScalarN() +// - SliderFloat() +// - SliderFloat2() +// - SliderFloat3() +// - SliderFloat4() +// - SliderAngle() +// - SliderInt() +// - SliderInt2() +// - SliderInt3() +// - SliderInt4() +// - VSliderScalar() +// - 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) +{ + 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 + else if (v_clamped >= v_max_fudged) + result = 1.0f; // Workaround for values that are in-range but above our fudge + else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions + { + float zero_point_center = (-(float)v_min) + / ((float)v_max + - (float)v_min); // The zero point in parametric space. There's an argument we should take the logarithmic nature into account when + // calculating this, but for now this should do (and the most common case of a symmetrical range works fine) + float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize; + float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize; + if (v == 0.0f) + result = zero_point_center; // Special case for exactly zero + else if (v < 0.0f) + result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) + * zero_point_snap_L; + else + result = zero_point_snap_R + + ((float)(ImLog((FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(v_max_fudged / logarithmic_zero_epsilon)) + * (1.0f - zero_point_snap_R)); + } else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider + 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 { + // Linear slider + 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) +{ + // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct" + // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler. + if (t <= 0.0f || v_min == v_max) + 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 + float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize; + float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize; + if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R) + result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise) + else if (t_with_flip < zero_point_center) + result = (TYPE) + - (logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L)))); + else + result = (TYPE)(logarithmic_zero_epsilon + * ImPow(v_max_fudged / logarithmic_zero_epsilon, (FLOATTYPE)((t_with_flip - zero_point_snap_R) / (1.0f - zero_point_snap_R)))); + } else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider + result = (TYPE) - (-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip))); + else + result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip)); + } else { + // Linear slider + const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); + if (is_floating_point) { + result = ImLerp(v_min, v_max, t); + } else if (t < 1.0) { + // - For integer values we want the clicking position to match the grab box so we round above + // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. + // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64 + // range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits. + FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - 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; + float grab_sz = style.GrabMinSize; + if (!is_floating_point && v_range_f >= 0.0f) // v_range_f < 0 may happen on integer overflows + grab_sz = ImMax(slider_sz / (v_range_f + 1), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit + grab_sz = ImMin(grab_sz, slider_sz); + 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) { + // 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); + 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) { + bool set_new_value = false; + float clicked_t = 0.0f; + if (g.ActiveIdSource == ImGuiInputSource_Mouse) { + if (!g.IO.MouseDown[0]) { + ClearActiveID(); + } else { + const float mouse_abs_pos = g.IO.MousePos[axis]; + if (g.ActiveIdIsJustActivated) { + float grab_t = ScaleRatioFromValueT(data_type, + *v, + v_min, + v_max, + is_logarithmic, + logarithmic_zero_epsilon, + zero_deadzone_halfsize); + if (axis == ImGuiAxis_Y) + grab_t = 1.0f - grab_t; + const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); + const bool clicked_around_grab = (mouse_abs_pos >= grab_pos - grab_sz * 0.5f - 1.0f) + && (mouse_abs_pos <= grab_pos + grab_sz * 0.5f + 1.0f); // No harm being extra generous here. + g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f; + } + if (slider_usable_sz > 0.0f) + clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz); + if (axis == ImGuiAxis_Y) + clicked_t = 1.0f - clicked_t; + set_new_value = true; + } + } else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) { + if (g.ActiveIdIsJustActivated) { + 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) { + const bool tweak_slow = + IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow); + const bool tweak_fast = + IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast); + const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0; + if (decimal_precision > 0) { + input_delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds + if (tweak_slow) + input_delta /= 10.0f; + } else { + if ((v_range_f >= -100.0f && v_range_f <= 100.0f && v_range_f != 0.0f) || tweak_slow) + input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / v_range_f; // Gamepad/keyboard tweak speeds in integer steps + else + input_delta /= 100.0f; + } + if (tweak_fast) + input_delta *= 10.0f; + + g.SliderCurrentAccum += input_delta; + g.SliderCurrentAccumDirty = true; + } + + float delta = g.SliderCurrentAccum; + if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) { + ClearActiveID(); + } 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; + g.SliderCurrentAccum = 0.0f; // If pushing up against the limits, don't continue to accumulate + } else { + 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.InFlags & 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) { + *v = v_new; + value_changed = true; + } + } + } + + if (slider_sz < 1.0f) { + *out_grab_bb = ImRect(bb.Min, bb.Min); + } else { + // Output grab position so it can be displayed by the caller + float grab_t = + ScaleRatioFromValueT(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + if (axis == ImGuiAxis_Y) + grab_t = 1.0f - grab_t; + const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); + if (axis == ImGuiAxis_X) + *out_grab_bb = ImRect(grab_pos - grab_sz * 0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz * 0.5f, bb.Max.y - grab_padding); + 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. +bool ImGui::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) +{ + // 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 flag! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); + + 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; + } + case ImGuiDataType_U8: { + ImU32 v32 = (ImU32) * (ImU8 *)p_v; + bool r = SliderBehaviorT(bb, id, ImGuiDataType_U32, &v32, *(const ImU8 *)p_min, *(const ImU8 *)p_max, format, flags, out_grab_bb); + if (r) + *(ImU8 *)p_v = (ImU8)v32; + return r; + } + case ImGuiDataType_S16: { + ImS32 v32 = (ImS32) * (ImS16 *)p_v; + bool r = + SliderBehaviorT(bb, id, ImGuiDataType_S32, &v32, *(const ImS16 *)p_min, *(const ImS16 *)p_max, format, flags, out_grab_bb); + if (r) + *(ImS16 *)p_v = (ImS16)v32; + return r; + } + case ImGuiDataType_U16: { + ImU32 v32 = (ImU32) * (ImU16 *)p_v; + bool r = + SliderBehaviorT(bb, id, ImGuiDataType_U32, &v32, *(const ImU16 *)p_min, *(const ImU16 *)p_max, format, flags, out_grab_bb); + if (r) + *(ImU16 *)p_v = (ImU16)v32; + return r; + } + case ImGuiDataType_S32: + IM_ASSERT(*(const ImS32 *)p_min >= IM_S32_MIN / 2 && *(const ImS32 *)p_max <= IM_S32_MAX / 2); + return SliderBehaviorT(bb, id, data_type, (ImS32 *)p_v, *(const ImS32 *)p_min, *(const ImS32 *)p_max, format, flags, out_grab_bb); + case ImGuiDataType_U32: + IM_ASSERT(*(const ImU32 *)p_max <= IM_U32_MAX / 2); + return SliderBehaviorT(bb, id, data_type, (ImU32 *)p_v, *(const ImU32 *)p_min, *(const ImU32 *)p_max, format, flags, out_grab_bb); + case ImGuiDataType_S64: + IM_ASSERT(*(const ImS64 *)p_min >= IM_S64_MIN / 2 && *(const ImS64 *)p_max <= IM_S64_MAX / 2); + return SliderBehaviorT(bb, id, data_type, (ImS64 *)p_v, *(const ImS64 *)p_min, *(const ImS64 *)p_max, format, flags, out_grab_bb); + case ImGuiDataType_U64: + IM_ASSERT(*(const ImU64 *)p_max <= IM_U64_MAX / 2); + return SliderBehaviorT(bb, id, data_type, (ImU64 *)p_v, *(const ImU64 *)p_min, *(const ImU64 *)p_max, format, flags, out_grab_bb); + case ImGuiDataType_Float: + IM_ASSERT(*(const float *)p_min >= -FLT_MAX / 2.0f && *(const float *)p_max <= FLT_MAX / 2.0f); + return SliderBehaviorT(bb, id, data_type, (float *)p_v, *(const float *)p_min, *(const float *)p_max, format, flags, out_grab_bb); + case ImGuiDataType_Double: + IM_ASSERT(*(const double *)p_min >= -DBL_MAX / 2.0f && *(const double *)p_max <= DBL_MAX / 2.0f); + return SliderBehaviorT(bb, + id, + data_type, + (double *)p_v, + *(const double *)p_min, + *(const double *)p_max, + format, + flags, + out_grab_bb); + case ImGuiDataType_COUNT: + break; + } + 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) +{ + 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.InFlags); + bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); + if (!temp_input_is_active) { + // Tabbing or CTRL-clicking on Slider turns it into an input box + const bool input_requested_by_tabbing = temp_input_allowed && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_FocusedByTabbing) != 0; + const bool clicked = hovered && IsMouseClicked(0, id); + const bool make_active = (input_requested_by_tabbing || clicked || g.NavActivateId == id); + if (make_active && clicked) + SetKeyOwner(ImGuiKey_MouseLeft, id); + if (make_active && temp_input_allowed) + if (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput))) + temp_input_is_active = true; + + if (make_active && !temp_input_is_active) { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + } + } + + if (temp_input_is_active) { + // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set + const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0; + return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL); + } + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavHighlight(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(); + PushID(label); + PushMultiItemsWidths(components, CalcItemWidth()); + size_t type_size = GDataTypeInfo[data_type].Size; + for (int i = 0; i < components; i++) { + PushID(i); + if (i > 0) + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags); + PopID(); + PopItemWidth(); + 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) + format = "%.0f deg"; + float v_deg = (*v_rad) * 360.0f / (2 * IM_PI); + bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags); + *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.InFlags); + const bool clicked = hovered && IsMouseClicked(0, id); + if (clicked || g.NavActivateId == id) { + if (clicked) + SetKeyOwner(ImGuiKey_MouseLeft, id); + SetActiveID(id, window); + SetFocusID(id, window); + 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); + RenderNavHighlight(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]; + const char *value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); + 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. +//------------------------------------------------------------------------- +// - ImParseFormatFindStart() [Internal] +// - ImParseFormatFindEnd() [Internal] +// - ImParseFormatTrimDecorations() [Internal] +// - ImParseFormatSanitizeForPrinting() [Internal] +// - ImParseFormatSanitizeForScanning() [Internal] +// - ImParseFormatPrecision() [Internal] +// - TempInputTextScalar() [Internal] +// - InputScalar() +// - InputScalarN() +// - InputFloat() +// - InputFloat2() +// - InputFloat3() +// - InputFloat4() +// - InputInt() +// - InputInt2() +// - InputInt3() +// - InputInt4() +// - InputDouble() +//------------------------------------------------------------------------- + +// We don't use strchr() because our strings are usually very short and often start with '%' +const char *ImParseFormatFindStart(const char *fmt) +{ + while (char c = fmt[0]) { + if (c == '%' && fmt[1] != '%') + return fmt; + else if (c == '%') + fmt++; + 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. + if (fmt[0] != '%') + return fmt; + const unsigned int ignored_uppercase_mask = (1 << ('I' - 'A')) | (1 << ('L' - 'A')); + const unsigned int ignored_lowercase_mask = + (1 << ('h' - 'a')) | (1 << ('j' - 'a')) | (1 << ('l' - 'a')) | (1 << ('t' - 'a')) | (1 << ('w' - 'a')) | (1 << ('z' - 'a')); + for (char c; (c = *fmt) != 0; fmt++) { + if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0) + return fmt + 1; + if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0) + return fmt + 1; + } + return fmt; +} + +// Extract the format out of a format string with leading or trailing decorations +// fmt = "blah blah" -> return "" +// fmt = "%.3f" -> return fmt +// fmt = "hello %.3f" -> return fmt + 6 +// fmt = "%.3f hello" -> return buf written with "%.3f" +const char *ImParseFormatTrimDecorations(const char *fmt, char *buf, size_t buf_size) +{ + const char *fmt_start = ImParseFormatFindStart(fmt); + if (fmt_start[0] != '%') + return ""; + const char *fmt_end = ImParseFormatFindEnd(fmt_start); + if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data. + return fmt_start; + 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. +void ImParseFormatSanitizeForPrinting(const char *fmt_in, char *fmt_out, size_t fmt_out_size) +{ + const char *fmt_end = ImParseFormatFindEnd(fmt_in); + IM_UNUSED(fmt_out_size); + IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you! + while (fmt_in < fmt_end) { + char c = *fmt_in++; + if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '. + *(fmt_out++) = c; + } + *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) +{ + const char *fmt_end = ImParseFormatFindEnd(fmt_in); + const char *fmt_out_begin = fmt_out; + IM_UNUSED(fmt_out_size); + IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you! + bool has_type = false; + while (fmt_in < fmt_end) { + char c = *fmt_in++; + if (!has_type && ((c >= '0' && c <= '9') || c == '.' || c == '+' || c == '#')) + continue; + has_type |= ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); // Stop skipping digits + if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '. + *(fmt_out++) = c; + } + *fmt_out = 0; // Zero-terminate + return fmt_out_begin; +} + +template +static const char *ImAtoi(const char *src, TYPE *output) +{ + int negative = 0; + if (*src == '-') { + negative = 1; + src++; + } + if (*src == '+') { + src++; + } + TYPE v = 0; + while (*src >= '0' && *src <= '9') + v = (v * 10) + (*src++ - '0'); + *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) +{ + fmt = ImParseFormatFindStart(fmt); + if (fmt[0] != '%') + return default_precision; + fmt++; + while (*fmt >= '0' && *fmt <= '9') + fmt++; + int precision = INT_MAX; + if (*fmt == '.') { + fmt = ImAtoi(fmt + 1, &precision); + if (precision < 0 || precision > 99) + precision = default_precision; + } + if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation + precision = -1; + if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX) + 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. +bool ImGui::TempInputText(const ImRect &bb, ImGuiID id, const char *label, char *buf, int buf_size, ImGuiInputTextFlags flags) +{ + // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id. + // We clear ActiveID on the first frame to allow the InputText() taking it back. + ImGuiContext &g = *GImGui; + const bool init = (g.TempInputId != id); + if (init) + ClearActiveID(); + + g.CurrentWindow->DC.CursorPos = bb.Min; + bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem); + if (init) { + // First frame we started displaying the InputText widget, we expect it to take the active id. + IM_ASSERT(g.ActiveId == id); + g.TempInputId = g.ActiveId; + } + return value_changed; +} + +static inline ImGuiInputTextFlags InputScalar_DefaultCharsFilter(ImGuiDataType data_type, const char *format) +{ + if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) + return ImGuiInputTextFlags_CharsScientific; + const char format_last_char = format[0] ? format[strlen(format) - 1] : 0; + return (format_last_char == 'x' || format_last_char == 'X') ? ImGuiInputTextFlags_CharsHexadecimal : ImGuiInputTextFlags_CharsDecimal; +} + +// 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. +bool ImGui::TempInputScalar(const ImRect &bb, + ImGuiID id, + const char *label, + ImGuiDataType data_type, + void *p_data, + const char *format, + const void *p_clamp_min, + const void *p_clamp_max) +{ + // FIXME: May need to clarify display behavior if format doesn't contain %. + // "%d" -> "%d" / "There are %d items" -> "%d" / "items" -> "%d" (fallback). Also see #6405 + const ImGuiDataTypeInfo *type_info = DataTypeGetInfo(data_type); + char fmt_buf[32]; + char data_buf[32]; + format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf)); + if (format[0] == 0) + format = type_info->PrintFmt; + DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format); + ImStrTrimBlanks(data_buf); + + ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoMarkEdited; + flags |= InputScalar_DefaultCharsFilter(data_type, format); + + bool value_changed = false; + if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags)) { + // Backup old value + size_t data_type_size = type_info->Size; + ImGuiDataTypeTempStorage 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); + if (p_clamp_min || p_clamp_max) { + if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0) + 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 + value_changed = memcmp(&data_backup, p_data, data_type_size) != 0; + if (value_changed) + MarkItemEdited(id); + } + return value_changed; +} + +// 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) +{ + ImGuiWindow *window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext &g = *GImGui; + ImGuiStyle &style = g.Style; + + if (format == NULL) + format = DataTypeGetInfo(data_type)->PrintFmt; + + char buf[64]; + DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format); + + // Testing ActiveId as a minor optimization as filtering is not needed until active + if (g.ActiveId == 0 && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0) + flags |= InputScalar_DefaultCharsFilter(data_type, format); + flags |= ImGuiInputTextFlags_AutoSelectAll + | ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves by comparing the actual data rather than the string. + + bool value_changed = false; + if (p_step == NULL) { + if (InputText(label, buf, IM_ARRAYSIZE(buf), flags)) + value_changed = DataTypeApplyFromText(buf, data_type, p_data, format); + } 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); + 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; + ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups; + if (flags & ImGuiInputTextFlags_ReadOnly) + BeginDisabled(); + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("-", ImVec2(button_size, button_size), button_flags)) { + DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); + value_changed = true; + } + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("+", ImVec2(button_size, button_size), button_flags)) { + DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); + value_changed = true; + } + if (flags & ImGuiInputTextFlags_ReadOnly) + EndDisabled(); + + const char *label_end = FindRenderedTextEnd(label); + if (label != label_end) { + SameLine(0, style.ItemInnerSpacing.x); + TextEx(label, label_end); + } + style.FramePadding = backup_frame_padding; + + PopID(); + EndGroup(); + } + 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(); + PushID(label); + PushMultiItemsWidths(components, CalcItemWidth()); + size_t type_size = GDataTypeInfo[data_type].Size; + for (int i = 0; i < components; i++) { + PushID(i); + if (i > 0) + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= InputScalar("", data_type, p_data, p_step, p_step_fast, format, flags); + PopID(); + PopItemWidth(); + 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) +{ + flags |= ImGuiInputTextFlags_CharsScientific; + 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) +{ + flags |= ImGuiInputTextFlags_CharsScientific; + 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 +//------------------------------------------------------------------------- +// - InputText() +// - InputTextWithHint() +// - InputTextMultiline() +// - InputTextGetCharInfo() [Internal] +// - InputTextReindexLines() [Internal] +// - InputTextReindexLinesRange() [Internal] +// - InputTextEx() [Internal] +// - DebugNodeInputTextState() [Internal] +//------------------------------------------------------------------------- + +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); +} + +static int InputTextCalcTextLenAndLineCount(const char *text_begin, const char **out_text_end) +{ + int line_count = 0; + const char *s = text_begin; + while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding + if (c == '\n') + line_count++; + s--; + if (s[0] != '\n' && s[0] != '\r') + line_count++; + *out_text_end = s; + return line_count; +} + +static ImVec2 InputTextCalcTextSizeW(ImGuiContext *ctx, + const ImWchar *text_begin, + const ImWchar *text_end, + const ImWchar **remaining, + ImVec2 *out_offset, + bool stop_on_new_line) +{ + ImGuiContext &g = *ctx; + ImFont *font = g.Font; + const float line_height = g.FontSize; + const float scale = line_height / font->FontSize; + + ImVec2 text_size = ImVec2(0, 0); + float line_width = 0.0f; + + const ImWchar *s = text_begin; + while (s < text_end) { + unsigned int c = (unsigned int)(*s++); + if (c == '\n') { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + if (stop_on_new_line) + break; + continue; + } + if (c == '\r') + continue; + + const float char_width = font->GetCharAdvance((ImWchar)c) * scale; + line_width += char_width; + } + + 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) +namespace ImStb +{ + +static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState *obj) +{ + return obj->CurLenW; +} +static ImWchar STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState *obj, int idx) +{ + return obj->TextW[idx]; +} +static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState *obj, int line_start_idx, int char_idx) +{ + ImWchar c = obj->TextW[line_start_idx + char_idx]; + if (c == '\n') + return STB_TEXTEDIT_GETWIDTH_NEWLINE; + ImGuiContext &g = *obj->Ctx; + return g.Font->GetCharAdvance(c) * (g.FontSize / g.Font->FontSize); +} +static int STB_TEXTEDIT_KEYTOTEXT(int key) +{ + return key >= 0x200000 ? 0 : key; +} +static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; +static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow *r, ImGuiInputTextState *obj, int line_start_idx) +{ + const ImWchar *text = obj->TextW.Data; + const ImWchar *text_remaining = NULL; + const ImVec2 size = InputTextCalcTextSizeW(obj->Ctx, text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); + r->x0 = 0.0f; + r->x1 = size.x; + r->baseline_y_delta = size.y; + r->ymin = 0.0f; + r->ymax = size.y; + r->num_chars = (int)(text_remaining - (text + line_start_idx)); +} + +static bool is_separator(unsigned int c) +{ + return c == ',' || c == ';' || c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c == ']' || c == '|' || c == '\n' || c == '\r' || c == '.' + || c == '!'; +} + +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; + + bool prev_white = ImCharIsBlankW(obj->TextW[idx - 1]); + bool prev_separ = is_separator(obj->TextW[idx - 1]); + bool curr_white = ImCharIsBlankW(obj->TextW[idx]); + bool curr_separ = is_separator(obj->TextW[idx]); + return ((prev_white || prev_separ) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ); +} +static int is_word_boundary_from_left(ImGuiInputTextState *obj, int idx) +{ + if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0) + return 0; + + bool prev_white = ImCharIsBlankW(obj->TextW[idx]); + bool prev_separ = is_separator(obj->TextW[idx]); + bool curr_white = ImCharIsBlankW(obj->TextW[idx - 1]); + bool curr_separ = is_separator(obj->TextW[idx - 1]); + return ((prev_white) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ); +} +static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState *obj, int idx) +{ + idx--; + while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) + idx--; + return idx < 0 ? 0 : idx; +} +static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState *obj, int idx) +{ + idx++; + int len = obj->CurLenW; + while (idx < len && !is_word_boundary_from_left(obj, idx)) + idx++; + return idx > len ? len : idx; +} +static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState *obj, int idx) +{ + idx++; + int len = obj->CurLenW; + while (idx < len && !is_word_boundary_from_right(obj, idx)) + idx++; + return idx > len ? len : 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) +{ + ImWchar *dst = obj->TextW.Data + pos; + + // We maintain our buffer length in both UTF-8 and wchar formats + obj->Edited = true; + obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n); + obj->CurLenW -= n; + + // Offset remaining text (FIXME-OPT: Use memmove) + const ImWchar *src = obj->TextW.Data + pos + n; + while (ImWchar c = *src++) + *dst++ = c; + *dst = '\0'; +} + +static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState *obj, int pos, const ImWchar *new_text, int new_text_len) +{ + const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0; + const int text_len = obj->CurLenW; + IM_ASSERT(pos <= text_len); + + const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len); + if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA)) + return false; + + // Grow internal buffer if needed + if (new_text_len + text_len + 1 > obj->TextW.Size) { + if (!is_resizable) + return false; + IM_ASSERT(text_len < obj->TextW.Size); + obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1); + } + + ImWchar *text = obj->TextW.Data; + if (pos != text_len) + memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar)); + memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar)); + + obj->Edited = true; + obj->CurLenW += new_text_len; + obj->CurLenA += new_text_len_utf8; + obj->TextW[obj->CurLenW] = '\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 +#define STB_TEXTEDIT_K_UP 0x200002 // keyboard input to move cursor up +#define STB_TEXTEDIT_K_DOWN 0x200003 // keyboard input to move cursor down +#define STB_TEXTEDIT_K_LINESTART 0x200004 // keyboard input to move cursor to start of line +#define STB_TEXTEDIT_K_LINEEND 0x200005 // keyboard input to move cursor to end of line +#define STB_TEXTEDIT_K_TEXTSTART 0x200006 // keyboard input to move cursor to start of text +#define STB_TEXTEDIT_K_TEXTEND 0x200007 // keyboard input to move cursor to end of text +#define STB_TEXTEDIT_K_DELETE 0x200008 // keyboard input to delete selection or character under cursor +#define STB_TEXTEDIT_K_BACKSPACE 0x200009 // keyboard input to delete selection or character left of cursor +#define STB_TEXTEDIT_K_UNDO 0x20000A // keyboard input to perform undo +#define STB_TEXTEDIT_K_REDO 0x20000B // keyboard input to perform redo +#define STB_TEXTEDIT_K_WORDLEFT 0x20000C // keyboard input to move cursor left one word +#define STB_TEXTEDIT_K_WORDRIGHT 0x20000D // keyboard input to move cursor right one word +#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 STB_TEXTEDIT_IMPLEMENTATION +#define STB_TEXTEDIT_memmove memmove +#include + +// 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 STB_TEXTEDIT_CHARTYPE *text, int text_len) +{ + stb_text_makeundo_replace(str, state, 0, str->CurLenW, text_len); + ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenW); + state->cursor = state->select_start = state->select_end = 0; + if (text_len <= 0) + return; + if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len)) { + state->cursor = state->select_start = state->select_end = text_len; + state->has_preferred_x = 0; + return; + } + IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace() +} + +} // namespace ImStb + +void ImGuiInputTextState::OnKeyPressed(int key) +{ + stb_textedit_key(this, &Stb, key); + CursorFollow = true; + CursorAnimReset(); +} + +ImGuiInputTextCallbackData::ImGuiInputTextCallbackData() +{ + memset(this, 0, sizeof(*this)); +} + +// Public API to manipulate UTF-8 text +// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar) +// FIXME: The existence of this rarely exercised code path is a bit of a nuisance. +void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count) +{ + IM_ASSERT(pos + bytes_count <= BufTextLen); + char *dst = Buf + pos; + const char *src = Buf + pos + bytes_count; + while (char c = *src++) + *dst++ = c; + *dst = '\0'; + + if (CursorPos >= pos + bytes_count) + CursorPos -= bytes_count; + else if (CursorPos >= pos) + CursorPos = pos; + SelectionStart = SelectionEnd = CursorPos; + 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; + + const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0; + const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text); + if (new_text_len + BufTextLen >= BufSize) { + if (!is_resizable) + return; + + // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the mildly similar code (until we remove the U16 buffer altogether!) + 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); + int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; + edit_state->TextA.reserve(new_buf_size + 1); + Buf = edit_state->TextA.Data; + BufSize = edit_state->BufCapacityA = 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; +} + +// Return false to discard a character. +static bool InputTextFilterCharacter(ImGuiContext *ctx, + unsigned int *p_char, + ImGuiInputTextFlags flags, + ImGuiInputTextCallback callback, + void *user_data, + ImGuiInputSource input_source) +{ + IM_ASSERT(input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Clipboard); + unsigned int c = *p_char; + + // Filter non-printable (NB: isprint is unreliable! see #2467) + bool apply_named_filters = true; + if (c < 0x20) { + bool pass = false; + pass |= + (c == '\n' && (flags & ImGuiInputTextFlags_Multiline)); // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code) + pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput)); + if (!pass) + return false; + apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted. + } + + if (input_source != ImGuiInputSource_Clipboard) { + // 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))) { + // The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf to use e.g. ',' + // instead of '.'. The standard mandate that programs starts in the "C" locale where the decimal point is '.'. We don't really intend to provide + // widespread support for it, but out of empathy for people stuck with using odd API, we support the bare minimum aka overriding the decimal point. + // Change the default decimal_point with: + // ImGui::GetIO()->PlatformLocaleDecimalPoint = *localeconv()->decimal_point; + // Users of non-default decimal point (in particular ',') may be affected by word-selection logic + // (is_word_boundary_from_right/is_word_boundary_from_left) functions. + ImGuiContext &g = *ctx; + const unsigned c_decimal_point = (unsigned int)g.IO.PlatformLocaleDecimalPoint; + if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific)) + 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) { + ImGuiContext &g = *GImGui; + ImGuiInputTextCallbackData callback_data; + callback_data.Ctx = &g; + callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; + callback_data.EventChar = (ImWchar)c; + callback_data.Flags = flags; + callback_data.UserData = user_data; + if (callback(&callback_data) != 0) + return false; + *p_char = callback_data.EventChar; + if (!callback_data.EventChar) + return false; + } + + return true; +} + +// Find the shortest single replacement we can make to get the new text from the old text. +// Important: needs to be run before TextW is rewritten with the new characters because calling STB_TEXTEDIT_GETCHAR() at the end. +// 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. +static void InputTextReconcileUndoStateAfterUserCallback(ImGuiInputTextState *state, const char *new_buf_a, int new_length_a) +{ + ImGuiContext &g = *GImGui; + const ImWchar *old_buf = state->TextW.Data; + const int old_length = state->CurLenW; + const int new_length = ImTextCountCharsFromUtf8(new_buf_a, new_buf_a + new_length_a); + g.TempBuffer.reserve_discard((new_length + 1) * sizeof(ImWchar)); + ImWchar *new_buf = (ImWchar *)(void *)g.TempBuffer.Data; + ImTextStrFromUtf8(new_buf, new_length + 1, new_buf_a, new_buf_a + new_length_a); + + const int shorter_length = ImMin(old_length, new_length); + int first_diff; + for (first_diff = 0; first_diff < shorter_length; first_diff++) + if (old_buf[first_diff] != new_buf[first_diff]) + 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) + if (STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->Stb.undostate, first_diff, delete_len, insert_len)) + for (int i = 0; i < delete_len; i++) + p[i] = ImStb::STB_TEXTEDIT_GETCHAR(state, 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, +// but that more likely be attractive when we do have _NoLiveEdit flag available. +void ImGui::InputTextDeactivateHook(ImGuiID id) +{ + ImGuiContext &g = *GImGui; + ImGuiInputTextState *state = &g.InputTextState; + if (id == 0 || state->ID != id) + return; + g.InputTextDeactivatedState.ID = state->ID; + if (state->Flags & ImGuiInputTextFlags_ReadOnly) { + g.InputTextDeactivatedState.TextA.resize(0); // In theory this data won't be used, but clear to be neat. + } else { + IM_ASSERT(state->TextA.Data != 0); + g.InputTextDeactivatedState.TextA.resize(state->CurLenA + 1); + memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->CurLenA + 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 +// Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator. +// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect. +// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h +// (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are +// doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally should stay in UTF-8 all the time. See +// https://github.com/nothings/stb/issues/188) +bool ImGui::InputTextEx(const char *label, + const char *hint, + char *buf, + int buf_size, + const ImVec2 &size_arg, + ImGuiInputTextFlags flags, + ImGuiInputTextCallback callback, + void *callback_user_data) +{ + 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) + + 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; + const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0; + const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; + const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; + 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! + + 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; + ImGuiItemStatusFlags item_status_flags = 0; + ImGuiLastItemData item_data_backup; + if (is_multiline) { + ImVec2 backup_pos = window->DC.CursorPos; + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable)) { + EndGroup(); + return false; + } + item_status_flags = g.LastItemData.StatusFlags; + item_data_backup = g.LastItemData; + window->DC.CursorPos = backup_pos; + + // We reproduce the contents of BeginChildFrame() in order to provide 'label' so our window internal data are easier to read/debug. + // FIXME-NAV: Pressing NavActivate will trigger general child activation right before triggering our own below. Harmless but bizarre. + PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]); + PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding); + PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize); + PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Ensure no clip rect so mouse hover can reach FramePadding edges + bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), true, ImGuiWindowFlags_NoMove); + PopStyleVar(3); + PopStyleColor(); + if (!child_visible) { + EndChild(); + EndGroup(); + return false; + } + draw_window = g.CurrentWindow; // Child window + draw_window->DC.NavLayersActiveMaskNext |= + (1 << draw_window->DC.NavLayerCurrent); // This is to ensure that EndChild() will display a navigation highlight so we can "enter" into it. + draw_window->DC.CursorPos += style.FramePadding; + inner_size.x -= draw_window->ScrollbarSizes.x; + } else { + // Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd) + ItemSize(total_bb, style.FramePadding.y); + if (!(flags & ImGuiInputTextFlags_MergedItem)) + if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable)) + return false; + item_status_flags = g.LastItemData.StatusFlags; + } + const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags); + if (hovered) + g.MouseCursor = ImGuiMouseCursor_TextInput; + + // We are only allowed to access the state if we are already the active widget. + ImGuiInputTextState *state = GetInputTextState(id); + + const bool input_requested_by_tabbing = (item_status_flags & ImGuiItemStatusFlags_FocusedByTabbing) != 0; + 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_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 || input_requested_by_tabbing); + const bool init_state = (init_make_active || user_scroll_active); + if ((init_state && g.ActiveId != id) || init_changed_specs) { + // 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 (both in original UTF-8 format and converted to wchar) + // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode) + const int buf_len = (int)strlen(buf); + state->InitialTextA.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->InitialTextA.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 differenciate recycle_cursor vs recycle_undostate? + bool recycle_state = (state->ID == id && !init_changed_specs); + if (recycle_state && (state->CurLenA != buf_len || (state->TextAIsValid && strncmp(state->TextA.Data, buf, buf_len) != 0))) + recycle_state = false; + + // Start edition + const char *buf_end = NULL; + state->ID = id; + state->TextW.resize(buf_size + 1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string. + state->TextA.resize(0); + state->TextAIsValid = false; // TextA is not valid yet (we will display buf until then) + state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end); + state->CurLenA = + (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. + + if (recycle_state) { + // 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. + state->CursorClamp(); + } else { + state->ScrollX = 0.0f; + stb_textedit_initialize_state(&state->Stb, !is_multiline); + } + + if (!is_multiline) { + if (flags & ImGuiInputTextFlags_AutoSelectAll) + select_all = true; + if (input_requested_by_nav && (!recycle_state || !(g.NavActivateFlags & ImGuiActivateFlags_TryToPreserveState))) + select_all = true; + if (input_requested_by_tabbing || (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) { + IM_ASSERT(state && state->ID == id); + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + } + if (g.ActiveId == id) { + // Declare some inputs, the other are registered and polled via Shortcut() routing system. + if (user_clicked) + SetKeyOwner(ImGuiKey_MouseLeft, id); + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory)) + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + SetKeyOwner(ImGuiKey_Home, id); + SetKeyOwner(ImGuiKey_End, id); + if (is_multiline) { + SetKeyOwner(ImGuiKey_PageUp, id); + SetKeyOwner(ImGuiKey_PageDown, id); + } + if (is_osx) + SetKeyOwner(ImGuiMod_Alt, id); + if (flags + & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput)) // Disable keyboard tabbing out as we will use the \t character. + SetShortcutRouting(ImGuiKey_Tab, id); + } + + // 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; + + // When read-only we always use the live data passed to the function + // FIXME-OPT: Because our selection/cursor code currently needs the wide text we need to convert it when active, which is not ideal :( + if (is_readonly && state != NULL && (render_cursor || render_selection)) { + const char *buf_end = NULL; + state->TextW.resize(buf_size + 1); + state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, buf, NULL, &buf_end); + state->CurLenA = (int)(buf_end - buf); + state->CursorClamp(); + render_selection &= state->HasSelection(); + } + + // Select the buffer to render. + const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state && state->TextAIsValid; + const 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) { + const ImFontGlyph *glyph = g.Font->FindGlyph('*'); + ImFont *password_font = &g.InputTextPasswordFont; + password_font->FontSize = g.Font->FontSize; + password_font->Scale = g.Font->Scale; + password_font->Ascent = g.Font->Ascent; + password_font->Descent = g.Font->Descent; + password_font->ContainerAtlas = g.Font->ContainerAtlas; + password_font->FallbackGlyph = glyph; + password_font->FallbackAdvanceX = glyph->AdvanceX; + IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty()); + PushFont(password_font); + } + + // Process mouse inputs and character inputs + int backup_current_text_length = 0; + if (g.ActiveId == id) { + IM_ASSERT(state != NULL); + backup_current_text_length = state->CurLenA; + state->Edited = false; + state->BufCapacityA = 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->ScrollX; + const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f)); + + if (select_all) { + state->SelectAll(); + state->SelectedAllMouseLock = true; + } else if (hovered && io.MouseClickedCount[0] >= 2 && !io.KeyShift) { + stb_textedit_click(state, &state->Stb, mouse_x, mouse_y); + const int multiclick_count = (io.MouseClickedCount[0] - 2); + if ((multiclick_count % 2) == 0) { + // Double-click: Select word + // We always use the "Mac" word advance for double-click select vs CTRL+Right which use the platform dependent variant: + // FIXME: There are likely many ways to improve this behavior, but there's no "right" behavior (depends on use-case, software, OS) + const bool is_bol = (state->Stb.cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb.cursor - 1) == '\n'; + if (STB_TEXT_HAS_SELECTION(&state->Stb) || !is_bol) + state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); + // state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); + if (!STB_TEXT_HAS_SELECTION(&state->Stb)) + ImStb::stb_textedit_prep_selection_at_cursor(&state->Stb); + state->Stb.cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb.cursor); + state->Stb.select_end = state->Stb.cursor; + ImStb::stb_textedit_clamp(state, &state->Stb); + } else { + // Triple-click: Select line + const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb.cursor) == '\n'; + state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART); + state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT); + state->OnKeyPressed(STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT); + if (!is_eol && is_multiline) { + ImSwap(state->Stb.select_start, state->Stb.select_end); + state->Stb.cursor = state->Stb.select_end; + } + state->CursorFollow = false; + } + state->CursorAnimReset(); + } else if (io.MouseClicked[0] && !state->SelectedAllMouseLock) { + if (hovered) { + if (io.KeyShift) + stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y); + else + stb_textedit_click(state, &state->Stb, mouse_x, mouse_y); + state->CursorAnimReset(); + } + } else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) { + stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y); + state->CursorAnimReset(); + state->CursorFollow = true; + } + 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) && Shortcut(ImGuiKey_Tab, id) && !is_readonly) { + unsigned int c = '\t'; // Insert TAB + if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) + state->OnKeyPressed((int)c); + } + + // 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.KeySuper); + if (io.InputQueueCharacters.Size > 0) { + if (!ignore_char_inputs && !is_readonly && !input_requested_by_nav) + for (int n = 0; n < io.InputQueueCharacters.Size; n++) { + // Insert character if they pass filtering + unsigned int c = (unsigned int)io.InputQueueCharacters[n]; + if (c == '\t') // Skip Tab, see above. + continue; + if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) + state->OnKeyPressed((int)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.KeySuper && !io.KeyCtrl && !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: formet 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; + const bool is_cut = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_X, id, f_repeat) || Shortcut(ImGuiMod_Shift | ImGuiKey_Delete, id, f_repeat)) && !is_readonly + && !is_password && (!is_multiline || state->HasSelection()); + const bool is_copy = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_C, id) || Shortcut(ImGuiMod_Ctrl | ImGuiKey_Insert, id)) && !is_password + && (!is_multiline || state->HasSelection()); + const bool is_paste = + (Shortcut(ImGuiMod_Shortcut | ImGuiKey_V, id, f_repeat) || Shortcut(ImGuiMod_Shift | ImGuiKey_Insert, id, f_repeat)) && !is_readonly; + const bool is_undo = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_Z, id, f_repeat)) && !is_readonly && is_undoable; + const bool is_redo = + (Shortcut(ImGuiMod_Shortcut | ImGuiKey_Y, id, f_repeat) || (is_osx && Shortcut(ImGuiMod_Shortcut | ImGuiMod_Shift | ImGuiKey_Z, id, f_repeat))) + && !is_readonly && is_undoable; + const bool is_select_all = Shortcut(ImGuiMod_Shortcut | ImGuiKey_A, 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, id, f_repeat) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, id, f_repeat)); + + // FIXME: Should use more Shortcut() and reduce IsKeyPressed()+SetKeyOwner(), but requires modifiers combination to be taken account of. + 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); + } else if (IsKeyPressed(ImGuiKey_RightArrow)) { + state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND + : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT + : STB_TEXTEDIT_K_RIGHT) + | k_mask); + } else if (IsKeyPressed(ImGuiKey_UpArrow) && is_multiline) { + if (io.KeyCtrl) + SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); + else + state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); + } else if (IsKeyPressed(ImGuiKey_DownArrow) && is_multiline) { + if (io.KeyCtrl) + SetScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); + else + state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); + } else if (IsKeyPressed(ImGuiKey_PageUp) && is_multiline) { + state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); + scroll_y -= row_count_per_page * g.FontSize; + } else if (IsKeyPressed(ImGuiKey_PageDown) && is_multiline) { + state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); + scroll_y += row_count_per_page * g.FontSize; + } else if (IsKeyPressed(ImGuiKey_Home)) { + state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); + } else if (IsKeyPressed(ImGuiKey_End)) { + state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); + } else if (IsKeyPressed(ImGuiKey_Delete) && !is_readonly && !is_cut) { + if (!state->HasSelection()) { + // OSX doesn't seem to have Super+Delete to delete until end-of-line, so we don't emulate that (as opposed to Super+Backspace) + if (is_wordmove_key_down) + state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); + } + state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); + } else if (IsKeyPressed(ImGuiKey_Backspace) && !is_readonly) { + if (!state->HasSelection()) { + if (is_wordmove_key_down) + state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT); + else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) + state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT); + } + state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); + } else if (is_enter_pressed || is_gamepad_validate) { + // 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)) { + validated = true; + if (io.ConfigInputTextEnterKeepActive && !is_multiline) + state->SelectAll(); // No need to scroll + else + clear_active_id = true; + } else if (!is_readonly) { + unsigned int c = '\n'; // Insert new line + if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) + state->OnKeyPressed((int)c); + } + } else if (is_cancel) { + if (flags & ImGuiInputTextFlags_EscapeClearsAll) { + if (buf[0] != 0) { + revert_edit = true; + } else { + render_cursor = render_selection = false; + clear_active_id = true; + } + } else { + clear_active_id = revert_edit = true; + render_cursor = render_selection = false; + } + } else if (is_undo || is_redo) { + state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO); + state->ClearSelection(); + } else if (is_select_all) { + state->SelectAll(); + state->CursorFollow = true; + } else if (is_cut || is_copy) { + // Cut, Copy + if (io.SetClipboardTextFn) { + const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0; + const int ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW; + const int clipboard_data_len = ImTextCountUtf8BytesFromStr(state->TextW.Data + ib, state->TextW.Data + ie) + 1; + char *clipboard_data = (char *)IM_ALLOC(clipboard_data_len * sizeof(char)); + ImTextStrToUtf8(clipboard_data, clipboard_data_len, state->TextW.Data + ib, state->TextW.Data + ie); + SetClipboardText(clipboard_data); + MemFree(clipboard_data); + } + if (is_cut) { + if (!state->HasSelection()) + state->SelectAll(); + state->CursorFollow = true; + stb_textedit_cut(state, &state->Stb); + } + } else if (is_paste) { + if (const char *clipboard = GetClipboardText()) { + // Filter pasted buffer + const int clipboard_len = (int)strlen(clipboard); + ImWchar *clipboard_filtered = (ImWchar *)IM_ALLOC((clipboard_len + 1) * sizeof(ImWchar)); + int clipboard_filtered_len = 0; + for (const char *s = clipboard; *s != 0;) { + unsigned int c; + s += ImTextCharFromUtf8(&c, s, NULL); + if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Clipboard)) + continue; + clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; + } + clipboard_filtered[clipboard_filtered_len] = 0; + if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation + { + stb_textedit_paste(state, &state->Stb, clipboard_filtered, clipboard_filtered_len); + state->CursorFollow = true; + } + MemFree(clipboard_filtered); + } + } + + // 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; + if (g.ActiveId == id) { + IM_ASSERT(state != NULL); + if (revert_edit && !is_readonly) { + if (flags & ImGuiInputTextFlags_EscapeClearsAll) { + // Clear input + IM_ASSERT(buf[0] != 0); + apply_new_text = ""; + apply_new_text_length = 0; + value_changed = true; + STB_TEXTEDIT_CHARTYPE empty_string; + stb_textedit_replace(state, &state->Stb, &empty_string, 0); + } else if (strcmp(buf, state->InitialTextA.Data) != 0) { + // 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 + apply_new_text = state->InitialTextA.Data; + apply_new_text_length = state->InitialTextA.Size - 1; + value_changed = true; + ImVector w_text; + if (apply_new_text_length > 0) { + w_text.resize(ImTextCountCharsFromUtf8(apply_new_text, apply_new_text + apply_new_text_length) + 1); + ImTextStrFromUtf8(w_text.Data, w_text.Size, apply_new_text, apply_new_text + apply_new_text_length); + } + stb_textedit_replace(state, &state->Stb, w_text.Data, (apply_new_text_length > 0) ? (w_text.Size - 1) : 0); + } + } + + // Apply ASCII value + if (!is_readonly) { + state->TextAIsValid = true; + state->TextA.resize(state->TextW.Size * 4 + 1); + ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL); + } + + // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer + // before clearing ActiveId, even though strictly speaking it wasn't modified on this frame. + // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. + // This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage + // (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object + // unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize). + const bool apply_edit_back_to_user_buffer = !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); + if (apply_edit_back_to_user_buffer) { + // Apply new value immediately - copy modified buffer back + // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer + // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect. + // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks. + + // 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; + if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, id)) { + event_flag = ImGuiInputTextFlags_CallbackCompletion; + event_key = ImGuiKey_Tab; + } else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_UpArrow)) { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_UpArrow; + } else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_DownArrow)) { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_DownArrow; + } else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited) { + event_flag = ImGuiInputTextFlags_CallbackEdit; + } else if (flags & ImGuiInputTextFlags_CallbackAlways) { + event_flag = ImGuiInputTextFlags_CallbackAlways; + } + + if (event_flag) { + ImGuiInputTextCallbackData callback_data; + callback_data.Ctx = &g; + callback_data.EventFlag = event_flag; + callback_data.Flags = flags; + callback_data.UserData = callback_user_data; + + char *callback_buf = is_readonly ? buf : state->TextA.Data; + callback_data.EventKey = event_key; + callback_data.Buf = callback_buf; + callback_data.BufTextLen = state->CurLenA; + callback_data.BufSize = state->BufCapacityA; + callback_data.BufDirty = false; + + // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see + // https://github.com/nothings/stb/issues/188) + ImWchar *text = state->TextW.Data; + const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + state->Stb.cursor); + const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_start); + const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + 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 + IM_ASSERT(callback_data.BufSize == state->BufCapacityA); + IM_ASSERT(callback_data.Flags == flags); + const bool buf_dirty = callback_data.BufDirty; + if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty) { + state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); + state->CursorFollow = true; + } + if (callback_data.SelectionStart != utf8_selection_start || buf_dirty) { + state->Stb.select_start = (callback_data.SelectionStart == callback_data.CursorPos) + ? state->Stb.cursor + : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); + } + if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty) { + state->Stb.select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) + ? state->Stb.select_start + : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); + } + if (buf_dirty) { + IM_ASSERT((flags & ImGuiInputTextFlags_ReadOnly) == 0); + IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! + InputTextReconcileUndoStateAfterUserCallback( + state, + callback_data.Buf, + callback_data + .BufTextLen); // FIXME: Move the rest of this block inside function and rename to InputTextReconcileStateAfterUserCallback() ? + if (callback_data.BufTextLen > backup_current_text_length && is_resizable) + state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length)); // Worse case scenario resize + state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL); + state->CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() + state->CursorAnimReset(); + } + } + } + + // Will copy result string if modified + if (!is_readonly && strcmp(state->TextA.Data, buf) != 0) { + apply_new_text = state->TextA.Data; + apply_new_text_length = state->CurLenA; + value_changed = true; + } + } + } + + // Handle reapplying final data on deactivation (see InputTextDeactivateHook() for details) + if (g.InputTextDeactivatedState.ID == id) { + if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0) { + apply_new_text = g.InputTextDeactivatedState.TextA.Data; + apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1; + value_changed = true; + // IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text); + } + g.InputTextDeactivatedState.ID = 0; + } + + // Copy result to user buffer. This can currently only happen when (g.ActiveId == id) + if (apply_new_text != NULL) { + // We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size + // of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used + // without any storage on user's side. + IM_ASSERT(apply_new_text_length >= 0); + if (is_resizable) { + ImGuiInputTextCallbackData callback_data; + callback_data.Ctx = &g; + callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; + callback_data.Flags = flags; + callback_data.Buf = buf; + callback_data.BufTextLen = apply_new_text_length; + callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1); + callback_data.UserData = callback_user_data; + callback(&callback_data); + buf = callback_data.Buf; + buf_size = callback_data.BufSize; + apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1); + 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) { + RenderNavHighlight(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 + if (is_displaying_hint) { + buf_display = hint; + buf_display_end = hint + strlen(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) { + IM_ASSERT(state != NULL); + if (!is_displaying_hint) + buf_display_end = buf_display + state->CurLenA; + + // 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) + // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation) + // - Measure text height (for scrollbar) + // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for + // selection rendering (we could merge them by an extra refactoring effort) + // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. + const ImWchar *text_begin = state->TextW.Data; + ImVec2 cursor_offset, select_start_offset; + + { + // Find lines numbers straddling 'cursor' (slot 0) and 'select_start' (slot 1) positions. + const ImWchar *searches_input_ptr[2] = {NULL, NULL}; + int searches_result_line_no[2] = {-1000, -1000}; + int searches_remaining = 0; + if (render_cursor) { + searches_input_ptr[0] = text_begin + state->Stb.cursor; + searches_result_line_no[0] = -1; + searches_remaining++; + } + if (render_selection) { + searches_input_ptr[1] = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); + searches_result_line_no[1] = -1; + searches_remaining++; + } + + // Iterate all lines to find our line numbers + // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter. + searches_remaining += is_multiline ? 1 : 0; + int line_count = 0; + // for (const ImWchar* s = text_begin; (s = (const ImWchar*)wcschr((const wchar_t*)s, (wchar_t)'\n')) != NULL; s++) // FIXME-OPT: Could use this + // when wchar_t are 16-bit + for (const ImWchar *s = text_begin; *s != 0; s++) + if (*s == '\n') { + line_count++; + if (searches_result_line_no[0] == -1 && s >= searches_input_ptr[0]) { + searches_result_line_no[0] = line_count; + if (--searches_remaining <= 0) + break; + } + if (searches_result_line_no[1] == -1 && s >= searches_input_ptr[1]) { + searches_result_line_no[1] = line_count; + if (--searches_remaining <= 0) + break; + } + } + line_count++; + if (searches_result_line_no[0] == -1) + searches_result_line_no[0] = line_count; + if (searches_result_line_no[1] == -1) + searches_result_line_no[1] = line_count; + + // Calculate 2d position by finding the beginning of the line and measuring distance + cursor_offset.x = InputTextCalcTextSizeW(&g, ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x; + cursor_offset.y = searches_result_line_no[0] * g.FontSize; + if (searches_result_line_no[1] >= 0) { + select_start_offset.x = InputTextCalcTextSizeW(&g, ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x; + select_start_offset.y = searches_result_line_no[1] * 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) { + // Horizontal scroll in chunks of quarter width + if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll)) { + const float scroll_increment_x = inner_size.x * 0.25f; + const float visible_width = inner_size.x - style.FramePadding.x; + if (cursor_offset.x < state->ScrollX) + state->ScrollX = IM_FLOOR(ImMax(0.0f, cursor_offset.x - scroll_increment_x)); + else if (cursor_offset.x - visible_width >= state->ScrollX) + state->ScrollX = IM_FLOOR(cursor_offset.x - visible_width + scroll_increment_x); + } else { + state->ScrollX = 0.0f; + } + + // Vertical scroll + if (is_multiline) { + // Test if cursor is vertically visible + if (cursor_offset.y - g.FontSize < scroll_y) + scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); + else if (cursor_offset.y - (inner_size.y - style.FramePadding.y * 2.0f) >= scroll_y) + scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f; + const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f); + scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y); + 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->ScrollX, 0.0f); + if (render_selection) { + const ImWchar *text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); + const ImWchar *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; + ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll; + for (const ImWchar *p = text_selected_begin; p < text_selected_end;) { + if (rect_pos.y > clip_rect.w + g.FontSize) + break; + if (rect_pos.y < clip_rect.y) { + // p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p); // FIXME-OPT: Could use this when wchar_t are 16-bit + // p = p ? p + 1 : text_selected_end; + while (p < text_selected_end) + if (*p++ == '\n') + break; + } else { + ImVec2 rect_size = InputTextCalcTextSizeW(&g, p, text_selected_end, &p, NULL, true); + if (rect_size.x <= 0.0f) + rect_size.x = IM_FLOOR(g.Font->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)) + draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); + } + rect_pos.x = draw_pos.x - draw_scroll.x; + 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. + if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) { + 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) { + state->CursorAnim += io.DeltaTime; + bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; + ImVec2 cursor_screen_pos = ImFloor(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)); + + // 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) { + 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; + } + } + } else { + // Render text only (no selection, no cursor) + if (is_multiline) + text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width + else if (!is_displaying_hint && g.ActiveId == id) + buf_display_end = buf_display + state->CurLenA; + else if (!is_displaying_hint) + buf_display_end = buf_display + strlen(buf_display); + + if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) { + ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); + draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); + } + } + + if (is_password && !is_displaying_hint) + PopFont(); + + if (is_multiline) { + // For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (ref issue #4761)... + Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y)); + g.NextItemData.ItemFlags |= 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(); + if (g.LastItemData.ID == 0) { + g.LastItemData.ID = id; + g.LastItemData.InFlags = item_data_backup.InFlags; + g.LastItemData.StatusFlags = item_data_backup.StatusFlags; + } + } + + // 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 && !(flags & ImGuiInputTextFlags_NoMarkEdited)) + 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 + ImGuiContext &g = *GImGui; + ImStb::STB_TexteditState *stb_state = &state->Stb; + ImStb::StbUndoState *undo_state = &stb_state->undostate; + Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId); + DebugLocateItemOnHover(state->ID); + Text("CurLenW: %d, CurLenA: %d, Cursor: %d, Selection: %d..%d", + state->CurLenW, + state->CurLenA, + stb_state->cursor, + stb_state->select_start, + stb_state->select_end); + 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); + if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 15), true)) // Visualize undo state + { + PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + for (int n = 0; n < STB_TEXTEDIT_UNDOSTATECOUNT; n++) { + ImStb::StbUndoRecord *undo_rec = &undo_state->undo_rec[n]; + const char undo_rec_type = (n < undo_state->undo_point) ? 'u' : (n >= undo_state->redo_point) ? 'r' : ' '; + if (undo_rec_type == ' ') + BeginDisabled(); + char buf[64] = ""; + if (undo_rec_type != ' ' && undo_rec->char_storage != -1) + ImTextStrToUtf8(buf, + IM_ARRAYSIZE(buf), + undo_state->undo_char + undo_rec->char_storage, + undo_state->undo_char + undo_rec->char_storage + undo_rec->insert_length); + Text("%c [%02d] where %03d, insert %03d, delete %03d, char_storage %03d \"%s\"", + undo_rec_type, + n, + undo_rec->where, + undo_rec->insert_length, + undo_rec->delete_length, + undo_rec->char_storage, + buf); + if (undo_rec_type == ' ') + EndDisabled(); + } + PopStyleVar(); + } + EndChild(); +#else + IM_UNUSED(state); +#endif +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. +//------------------------------------------------------------------------- +// - ColorEdit3() +// - ColorEdit4() +// - ColorPicker3() +// - RenderColorRectWithAlphaCheckerboard() [Internal] +// - ColorPicker4() +// - ColorButton() +// - SetColorEditOptions() +// - ColorTooltip() [Internal] +// - 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; + IM_ASSERT(g.ColorEditCurrentID != 0); + if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0))) + 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) +{ + ImGuiContext &g = *GImGui; + 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. +bool ImGui::ColorEdit4(const char *label, float col[4], ImGuiColorEditFlags flags) +{ + ImGuiWindow *window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext &g = *GImGui; + const ImGuiStyle &style = g.Style; + const float square_sz = GetFrameHeight(); + const float w_full = CalcItemWidth(); + const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x); + const float w_inputs = w_full - w_button; + const char *label_display_end = FindRenderedTextEnd(label); + 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_); + if (!(flags & ImGuiColorEditFlags_DataTypeMask_)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DataTypeMask_); + if (!(flags & ImGuiColorEditFlags_PickerMask_)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_); + if (!(flags & ImGuiColorEditFlags_InputMask_)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_InputMask_); + 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; + + // 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)) + ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); + else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV)) { + // Hue is lost when converting from grayscale rgb (saturation=0). Restore it. + ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); + 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_item_one = ImMax(1.0f, IM_FLOOR((w_inputs - (style.ItemInnerSpacing.x) * (components - 1)) / (float)components)); + const float w_item_last = ImMax(1.0f, IM_FLOOR(w_inputs - (w_item_one + style.ItemInnerSpacing.x) * (components - 1))); + + const bool hide_prefix = (w_item_one <= 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] = { + {"%3d", "%3d", "%3d", "%3d"}, // Short display + {"R:%3d", "G:%3d", "B:%3d", "A:%3d"}, // Long display for RGBA + {"H:%3d", "S:%3d", "V:%3d", "A:%3d"} // Long display for HSVA + }; + static const char *fmt_table_float[3][4] = { + {"%0.3f", "%0.3f", "%0.3f", "%0.3f"}, // Short display + {"R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f"}, // Long display for RGBA + {"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; + + for (int n = 0; n < components; n++) { + if (n > 0) + SameLine(0, style.ItemInnerSpacing.x); + SetNextItemWidth((n + 1 < components) ? w_item_one : w_item_last); + + // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0. + if (flags & ImGuiColorEditFlags_Float) { + value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]); + value_changed_as_float |= value_changed; + } else { + value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]); + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight); + } + } else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) { + // RGB Hexadecimal Input + char buf[64]; + if (alpha) + ImFormatString(buf, + IM_ARRAYSIZE(buf), + "#%02X%02X%02X%02X", + ImClamp(i[0], 0, 255), + ImClamp(i[1], 0, 255), + ImClamp(i[2], 0, 255), + ImClamp(i[3], 0, 255)); + else + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255)); + SetNextItemWidth(w_inputs); + if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase)) { + value_changed = true; + char *p = buf; + while (*p == '#' || ImCharIsBlankA(*p)) + p++; + i[0] = i[1] = i[2] = 0; + i[3] = 0xFF; // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha) + int r; + if (alpha) + r = sscanf(p, + "%02X%02X%02X%02X", + (unsigned int *)&i[0], + (unsigned int *)&i[1], + (unsigned int *)&i[2], + (unsigned int *)&i[3]); // Treat at unsigned (%X is unsigned) + else + r = sscanf(p, "%02X%02X%02X", (unsigned int *)&i[0], (unsigned int *)&i[1], (unsigned int *)&i[2]); + IM_UNUSED(r); // Fixes C6031: Return value ignored: 'sscanf'. + } + 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)) { + if (!(flags & ImGuiColorEditFlags_NoPicker)) { + // Store current color and open a picker + g.ColorPickerRef = col_v4; + OpenPopup("picker"); + SetNextWindowPos(g.LastItemData.Rect.GetBL() + ImVec2(0.0f, style.ItemSpacing.y)); + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight); + + if (BeginPopup("picker")) { + if (g.CurrentWindow->BeginCount == 1) { + picker_active_window = g.CurrentWindow; + if (label != label_display_end) { + TextEx(label, label_display_end); + Spacing(); + } + ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ + | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; + ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel + | ImGuiColorEditFlags_AlphaPreviewHalf; + SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? + value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); + } + EndPopup(); + } + } + + if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel)) { + // Position not necessarily next to last submitted button (e.g. if style.ColorButtonPosition == ImGuiDir_Left), + // but we need to use SameLine() to setup baseline correctly. Might want to refactor SameLine() to simplify this. + SameLine(0.0f, style.ItemInnerSpacing.x); + 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) { + if (!value_changed_as_float) + for (int n = 0; n < 4; n++) + f[n] = i[n] / 255.0f; + if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB)) { + g.ColorEditSavedHue = f[0]; + g.ColorEditSavedSat = f[1]; + ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); + g.ColorEditSavedID = g.ColorEditCurrentID; + g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(f[0], f[1], f[2], 0)); + } + 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) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget()) { + bool accepted_drag_drop = false; + if (const ImGuiPayload *payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) { + memcpy((float *)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512 //-V1086 + value_changed = accepted_drag_drop = true; + } + if (const ImGuiPayload *payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) { + 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}; + if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha)) + return false; + 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) +{ + ImU32 alpha8 = IM_F32_TO_INT8_SAT(alpha); + ImGui::RenderArrowPointingAt(draw_list, + ImVec2(pos.x + half_sz.x + 1, pos.y), + ImVec2(half_sz.x + 2, half_sz.y + 1), + ImGuiDir_Right, + IM_COL32(0, 0, 0, alpha8)); + ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32(255, 255, 255, alpha8)); + 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..) +// FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0) +bool ImGui::ColorPicker4(const char *label, float col[4], ImGuiColorEditFlags flags, const float *ref_col) +{ + ImGuiContext &g = *GImGui; + 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(); + 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_; + if (!(flags & ImGuiColorEditFlags_InputMask_)) + flags |= + ((g.ColorEditOptions & ImGuiColorEditFlags_InputMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check that only 1 is selected + 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); + ImVec2 picker_pos = window->DC.CursorPos; + float square_sz = GetFrameHeight(); + float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars + float sv_picker_size = ImMax(bars_width * 1, width - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box + 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_FLOOR(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) { + // Hue is lost when converting from grayscale rgb (saturation=0). Restore it. + ColorConvertRGBtoHSV(R, G, B, H, S, V); + ColorEditRestoreHS(col, &H, &S, &V); + } else if (flags & ImGuiColorEditFlags_InputHSV) { + 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) { + // Hue wheel + SV triangle logic + InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size)); + if (IsItemActive()) { + ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center; + ImVec2 current_off = g.IO.MousePos - wheel_center; + float initial_dist2 = ImLengthSqr(initial_off); + if (initial_dist2 >= (wheel_r_inner - 1) * (wheel_r_inner - 1) && initial_dist2 <= (wheel_r_outer + 1) * (wheel_r_outer + 1)) { + // Interactive with Hue wheel + H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f; + if (H < 0.0f) + H += 1.0f; + value_changed = value_changed_h = true; + } + float cos_hue_angle = ImCos(-H * 2.0f * IM_PI); + float sin_hue_angle = ImSin(-H * 2.0f * IM_PI); + if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle))) { + // Interacting with SV triangle + ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle); + if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated)) + current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated); + float uu, vv, ww; + ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww); + V = ImClamp(1.0f - vv, 0.0001f, 1.0f); + S = ImClamp(uu / V, 0.0001f, 1.0f); + value_changed = value_changed_sv = true; + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight); + } else if (flags & ImGuiColorEditFlags_PickerHueBar) { + // SV rectangle logic + InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size)); + if (IsItemActive()) { + S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1)); + V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1)); + ColorEditRestoreH(col, &H); // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square. + value_changed = value_changed_sv = true; + } + 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)); + if (IsItemActive()) { + H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1)); + value_changed = value_changed_h = true; + } + } + + // Alpha bar logic + if (alpha_bar) { + SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y)); + InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size)); + if (IsItemActive()) { + col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1)); + value_changed = true; + } + } + PopItemFlag(); // ImGuiItemFlags_NoNav + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) { + SameLine(0, style.ItemInnerSpacing.x); + BeginGroup(); + } + + if (!(flags & ImGuiColorEditFlags_NoLabel)) { + const char *label_display_end = FindRenderedTextEnd(label); + if (label != label_display_end) { + if ((flags & ImGuiColorEditFlags_NoSidePreview)) + SameLine(0, style.ItemInnerSpacing.x); + 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_AlphaPreview + | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip; + ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)); + if (ref_col != NULL) { + Text("Original"); + ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]); + if (ColorButton("##original", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2))) { + memcpy(col, ref_col, components * sizeof(float)); + value_changed = true; + } + } + PopItemFlag(); + EndGroup(); + } + + // Convert back color to RGB + if (value_changed_h || value_changed_sv) { + if (flags & ImGuiColorEditFlags_InputRGB) { + ColorConvertHSVtoRGB(H, S, V, col[0], col[1], col[2]); + g.ColorEditSavedHue = H; + g.ColorEditSavedSat = S; + g.ColorEditSavedID = g.ColorEditCurrentID; + g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)); + } else if (flags & ImGuiColorEditFlags_InputHSV) { + col[0] = H; + col[1] = S; + 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) { + PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x); + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR + | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview + | ImGuiColorEditFlags_AlphaPreviewHalf; + ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker; + if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags_DisplayMask_) == 0) + if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB)) { + // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget. + // For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050) + value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap); + value_changed = true; + } + if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags_DisplayMask_) == 0) + value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_DisplayHSV); + if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags_DisplayMask_) == 0) + 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)) { + float new_H, new_S, new_V; + ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V); + if (new_H <= 0 && H > 0) { + if (new_V <= 0 && V != new_V) + ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]); + else if (new_S <= 0) + 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) { + R = col[0]; + G = col[1]; + B = col[2]; + ColorConvertRGBtoHSV(R, G, B, H, S, V); + ColorEditRestoreHS(col, &H, &S, &V); // Fix local Hue as display below will use it immediately. + } else if (flags & ImGuiColorEditFlags_InputHSV) { + H = col[0]; + S = col[1]; + V = col[2]; + 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 + const float aeps = 0.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out). + const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12); + for (int n = 0; n < 6; n++) { + const float a0 = (n) / 6.0f * 2.0f * IM_PI - aeps; + const float a1 = (n + 1.0f) / 6.0f * 2.0f * IM_PI + aeps; + const int vert_start_idx = draw_list->VtxBuffer.Size; + 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); + ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner + wheel_r_outer) * 0.5f, + wheel_center.y + sin_hue_angle * (wheel_r_inner + wheel_r_outer) * 0.5f); + float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f; + int hue_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(hue_cursor_rad); // Lock segment count so the +1 one matches others. + 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); + ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle); + ImVec2 uv_white = GetFontTexUvWhitePixel(); + draw_list->PrimReserve(3, 3); + draw_list->PrimVtx(tra, uv_white, hue_color32); + draw_list->PrimVtx(trb, uv_white, col_black); + draw_list->PrimVtx(trc, uv_white, col_white); + draw_list->AddTriangle(tra, trb, trc, col_midgrey, 1.5f); + sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V)); + } else if (flags & ImGuiColorEditFlags_PickerHueBar) { + // Render SV Square + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white); + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black); + 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]); + float bar0_line_y = IM_ROUND(picker_pos.y + H * sv_picker_size); + 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 ? 10.0f : 6.0f; + 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) { + float alpha = ImSaturate(col[3]); + ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size); + RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); + draw_list->AddRectFilledMultiColor(bar1_bb.Min, + bar1_bb.Max, + user_col32_striped_of_alpha, + user_col32_striped_of_alpha, + user_col32_striped_of_alpha & ~IM_COL32_A_MASK, + user_col32_striped_of_alpha & ~IM_COL32_A_MASK); + float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size); + 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. +// Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set. +bool ImGui::ColorButton(const char *desc_id, const ImVec4 &col, ImGuiColorEditFlags flags, const ImVec2 &size_arg) +{ + ImGuiWindow *window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext &g = *GImGui; + const ImGuiID id = window->GetID(desc_id); + const float default_size = GetFrameHeight(); + const ImVec2 size(size_arg.x == 0.0f ? default_size : size_arg.x, size_arg.y == 0.0f ? default_size : size_arg.y); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + 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) + flags &= ~(ImGuiColorEditFlags_AlphaPreview | 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); + ImRect bb_inner = bb; + float off = 0.0f; + if ((flags & ImGuiColorEditFlags_NoBorder) == 0) { + off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good + // middle ground to reduce those artifacts. + bb_inner.Expand(off); + } + if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) { + float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f); + RenderColorRectWithAlphaCheckerboard(window->DrawList, + ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), + bb_inner.Max, + GetColorU32(col_rgb), + grid_step, + ImVec2(-grid_step + off, off), + rounding, + ImDrawFlags_RoundCornersRight); + window->DrawList->AddRectFilled(bb_inner.Min, + ImVec2(mid_x, bb_inner.Max.y), + GetColorU32(col_rgb_without_alpha), + rounding, + ImDrawFlags_RoundCornersLeft); + } else { + // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha + ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col_rgb : col_rgb_without_alpha; + if (col_source.w < 1.0f) + RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); + else + window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding); + } + RenderNavHighlight(bb, id); + if ((flags & ImGuiColorEditFlags_NoBorder) == 0) { + 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 button are often in need of some sort of border + } + + // 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()) { + if (flags & ImGuiColorEditFlags_NoAlpha) + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once); + else + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once); + ColorButton(desc_id, col, flags); + SameLine(); + TextEx("Color"); + EndDragDropSource(); + } + + // Tooltip + if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered && IsItemHovered(ImGuiHoveredFlags_ForTooltip)) + ColorTooltip( + desc_id, + &col.x, + flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); + + return pressed; +} + +// Initialize/override default color options +void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) +{ + ImGuiContext &g = *GImGui; + if ((flags & ImGuiColorEditFlags_DisplayMask_) == 0) + flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DisplayMask_; + if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0) + flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_; + if ((flags & ImGuiColorEditFlags_PickerMask_) == 0) + flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_; + if ((flags & ImGuiColorEditFlags_InputMask_) == 0) + flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DataTypeMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check only 1 option is selected + 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; + if (text_end > text) { + 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]); + ColorButton( + "##preview", + cf, + (flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) + | ImGuiColorEditFlags_NoTooltip, + sz); + SameLine(); + if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_)) { + if (flags & ImGuiColorEditFlags_NoAlpha) + Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]); + else + Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]); + } else if (flags & ImGuiColorEditFlags_InputHSV) { + if (flags & ImGuiColorEditFlags_NoAlpha) + Text("H: %.3f, S: %.3f, V: %.3f", col[0], col[1], col[2]); + else + Text("H: %.3f, S: %.3f, V: %.3f, A: %.3f", col[0], col[1], col[2], col[3]); + } + 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; + g.LockMarkEdited++; + ImGuiColorEditFlags opts = g.ColorEditOptions; + if (allow_opt_inputs) { + if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) + opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayRGB; + if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) + opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHSV; + if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) + opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHex; + } + if (allow_opt_datatype) { + if (allow_opt_inputs) + Separator(); + 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))) + OpenPopup("Copy"); + if (BeginPopup("Copy")) { + 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]); + char buf[64]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if (Selectable(buf)) + SetClipboardText(buf); + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb); + if (Selectable(buf)) + SetClipboardText(buf); + if (!(flags & ImGuiColorEditFlags_NoAlpha)) { + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + } + EndPopup(); + } + + g.ColorEditOptions = opts; + EndPopup(); + g.LockMarkEdited--; +} + +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; + g.LockMarkEdited++; + if (allow_opt_picker) { + ImVec2 picker_size( + g.FontSize * 8, + ImMax(g.FontSize * 8 - (GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function + PushItemWidth(picker_size.x); + for (int picker_type = 0; picker_type < 2; picker_type++) { + // Draw small/thumbnail version of each picker type (over an invisible button for selection) + if (picker_type > 0) + Separator(); + PushID(picker_type); + ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel + | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha); + if (picker_type == 0) + picker_flags |= ImGuiColorEditFlags_PickerHueBar; + if (picker_type == 1) + picker_flags |= ImGuiColorEditFlags_PickerHueWheel; + ImVec2 backup_pos = GetCursorScreenPos(); + if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup + g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_); + SetCursorScreenPos(backup_pos); + ImVec4 previewing_ref_col; + memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4)); + ColorPicker4("##previewing_picker", &previewing_ref_col.x, picker_flags); + PopID(); + } + PopItemWidth(); + } + if (allow_opt_alpha_bar) { + if (allow_opt_picker) + Separator(); + CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar); + } + EndPopup(); + g.LockMarkEdited--; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: TreeNode, CollapsingHeader, etc. +//------------------------------------------------------------------------- +// - TreeNode() +// - TreeNodeV() +// - TreeNodeEx() +// - TreeNodeExV() +// - TreeNodeBehavior() [Internal] +// - TreePush() +// - TreePop() +// - GetTreeNodeToLabelSpacing() +// - SetNextItemOpen() +// - CollapsingHeader() +//------------------------------------------------------------------------- + +bool ImGui::TreeNode(const char *str_id, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const void *ptr_id, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const char *label) +{ + ImGuiWindow *window = GetCurrentWindow(); + if (window->SkipItems) + return false; + return TreeNodeBehavior(window->GetID(label), 0, 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(); + if (window->SkipItems) + return false; + + return TreeNodeBehavior(window->GetID(label), flags, label, NULL); +} + +bool ImGui::TreeNodeEx(const char *str_id, ImGuiTreeNodeFlags flags, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, flags, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNodeEx(const void *ptr_id, ImGuiTreeNodeFlags flags, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, flags, fmt, args); + 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; + + const char *label, *label_end; + ImFormatStringToTempBufferV(&label, &label_end, fmt, args); + return TreeNodeBehavior(window->GetID(str_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; + + const char *label, *label_end; + ImFormatStringToTempBufferV(&label, &label_end, fmt, args); + return TreeNodeBehavior(window->GetID(ptr_id), flags, label, label_end); +} + +void ImGui::TreeNodeSetOpen(ImGuiID id, bool open) +{ + ImGuiContext &g = *GImGui; + ImGuiStorage *storage = g.CurrentWindow->DC.StateStorage; + storage->SetInt(id, open ? 1 : 0); +} + +bool ImGui::TreeNodeUpdateNextOpen(ImGuiID 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.Flags & ImGuiNextItemDataFlags_HasOpen) { + if (g.NextItemData.OpenCond & ImGuiCond_Always) { + is_open = g.NextItemData.OpenVal; + TreeNodeSetOpen(id, is_open); + } else { + // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently. + const int stored_value = storage->GetInt(id, -1); + if (stored_value == -1) { + is_open = g.NextItemData.OpenVal; + TreeNodeSetOpen(id, is_open); + } else { + is_open = stored_value != 0; + } + } + } else { + is_open = storage->GetInt(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; +} + +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); + + // 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); + ImRect frame_bb; + frame_bb.Min.x = (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x; + frame_bb.Min.y = window->DC.CursorPos.y; + frame_bb.Max.x = window->WorkRect.Max.x; + frame_bb.Max.y = window->DC.CursorPos.y + frame_height; + if (display_frame) { + // Framed header expand a little outside the default padding, to the edge of InnerClipRect + // (FIXME: May remove this at some point and make InnerClipRect align with WindowPadding.x instead of WindowPadding.x*0.5f) + frame_bb.Min.x -= IM_FLOOR(window->WindowPadding.x * 0.5f - 1.0f); + frame_bb.Max.x += IM_FLOOR(window->WindowPadding.x * 0.5f); + } + + const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2); // Collapser 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 > 0.0f ? label_size.x + padding.x * 2 : 0.0f); // Include collapser + 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 (!display_frame && (flags & (ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth)) == 0) + interact_bb.Max.x = frame_bb.Min.x + text_width + style.ItemSpacing.x * 2.0f; + + // Compute open and multi-select states before ItemAdd() as it clear NextItem data. + bool is_open = TreeNodeUpdateNextOpen(id, flags); + bool item_add = ItemAdd(interact_bb, id); + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; + g.LastItemData.DisplayRect = frame_bb; + + // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsBackHere 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. + // Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase. + if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) { + g.NavTreeNodeStack.resize(g.NavTreeNodeStack.Size + 1); + ImGuiNavTreeNodeData *nav_tree_node_data = &g.NavTreeNodeStack.back(); + nav_tree_node_data->ID = id; + nav_tree_node_data->InFlags = g.LastItemData.InFlags; + nav_tree_node_data->NavRect = g.LastItemData.NavRect; + window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth); + } + + const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; + if (!item_add) { + 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; + } + + ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None; + if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.InFlags & 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); + if (window != g.HoveredWindow || !is_mouse_x_over_arrow) + button_flags |= ImGuiButtonFlags_NoKeyModifiers; + + // 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) + // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=0) + // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=1) + // - Double-click on label = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1) + // - Double-click on arrow = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1 and _OpenOnArrow=0) + // It is rather standard that arrow click react on Down rather than Up. + // We set ImGuiButtonFlags_PressedOnClickRelease on OpenOnDoubleClick because we want the item to be active on the initial MouseDown in order for drag and + // drop to work. + if (is_mouse_x_over_arrow) + button_flags |= ImGuiButtonFlags_PressedOnClick; + else if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) + button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; + else + button_flags |= ImGuiButtonFlags_PressedOnClickRelease; + + bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0; + const bool was_selected = selected; + + bool hovered, held; + bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags); + bool toggled = false; + if (!is_leaf) { + if (pressed && g.DragDropHoldJustPressedId != id) { + if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id)) + toggled = true; + if (flags & ImGuiTreeNodeFlags_OpenOnArrow) + toggled |= is_mouse_x_over_arrow + && !g.NavDisableMouseHover; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job + if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseClickedCount[0] == 2) + toggled = true; + } else if (pressed && g.DragDropHoldJustPressedId == id) { + IM_ASSERT(button_flags & ImGuiButtonFlags_PressedOnDragDropHold); + if (!is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again. + toggled = true; + } + + if (g.NavId == id && g.NavMoveDir == ImGuiDir_Left && is_open) { + toggled = true; + NavClearPreferredPosForAxis(ImGuiAxis_X); + NavMoveRequestCancel(); + } + if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority? + { + toggled = true; + NavClearPreferredPosForAxis(ImGuiAxis_X); + NavMoveRequestCancel(); + } + + if (toggled) { + is_open = !is_open; + window->DC.StateStorage->SetInt(id, is_open); + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen; + } + } + + // In this branch, TreeNodeBehavior() cannot toggle the selection so this will never trigger. + if (selected != was_selected) //-V547 + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; + + // Render + const ImU32 text_col = GetColorU32(ImGuiCol_Text); + ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin; + if (display_frame) { + // Framed type + 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); + RenderNavHighlight(frame_bb, id, nav_highlight_flags); + 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) + RenderArrow(window->DrawList, + ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), + text_col, + is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, + 1.0f); + else // Leaf without bullet, left-adjusted text + text_pos.x -= text_offset_x - padding.x; + if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton) + frame_bb.Max.x -= g.FontSize + style.FramePadding.x; + + if (g.LogEnabled) + LogSetNextTextDecoration("###", "###"); + RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); + } else { + // Unframed typed for tree nodes + if (hovered || selected) { + const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false); + } + RenderNavHighlight(frame_bb, id, nav_highlight_flags); + 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) + RenderArrow(window->DrawList, + ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), + text_col, + is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, + 0.70f); + if (g.LogEnabled) + LogSetNextTextDecoration(">", NULL); + RenderText(text_pos, label, label_end, false); + } + + if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + TreePushOverrideID(id); + IMGUI_TEST_ENGINE_ITEM_INFO(id, + label, + g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); + return is_open; +} + +void ImGui::TreePush(const char *str_id) +{ + ImGuiWindow *window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(str_id); +} + +void ImGui::TreePush(const void *ptr_id) +{ + ImGuiWindow *window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(ptr_id); +} + +void ImGui::TreePushOverrideID(ImGuiID id) +{ + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + Indent(); + 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); + + // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled) + if (window->DC.TreeJumpToParentOnPopMask & tree_depth_mask) // Only set during request + { + ImGuiNavTreeNodeData *nav_tree_node_data = &g.NavTreeNodeStack.back(); + IM_ASSERT(nav_tree_node_data->ID == window->IDStack.back()); + if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) + NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, nav_tree_node_data); + g.NavTreeNodeStack.pop_back(); + } + window->DC.TreeJumpToParentOnPopMask &= tree_depth_mask - 1; + + 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) +{ + ImGuiContext &g = *GImGui; + if (g.CurrentWindow->SkipItems) + return; + g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasOpen; + g.NextItemData.OpenVal = is_open; + g.NextItemData.OpenCond = cond ? cond : ImGuiCond_Always; +} + +// 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) +{ + ImGuiWindow *window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + return TreeNodeBehavior(window->GetID(label), 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 +// Do not mistake this with the Open state of the header itself, which you can adjust with SetNextItemOpen() or ImGuiTreeNodeFlags_DefaultOpen. +bool ImGui::CollapsingHeader(const char *label, bool *p_visible, ImGuiTreeNodeFlags flags) +{ + 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) + flags |= ImGuiTreeNodeFlags_AllowOverlap | ImGuiTreeNodeFlags_ClipLabelForTrailingButton; + bool is_open = TreeNodeBehavior(id, flags, label); + if (p_visible != NULL) { + // Create a small overlapping close button + // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc. + // FIXME: CloseButton can overlap into text, need find a way to clip the text somehow. + ImGuiContext &g = *GImGui; + ImGuiLastItemData last_item_backup = g.LastItemData; + float button_size = g.FontSize; + float button_x = ImMax(g.LastItemData.Rect.Min.x, g.LastItemData.Rect.Max.x - g.Style.FramePadding.x - button_size); + float button_y = g.LastItemData.Rect.Min.y + g.Style.FramePadding.y; + ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id); + if (CloseButton(close_button_id, ImVec2(button_x, button_y))) + *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. +// FIXME: Selectable() with (size.x == 0.0f) and (SelectableTextAlign.x > 0.0f) followed by SameLine() is currently not supported. +bool ImGui::Selectable(const char *label, bool selected, ImGuiSelectableFlags flags, const ImVec2 &size_arg) +{ + 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); + ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); + 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; + const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x; + 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); + + // Text stays at the submission position, but bounding box may be extended on both sides + const ImVec2 text_min = pos; + const ImVec2 text_max(min_x + size.x, pos.y + size.y); + + // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable. + ImRect bb(min_x, pos.y, text_max.x, text_max.y); + if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0) { + const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x; + const float spacing_y = style.ItemSpacing.y; + const float spacing_L = IM_FLOOR(spacing_x * 0.50f); + const float spacing_U = IM_FLOOR(spacing_y * 0.50f); + bb.Min.x -= spacing_L; + bb.Min.y -= spacing_U; + bb.Max.x += (spacing_x - spacing_L); + bb.Max.y += (spacing_y - spacing_U); + } + // if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); } + + // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackground for every Selectable.. + const float backup_clip_rect_min_x = window->ClipRect.Min.x; + const float backup_clip_rect_max_x = window->ClipRect.Max.x; + if (span_all_columns) { + window->ClipRect.Min.x = window->ParentWorkRect.Min.x; + window->ClipRect.Max.x = window->ParentWorkRect.Max.x; + } + + const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0; + const bool item_add = ItemAdd(bb, id, NULL, disabled_item ? ImGuiItemFlags_Disabled : ImGuiItemFlags_None); + if (span_all_columns) { + window->ClipRect.Min.x = backup_clip_rect_min_x; + window->ClipRect.Max.x = backup_clip_rect_max_x; + } + + if (!item_add) + 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 && window->DC.CurrentColumns) + PushColumnsBackground(); + else if (span_all_columns && g.CurrentTable) + TablePushBackgroundChannel(); + + // 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; + } + if (flags & ImGuiSelectableFlags_NoSetKeyOwner) { + button_flags |= ImGuiButtonFlags_NoSetKeyOwner; + } + if (flags & ImGuiSelectableFlags_SelectOnClick) { + button_flags |= ImGuiButtonFlags_PressedOnClick; + } + if (flags & ImGuiSelectableFlags_SelectOnRelease) { + button_flags |= ImGuiButtonFlags_PressedOnRelease; + } + if (flags & ImGuiSelectableFlags_AllowDoubleClick) { + button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; + } + if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap)) { + button_flags |= ImGuiButtonFlags_AllowOverlap; + } + + const bool was_selected = selected; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + + // Auto-select when moved into + // - This will be more fully fleshed in the range-select branch + // - This is not exposed as it won't nicely work with some user side handling of shift/control + // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons + // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope()) + // - (2) usage will fail with clipped items + // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. + if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId) + 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 gamepad/keyboard + if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover))) { + if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) { + SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect) + g.NavDisableHighlight = true; + } + } + if (pressed) + MarkItemEdited(id); + + // In this branch, Selectable() cannot toggle the selection so this will never trigger. + if (selected != was_selected) //-V547 + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; + + // Render + if (hovered || selected) { + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + } + if (g.NavId == id) + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); + + if (span_all_columns && window->DC.CurrentColumns) + PopColumnsBackground(); + else if (span_all_columns && g.CurrentTable) + TablePopBackgroundChannel(); + + RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb); + + // Automatically close popups + if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) + && !(g.LastItemData.InFlags & ImGuiItemFlags_SelectableDontClosePopup)) + CloseCurrentPopup(); + + if (disabled_item && !disabled_global) + EndDisabled(); + + 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)) { + *p_selected = !*p_selected; + return true; + } + return false; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: ListBox +//------------------------------------------------------------------------- +// - BeginListBox() +// - EndListBox() +// - ListBox() +//------------------------------------------------------------------------- + +// Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty" +// Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling +// boundaries (e.g. 10.25 * item_height). +bool ImGui::BeginListBox(const char *label, const ImVec2 &size_arg) +{ + ImGuiContext &g = *GImGui; + 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 = ImFloor(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f)); + ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); + 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); + ItemAdd(bb, 0, &frame_bb); + return false; + } + + // FIXME-OPT: We could omit the BeginGroup() if label_size.x but would need to omit the EndGroup() as well. + BeginGroup(); + if (label_size.x > 0.0f) { + ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y); + RenderText(label_pos, label); + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size); + } + + BeginChildFrame(id, frame_bb.GetSize()); + 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); + + EndChildFrame(); + 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, bool (*items_getter)(void *, int, const char **), void *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, ImFloor(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; + ImGuiListClipper clipper; + clipper.Begin( + items_count, + GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to. + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { + const char *item_text; + if (!items_getter(data, i, &item_text)) + item_text = "*Unknown item*"; + + PushID(i); + const bool item_selected = (i == *current_item); + if (Selectable(item_text, item_selected)) { + *current_item = i; + value_changed = true; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); + } + EndListBox(); + + if (value_changed) + MarkItemEdited(g.LastItemData.ID); + + return value_changed; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: PlotLines, PlotHistogram +//------------------------------------------------------------------------- +// - PlotEx() [Internal] +// - PlotLines() +// - PlotHistogram() +//------------------------------------------------------------------------- +// Plot/Graph widgets are not very good. +// Consider writing your own, or using a third-party one, see: +// - 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)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, 0, &frame_bb)) + return -1; + const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags); + + // Determine scale from values if not specified + if (scale_min == FLT_MAX || scale_max == FLT_MAX) { + float v_min = FLT_MAX; + float v_max = -FLT_MAX; + for (int i = 0; i < values_count; i++) { + const float v = values_getter(data, i); + if (v != v) // Ignore NaN values + continue; + v_min = ImMin(v_min, v); + v_max = ImMax(v_max, v); + } + if (scale_min == FLT_MAX) + scale_min = v_min; + 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) + SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1); + else if (plot_type == ImGuiPlotType_Histogram) + 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; + const int v1_idx = (int)(t0 * item_count + 0.5f); + 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)); + if (plot_type == ImGuiPlotType_Lines) { + window->DrawList->AddLine(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base); + } else if (plot_type == ImGuiPlotType_Histogram) { + if (pos1.x >= pos0.x + 2.0f) + 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) { + char fmt[64]; + ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format); + Text(fmt, prefix, v); + } else { + Text("%s: %.3f", prefix, v); + } +} + +//------------------------------------------------------------------------- +// [SECTION] MenuItem, BeginMenu, EndMenu, etc. +//------------------------------------------------------------------------- +// - ImGuiMenuColumns [Internal] +// - BeginMenuBar() +// - EndMenuBar() +// - BeginMainMenuBar() +// - EndMainMenuBar() +// - BeginMenu() +// - EndMenu() +// - MenuItemEx() [Internal] +// - MenuItem() +//------------------------------------------------------------------------- + +// Helpers for internal use +void ImGuiMenuColumns::Update(float spacing, bool window_reappearing) +{ + if (window_reappearing) + memset(Widths, 0, sizeof(Widths)); + Spacing = (ImU16)spacing; + CalcNextTotalWidth(true); + memset(Widths, 0, sizeof(Widths)); + TotalWidth = NextTotalWidth; + NextTotalWidth = 0; +} + +void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets) +{ + ImU16 offset = 0; + bool want_spacing = false; + for (int i = 0; i < IM_ARRAYSIZE(Widths); i++) { + ImU16 width = Widths[i]; + if (want_spacing && width > 0) + offset += Spacing; + want_spacing |= (width > 0); + if (update_offsets) { + if (i == 1) { + OffsetLabel = offset; + } + if (i == 2) { + OffsetShortcut = offset; + } + if (i == 3) { + OffsetMark = offset; + } + } + offset += width; + } + 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); + Widths[1] = ImMax(Widths[1], (ImU16)w_label); + Widths[2] = ImMax(Widths[2], (ImU16)w_shortcut); + Widths[3] = ImMax(Widths[3], (ImU16)w_mark); + 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. Then later the same system could be used for multiple menu-bars, scrollbars, side-bars. +bool ImGui::BeginMenuBar() +{ + ImGuiWindow *window = GetCurrentWindow(); + if (window->SkipItems) + 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. + ImRect bar_rect = window->MenuBarRect(); + ImRect clip_rect(IM_ROUND(bar_rect.Min.x + window->WindowBorderSize), + IM_ROUND(bar_rect.Min.y + window->WindowBorderSize), + IM_ROUND(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize))), + IM_ROUND(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; + window->DC.IsSameLine = false; + window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; + window->DC.MenuBarAppending = true; + AlignTextToFramePadding(); + return true; +} + +void ImGui::EndMenuBar() +{ + ImGuiWindow *window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext &g = *GImGui; + + // 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)) { + // Try to find out if the request is for one of our child menu + ImGuiWindow *nav_earliest_child = g.NavWindow; + while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu)) + nav_earliest_child = nav_earliest_child->ParentWindow; + if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal + && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0) { + // To do so we claim focus back, restore NavId and then process the movement request for yet another frame. + // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window + // (probably not worth bothering) + const ImGuiNavLayer layer = ImGuiNavLayer_Menu; + IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check (FIXME: Seems unnecessary) + FocusWindow(window); + SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); + g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection. + g.NavDisableMouseHover = g.NavMousePosDirty = true; + NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); // Repeat + } + } + + 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); + PopClipRect(); + PopID(); + 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. + g.GroupStack.back().EmitItem = false; + EndGroup(); // Restore position on layer 0 + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.IsSameLine = false; + window->DC.NavLayerCurrent = ImGuiNavLayer_Main; + window->DC.MenuBarAppending = false; +} + +// 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); + if (bar_window == NULL || bar_window->BeginCount == 0) { + // Calculate and set window size/position + ImGuiViewportP *viewport = (ImGuiViewportP *)(void *)(viewport_p ? viewport_p : GetMainViewport()); + ImRect avail_rect = viewport->GetBuildWorkRect(); + ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X; + ImVec2 pos = avail_rect.Min; + if (dir == ImGuiDir_Right || dir == ImGuiDir_Down) + pos[axis] = avail_rect.Max[axis] - axis_size; + ImVec2 size = avail_rect.GetSize(); + 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->BuildWorkOffsetMin[axis] += axis_size; + else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right) + viewport->BuildWorkOffsetMax[axis] -= axis_size; + } + + window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; + 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(); + + // 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. + g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f)); + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; + float height = GetFrameHeight(); + bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags); + g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); + + if (is_open) + BeginMenuBar(); + else + End(); + return is_open; +} + +void ImGui::EndMainMenuBar() +{ + EndMenuBar(); + + // 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. + ImGuiContext &g = *GImGui; + if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest) + 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. + // Previously this worked between popup and a first child menu because the first child menu always had the _ChildWindow flag, + // making hovering on parent popup possible while first child menu was focused - but this was generally a bug with other side effects. + // Instead we don't treat Popup specifically (in order to consistently support menu features in them), maybe the first child menu of a Popup + // doesn't have the _ChildWindow flag, and we rely on this IsRootOfOpenMenuSet() check to allow hovering between root window/popup and first child menu. + // In the end, lack of ID check made it so we could no longer differentiate between separate menu sets. To compensate for that, we at least check parent + // window nav layer. This fixes the most common case of menu opening on hover when moving between window content and menu bar. Multiple different menu sets + // in same nav layer would still open on hover, but that should be a lesser problem, because if such menus are close in proximity in window content then it + // won't feel weird and if they are far apart it likely won't be a problem anyone runs into. + const ImGuiPopupData *upper_popup = &g.OpenPopupStack[g.BeginPopupStack.Size]; + if (window->DC.NavLayerCurrent != upper_popup->ParentNavLayer) + return false; + return upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu) && ImGui::IsWindowChildOf(upper_popup->Window, window, true); +} + +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. + if (g.MenusIdSubmittedThisFrame.contains(id)) { + if (menu_is_open) + menu_is_open = BeginPopupEx(id, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + else + 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. + ImVec2 popup_pos, pos = window->DC.CursorPos; + PushID(label); + if (!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_DontClosePopups; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { + // Menu inside an horizontal menu bar + // Selectable extend their highlight by half ItemSpacing in each direction. + // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin() + popup_pos = ImVec2(pos.x - 1.0f - IM_FLOOR(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight()); + window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); + PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); + float w = label_size.x; + ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + pressed = Selectable("", menu_is_open, selectable_flags, ImVec2(w, label_size.y)); + RenderText(text_pos, label); + PopStyleVar(); + window->DC.CursorPos.x += + IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work + // to call SameLine() ourselves after the PopStyleVar(). + } else { + // Menu inside a regular/vertical menu + // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. + // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. + popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); + float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f; + float checkmark_w = IM_FLOOR(g.FontSize * 1.20f); + float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); + ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); + RenderText(text_pos, label); + if (icon_w > 0.0f) + RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); + RenderArrow(window->DrawList, pos + ImVec2(offsets->OffsetMark + extra_w + g.FontSize * 0.30f, 0.0f), GetColorU32(ImGuiCol_Text), ImGuiDir_Right); + } + if (!enabled) + EndDisabled(); + + const bool hovered = (g.HoveredId == id) && enabled && !g.NavDisableMouseHover; + if (menuset_is_open) + PopItemFlag(); + + bool want_open = false; + bool want_close = false; + if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) + { + // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu + // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. + bool moving_toward_child_menu = false; + ImGuiPopupData *child_popup = + (g.BeginPopupStack.Size < g.OpenPopupStack.Size) ? &g.OpenPopupStack[g.BeginPopupStack.Size] : NULL; // Popup candidate (testing below) + ImGuiWindow *child_menu_window = (child_popup && child_popup->Window && child_popup->Window->ParentWindow == window) ? child_popup->Window : NULL; + if (g.HoveredWindow == window && child_menu_window != NULL) { + float ref_unit = g.FontSize; // FIXME-DPI + float child_dir = (window->Pos.x < child_menu_window->Pos.x) ? 1.0f : -1.0f; + ImRect next_window_rect = child_menu_window->Rect(); + ImVec2 ta = (g.IO.MousePos - g.IO.MouseDelta); + ImVec2 tb = (child_dir > 0.0f) ? next_window_rect.GetTL() : next_window_rect.GetTR(); + ImVec2 tc = (child_dir > 0.0f) ? next_window_rect.GetBL() : next_window_rect.GetBR(); + float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, ref_unit * 0.5f, ref_unit * 2.5f); // add a bit of extra slack. + ta.x += child_dir * -0.5f; + tb.x += child_dir * ref_unit; + tc.x += child_dir * ref_unit; + tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -ref_unit * 8.0f); // triangle has maximum height to limit the slope and the bias toward large sub-menus + tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +ref_unit * 8.0f); + 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.NavDisableMouseHover) + want_close = true; + + // Open + if (!menu_is_open && pressed) // Click/activate to open + want_open = true; + else if (!menu_is_open && hovered && !moving_toward_child_menu) // Hover to open + want_open = true; + if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open + { + want_open = true; + NavMoveRequestCancel(); + } + } else { + // Menu bar + if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it + { + want_close = true; + want_open = menu_is_open = false; + } else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others + { + want_open = true; + } else if (g.NavId == id && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open + { + want_open = true; + 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, first close the other menu and yield for a frame. + OpenPopup(label); + } else if (want_open) { + menu_is_open = true; + OpenPopup(label); + } + + if (menu_is_open) { + ImGuiLastItemData last_item_in_parent = g.LastItemData; + SetNextWindowPos(popup_pos, ImGuiCond_Always); // Note: misleading: the value will serve as reference for FindBestWindowPosForPopup(), not actual pos. + PushStyleVar(ImGuiStyleVar_ChildRounding, style.PopupRounding); // First level will use _PopupRounding, subsequent will use _ChildRounding + menu_is_open = BeginPopupEx(id, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + PopStyleVar(); + if (menu_is_open) { + // 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; + if (g.HoveredWindow == window) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + } + } else { + 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. + ImGuiContext &g = *GImGui; + ImGuiWindow *window = g.CurrentWindow; + IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginMenu()/EndMenu() calls + ImGuiWindow *parent_window = window->ParentWindow; // Should always be != NULL is we passed assert. + if (window->BeginCount == window->BeginCountPreviousFrame) + if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet()) + if (g.NavWindow && (g.NavWindow->RootWindowForNav == window) && parent_window->DC.LayoutType == ImGuiLayoutType_Vertical) { + 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; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { + // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful + // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark. + float w = label_size.x; + window->DC.CursorPos.x += IM_FLOOR(style.ItemSpacing.x * 0.5f); + ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); + pressed = Selectable("", selected, selectable_flags, ImVec2(w, 0.0f)); + PopStyleVar(); + if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) + RenderText(text_pos, label); + window->DC.CursorPos.x += + IM_FLOOR(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work + // to call SameLine() ourselves after the PopStyleVar(). + } else { + // Menu item inside a vertical menu + // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. + // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. + float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f; + float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f; + float checkmark_w = IM_FLOOR(g.FontSize * 1.20f); + float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame + float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); + pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); + if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) { + RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label); + if (icon_w > 0.0f) + RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); + if (shortcut_w > 0.0f) { + PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); + RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false); + PopStyleColor(); + } + if (selected) + RenderCheckMark(window->DrawList, + pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), + GetColorU32(ImGuiCol_Text), + g.FontSize * 0.866f); + } + } + IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, + label, + g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); + if (!enabled) + EndDisabled(); + 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)) { + if (p_selected) + *p_selected = !*p_selected; + return true; + } + return false; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: BeginTabBar, EndTabBar, etc. +//------------------------------------------------------------------------- +// - BeginTabBar() +// - BeginTabBarEx() [Internal] +// - EndTabBar() +// - TabBarLayout() [Internal] +// - TabBarCalcTabID() [Internal] +// - TabBarCalcMaxTabWidth() [Internal] +// - TabBarFindTabById() [Internal] +// - TabBarFindTabByOrder() [Internal] +// - TabBarGetCurrentTab() [Internal] +// - TabBarGetTabName() [Internal] +// - TabBarRemoveTab() [Internal] +// - TabBarCloseTab() [Internal] +// - TabBarScrollClamp() [Internal] +// - TabBarScrollToTab() [Internal] +// - TabBarQueueFocus() [Internal] +// - TabBarQueueReorder() [Internal] +// - TabBarProcessReorderFromMousePos() [Internal] +// - TabBarProcessReorder() [Internal] +// - 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 Spacing; // Horizontal spacing at the end of the section. + + ImGuiTabBarSection() + { + memset(this, 0, sizeof(*this)); + } +}; + +namespace ImGui +{ +static void TabBarLayout(ImGuiTabBar *tab_bar); +static ImU32 TabBarCalcTabID(ImGuiTabBar *tab_bar, const char *label, ImGuiWindow *docked_window); +static float TabBarCalcMaxTabWidth(); +static float TabBarScrollClamp(ImGuiTabBar *tab_bar, float scrolling); +static void TabBarScrollToTab(ImGuiTabBar *tab_bar, ImGuiID tab_id, ImGuiTabBarSection *sections); +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; + const ImGuiTabItem *b = (const ImGuiTabItem *)rhs; + const int a_section = TabItemGetSectionIdx(a); + const int b_section = TabItemGetSectionIdx(b); + if (a_section != b_section) + 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; + if (g.TabBars.Contains(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; + return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused); +} + +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; + + if ((flags & ImGuiTabBarFlags_DockNode) == 0) + PushOverrideID(tab_bar->ID); + + // Add to stack + g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar)); + g.CurrentTabBar = tab_bar; + + // Append with multiple BeginTabBar()/EndTabBar() pairs. + tab_bar->BackupCursorPos = window->DC.CursorPos; + if (tab_bar->CurrFrameVisible == g.FrameCount) { + window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY); + 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))) + 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() + tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible; + tab_bar->CurrFrameVisible = g.FrameCount; + tab_bar->PrevTabsContentsHeight = tab_bar->CurrTabsContentsHeight; + tab_bar->CurrTabsContentsHeight = 0.0f; + tab_bar->ItemSpacingY = g.Style.ItemSpacing.y; + tab_bar->FramePadding = g.Style.FramePadding; + 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 + const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive); + const float y = tab_bar->BarRect.Max.y - 1.0f; + { + const float separator_min_x = tab_bar->BarRect.Min.x - IM_FLOOR(window->WindowPadding.x * 0.5f); + const float separator_max_x = tab_bar->BarRect.Max.x + IM_FLOOR(window->WindowPadding.x * 0.5f); + window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f); + } + 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) { + tab_bar->CurrTabsContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, tab_bar->CurrTabsContentsHeight); + window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->CurrTabsContentsHeight; + } else { + window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->PrevTabsContentsHeight; + } + 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; + + // 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; + bool need_sort_by_section = false; + ImGuiTabBarSection sections[3]; // Layout sections: Leading, Central, Trailing + for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++) { + ImGuiTabItem *tab = &tab_bar->Tabs[tab_src_n]; + if (tab->LastFrameVisible < tab_bar->PrevFrameVisible || tab->WantClose) { + // Remove tab + if (tab_bar->VisibleTabId == tab->ID) { + tab_bar->VisibleTabId = 0; + } + if (tab_bar->SelectedTabId == tab->ID) { + tab_bar->SelectedTabId = 0; + } + if (tab_bar->NextSelectedTabId == tab->ID) { + tab_bar->NextSelectedTabId = 0; + } + continue; + } + 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) { + ImGuiTabItem *prev_tab = &tab_bar->Tabs[tab_dst_n - 1]; + int prev_tab_section_n = TabItemGetSectionIdx(prev_tab); + if (curr_tab_section_n == 0 && prev_tab_section_n != 0) + need_sort_by_section = true; + 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) { + tab_bar->SelectedTabId = tab_bar->NextSelectedTabId; + 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) { + if (TabBarProcessReorder(tab_bar)) + if (tab_bar->ReorderRequestTabId == tab_bar->SelectedTabId) + 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); + + // Compute ideal tabs widths + store them into shrink buffer + ImGuiTabItem *most_recently_selected_tab = NULL; + int curr_section_n = -1; + bool found_selected_tab_id = false; + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) { + 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; + + 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); + 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]++]; + shrink_width_item->Index = tab_n; + 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) + 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; + + // 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)) + 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; + } + + // 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; + float section_2_w = sections[2].Width + sections[2].Spacing; + bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth(); + float width_excess; + if (central_section_is_visible) + 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)) { + 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); + + // Apply shrunk values into tabs and sections + for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++) { + ImGuiTabItem *tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index]; + float shrinked_width = IM_FLOOR(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; + tab_bar->WidthAllTabs = 0.0f; + for (int section_n = 0; section_n < 3; section_n++) { + 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]; + tab->Offset = tab_offset; + tab->NameOffset = -1; + tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); + } + tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f); + 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; + + // 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)) { + const float wheel = g.IO.MouseWheelRequestAxisSwap ? g.IO.MouseWheel : g.IO.MouseWheelH; + const ImGuiKey wheel_key = g.IO.MouseWheelRequestAxisSwap ? ImGuiKey_MouseWheelY : ImGuiKey_MouseWheelX; + if (TestKeyOwner(wheel_key, tab_bar->ID) && wheel != 0.0f) { + const float scroll_step = wheel * TabBarCalcScrollableWidth(tab_bar, sections) / 3.0f; + tab_bar->ScrollingTargetDistToVisibility = 0.0f; + tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget - scroll_step); + } + 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); + if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget) { + // Scrolling speed adjust itself so we can always reach our target in 1/3 seconds. + // Teleport if we are aiming far off the visible line + tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, 70.0f * g.FontSize); + tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, ImFabs(tab_bar->ScrollingTarget - tab_bar->ScrollingAnim) / 0.3f); + const bool teleport = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) || (tab_bar->ScrollingTargetDistToVisibility > 10.0f * g.FontSize); + tab_bar->ScrollingAnim = + teleport ? tab_bar->ScrollingTarget : ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, g.IO.DeltaTime * tab_bar->ScrollingSpeed); + } else { + tab_bar->ScrollingSpeed = 0.0f; + } + 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) +{ + IM_ASSERT(docked_window == NULL); // master branch only + IM_UNUSED(docked_window); + if (tab_bar->Flags & ImGuiTabBarFlags_DockNode) { + ImGuiID id = ImHashStr(label); + KeepAliveID(id); + return id; + } else { + ImGuiWindow *window = GImGui->CurrentWindow; + 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) + for (int n = 0; n < tab_bar->Tabs.Size; n++) + if (tab_bar->Tabs[n].ID == 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) +{ + if (order < 0 || order >= tab_bar->Tabs.Size) + return NULL; + return &tab_bar->Tabs[order]; +} + +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->NameOffset == -1) + return "N/A"; + IM_ASSERT(tab->NameOffset < tab_bar->TabsNames.Buf.Size); + return tab_bar->TabsNames.Buf.Data + tab->NameOffset; +} + +// 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) +{ + if (ImGuiTabItem *tab = TabBarFindTabByID(tab_bar, tab_id)) + tab_bar->Tabs.erase(tab); + if (tab_bar->VisibleTabId == tab_id) { + tab_bar->VisibleTabId = 0; + } + 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)) { + // This will remove a frame of lag for selecting another tab on closure. + // However we don't run it in the case where the 'Unsaved' flag is set, so user gets a chance to fully undo the closure + tab->WantClose = true; + if (tab_bar->VisibleTabId == tab->ID) { + tab->LastFrameVisible = -1; + tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0; + } + } else { + // Actually select before expecting closure attempt (on an UnsavedDocument tab user is expect to e.g. show a popup) + if (tab_bar->VisibleTabId != tab->ID) + 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) +{ + ImGuiTabItem *tab = TabBarFindTabByID(tab_bar, tab_id); + if (tab == NULL) + 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); + tab_bar->ScrollingTargetDistToVisibility = 0.0f; + if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width)) { + // Scroll to the left + tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f); + tab_bar->ScrollingTarget = tab_x1; + } else if (tab_bar->ScrollingTarget < tab_x2 - scrollable_width) { + // Scroll to the right + tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - scrollable_width) - tab_bar->ScrollingAnim, 0.0f); + tab_bar->ScrollingTarget = tab_x2 - scrollable_width; + } +} + +void ImGui::TabBarQueueFocus(ImGuiTabBar *tab_bar, ImGuiTabItem *tab) +{ + tab_bar->NextSelectedTabId = tab->ID; +} + +void ImGui::TabBarQueueReorder(ImGuiTabBar *tab_bar, ImGuiTabItem *tab, int offset) +{ + IM_ASSERT(offset != 0); + IM_ASSERT(tab_bar->ReorderRequestTabId == 0); + 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); + int dst_idx = src_idx; + for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir) { + // Reordered tabs must share the same section + const ImGuiTabItem *dst_tab = &tab_bar->Tabs[i]; + if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder) + break; + 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; + // GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255)); + 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]; + if (tab2->Flags & ImGuiTabItemFlags_NoReorder) + 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)); + const float backup_repeat_delay = g.IO.KeyRepeatDelay; + const float backup_repeat_rate = g.IO.KeyRepeatRate; + g.IO.KeyRepeatDelay = 0.250f; + g.IO.KeyRepeatRate = 0.200f; + float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width); + window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y); + if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat)) + select_dir = -1; + window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y); + if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat)) + select_dir = +1; + 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) { + target_order += select_dir; + selected_order += select_dir; + tab_to_scroll_to = (target_order < 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL; + } + } + } + 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) { + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) { + 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. +//------------------------------------------------------------------------- +// - BeginTabItem() +// - EndTabItem() +// - TabItemButton() +// - TabItemEx() [Internal] +// - SetTabItemClosed() +// - TabItemCalcSize() [Internal] +// - 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) { + IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!"); + return false; + } + IM_ASSERT(!(flags & ImGuiTabItemFlags_Button)); // 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)) { + ImGuiTabItem *tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx]; + PushOverrideID(tab->ID); // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label) + } + 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) { + IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); + return; + } + IM_ASSERT(tab_bar->LastTabItemIdx >= 0); + ImGuiTabItem *tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx]; + 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) { + IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); + return false; + } + return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, 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 + ImGuiContext &g = *GImGui; + if (tab_bar->WantLayout) { + ImGuiNextItemData backup_next_item_data = g.NextItemData; + TabBarLayout(tab_bar); + g.NextItemData = backup_next_item_data; + } + 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); + if (p_open && !*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; + if (tab == NULL) { + tab_bar->Tabs.push_back(ImGuiTabItem()); + tab = &tab_bar->Tabs.back(); + tab->ID = id; + 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; + if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasWidth) + size.x = tab->RequestedWidth = g.NextItemData.Width; + if (tab_is_new) + 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); + const bool tab_just_unsaved = (flags & ImGuiTabItemFlags_UnsavedDocument) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument); + const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0; + tab->LastFrameVisible = g.FrameCount; + tab->Flags = flags; + + // Append name _WITH_ the zero-terminator + if (docked_window != NULL) { + IM_ASSERT(docked_window == NULL); // master branch only + } else { + tab->NameOffset = (ImS32)tab_bar->TabsNames.size(); + tab_bar->TabsNames.append(label, label + strlen(label) + 1); + } + + // Update selected tab + if (!is_tab_button) { + if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0) + if (!tab_bar_appearing || tab_bar->SelectedTabId == 0) + TabBarQueueFocus(tab_bar, tab); // New tabs gets activated + 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) + 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)) { + ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav); + if (is_tab_button) + 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; + if (is_central_section) + window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_FLOOR(tab->Offset - tab_bar->ScrollingAnim), 0.0f); + else + 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) + PopClipRect(); + window->DC.CursorPos = backup_main_cursor_pos; + return tab_contents_visible; + } + + // Click to Select a tab + // Allow the close button to overlap + ImGuiButtonFlags button_flags = + ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap); + if (g.DragDropActive) + button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + if (pressed && !is_tab_button) + TabBarQueueFocus(tab_bar, tab); + + // Drag and drop: re-order tabs + if (held && !tab_appearing && IsMouseDragging(0)) { + if (!g.DragDropActive && (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)) { + // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x + if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x) { + TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos); + } else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x) { + TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos); + } + } + } + +#if 0 + if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth) + { + // Enlarge tab display when hovering + bb.Max.x = bb.Min.x + IM_FLOOR(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f))); + display_draw_list = GetForegroundDrawList(window); + TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive)); + } +#endif + + // Render tab shape + ImDrawList *display_draw_list = window->DrawList; + const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered + : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive) + : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabUnfocused)); + TabItemBackground(display_draw_list, bb, flags, tab_col); + RenderNavHighlight(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 (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, id) : 0; + bool just_closed; + bool text_clipped; + TabItemLabelAndCloseButton(display_draw_list, + bb, + tab_just_unsaved ? (flags & ~ImGuiTabItemFlags_UnsavedDocument) : flags, + tab_bar->FramePadding, + label, + id, + close_button_id, + tab_contents_visible, + &just_closed, + &text_clipped); + if (just_closed && p_open != NULL) { + *p_open = false; + TabBarCloseTab(tab_bar, tab); + } + + // Restore main window position so user can draw there + if (want_clip_rect) + PopClipRect(); + window->DC.CursorPos = backup_main_cursor_pos; + + // 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) + // FIXME: This is a mess. + // FIXME: We may want disabled tab to still display the tooltip? + if (text_clipped && g.HoveredId == id && !held) + if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip)) + SetItemTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label); + + 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. +void ImGui::SetTabItemClosed(const char *label) +{ + ImGuiContext &g = *GImGui; + bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode); + if (is_within_manual_tab_bar) { + ImGuiTabBar *tab_bar = g.CurrentTabBar; + ImGuiID tab_id = TabBarCalcTabID(tab_bar, label, NULL); + if (ImGuiTabItem *tab = TabBarFindTabByID(tab_bar, tab_id)) + tab->WantClose = true; // Will be processed by next call to TabBarLayout() + } +} + +ImVec2 ImGui::TabItemCalcSize(const char *label, bool has_close_button_or_unsaved_marker) +{ + ImGuiContext &g = *GImGui; + ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f); + if (has_close_button_or_unsaved_marker) + size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle. + else + size.x += g.Style.FramePadding.x + 1.0f; + return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y); +} + +ImVec2 ImGui::TabItemCalcSize(ImGuiWindow *) +{ + IM_ASSERT(0); // This function exists to facilitate merge with 'docking' branch. + return ImVec2(0.0f, 0.0f); +} + +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. + ImGuiContext &g = *GImGui; + const float width = bb.GetWidth(); + IM_UNUSED(flags); + IM_ASSERT(width > 0.0f); + const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f)); + const float y1 = bb.Min.y + 1.0f; + const float y2 = bb.Max.y - 1.0f; + draw_list->PathLineTo(ImVec2(bb.Min.x, y2)); + draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9); + draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12); + draw_list->PathLineTo(ImVec2(bb.Max.x, y2)); + draw_list->PathFillConvex(col); + if (g.Style.TabBorderSize > 0.0f) { + draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2)); + draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9); + draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12); + draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2)); + 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 + const float backup_alpha = g.Style.Alpha; + 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; + + // 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; + // 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 + // 'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button + // 'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false + bool close_button_pressed = false; + bool close_button_visible = false; + if (close_button_id != 0) + if (is_contents_visible || bb.GetWidth() >= ImMax(button_sz, g.Style.TabMinWidthForCloseButton)) + if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id) + close_button_visible = true; + bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x); + + if (close_button_visible) { + ImGuiLastItemData last_item_backup = g.LastItemData; + if (CloseButton(close_button_id, button_pos)) + close_button_pressed = true; + g.LastItemData = last_item_backup; + + // Close with middle mouse button + if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2)) + close_button_pressed = true; + } else if (unsaved_marker_visible) { + const ImRect bullet_bb(button_pos, button_pos + ImVec2(button_sz, button_sz) + g.Style.FramePadding * 2.0f); + RenderBullet(draw_list, bullet_bb.GetCenter(), GetColorU32(ImGuiCol_Text)); + } + + // 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; + 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; + } + 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); + +#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/parts/mdl/mdlpart.cpp b/parts/mdl/mdlpart.cpp index d94555f..6d0bee9 100644 --- a/parts/mdl/mdlpart.cpp +++ b/parts/mdl/mdlpart.cpp @@ -16,6 +16,7 @@ #include #include "filecache.h" +#include "imgui.h" #include "tiny_gltf.h" #ifndef USE_STANDALONE_WINDOW @@ -116,9 +117,18 @@ public: } void render() { + ImGui::SetCurrentContext(m_renderer->ctx); + + auto &io = ImGui::GetIO(); + io.DisplaySize = ImVec2(width(), height()); + + ImGui::NewFrame(); + if (part->requestUpdate) part->requestUpdate(); + ImGui::Render(); + glm::vec3 position( part->cameraDistance * sin(part->yaw), part->cameraDistance * part->pitch, diff --git a/renderer/CMakeLists.txt b/renderer/CMakeLists.txt index 6432105..1927df7 100644 --- a/renderer/CMakeLists.txt +++ b/renderer/CMakeLists.txt @@ -7,9 +7,9 @@ if(USE_STANDALONE_WINDOW) set(EXTRA_LIBRARIES SDL2::SDL2) endif() -add_library(renderer STATIC src/renderer.cpp ${EXTRA_SRC}) +add_library(renderer STATIC src/renderer.cpp src/imguipass.cpp src/imguipass.h ${EXTRA_SRC}) target_include_directories(renderer PUBLIC include) -target_link_libraries(renderer PUBLIC Qt6::Core Vulkan::Vulkan physis z glm::glm ${EXTRA_LIBRARIES}) +target_link_libraries(renderer PUBLIC Qt6::Core Vulkan::Vulkan physis z glm::glm imgui ${EXTRA_LIBRARIES}) target_compile_definitions(renderer PUBLIC GLM_FORCE_RADIANS GLM_FORCE_DEPTH_ZERO_TO_ONE) if(USE_STANDALONE_WINDOW) diff --git a/renderer/include/renderer.hpp b/renderer/include/renderer.hpp index eb35e3a..75c1666 100644 --- a/renderer/include/renderer.hpp +++ b/renderer/include/renderer.hpp @@ -51,6 +51,9 @@ struct RenderModel { VkDeviceMemory boneInfoMemory = VK_NULL_HANDLE; }; +class ImGuiPass; +struct ImGuiContext; + class Renderer { public: Renderer(); @@ -124,6 +127,10 @@ public: glm::mat4 view; + ImGuiContext *ctx = nullptr; + private: void createDummyTexture(); + + ImGuiPass *imGuiPass = nullptr; }; \ No newline at end of file diff --git a/renderer/shaders/compile_shaders.sh b/renderer/shaders/compile_shaders.sh index c50690b..95271ea 100755 --- a/renderer/shaders/compile_shaders.sh +++ b/renderer/shaders/compile_shaders.sh @@ -3,4 +3,6 @@ # SPDX-License-Identifier: CC0-1.0 glslc mesh.vert -o mesh.vert.spv && -glslc mesh.frag -o mesh.frag.spv \ No newline at end of file +glslc mesh.frag -o mesh.frag.spv && +glslc imgui.vert -o imgui.vert.spv && +glslc imgui.frag -o imgui.frag.spv \ No newline at end of file diff --git a/renderer/shaders/imgui.frag b/renderer/shaders/imgui.frag new file mode 100644 index 0000000..225a139 --- /dev/null +++ b/renderer/shaders/imgui.frag @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 Joshua Goins +// SPDX-License-Identifier: CC0-1.0 + +#version 460 core + +layout(location = 0) in vec2 inUV; +layout(location = 1) in vec4 inColor; + +layout(location = 0) out vec4 outColor; + +layout(binding = 0) uniform sampler2D boundSampler; + +void main() { + outColor = inColor * texture(boundSampler, inUV); +} diff --git a/renderer/shaders/imgui.frag.spv b/renderer/shaders/imgui.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..77c6e5fd68e6e723fe52c67dbf16475ade2fff59 GIT binary patch literal 740 zcmYk2O-lk{6os!JW2X6)r9}{0dudS-L~SBwGvGq1uuOx&_01KIx)m}bo`#jJs0gOx zC?T;gRD~mLj}TvZ z#}_4&copm#965G-JLY8lXRGvS7S9%HC#9Ip6D4cO(OymI@3U1BjK9RB;5IZ9q$ zx##L0yPQLG;OqrwcQ|z}1kRTC*EHkQ+um<$d-m^VFJak#NB<>hJ)x_5yc3+Adis_X zd2RIj4QD1a@)gCLu+77n5$#md*A%G--#uG?rb=}s@KLX^F`>~9&W;@BJ5;0|d*E*? zQVShFoSo5b_44@Gg~QB0QXItxsmy#JFfW{*ErEV;Y90&Jd($oa9RUye*GB8=pE~3o N)rI##`_&Y8!XLFrIA#C< literal 0 HcmV?d00001 diff --git a/renderer/shaders/imgui.vert b/renderer/shaders/imgui.vert new file mode 100644 index 0000000..b843732 --- /dev/null +++ b/renderer/shaders/imgui.vert @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2023 Joshua Goins +// SPDX-License-Identifier: CC0-1.0 + +#version 460 core + +layout(location = 0) in vec2 inPos; +layout(location = 1) in vec2 inUV; +layout(location = 2) in vec4 inColor; + +layout(push_constant) uniform PushConstants { + vec2 scale, translate; +} pushConstants; + +layout(location = 0) out vec2 outUV; +layout(location = 1) out vec4 outColor; + +void main() { + gl_Position = vec4(inPos * pushConstants.scale + pushConstants.translate, 0.0, 1.0); + outUV = inUV; + outColor = inColor; +} diff --git a/renderer/shaders/imgui.vert.spv b/renderer/shaders/imgui.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..0f0ee37a78b7d072b0a39d41d1cf0db311782540 GIT binary patch literal 1528 zcmZ9L+iMd+6voHgTB~iXwXK($rp9|U_@E+)ibPo-0*w^=x`brWfps@zC!#*-ga4g> zkS~JYZ+0gHr<|Viedju7HuYL}#h5eZoLM(LQ=TnT6Jx}+yp9GZgI?h#y~j@;5wT(F zK9HDAMR6LE&m)^*uS+gU?nv%R?n@p>>iVlm|5jqdteI#KN5{v};B7pfPUFITOOrUY zSrSj|EEzldB_VEgDb8l&bUs-QXewg4cT+Pn(Z@6%CbQFI=8~_1VoM$yUtU;e^GxiD zkLNC)x0yS!-_@C%(#Op5^;0{2V+%LR#>v9RCh*PEbm432p6r0beC)zzYUy#yT#_En zi%9y9-r zFf(){oTCb#l}*2W;WcUU;o7peZ>WI}=_u~JblA=F3iF-7(PzWQm38R@-jr~d>w+|W zn3;N8(#!x)Juu%ITw9o(;X{buk!D7C;=z1JaQOEsoA}WGnr!NU>3tyK1l;lH$1C!0 z%H}TI9elgOVLmW#M@*QPx1&CoeBKT}`A5?DsD~fSTf#lnx5FNIBMv!##5AQ}h^6PY zgnhgg%iS(ZxGy^mF;^=7mtx4dDj^R($2g0R(X71q>~l$9_8!db5vP621{;eyLl6i4Sw!lg$qCv0E@c>OPdPFMRA3{8ycy GN&W#LxOM&j literal 0 HcmV?d00001 diff --git a/renderer/src/imguipass.cpp b/renderer/src/imguipass.cpp new file mode 100644 index 0000000..f7d5021 --- /dev/null +++ b/renderer/src/imguipass.cpp @@ -0,0 +1,341 @@ +// SPDX-FileCopyrightText: 2023 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "imguipass.h" + +#include +#include +#include +#include + +#include "renderer.hpp" + +ImGuiPass::ImGuiPass(Renderer &renderer) + : renderer_(renderer) +{ + createDescriptorSetLayout(); + createPipeline(); + createFontImage(); +} + +ImGuiPass::~ImGuiPass() +{ + vkDestroySampler(renderer_.device, fontSampler_, nullptr); + vkDestroyImageView(renderer_.device, fontImageView_, nullptr); + vkFreeMemory(renderer_.device, fontMemory_, nullptr); + vkDestroyImage(renderer_.device, fontImage_, nullptr); + + vkDestroyPipeline(renderer_.device, pipeline_, nullptr); + vkDestroyPipelineLayout(renderer_.device, pipelineLayout_, nullptr); + + vkDestroyDescriptorSetLayout(renderer_.device, setLayout_, nullptr); +} + +void ImGuiPass::render(VkCommandBuffer commandBuffer) +{ + ImDrawData *drawData = ImGui::GetDrawData(); + if (drawData == nullptr) { + return; + } + + const size_t newVertexSize = drawData->TotalVtxCount * sizeof(ImDrawVert); + if (newVertexSize > vertexSize) { + createBuffer(vertexBuffer, vertexMemory, newVertexSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); + vertexSize = newVertexSize; + } + + const size_t newIndexSize = drawData->TotalIdxCount * sizeof(ImDrawIdx); + if (newIndexSize > indexSize) { + createBuffer(indexBuffer, indexMemory, newIndexSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); + indexSize = newIndexSize; + } + + if (vertexSize == 0 || indexSize == 0) + return; + + ImDrawVert *vertexData = nullptr; + ImDrawIdx *indexData = nullptr; + vkMapMemory(renderer_.device, vertexMemory, 0, vertexSize, 0, reinterpret_cast(&vertexData)); + vkMapMemory(renderer_.device, indexMemory, 0, indexSize, 0, reinterpret_cast(&indexData)); + + for (int i = 0; i < drawData->CmdListsCount; i++) { + const ImDrawList *cmd_list = drawData->CmdLists[i]; + + memcpy(vertexData, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); + vertexData += cmd_list->VtxBuffer.Size; + + memcpy(indexData, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + indexData += cmd_list->IdxBuffer.Size; + } + + vkUnmapMemory(renderer_.device, vertexMemory); + vkUnmapMemory(renderer_.device, indexMemory); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_); + + VkDeviceSize offset = 0; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer, &offset); + vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + float scale[2]; + scale[0] = 2.0f / drawData->DisplaySize.x; + scale[1] = 2.0f / drawData->DisplaySize.y; + + float translate[2]; + translate[0] = -1.0f - drawData->DisplayPos.x * scale[0]; + translate[1] = -1.0f - drawData->DisplayPos.y * scale[1]; + + vkCmdPushConstants(commandBuffer, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 0, sizeof(float) * 2, scale); + vkCmdPushConstants(commandBuffer, pipelineLayout_, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 2, sizeof(float) * 2, translate); + + int vertexOffset = 0, indexOffset = 0; + const ImVec2 displayPos = drawData->DisplayPos; + for (int n = 0; n < drawData->CmdListsCount; n++) { + const ImDrawList *cmd_list = drawData->CmdLists[n]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { + const ImDrawCmd *pcmd = &cmd_list->CmdBuffer[cmd_i]; + + if (descriptorSets_.count((VkImageView)pcmd->TextureId)) { + vkCmdBindDescriptorSets(commandBuffer, + VK_PIPELINE_BIND_POINT_GRAPHICS, + pipelineLayout_, + 0, + 1, + &descriptorSets_[(VkImageView)pcmd->TextureId], + 0, + nullptr); + } else { + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = renderer_.descriptorPool; + allocInfo.descriptorSetCount = 1; + allocInfo.pSetLayouts = &setLayout_; + + VkDescriptorSet set = nullptr; + vkAllocateDescriptorSets(renderer_.device, &allocInfo, &set); + + VkDescriptorImageInfo imageInfo = {}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo.imageView = (VkImageView)pcmd->TextureId; + imageInfo.sampler = fontSampler_; + + VkWriteDescriptorSet descriptorWrite = {}; + descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrite.descriptorCount = 1; + descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrite.dstSet = set; + descriptorWrite.pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(renderer_.device, 1, &descriptorWrite, 0, nullptr); + + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_, 0, 1, &set, 0, nullptr); + + descriptorSets_[(VkImageView)pcmd->TextureId] = set; + } + + if (pcmd->UserCallback) { + pcmd->UserCallback(cmd_list, pcmd); + } else { + VkRect2D scissor; + scissor.offset.x = (int32_t)(pcmd->ClipRect.x - displayPos.x) > 0 ? (int32_t)(pcmd->ClipRect.x - displayPos.x) : 0; + scissor.offset.y = (int32_t)(pcmd->ClipRect.y - displayPos.y) > 0 ? (int32_t)(pcmd->ClipRect.y - displayPos.y) : 0; + scissor.extent.width = (uint32_t)(pcmd->ClipRect.z - pcmd->ClipRect.x); + scissor.extent.height = (uint32_t)(pcmd->ClipRect.w - pcmd->ClipRect.y + 1); + + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + vkCmdDrawIndexed(commandBuffer, pcmd->ElemCount, 1, indexOffset, vertexOffset, 0); + } + + indexOffset += pcmd->ElemCount; + } + + vertexOffset += cmd_list->VtxBuffer.Size; + } +} + +void ImGuiPass::createDescriptorSetLayout() +{ + VkDescriptorSetLayoutBinding samplerBinding = {}; + samplerBinding.descriptorCount = 1; + samplerBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + samplerBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + VkDescriptorSetLayoutCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + createInfo.bindingCount = 1; + createInfo.pBindings = &samplerBinding; + + vkCreateDescriptorSetLayout(renderer_.device, &createInfo, nullptr, &setLayout_); +} + +void ImGuiPass::createPipeline() +{ + VkShaderModule vertShaderModule = renderer_.loadShaderFromDisk("imgui.vert.spv"); + VkShaderModule fragShaderModule = renderer_.loadShaderFromDisk("imgui.frag.spv"); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo = {}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + const std::array shaderStages = {vertShaderStageInfo, fragShaderStageInfo}; + + VkVertexInputBindingDescription vertexBindingDescription = {}; + vertexBindingDescription.stride = sizeof(ImDrawVert); + vertexBindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + VkVertexInputAttributeDescription positionAttribute = {}; + positionAttribute.format = VK_FORMAT_R32G32_SFLOAT; + positionAttribute.offset = offsetof(ImDrawVert, pos); + + VkVertexInputAttributeDescription uvAttribute = {}; + uvAttribute.location = 1; + uvAttribute.format = VK_FORMAT_R32G32_SFLOAT; + uvAttribute.offset = offsetof(ImDrawVert, uv); + + VkVertexInputAttributeDescription colorAttribute = {}; + colorAttribute.location = 2; + colorAttribute.format = VK_FORMAT_R8G8B8A8_UNORM; + colorAttribute.offset = offsetof(ImDrawVert, col); + + const std::array attributes = {positionAttribute, uvAttribute, colorAttribute}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 1; + vertexInputInfo.pVertexBindingDescriptions = &vertexBindingDescription; + vertexInputInfo.vertexAttributeDescriptionCount = attributes.size(); + vertexInputInfo.pVertexAttributeDescriptions = attributes.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + + VkPipelineViewportStateCreateInfo viewportState = {}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.cullMode = VK_CULL_MODE_NONE; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rasterizer.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_TRUE; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + + VkPipelineColorBlendStateCreateInfo colorBlending = {}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + + const std::array dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; + + VkPipelineDynamicStateCreateInfo dynamicState = {}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = dynamicStates.size(); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPushConstantRange pushConstant = {}; + pushConstant.size = sizeof(glm::vec4); + pushConstant.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &setLayout_; + pipelineLayoutInfo.pushConstantRangeCount = 1; + pipelineLayoutInfo.pPushConstantRanges = &pushConstant; + + vkCreatePipelineLayout(renderer_.device, &pipelineLayoutInfo, nullptr, &pipelineLayout_); + + VkPipelineDepthStencilStateCreateInfo depthStencilStateCreateInfo = {}; + depthStencilStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + + VkGraphicsPipelineCreateInfo pipelineInfo = {}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = shaderStages.size(); + pipelineInfo.pStages = shaderStages.data(); + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout_; + pipelineInfo.pDepthStencilState = &depthStencilStateCreateInfo; + pipelineInfo.renderPass = renderer_.renderPass; + + vkCreateGraphicsPipelines(renderer_.device, nullptr, 1, &pipelineInfo, nullptr, &pipeline_); + + vkDestroyShaderModule(renderer_.device, fragShaderModule, nullptr); + vkDestroyShaderModule(renderer_.device, vertShaderModule, nullptr); +} + +void ImGuiPass::createFontImage() +{ + ImGuiIO &io = ImGui::GetIO(); + + unsigned char *pixels = nullptr; + int width = 0, height = 0; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + + qInfo() << "Uploading imgui font size" << width << "x" << height; + + auto texture = renderer_.addTexture(width, height, pixels, width * height * 4); + fontImageView_ = texture.view; + fontSampler_ = texture.sampler; + + io.Fonts->SetTexID(static_cast(fontImageView_)); +} + +void ImGuiPass::createBuffer(VkBuffer &buffer, VkDeviceMemory &memory, VkDeviceSize size, VkBufferUsageFlagBits bufferUsage) +{ + if (buffer != nullptr) + vkDestroyBuffer(renderer_.device, buffer, nullptr); + + if (memory != nullptr) + vkFreeMemory(renderer_.device, memory, nullptr); + + VkBufferCreateInfo bufferInfo = {}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.size = size; + bufferInfo.usage = bufferUsage; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + vkCreateBuffer(renderer_.device, &bufferInfo, nullptr, &buffer); + + VkMemoryRequirements memRequirements = {}; + vkGetBufferMemoryRequirements(renderer_.device, buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = + renderer_.findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + vkAllocateMemory(renderer_.device, &allocInfo, nullptr, &memory); + vkBindBufferMemory(renderer_.device, buffer, memory, 0); +} diff --git a/renderer/src/imguipass.h b/renderer/src/imguipass.h new file mode 100644 index 0000000..6ae369b --- /dev/null +++ b/renderer/src/imguipass.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +class Renderer; +struct RenderTarget; + +class ImGuiPass +{ +public: + ImGuiPass(Renderer &renderer); + ~ImGuiPass(); + + void render(VkCommandBuffer commandBuffer); + +private: + void createDescriptorSetLayout(); + void createPipeline(); + void createFontImage(); + void createBuffer(VkBuffer &buffer, VkDeviceMemory &memory, VkDeviceSize size, VkBufferUsageFlagBits bufferUsage); + + VkDescriptorSetLayout setLayout_ = nullptr; + + VkPipelineLayout pipelineLayout_ = nullptr; + VkPipeline pipeline_ = nullptr; + + VkImage fontImage_ = nullptr; + VkDeviceMemory fontMemory_ = nullptr; + VkImageView fontImageView_ = nullptr; + VkSampler fontSampler_ = nullptr; + + VkBuffer vertexBuffer = VK_NULL_HANDLE, indexBuffer = VK_NULL_HANDLE; + VkDeviceMemory vertexMemory = VK_NULL_HANDLE, indexMemory = VK_NULL_HANDLE; + size_t vertexSize = 0, indexSize = 0; + + std::map descriptorSets_ = {}; + + Renderer &renderer_; +}; diff --git a/renderer/src/renderer.cpp b/renderer/src/renderer.cpp index ffe4609..cba4507 100644 --- a/renderer/src/renderer.cpp +++ b/renderer/src/renderer.cpp @@ -12,6 +12,9 @@ #include #include +#include "imgui.h" +#include "imguipass.h" + VkResult CreateDebugUtilsMessengerEXT( VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, @@ -39,6 +42,11 @@ DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, } Renderer::Renderer() { + ctx = ImGui::CreateContext(); + ImGui::SetCurrentContext(ctx); + + ImGui::StyleColorsDark(); + VkApplicationInfo applicationInfo = {}; std::vector instanceExtensions = {"VK_EXT_debug_utils"}; @@ -422,6 +430,9 @@ bool Renderer::initSwapchain(VkSurfaceKHR surface, int width, int height) { vkCreateFence(device, &fenceCreateInfo, nullptr, &inFlightFences[i]); } + ImGui::SetCurrentContext(ctx); + imGuiPass = new ImGuiPass(*this); + return true; } @@ -538,8 +549,12 @@ void Renderer::render(std::vector models) { } } - vkCmdEndRenderPass(commandBuffer); + if (imGuiPass != nullptr) { + ImGui::SetCurrentContext(ctx); + imGuiPass->render(commandBuffer); + } + vkCmdEndRenderPass(commandBuffer); vkEndCommandBuffer(commandBuffer); VkSubmitInfo submitInfo = {}; @@ -1191,7 +1206,7 @@ VkDescriptorSet Renderer::createDescriptorFor(const RenderModel& model, const Re vkAllocateDescriptorSets(device, &allocateInfo, &set); if (set == VK_NULL_HANDLE) { - qFatal("Failed to create descriptor set!"); + // qFatal("Failed to create descriptor set!"); return VK_NULL_HANDLE; }