diff --git a/README.md b/README.md index 0ce9b1a..e7fb235 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,9 @@ Your customized AI characters on commodity hardware. This project aims to be a framework to enable the creation of AI characters that can be used in games, simulations, virtual assistants, and other applications without depending on specific platforms, cloud services, or specialized hardware. -**Some of the applications you can build with CustomChar:** +![](docs/customchar.png) + +**Some of applications you can build with CustomChar:** - Game characters that can talk to you and interact with you. - Your customized virtual assistant. Think about a [JARVIS](https://en.wikipedia.org/wiki/J.A.R.V.I.S.) version on your computer. diff --git a/customchar-ui/.gitignore b/customchar-ui/.gitignore new file mode 100644 index 0000000..8c8684b --- /dev/null +++ b/customchar-ui/.gitignore @@ -0,0 +1,6 @@ +build +.DS_Store +.vscode +.idea +imgui.ini +*.o diff --git a/customchar-ui/Makefile b/customchar-ui/Makefile new file mode 100644 index 0000000..c6341e0 --- /dev/null +++ b/customchar-ui/Makefile @@ -0,0 +1,107 @@ +# +# Cross Platform Makefile +# Compatible with MSYS2/MINGW, Ubuntu 14.04.1 and Mac OS X +# +# You will need GLFW (http://www.glfw.org): +# Linux: +# apt-get install libglfw-dev +# Mac OS X: +# brew install glfw +# MSYS2: +# pacman -S --noconfirm --needed mingw-w64-x86_64-toolchain mingw-w64-x86_64-glfw +# + +# CXX = g++ +#CXX = clang++ + +EXE_BASE = customchar-ui +IMGUI_DIR = ./libs/imgui +SOURCES = main.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/backends/imgui_impl_glfw.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl3.cpp +SOURCES += chat_message.cpp chat_history.cpp +OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) +UNAME_S := $(shell uname -s) +LINUX_GL_LIBS = -lGL + +CXXFLAGS = -std=c++2a -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends -I./libs/imgui/include -pthread +CXXFLAGS += -g -Wall -Wformat +LIBS = + +##--------------------------------------------------------------------- +## OPENGL ES +##--------------------------------------------------------------------- + +## This assumes a GL ES library available in the system, e.g. libGLESv2.so +# CXXFLAGS += -DIMGUI_IMPL_OPENGL_ES2 +# LINUX_GL_LIBS = -lGLESv2 + +##--------------------------------------------------------------------- +## BUILD FLAGS PER PLATFORM +##--------------------------------------------------------------------- + +ifeq ($(UNAME_S), Linux) #LINUX + CXX = g++-11 + ECHO_MESSAGE = "Linux" + LIBS += $(LINUX_GL_LIBS) `pkg-config --static --libs glfw3` + + CXXFLAGS += `pkg-config --cflags glfw3` + CFLAGS = $(CXXFLAGS) + + EXE = $(EXE_BASE) +endif + +ifeq ($(UNAME_S), Darwin) #APPLE + ECHO_MESSAGE = "Mac OS X" + LIBS += -framework OpenGL -framework Cocoa -framework IOKit -framework CoreVideo + LIBS += -L/usr/local/lib -L/opt/local/lib -L/opt/homebrew/lib + #LIBS += -lglfw3 + LIBS += -lglfw + + CXXFLAGS += -I/usr/local/include -I/opt/local/include -I/opt/homebrew/include + CFLAGS = $(CXXFLAGS) + + EXE = $(EXE_BASE).app +endif + +ifeq ($(OS), Windows_NT) + ECHO_MESSAGE = "MinGW" + LIBS += -lglfw3 -lgdi32 -lopengl32 -limm32 + + CXXFLAGS += `pkg-config --cflags glfw3` + CFLAGS = $(CXXFLAGS) + + EXE = $(EXE_BASE).exe +endif + +##--------------------------------------------------------------------- +## BUILD RULES +##--------------------------------------------------------------------- + +%.o:%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o:src/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/backends/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +all: $(EXE) +# Probably a better way to do this, but this works for now + rm -rf build + mkdir -p build + find . -name "*.o" -exec mv {} ./build \; + mv $(EXE) ./build +# Success message + @echo Build complete for $(ECHO_MESSAGE) + +$(EXE): $(OBJS) + $(CXX) -o $@ $^ $(CXXFLAGS) $(LIBS) + +clean: + rm -f $(EXE) $(OBJS) + rm -rf build diff --git a/customchar-ui/README.md b/customchar-ui/README.md new file mode 100644 index 0000000..b99fca6 --- /dev/null +++ b/customchar-ui/README.md @@ -0,0 +1,10 @@ +# CustomChar UI + +C++ UI for CustomChar using Dear ImGui. + +- [ ] Basic chat window. +- [ ] Format messages. +- [ ] Animation when talking. +- [ ] Tranparent window. +- [ ] Custom font. +- [ ] Dynamic window size. diff --git a/customchar-ui/chat_history.cpp b/customchar-ui/chat_history.cpp new file mode 100644 index 0000000..3ac2474 --- /dev/null +++ b/customchar-ui/chat_history.cpp @@ -0,0 +1,23 @@ +#include "chat_history.h" + +ChatHistory::ChatHistory() { old_size = 0; } + +ChatHistory::~ChatHistory() {} + +void ChatHistory::add_message(std::string message, std::string sender) { + ChatMessage new_message; + new_message.set_time(); + new_message.set_message(message, sender); + hist_vec.push_back(new_message); +} + +std::vector ChatHistory::get_char_history() { return hist_vec; } + +bool ChatHistory::has_new_message() { + if (old_size != hist_vec.size()) { + old_size = hist_vec.size(); + return true; + } + + return false; +} diff --git a/customchar-ui/chat_history.h b/customchar-ui/chat_history.h new file mode 100644 index 0000000..3a54112 --- /dev/null +++ b/customchar-ui/chat_history.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include "chat_message.h" + +class ChatHistory { + public: + ChatHistory(); + ~ChatHistory(); + + void add_message(std::string, std::string); + std::vector get_char_history(); + bool has_new_message(); + + private: + std::vector hist_vec; + std::vector::size_type old_size; +}; diff --git a/customchar-ui/chat_message.cpp b/customchar-ui/chat_message.cpp new file mode 100644 index 0000000..32391d3 --- /dev/null +++ b/customchar-ui/chat_message.cpp @@ -0,0 +1,35 @@ +#include "chat_message.h" + +#include + +ChatMessage::ChatMessage() { + message = ""; + sender = "Unknown"; +} + +ChatMessage::ChatMessage(std::string in_message, std::string in_sender) { + set_message(in_message, in_sender); +} + +ChatMessage::~ChatMessage() {} + +void ChatMessage::set_message(std::string input) { message = input; } + +void ChatMessage::set_message(std::string in_message, std::string in_sender) { + message = in_message; + sender = in_sender; +} + +void ChatMessage::set_sender(std::string input) { sender = input; } + +void ChatMessage::set_time() { + time_t time_now = time(NULL); + char* ct = ctime(&time_now); + timestamp = ct; +} + +std::string ChatMessage::get_message() { return message; } + +std::string ChatMessage::get_sender() { return sender; } + +std::string ChatMessage::get_time() { return timestamp; } diff --git a/customchar-ui/chat_message.h b/customchar-ui/chat_message.h new file mode 100644 index 0000000..0ea0d38 --- /dev/null +++ b/customchar-ui/chat_message.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +class ChatMessage { + public: + ChatMessage(); + ChatMessage(std::string, std::string); + ~ChatMessage(); + + void set_message(std::string); + void set_message(std::string, std::string); + void set_sender(std::string); + void set_time(); + + std::string get_message(); + std::string get_sender(); + std::string get_time(); + + private: + std::string message; + std::string sender; + std::string timestamp; +}; diff --git a/customchar-ui/libs/imgui/backends/imgui_impl_glfw.cpp b/customchar-ui/libs/imgui/backends/imgui_impl_glfw.cpp new file mode 100644 index 0000000..b9b7fab --- /dev/null +++ b/customchar-ui/libs/imgui/backends/imgui_impl_glfw.cpp @@ -0,0 +1,883 @@ +// dear imgui: Platform Backend for GLFW +// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan, WebGPU..) +// (Info: GLFW is a cross-platform general purpose library for handling windows, +// inputs, OpenGL/Vulkan graphics context creation, etc.) (Requires: GLFW 3.1+) + +// Implemented features: +// [X] Platform: Clipboard support. +// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() +// function. Pass ImGuiKey values to all key functions e.g. +// ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy GLFW_KEY_* values will also be +// supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set] [X] Platform: Gamepad +// support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. +// [X] Platform: Mouse cursor shape and visibility. Disable with +// 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange' (note: the resizing +// cursors requires GLFW 3.4+). + +// You can use unmodified imgui_impl_* files in your project. See examples/ +// folder for examples of using this. Prefer including the entire imgui/ +// repository into your project (either as a copy or as a submodule), and only +// build the backends you need. If you are new to Dear ImGui, read documentation +// from the docs/ folder + read the top of imgui.cpp. Read online: +// https://github.com/ocornut/imgui/tree/master/docs + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2022-04-30: Inputs: Fixed ImGui_ImplGlfw_TranslateUntranslatedKey() for +// lower case letters on OSX. 2022-03-23: Inputs: Fixed a regression in 1.87 +// which resulted in keyboard modifiers events being reported incorrectly on +// Linux/X11. 2022-02-07: Added +// ImGui_ImplGlfw_InstallCallbacks()/ImGui_ImplGlfw_RestoreCallbacks() helpers +// to facilitate user installing callbacks after initializing backend. +// 2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two +// weeks ago)with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the +// confusion. 2021-01-20: Inputs: calling new io.AddKeyAnalogEvent() for +// gamepad support, instead of writing directly to io.NavInputs[]. 2022-01-17: +// Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), +// io.AddMouseWheelEvent() API (1.87+). 2022-01-17: Inputs: always update key +// mods next and before key event (not in NewFrame) to fix input queue with +// very low framerates. 2022-01-12: *BREAKING CHANGE*: Now using +// glfwSetCursorPosCallback(). If you called ImGui_ImplGlfw_InitXXX() with +// install_callbacks = false, you MUST install glfwSetCursorPosCallback() and +// forward it to the backend via ImGui_ImplGlfw_CursorPosCallback(). +// 2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + +// io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range. +// 2022-01-05: Inputs: Converting GLFW untranslated keycodes back to translated +// keycodes (in the ImGui_ImplGlfw_KeyCallback() function) in order to match +// the behavior of every other backend, and facilitate the use of GLFW with +// lettered-shortcuts API. 2021-08-17: *BREAKING CHANGE*: Now using +// glfwSetWindowFocusCallback() to calling io.AddFocusEvent(). If you called +// ImGui_ImplGlfw_InitXXX() with install_callbacks = false, you MUST install +// glfwSetWindowFocusCallback() and forward it to the backend via +// ImGui_ImplGlfw_WindowFocusCallback(). 2021-07-29: *BREAKING CHANGE*: Now +// using glfwSetCursorEnterCallback(). MousePos is correctly reported when the +// host platform window is hovered but not focused. If you called +// ImGui_ImplGlfw_InitXXX() with install_callbacks = false, you MUST install +// glfwSetWindowFocusCallback() callback and forward it to the backend via +// ImGui_ImplGlfw_CursorEnterCallback(). 2021-06-29: Reorganized backend to +// pull data from a single structure to facilitate usage with multiple-contexts +// (all g_XXXX access changed to bd->XXXX). 2020-01-17: Inputs: Disable error +// callback while assigning mouse cursors because some X11 setup don't have +// them and it generates errors. 2019-12-05: Inputs: Added support for new +// mouse cursors added in GLFW 3.4+ (resizing cursors, not allowed cursor). +// 2019-10-18: Misc: Previously installed user callbacks are now restored on +// shutdown. 2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter. +// 2019-05-11: Inputs: Don't filter value from character callback before +// calling AddInputCharacter(). 2019-03-12: Misc: Preserve +// DisplayFramebufferScale when main window is minimized. 2018-11-30: Misc: +// Setting up io.BackendPlatformName so it can be displayed in the About +// Window. 2018-11-07: Inputs: When installing our GLFW callbacks, we save +// user's previously installed ones - if any - and chain call them. 2018-08-01: +// Inputs: Workaround for Emscripten which doesn't seem to handle focus related +// calls. 2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand +// cursor. 2018-06-08: Misc: Extracted imgui_impl_glfw.cpp/.h away from the old +// combined GLFW+OpenGL/Vulkan examples. 2018-03-20: Misc: Setup +// io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor +// ImGuiConfigFlags_NoMouseCursorChange flag. 2018-02-20: Inputs: Added support +// for mouse cursors (ImGui::GetMouseCursor() value, passed to +// glfwSetCursor()). 2018-02-06: Misc: Removed call to ImGui::Shutdown() which +// is not available from 1.60 WIP, user needs to call +// CreateContext/DestroyContext themselves. 2018-02-06: Inputs: Added mapping +// for ImGuiKey_Space. 2018-01-25: Inputs: Added gamepad support if +// ImGuiConfigFlags_NavEnableGamepad is set. 2018-01-25: Inputs: Honoring the +// io.WantSetMousePos by repositioning the mouse (when using navigation and +// ImGuiConfigFlags_NavMoveMouse is set). 2018-01-20: Inputs: Added Horizontal +// Mouse Wheel support. 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert. +// 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is +// unavailable/missing (instead of -1,-1). 2016-10-15: Misc: Added a void* +// user_data parameter to Clipboard function handlers. + +#include "imgui_impl_glfw.h" +#include "imgui.h" + +// Clang warnings with -Weverything +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored \ + "-Wold-style-cast" // warning: use of old-style cast +#pragma clang diagnostic ignored \ + "-Wsign-conversion" // warning: implicit conversion changes signedness +#if __has_warning("-Wzero-as-null-pointer-constant") +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif +#endif + +// GLFW +#include +#ifdef _WIN32 +#undef APIENTRY +#define GLFW_EXPOSE_NATIVE_WIN32 +#include // for glfwGetWin32Window +#endif +#ifdef GLFW_RESIZE_NESW_CURSOR // Let's be nice to people who pulled GLFW + // between 2019-04-16 (3.4 define) and + // 2019-11-29 (cursors defines) // FIXME: Remove + // when GLFW 3.4 is released? +#define GLFW_HAS_NEW_CURSORS \ + (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= \ + 3400) // 3.4+ GLFW_RESIZE_ALL_CURSOR, GLFW_RESIZE_NESW_CURSOR, + // GLFW_RESIZE_NWSE_CURSOR, GLFW_NOT_ALLOWED_CURSOR +#else +#define GLFW_HAS_NEW_CURSORS (0) +#endif +#define GLFW_HAS_GAMEPAD_API \ + (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= \ + 3300) // 3.3+ glfwGetGamepadState() new api +#define GLFW_HAS_GET_KEY_NAME \ + (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= \ + 3200) // 3.2+ glfwGetKeyName() + +// GLFW data +enum GlfwClientApi { + GlfwClientApi_Unknown, + GlfwClientApi_OpenGL, + GlfwClientApi_Vulkan +}; + +struct ImGui_ImplGlfw_Data { + GLFWwindow* Window; + GlfwClientApi ClientApi; + double Time; + GLFWwindow* MouseWindow; + GLFWcursor* MouseCursors[ImGuiMouseCursor_COUNT]; + ImVec2 LastValidMousePos; + bool InstalledCallbacks; + + // Chain GLFW callbacks: our callbacks will call the user's previously + // installed callbacks, if any. + GLFWwindowfocusfun PrevUserCallbackWindowFocus; + GLFWcursorposfun PrevUserCallbackCursorPos; + GLFWcursorenterfun PrevUserCallbackCursorEnter; + GLFWmousebuttonfun PrevUserCallbackMousebutton; + GLFWscrollfun PrevUserCallbackScroll; + GLFWkeyfun PrevUserCallbackKey; + GLFWcharfun PrevUserCallbackChar; + GLFWmonitorfun PrevUserCallbackMonitor; + + ImGui_ImplGlfw_Data() { memset((void*)this, 0, sizeof(*this)); } +}; + +// Backend data stored in io.BackendPlatformUserData to allow support for +// multiple Dear ImGui contexts It is STRONGLY preferred that you use docking +// branch with multi-viewports (== single Dear ImGui context + multiple windows) +// instead of multiple Dear ImGui contexts. +// FIXME: multi-context support is not well tested and probably dysfunctional in +// this backend. +// - Because glfwPollEvents() process all windows and some events may be called +// outside of it, you will need to register your own callbacks +// (passing install_callbacks=false in ImGui_ImplGlfw_InitXXX functions), set +// the current dear imgui context and then call our callbacks. +// - Otherwise we may need to store a GLFWWindow* -> ImGuiContext* map and +// handle this in the backend, adding a little bit of extra complexity to it. +// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled +// when using multi-context. +static ImGui_ImplGlfw_Data* ImGui_ImplGlfw_GetBackendData() { + return ImGui::GetCurrentContext() + ? (ImGui_ImplGlfw_Data*)ImGui::GetIO().BackendPlatformUserData + : NULL; +} + +// Functions +static const char* ImGui_ImplGlfw_GetClipboardText(void* user_data) { + return glfwGetClipboardString((GLFWwindow*)user_data); +} + +static void ImGui_ImplGlfw_SetClipboardText(void* user_data, const char* text) { + glfwSetClipboardString((GLFWwindow*)user_data, text); +} + +static ImGuiKey ImGui_ImplGlfw_KeyToImGuiKey(int key) { + switch (key) { + case GLFW_KEY_TAB: + return ImGuiKey_Tab; + case GLFW_KEY_LEFT: + return ImGuiKey_LeftArrow; + case GLFW_KEY_RIGHT: + return ImGuiKey_RightArrow; + case GLFW_KEY_UP: + return ImGuiKey_UpArrow; + case GLFW_KEY_DOWN: + return ImGuiKey_DownArrow; + case GLFW_KEY_PAGE_UP: + return ImGuiKey_PageUp; + case GLFW_KEY_PAGE_DOWN: + return ImGuiKey_PageDown; + case GLFW_KEY_HOME: + return ImGuiKey_Home; + case GLFW_KEY_END: + return ImGuiKey_End; + case GLFW_KEY_INSERT: + return ImGuiKey_Insert; + case GLFW_KEY_DELETE: + return ImGuiKey_Delete; + case GLFW_KEY_BACKSPACE: + return ImGuiKey_Backspace; + case GLFW_KEY_SPACE: + return ImGuiKey_Space; + case GLFW_KEY_ENTER: + return ImGuiKey_Enter; + case GLFW_KEY_ESCAPE: + return ImGuiKey_Escape; + case GLFW_KEY_APOSTROPHE: + return ImGuiKey_Apostrophe; + case GLFW_KEY_COMMA: + return ImGuiKey_Comma; + case GLFW_KEY_MINUS: + return ImGuiKey_Minus; + case GLFW_KEY_PERIOD: + return ImGuiKey_Period; + case GLFW_KEY_SLASH: + return ImGuiKey_Slash; + case GLFW_KEY_SEMICOLON: + return ImGuiKey_Semicolon; + case GLFW_KEY_EQUAL: + return ImGuiKey_Equal; + case GLFW_KEY_LEFT_BRACKET: + return ImGuiKey_LeftBracket; + case GLFW_KEY_BACKSLASH: + return ImGuiKey_Backslash; + case GLFW_KEY_RIGHT_BRACKET: + return ImGuiKey_RightBracket; + case GLFW_KEY_GRAVE_ACCENT: + return ImGuiKey_GraveAccent; + case GLFW_KEY_CAPS_LOCK: + return ImGuiKey_CapsLock; + case GLFW_KEY_SCROLL_LOCK: + return ImGuiKey_ScrollLock; + case GLFW_KEY_NUM_LOCK: + return ImGuiKey_NumLock; + case GLFW_KEY_PRINT_SCREEN: + return ImGuiKey_PrintScreen; + case GLFW_KEY_PAUSE: + return ImGuiKey_Pause; + case GLFW_KEY_KP_0: + return ImGuiKey_Keypad0; + case GLFW_KEY_KP_1: + return ImGuiKey_Keypad1; + case GLFW_KEY_KP_2: + return ImGuiKey_Keypad2; + case GLFW_KEY_KP_3: + return ImGuiKey_Keypad3; + case GLFW_KEY_KP_4: + return ImGuiKey_Keypad4; + case GLFW_KEY_KP_5: + return ImGuiKey_Keypad5; + case GLFW_KEY_KP_6: + return ImGuiKey_Keypad6; + case GLFW_KEY_KP_7: + return ImGuiKey_Keypad7; + case GLFW_KEY_KP_8: + return ImGuiKey_Keypad8; + case GLFW_KEY_KP_9: + return ImGuiKey_Keypad9; + case GLFW_KEY_KP_DECIMAL: + return ImGuiKey_KeypadDecimal; + case GLFW_KEY_KP_DIVIDE: + return ImGuiKey_KeypadDivide; + case GLFW_KEY_KP_MULTIPLY: + return ImGuiKey_KeypadMultiply; + case GLFW_KEY_KP_SUBTRACT: + return ImGuiKey_KeypadSubtract; + case GLFW_KEY_KP_ADD: + return ImGuiKey_KeypadAdd; + case GLFW_KEY_KP_ENTER: + return ImGuiKey_KeypadEnter; + case GLFW_KEY_KP_EQUAL: + return ImGuiKey_KeypadEqual; + case GLFW_KEY_LEFT_SHIFT: + return ImGuiKey_LeftShift; + case GLFW_KEY_LEFT_CONTROL: + return ImGuiKey_LeftCtrl; + case GLFW_KEY_LEFT_ALT: + return ImGuiKey_LeftAlt; + case GLFW_KEY_LEFT_SUPER: + return ImGuiKey_LeftSuper; + case GLFW_KEY_RIGHT_SHIFT: + return ImGuiKey_RightShift; + case GLFW_KEY_RIGHT_CONTROL: + return ImGuiKey_RightCtrl; + case GLFW_KEY_RIGHT_ALT: + return ImGuiKey_RightAlt; + case GLFW_KEY_RIGHT_SUPER: + return ImGuiKey_RightSuper; + case GLFW_KEY_MENU: + return ImGuiKey_Menu; + case GLFW_KEY_0: + return ImGuiKey_0; + case GLFW_KEY_1: + return ImGuiKey_1; + case GLFW_KEY_2: + return ImGuiKey_2; + case GLFW_KEY_3: + return ImGuiKey_3; + case GLFW_KEY_4: + return ImGuiKey_4; + case GLFW_KEY_5: + return ImGuiKey_5; + case GLFW_KEY_6: + return ImGuiKey_6; + case GLFW_KEY_7: + return ImGuiKey_7; + case GLFW_KEY_8: + return ImGuiKey_8; + case GLFW_KEY_9: + return ImGuiKey_9; + case GLFW_KEY_A: + return ImGuiKey_A; + case GLFW_KEY_B: + return ImGuiKey_B; + case GLFW_KEY_C: + return ImGuiKey_C; + case GLFW_KEY_D: + return ImGuiKey_D; + case GLFW_KEY_E: + return ImGuiKey_E; + case GLFW_KEY_F: + return ImGuiKey_F; + case GLFW_KEY_G: + return ImGuiKey_G; + case GLFW_KEY_H: + return ImGuiKey_H; + case GLFW_KEY_I: + return ImGuiKey_I; + case GLFW_KEY_J: + return ImGuiKey_J; + case GLFW_KEY_K: + return ImGuiKey_K; + case GLFW_KEY_L: + return ImGuiKey_L; + case GLFW_KEY_M: + return ImGuiKey_M; + case GLFW_KEY_N: + return ImGuiKey_N; + case GLFW_KEY_O: + return ImGuiKey_O; + case GLFW_KEY_P: + return ImGuiKey_P; + case GLFW_KEY_Q: + return ImGuiKey_Q; + case GLFW_KEY_R: + return ImGuiKey_R; + case GLFW_KEY_S: + return ImGuiKey_S; + case GLFW_KEY_T: + return ImGuiKey_T; + case GLFW_KEY_U: + return ImGuiKey_U; + case GLFW_KEY_V: + return ImGuiKey_V; + case GLFW_KEY_W: + return ImGuiKey_W; + case GLFW_KEY_X: + return ImGuiKey_X; + case GLFW_KEY_Y: + return ImGuiKey_Y; + case GLFW_KEY_Z: + return ImGuiKey_Z; + case GLFW_KEY_F1: + return ImGuiKey_F1; + case GLFW_KEY_F2: + return ImGuiKey_F2; + case GLFW_KEY_F3: + return ImGuiKey_F3; + case GLFW_KEY_F4: + return ImGuiKey_F4; + case GLFW_KEY_F5: + return ImGuiKey_F5; + case GLFW_KEY_F6: + return ImGuiKey_F6; + case GLFW_KEY_F7: + return ImGuiKey_F7; + case GLFW_KEY_F8: + return ImGuiKey_F8; + case GLFW_KEY_F9: + return ImGuiKey_F9; + case GLFW_KEY_F10: + return ImGuiKey_F10; + case GLFW_KEY_F11: + return ImGuiKey_F11; + case GLFW_KEY_F12: + return ImGuiKey_F12; + default: + return ImGuiKey_None; + } +} + +static int ImGui_ImplGlfw_KeyToModifier(int key) { + if (key == GLFW_KEY_LEFT_CONTROL || key == GLFW_KEY_RIGHT_CONTROL) + return GLFW_MOD_CONTROL; + if (key == GLFW_KEY_LEFT_SHIFT || key == GLFW_KEY_RIGHT_SHIFT) + return GLFW_MOD_SHIFT; + if (key == GLFW_KEY_LEFT_ALT || key == GLFW_KEY_RIGHT_ALT) + return GLFW_MOD_ALT; + if (key == GLFW_KEY_LEFT_SUPER || key == GLFW_KEY_RIGHT_SUPER) + return GLFW_MOD_SUPER; + return 0; +} + +static void ImGui_ImplGlfw_UpdateKeyModifiers(int mods) { + ImGuiIO& io = ImGui::GetIO(); + io.AddKeyEvent(ImGuiKey_ModCtrl, (mods & GLFW_MOD_CONTROL) != 0); + io.AddKeyEvent(ImGuiKey_ModShift, (mods & GLFW_MOD_SHIFT) != 0); + io.AddKeyEvent(ImGuiKey_ModAlt, (mods & GLFW_MOD_ALT) != 0); + io.AddKeyEvent(ImGuiKey_ModSuper, (mods & GLFW_MOD_SUPER) != 0); +} + +void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, + int action, int mods) { + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + if (bd->PrevUserCallbackMousebutton != NULL && window == bd->Window) + bd->PrevUserCallbackMousebutton(window, button, action, mods); + + ImGui_ImplGlfw_UpdateKeyModifiers(mods); + + ImGuiIO& io = ImGui::GetIO(); + if (button >= 0 && button < ImGuiMouseButton_COUNT) + io.AddMouseButtonEvent(button, action == GLFW_PRESS); +} + +void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, + double yoffset) { + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + if (bd->PrevUserCallbackScroll != NULL && window == bd->Window) + bd->PrevUserCallbackScroll(window, xoffset, yoffset); + + ImGuiIO& io = ImGui::GetIO(); + io.AddMouseWheelEvent((float)xoffset, (float)yoffset); +} + +static int ImGui_ImplGlfw_TranslateUntranslatedKey(int key, int scancode) { +#if GLFW_HAS_GET_KEY_NAME && !defined(__EMSCRIPTEN__) + // GLFW 3.1+ attempts to "untranslate" keys, which goes the opposite of what + // every other framework does, making using lettered shortcuts difficult. (It + // had reasons to do so: namely GLFW is/was more likely to be used for + // WASD-type game controls rather than lettered shortcuts, but IHMO the 3.1 + // change could have been done differently) See + // https://github.com/glfw/glfw/issues/1502 for details. Adding a workaround + // to undo this (so our keys are translated->untranslated->translated, likely + // a lossy process). This won't cover edge cases but this is at least going to + // cover common cases. + if (key >= GLFW_KEY_KP_0 && key <= GLFW_KEY_KP_EQUAL) return key; + const char* key_name = glfwGetKeyName(key, scancode); + if (key_name && key_name[0] != 0 && key_name[1] == 0) { + const char char_names[] = "`-=[]\\,;\'./"; + const int char_keys[] = { + GLFW_KEY_GRAVE_ACCENT, GLFW_KEY_MINUS, GLFW_KEY_EQUAL, + GLFW_KEY_LEFT_BRACKET, GLFW_KEY_RIGHT_BRACKET, GLFW_KEY_BACKSLASH, + GLFW_KEY_COMMA, GLFW_KEY_SEMICOLON, GLFW_KEY_APOSTROPHE, + GLFW_KEY_PERIOD, GLFW_KEY_SLASH, 0}; + IM_ASSERT(IM_ARRAYSIZE(char_names) == IM_ARRAYSIZE(char_keys)); + if (key_name[0] >= '0' && key_name[0] <= '9') { + key = GLFW_KEY_0 + (key_name[0] - '0'); + } else if (key_name[0] >= 'A' && key_name[0] <= 'Z') { + key = GLFW_KEY_A + (key_name[0] - 'A'); + } else if (key_name[0] >= 'a' && key_name[0] <= 'z') { + key = GLFW_KEY_A + (key_name[0] - 'a'); + } else if (const char* p = strchr(char_names, key_name[0])) { + key = char_keys[p - char_names]; + } + } + // if (action == GLFW_PRESS) printf("key %d scancode %d name '%s'\n", key, + // scancode, key_name); +#else + IM_UNUSED(scancode); +#endif + return key; +} + +void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int keycode, int scancode, + int action, int mods) { + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + if (bd->PrevUserCallbackKey != NULL && window == bd->Window) + bd->PrevUserCallbackKey(window, keycode, scancode, action, mods); + + if (action != GLFW_PRESS && action != GLFW_RELEASE) return; + + // Workaround: X11 does not include current pressed/released modifier key in + // 'mods' flags. https://github.com/glfw/glfw/issues/1630 + if (int keycode_to_mod = ImGui_ImplGlfw_KeyToModifier(keycode)) + mods = (action == GLFW_PRESS) ? (mods | keycode_to_mod) + : (mods & ~keycode_to_mod); + ImGui_ImplGlfw_UpdateKeyModifiers(mods); + + keycode = ImGui_ImplGlfw_TranslateUntranslatedKey(keycode, scancode); + + ImGuiIO& io = ImGui::GetIO(); + ImGuiKey imgui_key = ImGui_ImplGlfw_KeyToImGuiKey(keycode); + io.AddKeyEvent(imgui_key, (action == GLFW_PRESS)); + io.SetKeyEventNativeData( + imgui_key, keycode, + scancode); // To support legacy indexing (<1.87 user code) +} + +void ImGui_ImplGlfw_WindowFocusCallback(GLFWwindow* window, int focused) { + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + if (bd->PrevUserCallbackWindowFocus != NULL && window == bd->Window) + bd->PrevUserCallbackWindowFocus(window, focused); + + ImGuiIO& io = ImGui::GetIO(); + io.AddFocusEvent(focused != 0); +} + +void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y) { + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + if (bd->PrevUserCallbackCursorPos != NULL && window == bd->Window) + bd->PrevUserCallbackCursorPos(window, x, y); + + ImGuiIO& io = ImGui::GetIO(); + io.AddMousePosEvent((float)x, (float)y); + bd->LastValidMousePos = ImVec2((float)x, (float)y); +} + +// Workaround: X11 seems to send spurious Leave/Enter events which would make us +// lose our position, so we back it up and restore on Leave/Enter (see +// https://github.com/ocornut/imgui/issues/4984) +void ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered) { + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + if (bd->PrevUserCallbackCursorEnter != NULL && window == bd->Window) + bd->PrevUserCallbackCursorEnter(window, entered); + + ImGuiIO& io = ImGui::GetIO(); + if (entered) { + bd->MouseWindow = window; + io.AddMousePosEvent(bd->LastValidMousePos.x, bd->LastValidMousePos.y); + } else if (!entered && bd->MouseWindow == window) { + bd->LastValidMousePos = io.MousePos; + bd->MouseWindow = NULL; + io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); + } +} + +void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c) { + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + if (bd->PrevUserCallbackChar != NULL && window == bd->Window) + bd->PrevUserCallbackChar(window, c); + + ImGuiIO& io = ImGui::GetIO(); + io.AddInputCharacter(c); +} + +void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor*, int) { + // Unused in 'master' branch but 'docking' branch will use this, so we declare + // it ahead of it so if you have to install callbacks you can install this one + // too. +} + +void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window) { + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + IM_ASSERT(bd->InstalledCallbacks == false && "Callbacks already installed!"); + IM_ASSERT(bd->Window == window); + + bd->PrevUserCallbackWindowFocus = + glfwSetWindowFocusCallback(window, ImGui_ImplGlfw_WindowFocusCallback); + bd->PrevUserCallbackCursorEnter = + glfwSetCursorEnterCallback(window, ImGui_ImplGlfw_CursorEnterCallback); + bd->PrevUserCallbackCursorPos = + glfwSetCursorPosCallback(window, ImGui_ImplGlfw_CursorPosCallback); + bd->PrevUserCallbackMousebutton = + glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback); + bd->PrevUserCallbackScroll = + glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback); + bd->PrevUserCallbackKey = + glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback); + bd->PrevUserCallbackChar = + glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback); + bd->PrevUserCallbackMonitor = + glfwSetMonitorCallback(ImGui_ImplGlfw_MonitorCallback); + bd->InstalledCallbacks = true; +} + +void ImGui_ImplGlfw_RestoreCallbacks(GLFWwindow* window) { + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + IM_ASSERT(bd->InstalledCallbacks == true && "Callbacks not installed!"); + IM_ASSERT(bd->Window == window); + + glfwSetWindowFocusCallback(window, bd->PrevUserCallbackWindowFocus); + glfwSetCursorEnterCallback(window, bd->PrevUserCallbackCursorEnter); + glfwSetCursorPosCallback(window, bd->PrevUserCallbackCursorPos); + glfwSetMouseButtonCallback(window, bd->PrevUserCallbackMousebutton); + glfwSetScrollCallback(window, bd->PrevUserCallbackScroll); + glfwSetKeyCallback(window, bd->PrevUserCallbackKey); + glfwSetCharCallback(window, bd->PrevUserCallbackChar); + glfwSetMonitorCallback(bd->PrevUserCallbackMonitor); + bd->InstalledCallbacks = false; + bd->PrevUserCallbackWindowFocus = NULL; + bd->PrevUserCallbackCursorEnter = NULL; + bd->PrevUserCallbackCursorPos = NULL; + bd->PrevUserCallbackMousebutton = NULL; + bd->PrevUserCallbackScroll = NULL; + bd->PrevUserCallbackKey = NULL; + bd->PrevUserCallbackChar = NULL; + bd->PrevUserCallbackMonitor = NULL; +} + +static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, + GlfwClientApi client_api) { + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendPlatformUserData == NULL && + "Already initialized a platform backend!"); + + // Setup backend capabilities flags + ImGui_ImplGlfw_Data* bd = IM_NEW(ImGui_ImplGlfw_Data)(); + io.BackendPlatformUserData = (void*)bd; + io.BackendPlatformName = "imgui_impl_glfw"; + io.BackendFlags |= + ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() + // values (optional) + io.BackendFlags |= + ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos + // requests (optional, rarely used) + + bd->Window = window; + bd->Time = 0.0; + + io.SetClipboardTextFn = ImGui_ImplGlfw_SetClipboardText; + io.GetClipboardTextFn = ImGui_ImplGlfw_GetClipboardText; + io.ClipboardUserData = bd->Window; + + // Set platform dependent data in viewport +#if defined(_WIN32) + ImGui::GetMainViewport()->PlatformHandleRaw = + (void*)glfwGetWin32Window(bd->Window); +#endif + + // Create mouse cursors + // (By design, on X11 cursors are user configurable and some cursors may be + // missing. When a cursor doesn't exist, GLFW will emit an error which will + // often be printed by the app, so we temporarily disable error reporting. + // Missing cursors will return NULL and our _UpdateMouseCursor() function will + // use the Arrow cursor instead.) + GLFWerrorfun prev_error_callback = glfwSetErrorCallback(NULL); + bd->MouseCursors[ImGuiMouseCursor_Arrow] = + glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_TextInput] = + glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = + glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = + glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_Hand] = + glfwCreateStandardCursor(GLFW_HAND_CURSOR); +#if GLFW_HAS_NEW_CURSORS + bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = + glfwCreateStandardCursor(GLFW_RESIZE_ALL_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = + glfwCreateStandardCursor(GLFW_RESIZE_NESW_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = + glfwCreateStandardCursor(GLFW_RESIZE_NWSE_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = + glfwCreateStandardCursor(GLFW_NOT_ALLOWED_CURSOR); +#else + bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = + glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = + glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = + glfwCreateStandardCursor(GLFW_ARROW_CURSOR); + bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = + glfwCreateStandardCursor(GLFW_ARROW_CURSOR); +#endif + glfwSetErrorCallback(prev_error_callback); + + // Chain GLFW callbacks: our callbacks will call the user's previously + // installed callbacks, if any. + if (install_callbacks) ImGui_ImplGlfw_InstallCallbacks(window); + + bd->ClientApi = client_api; + return true; +} + +bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks) { + return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_OpenGL); +} + +bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks) { + return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Vulkan); +} + +bool ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool install_callbacks) { + return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Unknown); +} + +void ImGui_ImplGlfw_Shutdown() { + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + IM_ASSERT(bd != NULL && + "No platform backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + if (bd->InstalledCallbacks) ImGui_ImplGlfw_RestoreCallbacks(bd->Window); + + for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; + cursor_n++) + glfwDestroyCursor(bd->MouseCursors[cursor_n]); + + io.BackendPlatformName = NULL; + io.BackendPlatformUserData = NULL; + IM_DELETE(bd); +} + +static void ImGui_ImplGlfw_UpdateMouseData() { + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + ImGuiIO& io = ImGui::GetIO(); + +#ifdef __EMSCRIPTEN__ + const bool is_app_focused = true; +#else + const bool is_app_focused = + glfwGetWindowAttrib(bd->Window, GLFW_FOCUSED) != 0; +#endif + if (is_app_focused) { + // (Optional) Set OS mouse position from Dear ImGui if requested (rarely + // used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) + if (io.WantSetMousePos) + glfwSetCursorPos(bd->Window, (double)io.MousePos.x, + (double)io.MousePos.y); + + // (Optional) Fallback to provide mouse position when focused + // (ImGui_ImplGlfw_CursorPosCallback already provides this when hovered or + // captured) + if (is_app_focused && bd->MouseWindow == NULL) { + double mouse_x, mouse_y; + glfwGetCursorPos(bd->Window, &mouse_x, &mouse_y); + io.AddMousePosEvent((float)mouse_x, (float)mouse_y); + bd->LastValidMousePos = ImVec2((float)mouse_x, (float)mouse_y); + } + } +} + +static void ImGui_ImplGlfw_UpdateMouseCursor() { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + if ((io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) || + glfwGetInputMode(bd->Window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED) + return; + + ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); + if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor) { + // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor + glfwSetInputMode(bd->Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + } else { + // Show OS mouse cursor + // FIXME-PLATFORM: Unfocused windows seems to fail changing the mouse cursor + // with GLFW 3.2, but 3.3 works here. + glfwSetCursor(bd->Window, bd->MouseCursors[imgui_cursor] + ? bd->MouseCursors[imgui_cursor] + : bd->MouseCursors[ImGuiMouseCursor_Arrow]); + glfwSetInputMode(bd->Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } +} + +// Update gamepad inputs +static inline float Saturate(float v) { + return v < 0.0f ? 0.0f : v > 1.0f ? 1.0f : v; +} +static void ImGui_ImplGlfw_UpdateGamepads() { + ImGuiIO& io = ImGui::GetIO(); + if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) return; + + io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; +#if GLFW_HAS_GAMEPAD_API + GLFWgamepadstate gamepad; + if (!glfwGetGamepadState(GLFW_JOYSTICK_1, &gamepad)) return; +#define MAP_BUTTON(KEY_NO, BUTTON_NO, _UNUSED) \ + do { \ + io.AddKeyEvent(KEY_NO, gamepad.buttons[BUTTON_NO] != 0); \ + } while (0) +#define MAP_ANALOG(KEY_NO, AXIS_NO, _UNUSED, V0, V1) \ + do { \ + float v = gamepad.axes[AXIS_NO]; \ + v = (v - V0) / (V1 - V0); \ + io.AddKeyAnalogEvent(KEY_NO, v > 0.10f, Saturate(v)); \ + } while (0) +#else + int axes_count = 0, buttons_count = 0; + const float* axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count); + const unsigned char* buttons = + glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count); + if (axes_count == 0 || buttons_count == 0) return; +#define MAP_BUTTON(KEY_NO, _UNUSED, BUTTON_NO) \ + do { \ + io.AddKeyEvent(KEY_NO, (buttons_count > BUTTON_NO && \ + buttons[BUTTON_NO] == GLFW_PRESS)); \ + } while (0) +#define MAP_ANALOG(KEY_NO, _UNUSED, AXIS_NO, V0, V1) \ + do { \ + float v = (axes_count > AXIS_NO) ? axes[AXIS_NO] : V0; \ + v = (v - V0) / (V1 - V0); \ + io.AddKeyAnalogEvent(KEY_NO, v > 0.10f, Saturate(v)); \ + } while (0) +#endif + io.BackendFlags |= ImGuiBackendFlags_HasGamepad; + MAP_BUTTON(ImGuiKey_GamepadStart, GLFW_GAMEPAD_BUTTON_START, 7); + MAP_BUTTON(ImGuiKey_GamepadBack, GLFW_GAMEPAD_BUTTON_BACK, 6); + MAP_BUTTON(ImGuiKey_GamepadFaceDown, GLFW_GAMEPAD_BUTTON_A, + 0); // Xbox A, PS Cross + MAP_BUTTON(ImGuiKey_GamepadFaceRight, GLFW_GAMEPAD_BUTTON_B, + 1); // Xbox B, PS Circle + MAP_BUTTON(ImGuiKey_GamepadFaceLeft, GLFW_GAMEPAD_BUTTON_X, + 2); // Xbox X, PS Square + MAP_BUTTON(ImGuiKey_GamepadFaceUp, GLFW_GAMEPAD_BUTTON_Y, + 3); // Xbox Y, PS Triangle + MAP_BUTTON(ImGuiKey_GamepadDpadLeft, GLFW_GAMEPAD_BUTTON_DPAD_LEFT, 13); + MAP_BUTTON(ImGuiKey_GamepadDpadRight, GLFW_GAMEPAD_BUTTON_DPAD_RIGHT, 11); + MAP_BUTTON(ImGuiKey_GamepadDpadUp, GLFW_GAMEPAD_BUTTON_DPAD_UP, 10); + MAP_BUTTON(ImGuiKey_GamepadDpadDown, GLFW_GAMEPAD_BUTTON_DPAD_DOWN, 12); + MAP_BUTTON(ImGuiKey_GamepadL1, GLFW_GAMEPAD_BUTTON_LEFT_BUMPER, 4); + MAP_BUTTON(ImGuiKey_GamepadR1, GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER, 5); + MAP_ANALOG(ImGuiKey_GamepadL2, GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, 4, -0.75f, + +1.0f); + MAP_ANALOG(ImGuiKey_GamepadR2, GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, 5, -0.75f, + +1.0f); + MAP_BUTTON(ImGuiKey_GamepadL3, GLFW_GAMEPAD_BUTTON_LEFT_THUMB, 8); + MAP_BUTTON(ImGuiKey_GamepadR3, GLFW_GAMEPAD_BUTTON_RIGHT_THUMB, 9); + MAP_ANALOG(ImGuiKey_GamepadLStickLeft, GLFW_GAMEPAD_AXIS_LEFT_X, 0, -0.25f, + -1.0f); + MAP_ANALOG(ImGuiKey_GamepadLStickRight, GLFW_GAMEPAD_AXIS_LEFT_X, 0, +0.25f, + +1.0f); + MAP_ANALOG(ImGuiKey_GamepadLStickUp, GLFW_GAMEPAD_AXIS_LEFT_Y, 1, -0.25f, + -1.0f); + MAP_ANALOG(ImGuiKey_GamepadLStickDown, GLFW_GAMEPAD_AXIS_LEFT_Y, 1, +0.25f, + +1.0f); + MAP_ANALOG(ImGuiKey_GamepadRStickLeft, GLFW_GAMEPAD_AXIS_RIGHT_X, 2, -0.25f, + -1.0f); + MAP_ANALOG(ImGuiKey_GamepadRStickRight, GLFW_GAMEPAD_AXIS_RIGHT_X, 2, +0.25f, + +1.0f); + MAP_ANALOG(ImGuiKey_GamepadRStickUp, GLFW_GAMEPAD_AXIS_RIGHT_Y, 3, -0.25f, + -1.0f); + MAP_ANALOG(ImGuiKey_GamepadRStickDown, GLFW_GAMEPAD_AXIS_RIGHT_Y, 3, +0.25f, + +1.0f); +#undef MAP_BUTTON +#undef MAP_ANALOG +} + +void ImGui_ImplGlfw_NewFrame() { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplGlfw_InitForXXX()?"); + + // Setup display size (every frame to accommodate for window resizing) + int w, h; + int display_w, display_h; + glfwGetWindowSize(bd->Window, &w, &h); + glfwGetFramebufferSize(bd->Window, &display_w, &display_h); + io.DisplaySize = ImVec2((float)w, (float)h); + if (w > 0 && h > 0) + io.DisplayFramebufferScale = + ImVec2((float)display_w / (float)w, (float)display_h / (float)h); + + // Setup time step + double current_time = glfwGetTime(); + io.DeltaTime = + bd->Time > 0.0 ? (float)(current_time - bd->Time) : (float)(1.0f / 60.0f); + bd->Time = current_time; + + ImGui_ImplGlfw_UpdateMouseData(); + ImGui_ImplGlfw_UpdateMouseCursor(); + + // Update game controllers (if enabled and available) + ImGui_ImplGlfw_UpdateGamepads(); +} + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif diff --git a/customchar-ui/libs/imgui/backends/imgui_impl_opengl3.cpp b/customchar-ui/libs/imgui/backends/imgui_impl_opengl3.cpp new file mode 100644 index 0000000..013f457 --- /dev/null +++ b/customchar-ui/libs/imgui/backends/imgui_impl_opengl3.cpp @@ -0,0 +1,1025 @@ +// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic +// pipeline +// - Desktop GL: 2.x 3.x 4.x +// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) +// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, +// custom..) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier +// as void*/ImTextureID. Read the FAQ about ImTextureID! [x] Renderer: Desktop +// GL only: Support for large meshes (64k+ vertices) with 16-bit indices. + +// You can use unmodified imgui_impl_* files in your project. See examples/ +// folder for examples of using this. Prefer including the entire imgui/ +// repository into your project (either as a copy or as a submodule), and only +// build the backends you need. If you are new to Dear ImGui, read documentation +// from the docs/ folder + read the top of imgui.cpp. Read online: +// https://github.com/ocornut/imgui/tree/master/docs + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2022-05-23: OpenGL: Reworking 2021-12-15 "Using buffer orphaning" so it only +// happens on Intel GPU, seems to cause problems otherwise. (#4468, #4825, +// #4832, #5127). 2022-05-13: OpenGL: Fix state corruption on OpenGL ES 2.0 due +// to not preserving GL_ELEMENT_ARRAY_BUFFER_BINDING and vertex attribute +// states. 2021-12-15: OpenGL: Using buffer orphaning + glBufferSubData(), +// seems to fix leaks with multi-viewports with some Intel HD drivers. +// 2021-08-23: OpenGL: Fixed ES 3.0 shader ("#version 300 es") use normal +// precision floats to avoid wobbly rendering at HD resolutions. 2021-08-19: +// OpenGL: Embed and use our own minimal GL loader +// (imgui_impl_opengl3_loader.h), removing requirement and support for +// third-party loader. 2021-06-29: Reorganized backend to pull data from a +// single structure to facilitate usage with multiple-contexts (all g_XXXX +// access changed to bd->XXXX). 2021-06-25: OpenGL: Use OES_vertex_array +// extension on Emscripten + backup/restore current state. 2021-06-21: OpenGL: +// Destroy individual vertex/fragment shader objects right after they are +// linked into the main shader. 2021-05-24: OpenGL: Access GL_CLIP_ORIGIN when +// "GL_ARB_clip_control" extension is detected, inside of just OpenGL 4.5 +// version. 2021-05-19: OpenGL: Replaced direct access to ImDrawCmd::TextureId +// with a call to ImDrawCmd::GetTexID(). (will become a requirement) +// 2021-04-06: OpenGL: Don't try to read GL_CLIP_ORIGIN unless we're OpenGL 4.5 +// or greater. 2021-02-18: OpenGL: Change blending equation to preserve alpha +// in output buffer. 2021-01-03: OpenGL: Backup, setup and restore +// GL_STENCIL_TEST state. 2020-10-23: OpenGL: Backup, setup and restore +// GL_PRIMITIVE_RESTART state. 2020-10-15: OpenGL: Use glGetString(GL_VERSION) +// instead of glGetIntegerv(GL_MAJOR_VERSION, ...) when the later returns zero +// (e.g. Desktop GL 2.x) 2020-09-17: OpenGL: Fix to avoid compiling/calling +// glBindSampler() on ES or pre 3.3 context which have the defines set by a +// loader. 2020-07-10: OpenGL: Added support for glad2 OpenGL loader. +// 2020-05-08: OpenGL: Made default GLSL version 150 (instead of 130) on OSX. +// 2020-04-21: OpenGL: Fixed handling of glClipControl(GL_UPPER_LEFT) by +// inverting projection matrix. 2020-04-12: OpenGL: Fixed context version check +// mistakenly testing for 4.0+ instead of 3.2+ to enable +// ImGuiBackendFlags_RendererHasVtxOffset. 2020-03-24: OpenGL: Added support +// for glbinding 2.x OpenGL loader. 2020-01-07: OpenGL: Added support for +// glbinding 3.x OpenGL loader. 2019-10-25: OpenGL: Using a combination of GL +// define and runtime GL version to decide whether to use +// glDrawElementsBaseVertex(). Fix building with pre-3.2 GL loaders. +// 2019-09-22: OpenGL: Detect default GL loader using __has_include compiler +// facility. 2019-09-16: OpenGL: Tweak initialization code to allow application +// calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() +// call. 2019-05-29: OpenGL: Desktop GL only: Added support for large mesh +// (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. +// 2019-04-30: OpenGL: Added support for special +// ImDrawCallback_ResetRenderState callback to reset render state. 2019-03-29: +// OpenGL: Not calling glBindBuffer more than necessary in the render loop. +// 2019-03-15: OpenGL: Added a GL call + comments in ImGui_ImplOpenGL3_Init() +// to detect uninitialized GL function loaders early. 2019-03-03: OpenGL: Fix +// support for ES 2.0 (WebGL 1.0). 2019-02-20: OpenGL: Fix for OSX not +// supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined +// by the headers/loader. 2019-02-11: OpenGL: Projecting clipping rectangles +// correctly using draw_data->FramebufferScale to allow multi-viewports for +// retina display. 2019-02-01: OpenGL: Using GLSL 410 shaders for any version +// over 410 (e.g. 430, 450). 2018-11-30: Misc: Setting up +// io.BackendRendererName so it can be displayed in the About Window. +// 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / +// GL_CLIP_ORIGIN. 2018-08-29: OpenGL: Added support for more OpenGL loaders: +// glew and glad, with comments indicative that any loader can be used. +// 2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version +// default to "#version 300 ES". 2018-07-30: OpenGL: Support for GLSL 300 ES +// and 410 core. Fixes for Emscripten compilation. 2018-07-10: OpenGL: Support +// for more GLSL versions (based on the GLSL version string). Added error +// output when shaders fail to compile/link. 2018-06-08: Misc: Extracted +// imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 +// examples. 2018-06-08: OpenGL: Use draw_data->DisplayPos and +// draw_data->DisplaySize to setup projection matrix and clipping rectangle. +// 2018-05-25: OpenGL: Removed unnecessary backup/restore of +// GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state. +// 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 +// context won't fail if the function is a NULL pointer. 2018-03-06: OpenGL: +// Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user +// can override the GLSL version e.g. "#version 150". 2018-02-23: OpenGL: +// Create the VAO in the render function so the setup can more easily be used +// with multiple shared GL context. 2018-02-16: Misc: Obsoleted the +// io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() +// in the .h file so you can call it yourself. 2018-01-07: OpenGL: Changed GLSL +// shader version from 330 to 150. 2017-09-01: OpenGL: Save and restore current +// bound sampler. Save and restore current polygon mode. 2017-05-01: OpenGL: +// Fixed save and restore of current blend func state. 2017-05-01: OpenGL: +// Fixed save and restore of current GL_ACTIVE_TEXTURE. 2016-09-05: OpenGL: +// Fixed save and restore of current scissor rectangle. 2016-07-29: OpenGL: +// Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes +// it. (#752) + +//---------------------------------------- +// OpenGL GLSL GLSL +// version version string +//---------------------------------------- +// 2.0 110 "#version 110" +// 2.1 120 "#version 120" +// 3.0 130 "#version 130" +// 3.1 140 "#version 140" +// 3.2 150 "#version 150" +// 3.3 330 "#version 330 core" +// 4.0 400 "#version 400 core" +// 4.1 410 "#version 410 core" +// 4.2 420 "#version 410 core" +// 4.3 430 "#version 430 core" +// ES 2.0 100 "#version 100" = WebGL 1.0 +// ES 3.0 300 "#version 300 es" = WebGL 2.0 +//---------------------------------------- + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui_impl_opengl3.h" +#include +#include "imgui.h" +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif +#if defined(__APPLE__) +#include +#endif + +// Clang warnings with -Weverything +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored \ + "-Wold-style-cast" // warning: use of old-style cast +#pragma clang diagnostic ignored \ + "-Wsign-conversion" // warning: implicit conversion changes signedness +#if __has_warning("-Wzero-as-null-pointer-constant") +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif +#endif + +// GL includes +#if defined(IMGUI_IMPL_OPENGL_ES2) +#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) +#include // Use GL ES 2 +#else +#include // Use GL ES 2 +#endif +#if defined(__EMSCRIPTEN__) +#ifndef GL_GLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES +#endif +#include +#endif +#elif defined(IMGUI_IMPL_OPENGL_ES3) +#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) +#include // Use GL ES 3 +#else +#include // Use GL ES 3 +#endif +#elif !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) +// Modern desktop OpenGL doesn't have a standard portable header file to load +// OpenGL function pointers. Helper libraries are often used for this purpose! +// Here we are using our own minimal custom loader based on gl3w. In the rest of +// your app/engine, you can use another loader of your choice (gl3w, glew, glad, +// glbinding, glext, glLoadGen, etc.). If you happen to be developing a new +// feature for this backend (imgui_impl_opengl3.cpp): +// - You may need to regenerate imgui_impl_opengl3_loader.h to add new symbols. +// See https://github.com/dearimgui/gl3w_stripped +// - You can temporarily use an unstripped version. See +// https://github.com/dearimgui/gl3w_stripped/releases Changes to this backend +// using new APIs should be accompanied by a regenerated stripped loader +// version. +#define IMGL3W_IMPL +#include "imgui_impl_opengl3_loader.h" +#endif + +// Vertex arrays are not supported on ES2/WebGL1 unless Emscripten which uses an +// extension +#ifndef IMGUI_IMPL_OPENGL_ES2 +#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY +#elif defined(__EMSCRIPTEN__) +#define IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY +#define glBindVertexArray glBindVertexArrayOES +#define glGenVertexArrays glGenVertexArraysOES +#define glDeleteVertexArrays glDeleteVertexArraysOES +#define GL_VERTEX_ARRAY_BINDING GL_VERTEX_ARRAY_BINDING_OES +#endif + +// Desktop GL 2.0+ has glPolygonMode() which GL ES and WebGL don't have. +#ifdef GL_POLYGON_MODE +#define IMGUI_IMPL_HAS_POLYGON_MODE +#endif + +// Desktop GL 3.2+ has glDrawElementsBaseVertex() which GL ES and WebGL don't +// have. +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && \ + defined(GL_VERSION_3_2) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET +#endif + +// Desktop GL 3.3+ has glBindSampler() +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && \ + defined(GL_VERSION_3_3) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER +#endif + +// Desktop GL 3.1+ has GL_PRIMITIVE_RESTART state +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && \ + defined(GL_VERSION_3_1) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART +#endif + +// Desktop GL use extension detection +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) +#define IMGUI_IMPL_OPENGL_MAY_HAVE_EXTENSIONS +#endif + +// OpenGL Data +struct ImGui_ImplOpenGL3_Data { + GLuint GlVersion; // Extracted at runtime using GL_MAJOR_VERSION, + // GL_MINOR_VERSION queries (e.g. 320 for GL 3.2) + char GlslVersionString[32]; // Specified by user or detected based on compile + // time GL settings. + GLuint FontTexture; + GLuint ShaderHandle; + GLint AttribLocationTex; // Uniforms location + GLint AttribLocationProjMtx; + GLuint AttribLocationVtxPos; // Vertex attributes location + GLuint AttribLocationVtxUV; + GLuint AttribLocationVtxColor; + unsigned int VboHandle, ElementsHandle; + GLsizeiptr VertexBufferSize; + GLsizeiptr IndexBufferSize; + bool HasClipOrigin; + bool UseBufferSubData; + + ImGui_ImplOpenGL3_Data() { memset((void*)this, 0, sizeof(*this)); } +}; + +// Backend data stored in io.BackendRendererUserData to allow support for +// multiple Dear ImGui contexts It is STRONGLY preferred that you use docking +// branch with multi-viewports (== single Dear ImGui context + multiple windows) +// instead of multiple Dear ImGui contexts. +static ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData() { + return ImGui::GetCurrentContext() + ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData + : NULL; +} + +// OpenGL vertex attribute state (for ES 1.0 and ES 2.0 only) +#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY +struct ImGui_ImplOpenGL3_VtxAttribState { + GLint Enabled, Size, Type, Normalized, Stride; + GLvoid* Ptr; + + void GetState(GLint index) { + glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &Enabled); + glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_SIZE, &Size); + glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_TYPE, &Type); + glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &Normalized); + glGetVertexAttribiv(index, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &Stride); + glGetVertexAttribPointerv(index, GL_VERTEX_ATTRIB_ARRAY_POINTER, &Ptr); + } + void SetState(GLint index) { + glVertexAttribPointer(index, Size, Type, (GLboolean)Normalized, Stride, + Ptr); + if (Enabled) + glEnableVertexAttribArray(index); + else + glDisableVertexAttribArray(index); + } +}; +#endif + +// Functions +bool ImGui_ImplOpenGL3_Init(const char* glsl_version) { + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendRendererUserData == NULL && + "Already initialized a renderer backend!"); + + // Initialize our loader +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) && \ + !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) + if (imgl3wInit() != 0) { + fprintf(stderr, "Failed to initialize OpenGL loader!\n"); + return false; + } +#endif + + // Setup backend capabilities flags + ImGui_ImplOpenGL3_Data* bd = IM_NEW(ImGui_ImplOpenGL3_Data)(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_opengl3"; + + // Query for GL version (e.g. 320 for GL 3.2) +#if !defined(IMGUI_IMPL_OPENGL_ES2) + GLint major = 0; + GLint minor = 0; + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + if (major == 0 && minor == 0) { + // Query GL_VERSION in desktop GL 2.x, the string will start with + // "." + const char* gl_version = (const char*)glGetString(GL_VERSION); + sscanf(gl_version, "%d.%d", &major, &minor); + } + bd->GlVersion = (GLuint)(major * 100 + minor * 10); + + // Query vendor to enable glBufferSubData kludge +#ifdef _WIN32 + if (const char* vendor = (const char*)glGetString(GL_VENDOR)) + if (strncmp(vendor, "Intel", 5) == 0) bd->UseBufferSubData = true; +#endif + // printf("GL_MAJOR_VERSION = %d\nGL_MINOR_VERSION = %d\nGL_VENDOR = + // '%s'\nGL_RENDERER = '%s'\n", major, minor, (const + // char*)glGetString(GL_VENDOR), (const char*)glGetString(GL_RENDERER)); + // // [DEBUG] +#else + bd->GlVersion = 200; // GLES 2 +#endif + +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET + if (bd->GlVersion >= 320) + io.BackendFlags |= + ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the + // ImDrawCmd::VtxOffset field, + // allowing for large meshes. +#endif + + // Store GLSL version string so we can refer to it later in case we recreate + // shaders. Note: GLSL version is NOT the same as GL version. Leave this to + // NULL if unsure. + if (glsl_version == NULL) { +#if defined(IMGUI_IMPL_OPENGL_ES2) + glsl_version = "#version 100"; +#elif defined(IMGUI_IMPL_OPENGL_ES3) + glsl_version = "#version 300 es"; +#elif defined(__APPLE__) + glsl_version = "#version 150"; +#else + glsl_version = "#version 130"; +#endif + } + IM_ASSERT((int)strlen(glsl_version) + 2 < + IM_ARRAYSIZE(bd->GlslVersionString)); + strcpy(bd->GlslVersionString, glsl_version); + strcat(bd->GlslVersionString, "\n"); + + // Make an arbitrary GL call (we don't actually need the result) + // IF YOU GET A CRASH HERE: it probably means the OpenGL function loader + // didn't do its job. Let us know! + GLint current_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture); + + // Detect extensions we support + bd->HasClipOrigin = (bd->GlVersion >= 450); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_EXTENSIONS + GLint num_extensions = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions); + for (GLint i = 0; i < num_extensions; i++) { + const char* extension = (const char*)glGetStringi(GL_EXTENSIONS, i); + if (extension != NULL && strcmp(extension, "GL_ARB_clip_control") == 0) + bd->HasClipOrigin = true; + } +#endif + + return true; +} + +void ImGui_ImplOpenGL3_Shutdown() { + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + IM_ASSERT(bd != NULL && + "No renderer backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + ImGui_ImplOpenGL3_DestroyDeviceObjects(); + io.BackendRendererName = NULL; + io.BackendRendererUserData = NULL; + IM_DELETE(bd); +} + +void ImGui_ImplOpenGL3_NewFrame() { + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplOpenGL3_Init()?"); + + if (!bd->ShaderHandle) ImGui_ImplOpenGL3_CreateDeviceObjects(); +} + +static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, + int fb_width, int fb_height, + GLuint vertex_array_object) { + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + + // Setup render state: alpha-blending enabled, no face culling, no depth + // testing, scissor enabled, polygon fill + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, + GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glEnable(GL_SCISSOR_TEST); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART + if (bd->GlVersion >= 310) glDisable(GL_PRIMITIVE_RESTART); +#endif +#ifdef IMGUI_IMPL_HAS_POLYGON_MODE + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif + + // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT) +#if defined(GL_CLIP_ORIGIN) + bool clip_origin_lower_left = true; + if (bd->HasClipOrigin) { + GLenum current_clip_origin = 0; + glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)¤t_clip_origin); + if (current_clip_origin == GL_UPPER_LEFT) clip_origin_lower_left = false; + } +#endif + + // Setup viewport, orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPos (top left) to + // draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is + // (0,0) for single viewport apps. + glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); + float L = draw_data->DisplayPos.x; + float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; + float T = draw_data->DisplayPos.y; + float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; +#if defined(GL_CLIP_ORIGIN) + if (!clip_origin_lower_left) { + float tmp = T; + T = B; + B = tmp; + } // Swap top and bottom if origin is upper left +#endif + const float ortho_projection[4][4] = { + {2.0f / (R - L), 0.0f, 0.0f, 0.0f}, + {0.0f, 2.0f / (T - B), 0.0f, 0.0f}, + {0.0f, 0.0f, -1.0f, 0.0f}, + {(R + L) / (L - R), (T + B) / (B - T), 0.0f, 1.0f}, + }; + glUseProgram(bd->ShaderHandle); + glUniform1i(bd->AttribLocationTex, 0); + glUniformMatrix4fv(bd->AttribLocationProjMtx, 1, GL_FALSE, + &ortho_projection[0][0]); + +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + if (bd->GlVersion >= 330) + glBindSampler(0, 0); // We use combined texture/sampler state. Applications + // using GL 3.3 may set that otherwise. +#endif + + (void)vertex_array_object; +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + glBindVertexArray(vertex_array_object); +#endif + + // Bind vertex/index buffers and setup attributes for ImDrawVert + glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle); + glEnableVertexAttribArray(bd->AttribLocationVtxPos); + glEnableVertexAttribArray(bd->AttribLocationVtxUV); + glEnableVertexAttribArray(bd->AttribLocationVtxColor); + glVertexAttribPointer(bd->AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, + sizeof(ImDrawVert), + (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); + glVertexAttribPointer(bd->AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, + sizeof(ImDrawVert), + (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); + glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, + GL_TRUE, sizeof(ImDrawVert), + (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); +} + +// OpenGL3 Render function. +// Note that this implementation is little overcomplicated because we are +// saving/setting up/restoring every OpenGL state explicitly. This is in order +// to be able to run within an OpenGL engine that doesn't do so. +void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) { + // Avoid rendering when minimized, scale coordinates for retina displays + // (screen coordinates != framebuffer coordinates) + int fb_width = + (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); + int fb_height = + (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + if (fb_width <= 0 || fb_height <= 0) return; + + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + + // Backup GL state + GLenum last_active_texture; + glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture); + glActiveTexture(GL_TEXTURE0); + GLuint last_program; + glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&last_program); + GLuint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&last_texture); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + GLuint last_sampler; + if (bd->GlVersion >= 330) { + glGetIntegerv(GL_SAMPLER_BINDING, (GLint*)&last_sampler); + } else { + last_sampler = 0; + } +#endif + GLuint last_array_buffer; + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)&last_array_buffer); +#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + // This is part of VAO on OpenGL 3.0+ and OpenGL ES 3.0+. + GLint last_element_array_buffer; + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer); + ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_pos; + last_vtx_attrib_state_pos.GetState(bd->AttribLocationVtxPos); + ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_uv; + last_vtx_attrib_state_uv.GetState(bd->AttribLocationVtxUV); + ImGui_ImplOpenGL3_VtxAttribState last_vtx_attrib_state_color; + last_vtx_attrib_state_color.GetState(bd->AttribLocationVtxColor); +#endif +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + GLuint last_vertex_array_object; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, (GLint*)&last_vertex_array_object); +#endif +#ifdef IMGUI_IMPL_HAS_POLYGON_MODE + GLint last_polygon_mode[2]; + glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); +#endif + GLint last_viewport[4]; + glGetIntegerv(GL_VIEWPORT, last_viewport); + GLint last_scissor_box[4]; + glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); + GLenum last_blend_src_rgb; + glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb); + GLenum last_blend_dst_rgb; + glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb); + GLenum last_blend_src_alpha; + glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha); + GLenum last_blend_dst_alpha; + glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha); + GLenum last_blend_equation_rgb; + glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb); + GLenum last_blend_equation_alpha; + glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha); + GLboolean last_enable_blend = glIsEnabled(GL_BLEND); + GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE); + GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); + GLboolean last_enable_stencil_test = glIsEnabled(GL_STENCIL_TEST); + GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART + GLboolean last_enable_primitive_restart = + (bd->GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE; +#endif + + // Setup desired GL state + // Recreate the VAO every time (this is to easily allow multiple GL contexts + // to be rendered to. VAO are not shared among GL contexts) The renderer would + // actually work without any VAO bound, but then our VertexAttrib calls would + // overwrite the default one currently bound. + GLuint vertex_array_object = 0; +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + glGenVertexArrays(1, &vertex_array_object); +#endif + ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, + vertex_array_object); + + // Will project scissor/clipping rectangles into framebuffer space + ImVec2 clip_off = + draw_data->DisplayPos; // (0,0) unless using multi-viewports + ImVec2 clip_scale = + draw_data->FramebufferScale; // (1,1) unless using retina display which + // are often (2,2) + + // Render command lists + for (int n = 0; n < draw_data->CmdListsCount; n++) { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + + // Upload vertex/index buffers + // - On Intel windows drivers we got reports that regular glBufferData() led + // to accumulating leaks when using multi-viewports, so we started using + // orphaning + glBufferSubData(). (See + // https://github.com/ocornut/imgui/issues/4468) + // - On NVIDIA drivers we got reports that using orphaning + + // glBufferSubData() led to glitches when using multi-viewports. + // - OpenGL drivers are in a very sorry state in 2022, for now we are + // switching code path based on vendors. + const GLsizeiptr vtx_buffer_size = + (GLsizeiptr)cmd_list->VtxBuffer.Size * (int)sizeof(ImDrawVert); + const GLsizeiptr idx_buffer_size = + (GLsizeiptr)cmd_list->IdxBuffer.Size * (int)sizeof(ImDrawIdx); + if (bd->UseBufferSubData) { + if (bd->VertexBufferSize < vtx_buffer_size) { + bd->VertexBufferSize = vtx_buffer_size; + glBufferData(GL_ARRAY_BUFFER, bd->VertexBufferSize, NULL, + GL_STREAM_DRAW); + } + if (bd->IndexBufferSize < idx_buffer_size) { + bd->IndexBufferSize = idx_buffer_size; + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bd->IndexBufferSize, NULL, + GL_STREAM_DRAW); + } + glBufferSubData(GL_ARRAY_BUFFER, 0, vtx_buffer_size, + (const GLvoid*)cmd_list->VtxBuffer.Data); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, idx_buffer_size, + (const GLvoid*)cmd_list->IdxBuffer.Data); + } else { + glBufferData(GL_ARRAY_BUFFER, vtx_buffer_size, + (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size, + (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW); + } + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback != NULL) { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by + // the user to request the renderer to reset render state.) + if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, + vertex_array_object); + else + pcmd->UserCallback(cmd_list, pcmd); + } else { + // Project scissor/clipping rectangles into framebuffer space + ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, + (pcmd->ClipRect.y - clip_off.y) * clip_scale.y); + ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, + (pcmd->ClipRect.w - clip_off.y) * clip_scale.y); + if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) continue; + + // Apply scissor/clipping rectangle (Y is inverted in OpenGL) + glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), + (int)(clip_max.x - clip_min.x), + (int)(clip_max.y - clip_min.y)); + + // Bind texture, Draw + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET + if (bd->GlVersion >= 320) + glDrawElementsBaseVertex( + GL_TRIANGLES, (GLsizei)pcmd->ElemCount, + sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, + (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), + (GLint)pcmd->VtxOffset); + else +#endif + glDrawElements( + GL_TRIANGLES, (GLsizei)pcmd->ElemCount, + sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, + (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx))); + } + } + } + + // Destroy the temporary VAO +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + glDeleteVertexArrays(1, &vertex_array_object); +#endif + + // Restore modified GL state + glUseProgram(last_program); + glBindTexture(GL_TEXTURE_2D, last_texture); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER + if (bd->GlVersion >= 330) glBindSampler(0, last_sampler); +#endif + glActiveTexture(last_active_texture); +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + glBindVertexArray(last_vertex_array_object); +#endif + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); +#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer); + last_vtx_attrib_state_pos.SetState(bd->AttribLocationVtxPos); + last_vtx_attrib_state_uv.SetState(bd->AttribLocationVtxUV); + last_vtx_attrib_state_color.SetState(bd->AttribLocationVtxColor); +#endif + glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); + glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, + last_blend_src_alpha, last_blend_dst_alpha); + if (last_enable_blend) + glEnable(GL_BLEND); + else + glDisable(GL_BLEND); + if (last_enable_cull_face) + glEnable(GL_CULL_FACE); + else + glDisable(GL_CULL_FACE); + if (last_enable_depth_test) + glEnable(GL_DEPTH_TEST); + else + glDisable(GL_DEPTH_TEST); + if (last_enable_stencil_test) + glEnable(GL_STENCIL_TEST); + else + glDisable(GL_STENCIL_TEST); + if (last_enable_scissor_test) + glEnable(GL_SCISSOR_TEST); + else + glDisable(GL_SCISSOR_TEST); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART + if (bd->GlVersion >= 310) { + if (last_enable_primitive_restart) + glEnable(GL_PRIMITIVE_RESTART); + else + glDisable(GL_PRIMITIVE_RESTART); + } +#endif + +#ifdef IMGUI_IMPL_HAS_POLYGON_MODE + glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); +#endif + glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], + (GLsizei)last_viewport[3]); + glScissor(last_scissor_box[0], last_scissor_box[1], + (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); + (void)bd; // Not all compilation paths use this +} + +bool ImGui_ImplOpenGL3_CreateFontsTexture() { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + + // Build texture atlas + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32( + &pixels, &width, + &height); // Load as RGBA 32-bit (75% of the memory is wasted, but + // default font is so small) because it is more likely to be + // compatible with user's existing shaders. If your ImTextureId + // represent a higher-level concept than just a GL texture id, + // consider calling GetTexDataAsAlpha8() instead to save on GPU + // memory. + + // Upload texture to graphics system + // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= + // ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to + // allow point/nearest sampling) + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &bd->FontTexture); + glBindTexture(GL_TEXTURE_2D, bd->FontTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +#ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); +#endif + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, + GL_UNSIGNED_BYTE, pixels); + + // Store our identifier + io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); + + // Restore state + glBindTexture(GL_TEXTURE_2D, last_texture); + + return true; +} + +void ImGui_ImplOpenGL3_DestroyFontsTexture() { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + if (bd->FontTexture) { + glDeleteTextures(1, &bd->FontTexture); + io.Fonts->SetTexID(0); + bd->FontTexture = 0; + } +} + +// If you get an error please report on github. You may try different GL context +// version or GLSL version. See GL<>GLSL version table at the top of this file. +static bool CheckShader(GLuint handle, const char* desc) { + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + GLint status = 0, log_length = 0; + glGetShaderiv(handle, GL_COMPILE_STATUS, &status); + glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length); + if ((GLboolean)status == GL_FALSE) + fprintf(stderr, + "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile " + "%s! With GLSL: %s\n", + desc, bd->GlslVersionString); + if (log_length > 1) { + ImVector buf; + buf.resize((int)(log_length + 1)); + glGetShaderInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); + fprintf(stderr, "%s\n", buf.begin()); + } + return (GLboolean)status == GL_TRUE; +} + +// If you get an error please report on GitHub. You may try different GL context +// version or GLSL version. +static bool CheckProgram(GLuint handle, const char* desc) { + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + GLint status = 0, log_length = 0; + glGetProgramiv(handle, GL_LINK_STATUS, &status); + glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length); + if ((GLboolean)status == GL_FALSE) + fprintf(stderr, + "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! " + "With GLSL %s\n", + desc, bd->GlslVersionString); + if (log_length > 1) { + ImVector buf; + buf.resize((int)(log_length + 1)); + glGetProgramInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); + fprintf(stderr, "%s\n", buf.begin()); + } + return (GLboolean)status == GL_TRUE; +} + +bool ImGui_ImplOpenGL3_CreateDeviceObjects() { + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + + // Backup GL state + GLint last_texture, last_array_buffer; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + GLint last_vertex_array; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); +#endif + + // Parse GLSL version string + int glsl_version = 130; + sscanf(bd->GlslVersionString, "#version %d", &glsl_version); + + const GLchar* vertex_shader_glsl_120 = + "uniform mat4 ProjMtx;\n" + "attribute vec2 Position;\n" + "attribute vec2 UV;\n" + "attribute vec4 Color;\n" + "varying vec2 Frag_UV;\n" + "varying vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* vertex_shader_glsl_130 = + "uniform mat4 ProjMtx;\n" + "in vec2 Position;\n" + "in vec2 UV;\n" + "in vec4 Color;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* vertex_shader_glsl_300_es = + "precision highp float;\n" + "layout (location = 0) in vec2 Position;\n" + "layout (location = 1) in vec2 UV;\n" + "layout (location = 2) in vec4 Color;\n" + "uniform mat4 ProjMtx;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* vertex_shader_glsl_410_core = + "layout (location = 0) in vec2 Position;\n" + "layout (location = 1) in vec2 UV;\n" + "layout (location = 2) in vec4 Color;\n" + "uniform mat4 ProjMtx;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_120 = + "#ifdef GL_ES\n" + " precision mediump float;\n" + "#endif\n" + "uniform sampler2D Texture;\n" + "varying vec2 Frag_UV;\n" + "varying vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_130 = + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_300_es = + "precision mediump float;\n" + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "layout (location = 0) out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_410_core = + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "uniform sampler2D Texture;\n" + "layout (location = 0) out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + // Select shaders matching our GLSL versions + const GLchar* vertex_shader = NULL; + const GLchar* fragment_shader = NULL; + if (glsl_version < 130) { + vertex_shader = vertex_shader_glsl_120; + fragment_shader = fragment_shader_glsl_120; + } else if (glsl_version >= 410) { + vertex_shader = vertex_shader_glsl_410_core; + fragment_shader = fragment_shader_glsl_410_core; + } else if (glsl_version == 300) { + vertex_shader = vertex_shader_glsl_300_es; + fragment_shader = fragment_shader_glsl_300_es; + } else { + vertex_shader = vertex_shader_glsl_130; + fragment_shader = fragment_shader_glsl_130; + } + + // Create shaders + const GLchar* vertex_shader_with_version[2] = {bd->GlslVersionString, + vertex_shader}; + GLuint vert_handle = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vert_handle, 2, vertex_shader_with_version, NULL); + glCompileShader(vert_handle); + CheckShader(vert_handle, "vertex shader"); + + const GLchar* fragment_shader_with_version[2] = {bd->GlslVersionString, + fragment_shader}; + GLuint frag_handle = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(frag_handle, 2, fragment_shader_with_version, NULL); + glCompileShader(frag_handle); + CheckShader(frag_handle, "fragment shader"); + + // Link + bd->ShaderHandle = glCreateProgram(); + glAttachShader(bd->ShaderHandle, vert_handle); + glAttachShader(bd->ShaderHandle, frag_handle); + glLinkProgram(bd->ShaderHandle); + CheckProgram(bd->ShaderHandle, "shader program"); + + glDetachShader(bd->ShaderHandle, vert_handle); + glDetachShader(bd->ShaderHandle, frag_handle); + glDeleteShader(vert_handle); + glDeleteShader(frag_handle); + + bd->AttribLocationTex = glGetUniformLocation(bd->ShaderHandle, "Texture"); + bd->AttribLocationProjMtx = glGetUniformLocation(bd->ShaderHandle, "ProjMtx"); + bd->AttribLocationVtxPos = + (GLuint)glGetAttribLocation(bd->ShaderHandle, "Position"); + bd->AttribLocationVtxUV = (GLuint)glGetAttribLocation(bd->ShaderHandle, "UV"); + bd->AttribLocationVtxColor = + (GLuint)glGetAttribLocation(bd->ShaderHandle, "Color"); + + // Create buffers + glGenBuffers(1, &bd->VboHandle); + glGenBuffers(1, &bd->ElementsHandle); + + ImGui_ImplOpenGL3_CreateFontsTexture(); + + // Restore modified GL state + glBindTexture(GL_TEXTURE_2D, last_texture); + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); +#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY + glBindVertexArray(last_vertex_array); +#endif + + return true; +} + +void ImGui_ImplOpenGL3_DestroyDeviceObjects() { + ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); + if (bd->VboHandle) { + glDeleteBuffers(1, &bd->VboHandle); + bd->VboHandle = 0; + } + if (bd->ElementsHandle) { + glDeleteBuffers(1, &bd->ElementsHandle); + bd->ElementsHandle = 0; + } + if (bd->ShaderHandle) { + glDeleteProgram(bd->ShaderHandle); + bd->ShaderHandle = 0; + } + ImGui_ImplOpenGL3_DestroyFontsTexture(); +} + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif diff --git a/customchar-ui/libs/imgui/imgui.cpp b/customchar-ui/libs/imgui/imgui.cpp new file mode 100644 index 0000000..5619791 --- /dev/null +++ b/customchar-ui/libs/imgui/imgui.cpp @@ -0,0 +1,15888 @@ +// dear imgui, 1.88 WIP +// (main code and documentation) + +// Help: +// - Read FAQ at http://dearimgui.org/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.org/faq +// - Homepage & latest https://github.com/ocornut/imgui +// - Releases & changelog https://github.com/ocornut/imgui/releases +// - Gallery https://github.com/ocornut/imgui/issues/5243 (please +// post your screenshots/video there!) +// - Wiki https://github.com/ocornut/imgui/wiki (lots of good +// stuff there) +// - Glossary https://github.com/ocornut/imgui/wiki/Glossary +// - Issues & support https://github.com/ocornut/imgui/issues + +// Getting Started? +// - For first-time users having issues compiling/linking/running or issues +// 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 +// invoiced technical support, maintenance and sponsoring contracts. Please +// reach out to "contact AT dearimgui.com". Individuals: you can support +// continued development via donations. See docs/README or web page. + +// 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 +- END-USER 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 + - USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS +- API BREAKING CHANGES (read me when you update!) +- FREQUENTLY ASKED QUESTIONS (FAQ) + - Read all answers online: https://www.dearimgui.org/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 +// [SECTION] ImGuiListClipper +// [SECTION] STYLING +// [SECTION] RENDER HELPERS +// [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] VIEWPORTS +// [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 for developers and content-creators, not the typical end-user! Some of + the current weaknesses includes: + + - Doesn't look fancy, doesn't animate. + - Limited layout features, intricate layouts are typically crafted in code. + + + END-USER GUIDE + ============== + + - Double-click on title bar to collapse window. + - Click upper right corner to close a window, available when 'bool* p_open' is + passed to ImGui::Begin(). + - Click and drag on lower right corner to resize window (double-click to auto + fit window to its contents). + - Click and drag on any empty space to move window. + - TAB/SHIFT+TAB to cycle through keyboard editable fields. + - CTRL+Click on a slider or drag box to input value as text. + - Use mouse wheel to scroll. + - Text editor: + - Hold SHIFT or use mouse to select text. + - CTRL+Left/Right to word jump. + - CTRL+Shift+Left/Right to select words. + - CTRL+A our Double-Click to select all. + - CTRL+X,CTRL+C,CTRL+V to use OS clipboard/ + - CTRL+Z,CTRL+Y to undo/redo. + - ESCAPE to revert text to its original value. + - Controls are automatically adjusted for OSX to match standard OSX text + editing operations. + - General Keyboard controls: enable with ImGuiConfigFlags_NavEnableKeyboard. + - General Gamepad controls: enable with ImGuiConfigFlags_NavEnableGamepad. See + suggested mappings in imgui.h ImGuiNavInput_ + download PNG/PSD at + http://dearimgui.org/controls_sheets + + + PROGRAMMER GUIDE + ================ + + READ FIRST + ---------- + - Remember to check the wonderful Wiki (https://github.com/ocornut/imgui/wiki) + - Your code creates the UI, 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. + - 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. + - This codebase is also optimized to yield decent performances with typical + "Debug" builds settings. + - 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. + - C++: this is a very C-ish codebase: we don't rely on C++11, we don't include + any C++ headers, and ImGui:: is a namespace. + - 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. However, imgui_internal.h can optionally export math operators for + ImVec2/ImVec4, which we use in this codebase. + - C++: pay attention that ImVector<> manipulates plain-old-data and does not + honor construction/destruction (avoid using it in your code!). + + + HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI + ---------------------------------------------- + - 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 + --------------------------------------------------------------- + - 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 = NULL; + 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 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); + } + } + } + } + + + USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS + ------------------------------------------ + - The gamepad/keyboard navigation is fairly functional and keeps being + improved. + - Gamepad support is particularly useful to use Dear ImGui on a console system + (e.g. PS4, Switch, XB1) without a mouse! + - You can ask questions and report issues at + https://github.com/ocornut/imgui/issues/787 + - The initial focus was to support game controllers, but keyboard is becoming + increasingly and decently usable. + - Keyboard: + - Application: Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to + enable. + - Internally: NewFrame() will automatically fill io.NavInputs[] based on + backend's io.AddKeyEvent() calls. + - When keyboard navigation is active (io.NavActive + + ImGuiConfigFlags_NavEnableKeyboard), the io.WantCaptureKeyboard flag will be + set. For more advanced uses, you may want to read from: + - 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 (and usually + goes false when mouse is used). + - or query focus information with e.g. + IsWindowFocused(ImGuiFocusedFlags_AnyWindow), IsItemFocused() etc. functions. + Please reach out if you think the game vs navigation input sharing could + be improved. + - Gamepad: + - Application: Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to + enable. + - Backend: 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.). + - Internally: NewFrame() will automatically fill io.NavInputs[] based on + backend's io.AddKeyEvent() + io.AddKeyAnalogEvent() calls. + - BEFORE 1.87, BACKENDS USED TO WRITE DIRECTLY TO io.NavInputs[]. This is + going to be obsoleted in the future. Please call io functions instead! + - You can download PNG/PSD files depicting the gamepad controls for common + controllers at: http://dearimgui.org/controls_sheets + - 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. + - Mouse: + - PS4/PS5 users: Consider emulating a mouse cursor with DualShock4 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) + uSynergy.c (on your console/tablet/phone app) to share your PC + mouse/keyboard. + - 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, imgui will misbehave as it will see your mouse + moving back and 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.) + + + 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. + + - 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. + - 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 ImGuiKey_ModXXX 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 + ImGuiKey_ModXXX 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.org/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/ and explore them. + - 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. + - Dozens of 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.org/faq for details. + + Q&A: Integration + ================ + + Q: How to get started? + A: 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.org/faq for a fully detailed answer. You really + want to read this. + + Q. How can I enable keyboard controls? + Q: How can I use this without a mouse, without a keyboard or without a screen? + (gamepad, 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.org/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 works? + Q: How can I use my own math types instead of ImVec2/ImVec4? + 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.org/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.org/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.org/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 can hire more people working on this project. + - Individuals: you can support continued development via PayPal donations. + See README. + - If you are experienced with Dear ImGui and C++, look at the GitHub issues, + look at the Wiki, read docs/TODO.txt 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 + +#include "imgui.h" +#ifndef IMGUI_DISABLE + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui_internal.h" + +// System includes +#include // toupper +#include // vsnprintf, sscanf, printf +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +// [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 + // a 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 an has_warning equivalent +// and some forks/patches may not following 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 +#define IMGUI_DEBUG_INI_SETTINGS \ + 0 // Save additional comments in .ini file (particularly helps for Docking, + // but makes saving slower) + +// 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 = + 2.00f; // Lock scrolled window (so it doesn't pick child windows that are + // scrolling through) for a certain time, unless mouse moved. + +//------------------------------------------------------------------------- +// [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 AddDrawListToDrawData(ImVector* out_list, + ImDrawList* draw_list); +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); +static void SetClipboardTextFn_DefaultImpl(void* user_data, 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); +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(); + +// Misc +static void UpdateSettings(); +static void UpdateKeyboardInputs(); +static void UpdateMouseInputs(); +static void UpdateMouseWheel(); +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, + 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(); +static ImGuiWindow* FindBlockingModal(ImGuiWindow* window); + +// Viewports +static void UpdateViewportsNewFrame(); + +} // namespace ImGui + +//----------------------------------------------------------------------------- +// [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 + 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 = + 10.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 appears 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. + 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. + + // 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; + 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"; + MouseDoubleClickTime = 0.30f; + MouseDoubleClickMaxDist = 6.0f; +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + for (int i = 0; i < ImGuiKey_COUNT; i++) KeyMap[i] = -1; +#endif + KeyRepeatDelay = 0.275f; + KeyRepeatRate = 0.050f; + UserData = NULL; + + Fonts = NULL; + FontGlobalScale = 1.0f; + FontDefault = NULL; + FontAllowUserScaling = false; + DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + + // 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; + ConfigWindowsResizeFromEdges = true; + ConfigWindowsMoveFromTitleBarOnly = false; + ConfigMemoryCompactTimer = 60.0f; + + // Platform Functions + BackendPlatformName = BackendRendererName = NULL; + BackendPlatformUserData = BackendRendererUserData = BackendLanguageUserData = + NULL; + GetClipboardTextFn = + GetClipboardTextFn_DefaultImpl; // Platform dependent default + // implementations + SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; + ClipboardUserData = NULL; + SetPlatformImeDataFn = SetPlatformImeDataFn_DefaultImpl; + + // Input (NB: we already have memset zero the entire structure!) + MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX); + MouseDragThreshold = 6.0f; + 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; + } + for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) + NavInputsDownDuration[i] = -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) { + ImGuiContext& g = *GImGui; + IM_ASSERT(&g.IO == this && "Can only add events to current context."); + if (c == 0 || !AppAcceptingEvents) return; + + ImGuiInputEvent e; + e.Type = ImGuiInputEventType_Text; + e.Source = ImGuiInputSource_Keyboard; + 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); + if (c != 0) AddInputCharacter(c); + } +} + +void ImGuiIO::ClearInputCharacters() { InputQueueCharacters.resize(0); } + +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 = ImGuiModFlags_None; + for (int n = 0; n < IM_ARRAYSIZE(NavInputsDownDuration); n++) + NavInputsDownDuration[n] = NavInputsDownDurationPrev[n] = -1.0f; +} + +// 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 +void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) { + // if (e->Down) { IMGUI_DEBUG_PRINT("AddKeyEvent() Key='%s' %d, NativeKeycode + // = %d, NativeScancode = %d\n", ImGui::GetKeyName(e->Key), e->Down, + // e->NativeKeycode, e->NativeScancode); } + if (key == ImGuiKey_None || !AppAcceptingEvents) return; + ImGuiContext& g = *GImGui; + IM_ASSERT(&g.IO == this && "Can only add events to current context."); + IM_ASSERT(ImGui::IsNamedKey( + key)); // Backend needs to pass a valid ImGuiKey_ constant. 0..511 values + // are legacy native key codes which are not accepted by this API. + + // 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; + + // Partial filter of duplicates (not strictly needed, but makes data neater in + // particular for key mods and gamepad values which are most commonly spmamed) + ImGuiKeyData* key_data = ImGui::GetKeyData(key); + if (key_data->Down == down && key_data->AnalogValue == analog_value) { + bool found = false; + for (int n = g.InputEventsQueue.Size - 1; n >= 0 && !found; n--) + if (g.InputEventsQueue[n].Type == ImGuiInputEventType_Key && + g.InputEventsQueue[n].Key.Key == key) + found = true; + if (!found) return; + } + + // Add event + ImGuiInputEvent e; + e.Type = ImGuiInputEventType_Key; + e.Source = ImGui::IsGamepadKey(key) ? ImGuiInputSource_Gamepad + : ImGuiInputSource_Keyboard; + 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(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(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) { + ImGuiContext& g = *GImGui; + IM_ASSERT(&g.IO == this && "Can only add events to current context."); + if (!AppAcceptingEvents) return; + + ImGuiInputEvent e; + e.Type = ImGuiInputEventType_MousePos; + e.Source = ImGuiInputSource_Mouse; + e.MousePos.PosX = x; + e.MousePos.PosY = y; + g.InputEventsQueue.push_back(e); +} + +void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down) { + ImGuiContext& g = *GImGui; + IM_ASSERT(&g.IO == this && "Can only add events to current context."); + IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT); + if (!AppAcceptingEvents) return; + + ImGuiInputEvent e; + e.Type = ImGuiInputEventType_MouseButton; + e.Source = ImGuiInputSource_Mouse; + e.MouseButton.Button = mouse_button; + e.MouseButton.Down = down; + g.InputEventsQueue.push_back(e); +} + +// Queue a mouse wheel event (most mouse/API will only have a Y component) +void ImGuiIO::AddMouseWheelEvent(float wheel_x, float wheel_y) { + ImGuiContext& g = *GImGui; + IM_ASSERT(&g.IO == this && "Can only add events to current context."); + if ((wheel_x == 0.0f && wheel_y == 0.0f) || !AppAcceptingEvents) return; + + ImGuiInputEvent e; + e.Type = ImGuiInputEventType_MouseWheel; + e.Source = ImGuiInputSource_Mouse; + e.MouseWheel.WheelX = wheel_x; + e.MouseWheel.WheelY = wheel_y; + g.InputEventsQueue.push_back(e); +} + +void ImGuiIO::AddFocusEvent(bool focused) { + ImGuiContext& g = *GImGui; + IM_ASSERT(&g.IO == this && "Can only add events to current context."); + + ImGuiInputEvent e; + e.Type = ImGuiInputEventType_Focus; + 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 = toupper(*str2) - toupper(*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 = toupper(*str2) - toupper(*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)toupper(*needle); + while ((!haystack_end && *haystack) || + (haystack_end && haystack < haystack_end)) { + if (toupper(*haystack) == un0) { + const char* b = needle + 1; + for (const char* a = haystack + 1; b < needle_end; a++, b++) + if (toupper(*a) != toupper(*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 +#define STB_SPRINTF_IMPLEMENTATION +#ifdef IMGUI_STB_SPRINTF_FILENAME +#include IMGUI_STB_SPRINTF_FILENAME +#else +#include "stb_sprintf.h" +#endif +#endif + +#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); + 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; + 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, ImU32 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, ImU32 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) +//----------------------------------------------------------------------------- + +// 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; + + 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); + if (c == 0) break; + *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); + if (c == 0) break; + 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; +} + +//----------------------------------------------------------------------------- +// [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 (int i = 0; i != Filters.Size; i++) { + ImGuiTextRange& f = Filters[i]; + 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 (Filters[i].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 (int i = 0; i != Filters.Size; i++) { + const ImGuiTextRange& f = Filters[i]; + 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 +//----------------------------------------------------------------------------- + +// 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); +} + +//----------------------------------------------------------------------------- +// [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 assume 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)); + ItemsCount = -1; +} + +ImGuiListClipper::~ImGuiListClipper() { End(); } + +// Use case A: Begin() called from constructor with items_height<0, then called +// again from Step() in StepNo 1 Use case B: Begin() called from constructor +// with items_height>0 +// FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are +// part of the legacy API we still support. This is why some of the code in +// Step() calling Begin() and reassign some fields, spaghetti style. +void ImGuiListClipper::Begin(int items_count, float items_height) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + 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() { + ImGuiContext& g = *GImGui; + 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. + 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::ForceDisplayRangeByIndices(int item_min, int item_max) { + 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_min <= item_max); + if (item_min < item_max) + data->Ranges.push_back( + ImGuiListClipperRange::FromIndices(item_min, item_max)); +} + +bool ImGuiListClipper::Step() { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiListClipperData* data = (ImGuiListClipperData*)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 (ItemsCount == 0 || GetSkipItemForListClipping()) + return (void)End(), 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) { + DisplayStart = data->ItemsFrozen; + DisplayEnd = data->ItemsFrozen + 1; + if (DisplayStart >= ItemsCount) return (void)End(), false; + 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) { + StartPosY = window->DC.CursorPos.y; + if (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)); + DisplayStart = ImMax(data->Ranges[0].Min, data->ItemsFrozen); + DisplayEnd = ImMin(data->Ranges[0].Max, ItemsCount); + if (DisplayStart == DisplayEnd) return (void)End(), false; + 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 (ItemsHeight <= 0.0f) { + IM_ASSERT(data->StepNo == 1); + if (table) + IM_ASSERT(table->RowPosY1 == StartPosY && + table->RowPosY2 == window->DC.CursorPos.y); + + ItemsHeight = (window->DC.CursorPos.y - StartPosY) / + (float)(DisplayEnd - DisplayStart); + bool affected_by_floating_point_precision = + ImIsFloatAboveGuaranteedIntegerPrecision(StartPosY) || + ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y); + if (affected_by_floating_point_precision) + ItemsHeight = + window->DC.PrevLineSize.y + + g.Style.ItemSpacing + .y; // FIXME: Technically wouldn't allow multi-line entries. + + IM_ASSERT(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 = DisplayEnd; + if (calc_clipping) { + if (g.LogEnabled) { + // If logging is active, do not perform any clipping + data->Ranges.push_back(ImGuiListClipperRange::FromIndices(0, 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_Tabbing) && + g.NavTabbingDir == -1) + data->Ranges.push_back( + ImGuiListClipperRange::FromIndices(ItemsCount - 1, 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 (int i = 0; i < data->Ranges.Size; i++) + if (data->Ranges[i].PosToIndexConvert) { + int m1 = (int)(((double)data->Ranges[i].Min - window->DC.CursorPos.y - + data->LossynessOffset) / + ItemsHeight); + int m2 = (int)((((double)data->Ranges[i].Max - window->DC.CursorPos.y - + data->LossynessOffset) / + ItemsHeight) + + 0.999999f); + data->Ranges[i].Min = ImClamp( + already_submitted + m1 + data->Ranges[i].PosToIndexOffsetMin, + already_submitted, ItemsCount - 1); + data->Ranges[i].Max = ImClamp( + already_submitted + m2 + data->Ranges[i].PosToIndexOffsetMax, + data->Ranges[i].Min + 1, ItemsCount); + data->Ranges[i].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. + if (data->StepNo < data->Ranges.Size) { + DisplayStart = ImMax(data->Ranges[data->StepNo].Min, already_submitted); + DisplayEnd = ImMin(data->Ranges[data->StepNo].Max, ItemsCount); + if (DisplayStart > already_submitted) //-V1051 + ImGuiListClipper_SeekCursorForItem(this, DisplayStart); + data->StepNo++; + 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 (ItemsCount < INT_MAX) + ImGuiListClipper_SeekCursorForItem(this, ItemsCount); + + End(); + return false; +} + +//----------------------------------------------------------------------------- +// [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; + while (count > 0) { + ImGuiColorMod& backup = g.ColorStack.back(); + g.Style.Colors[backup.Col] = backup.BackupValue; + g.ColorStack.pop_back(); + count--; + } +} + +struct ImGuiStyleVarInfo { + ImGuiDataType Type; + ImU32 Count; + ImU32 Offset; + void* GetVarPtr(ImGuiStyle* style) const { + return (void*)((unsigned char*)style + Offset); + } +}; + +static const ImGuiStyleVarInfo 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 +}; + +static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx) { + IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT); + IM_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_COUNT); + return &GStyleVarInfo[idx]; +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) { + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) { + ImGuiContext& g = *GImGui; + float* pvar = (float*)var_info->GetVarPtr(&g.Style); + g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0 && + "Called PushStyleVar() float variant but variable is not a float!"); +} + +void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) { + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2) { + ImGuiContext& g = *GImGui; + ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); + g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT( + 0 && + "Called PushStyleVar() ImVec2 variant but variable is not a ImVec2!"); +} + +void ImGui::PopStyleVar(int count) { + ImGuiContext& g = *GImGui; + 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 ImGuiStyleVarInfo* 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) +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 char* text_end_ellipsis = NULL; + + ImWchar ellipsis_char = font->EllipsisChar; + int ellipsis_char_count = 1; + if (ellipsis_char == (ImWchar)-1) { + ellipsis_char = font->DotChar; + ellipsis_char_count = 3; + } + const ImFontGlyph* glyph = font->FindGlyph(ellipsis_char); + + float ellipsis_glyph_width = + glyph->X1; // Width of the glyph with no padding on either side + float ellipsis_total_width = + ellipsis_glyph_width; // Full width of entire ellipsis + + if (ellipsis_char_count > 1) { + // Full ellipsis size without free spacing after it. + const float spacing_between_dots = + 1.0f * (draw_list->_Data->FontSize / font->FontSize); + ellipsis_glyph_width = glyph->X1 - glyph->X0 + spacing_between_dots; + ellipsis_total_width = ellipsis_glyph_width * (float)ellipsis_char_count - + spacing_between_dots; + } + + // 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_total_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)); + float ellipsis_x = pos_min.x + text_size_clipped_x; + if (ellipsis_x + ellipsis_total_width <= ellipsis_max_x) + for (int i = 0; i < ellipsis_char_count; i++) { + font->RenderChar(draw_list, font_size, ImVec2(ellipsis_x, pos_min.y), + GetColorU32(ImGuiCol_Text), ellipsis_char); + ellipsis_x += ellipsis_glyph_width; + } + } 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); + for (int n = 0; n < g.Viewports.Size; n++) { + ImGuiViewportP* viewport = g.Viewports[n]; + ImDrawList* draw_list = GetForegroundDrawList(viewport); + ImFontAtlas* font_atlas = draw_list->_Data->Font->ContainerAtlas; + ImVec2 offset, size, uv[4]; + if (font_atlas->GetMouseCursorTexData(mouse_cursor, &offset, &size, &uv[0], + &uv[2])) { + const ImVec2 pos = base_pos - offset; + const float scale = base_scale; + 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] 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* context, const char* name) + : DrawListInst(NULL) { + memset(this, 0, sizeof(*this)); + 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 = + ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | + ImGuiCond_Appearing; + SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); + LastFrameActive = -1; + LastTimeActive = -1.0f; + FontWindowScale = 1.0f; + SettingsOffset = -1; + DrawList = &DrawListInst; + DrawList->_Data = &context->DrawListSharedData; + DrawList->_OwnerName = Name; +} + +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 = *GImGui; + 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 = *GImGui; + 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 = *GImGui; + 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(); +} + +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; + g.ActiveIdIsJustActivated = (g.ActiveId != id); + if (g.ActiveIdIsJustActivated) { + IMGUI_DEBUG_LOG_ACTIVEID( + "[activeid] SetActiveID(0x%08X) in Window \"%s\"\n", 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.NavActivateInputId == id || + g.NavJustMovedToId == id) + ? (ImGuiInputSource)ImGuiInputSource_Nav + : ImGuiInputSource_Mouse; + } + + // 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.ActiveIdUsingMouseWheel = false; + g.ActiveIdUsingNavDirMask = 0x00; + g.ActiveIdUsingNavInputMask = 0x00; + g.ActiveIdUsingKeyInputMask.ClearAllBits(); +} + +void ImGui::ClearActiveID() { + SetActiveID(0, NULL); // g.ActiveId = 0; +} + +void ImGui::SetHoveredID(ImGuiID id) { + ImGuiContext& g = *GImGui; + g.HoveredId = id; + g.HoveredIdAllowOverlap = false; + g.HoveredIdUsingMouseWheel = 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 need to fill the data. + ImGuiContext& g = *GImGui; + IM_ASSERT(g.ActiveId == id || g.ActiveId == 0 || g.DragDropActive); + IM_UNUSED( + id); // Avoid unused variable warnings when asserts are compiled out. + // IM_ASSERT(g.CurrentWindow->DC.LastItemId == id); + g.ActiveIdHasBeenEditedThisFrame = true; + g.ActiveIdHasBeenEditedBefore = true; + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; +} + +static inline bool 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 order of those two tests is important because + // Modal windows are also Popups. + if (focused_root_window->Flags & ImGuiWindowFlags_Modal) return false; + if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && + !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + return false; + } + return true; +} + +// 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; + if (g.NavDisableMouseHover && !g.NavDisableHighlight && + !(flags & ImGuiHoveredFlags_NoNavOverride)) { + if ((g.LastItemData.InFlags & ImGuiItemFlags_Disabled) && + !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) + return false; + if (!IsItemFocused()) return false; + } else { + // Test for bounding box overlap, as updated as ItemAdd() + ImGuiItemStatusFlags status_flags = g.LastItemData.StatusFlags; + if (!(status_flags & ImGuiItemStatusFlags_HoveredRect)) return false; + IM_ASSERT( + (flags & (ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_RootWindow | + ImGuiHoveredFlags_ChildWindows | + ImGuiHoveredFlags_NoPopupHierarchy)) == + 0); // Flags not supported by this function + + // 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_AllowWhenOverlapped) == 0) return false; + + // Test if another item is active (e.g. being dragged) + if ((flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) == 0) + if (g.ActiveId != 0 && g.ActiveId != g.LastItemData.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)) 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 collapsed (SkipItems==true) that last item + // will never be overwritten so we need to detect the case. + if (g.LastItemData.ID == window->MoveId && window->WriteAccessed) + return false; + } + + return true; +} + +// Internal facing ItemHoverable() used when submitting widgets. Differs +// slightly from IsItemHovered(). +bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) { + ImGuiContext& g = *GImGui; + if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (g.HoveredWindow != window) return false; + if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) + return false; + if (!IsMouseHoveringRect(bb.Min, bb.Max)) return false; + if (!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) SetHoveredID(id); + + // When disabled we'll return false but still set HoveredId + ImGuiItemFlags item_flags = + (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags); + if (item_flags & ImGuiItemFlags_Disabled) { + // Release active id if turning disabled + if (g.ActiveId == id) 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 + // perform the test in ItemAdd(), but that would incur a small runtime cost. + // #define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX in imconfig.h if you want this + // check to also be performed in ItemAdd(). + 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; +} + +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 +// window->DC.LastItemDisplayRect! +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 = 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; } + +// 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); +} + +// 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 (int n = 0; n < g.Hooks.Size; n++) + if (g.Hooks[n].HookId == hook_id) + g.Hooks[n].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 (int n = 0; n < g.Hooks.Size; n++) + if (g.Hooks[n].Type == hook_type) g.Hooks[n].Callback(&g, &g.Hooks[n]); +} + +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* GetViewportDrawList(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->DrawLists)); + ImDrawList* draw_list = viewport->DrawLists[drawlist_no]; + if (draw_list == NULL) { + draw_list = IM_NEW(ImDrawList)(&g.DrawListSharedData); + draw_list->_OwnerName = drawlist_name; + viewport->DrawLists[drawlist_no] = draw_list; + } + + // Our ImDrawList system requires that there is always a command + if (viewport->DrawListsLastFrame[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->DrawListsLastFrame[drawlist_no] = g.FrameCount; + } + return draw_list; +} + +ImDrawList* ImGui::GetBackgroundDrawList(ImGuiViewport* viewport) { + return GetViewportDrawList((ImGuiViewportP*)viewport, 0, "##Background"); +} + +ImDrawList* ImGui::GetBackgroundDrawList() { + ImGuiContext& g = *GImGui; + return GetBackgroundDrawList(g.Viewports[0]); +} + +ImDrawList* ImGui::GetForegroundDrawList(ImGuiViewport* viewport) { + return GetViewportDrawList((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; + SetActiveIdUsingNavAndKeys(); + + 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; + if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y) { + MarkIniSettingsDirty(moving_window); + 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 && + GetTopMostPopupModal() == NULL) { + // Clicking on void disable focus + FocusWindow(NULL); + } + } + + // 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); +} + +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(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) { + io.KeysData[ImGuiKey_ModCtrl].Down = io.KeyCtrl; + io.KeysData[ImGuiKey_ModShift].Down = io.KeyShift; + io.KeysData[ImGuiKey_ModAlt].Down = io.KeyAlt; + io.KeysData[ImGuiKey_ModSuper].Down = io.KeySuper; + } + } +#endif + + // Synchronize io.KeyMods with individual modifiers io.KeyXXX bools + io.KeyMods = GetMergedModFlags(); + + // 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 < IM_ARRAYSIZE(io.KeysData); 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; + } +} + +static void ImGui::UpdateMouseInputs() { + ImGuiContext& g = *GImGui; + ImGuiIO& io = g.IO; + + // 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); + + // 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 StartLockWheelingWindow(ImGuiWindow* window) { + ImGuiContext& g = *GImGui; + if (g.WheelingWindow == window) return; + g.WheelingWindow = window; + g.WheelingWindowRefMousePos = g.IO.MousePos; + g.WheelingWindowTimer = WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER; +} + +void ImGui::UpdateMouseWheel() { + ImGuiContext& g = *GImGui; + + // Reset the locked window if we move the mouse or after the timer elapses + if (g.WheelingWindow != NULL) { + g.WheelingWindowTimer -= g.IO.DeltaTime; + if (IsMousePosValid() && + ImLengthSqr(g.IO.MousePos - g.WheelingWindowRefMousePos) > + g.IO.MouseDragThreshold * g.IO.MouseDragThreshold) + g.WheelingWindowTimer = 0.0f; + if (g.WheelingWindowTimer <= 0.0f) { + g.WheelingWindow = NULL; + g.WheelingWindowTimer = 0.0f; + } + } + + float wheel_x = g.IO.MouseWheelH; + float wheel_y = g.IO.MouseWheel; + if (wheel_x == 0.0f && wheel_y == 0.0f) return; + + if ((g.ActiveId != 0 && g.ActiveIdUsingMouseWheel) || + (g.HoveredIdPreviousFrame != 0 && + g.HoveredIdPreviousFrameUsingMouseWheel)) + return; + + ImGuiWindow* window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow; + if (!window || 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) { + StartLockWheelingWindow(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; + } + + // Mouse wheel scrolling + // If a child window has the ImGuiWindowFlags_NoScrollWithMouse flag, we give + // a chance to scroll its parent + if (g.IO.KeyCtrl) return; + + // 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) + const bool swap_axis = g.IO.KeyShift && !g.IO.ConfigMacOSXBehaviors; + if (swap_axis) { + wheel_x = wheel_y; + wheel_y = 0.0f; + } + + // Vertical Mouse Wheel scrolling + if (wheel_y != 0.0f) { + StartLockWheelingWindow(window); + while ((window->Flags & ImGuiWindowFlags_ChildWindow) && + ((window->ScrollMax.y == 0.0f) || + ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && + !(window->Flags & ImGuiWindowFlags_NoMouseInputs)))) + window = window->ParentWindow; + if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && + !(window->Flags & ImGuiWindowFlags_NoMouseInputs)) { + 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); + } + } + + // Horizontal Mouse Wheel scrolling, or Vertical Mouse Wheel w/ Shift held + if (wheel_x != 0.0f) { + StartLockWheelingWindow(window); + while ((window->Flags & ImGuiWindowFlags_ChildWindow) && + ((window->ScrollMax.x == 0.0f) || + ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && + !(window->Flags & ImGuiWindowFlags_NoMouseInputs)))) + window = window->ParentWindow; + if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && + !(window->Flags & ImGuiWindowFlags_NoMouseInputs)) { + 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); + } + } +} + +// 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; +} + +// [Internal] Do not use directly (can read io.KeyMods instead) +ImGuiModFlags ImGui::GetMergedModFlags() { + ImGuiContext& g = *GImGui; + ImGuiModFlags key_mods = ImGuiModFlags_None; + if (g.IO.KeyCtrl) { + key_mods |= ImGuiModFlags_Ctrl; + } + if (g.IO.KeyShift) { + key_mods |= ImGuiModFlags_Shift; + } + if (g.IO.KeyAlt) { + key_mods |= ImGuiModFlags_Alt; + } + if (g.IO.KeySuper) { + key_mods |= ImGuiModFlags_Super; + } + return key_mods; +} + +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; + + 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 (int n = 0; n < g.Viewports.Size; n++) + virtual_space.Add(g.Viewports[n]->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 (int n = 0; n < g.Viewports.Size; n++) { + ImGuiViewportP* viewport = g.Viewports[n]; + viewport->DrawDataP.Clear(); + } + + // 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.HoveredIdPreviousFrameUsingMouseWheel = g.HoveredIdUsingMouseWheel; + g.HoveredId = 0; + g.HoveredIdAllowOverlap = false; + g.HoveredIdUsingMouseWheel = false; + g.HoveredIdDisabled = false; + + // Update ActiveId data (clear reference to active widget if the widget isn't + // alive anymore) + if (g.ActiveIdIsAlive != g.ActiveId && + g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0) + ClearActiveID(); + 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.ActiveIdUsingNavInputMask = 0x00; + g.ActiveIdUsingKeyInputMask.ClearAllBits(); + } + + // 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(); + + // Process input queue (trickle as many events as possible) + g.InputEventsTrail.resize(0); + UpdateInputEvents(g.IO.ConfigInputTrickleEventQueue); + + // 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 (int i = 0; i != g.Windows.Size; i++) { + ImGuiWindow* window = g.Windows[i]; + window->WasActive = window->Active; + window->BeginCount = 0; + window->Active = false; + window->WriteAccessed = false; + + // 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 (int i = 0; i < g.TablesTempData.Size; i++) + if (g.TablesTempData[i].LastTimeActive >= 0.0f && + g.TablesTempData[i].LastTimeActive < memory_compact_start_time) + TableGcCompactTransientBuffers(&g.TablesTempData[i]); + 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); + + // 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(); + + // 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 avoid ImGui:: calls from crashing. + g.WithinFrameScopeWithImplicitWindow = true; + SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver); + Begin("Debug##Default"); + IM_ASSERT(g.CurrentWindow->IsFallbackWindow == true); + + CallContextHooks(&g, ImGuiContextHookType_NewFramePost); +} + +void ImGui::Initialize() { + ImGuiContext& g = *GImGui; + IM_ASSERT(!g.Initialized && !g.SettingsLoaded); + + // Add .ini handle for ImGuiWindow type + { + 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); + } + + // Add .ini handle for ImGuiTable type + TableSettingsAddSettingsHandler(); + + // 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; + + // 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.ColorStack.clear(); + g.StyleVarStack.clear(); + g.FontStack.clear(); + g.OpenPopupStack.clear(); + g.BeginPopupStack.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.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.Initialized = false; +} + +// 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 AddDrawListToDrawData(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 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"); + + out_list->push_back(draw_list); +} + +static void AddWindowToDrawData(ImGuiWindow* window, int layer) { + ImGuiContext& g = *GImGui; + ImGuiViewportP* viewport = g.Viewports[0]; + g.IO.MetricsRenderWindows++; + AddDrawListToDrawData(&viewport->DrawDataBuilder.Layers[layer], + window->DrawList); + for (int i = 0; i < window->DC.ChildWindows.Size; i++) { + ImGuiWindow* child = window->DC.ChildWindows[i]; + 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)); +} + +void ImDrawDataBuilder::FlattenIntoSingleLayer() { + int n = Layers[0].Size; + int size = n; + for (int i = 1; i < IM_ARRAYSIZE(Layers); i++) size += Layers[i].Size; + Layers[0].resize(size); + for (int layer_n = 1; layer_n < IM_ARRAYSIZE(Layers); layer_n++) { + ImVector& layer = Layers[layer_n]; + if (layer.empty()) continue; + memcpy(&Layers[0][n], &layer[0], layer.Size * sizeof(ImDrawList*)); + n += layer.Size; + layer.resize(0); + } +} + +static void SetupViewportDrawData(ImGuiViewportP* viewport, + ImVector* draw_lists) { + ImGuiIO& io = ImGui::GetIO(); + ImDrawData* draw_data = &viewport->DrawDataP; + draw_data->Valid = true; + draw_data->CmdLists = (draw_lists->Size > 0) ? draw_lists->Data : NULL; + draw_data->CmdListsCount = draw_lists->Size; + draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0; + draw_data->DisplayPos = viewport->Pos; + draw_data->DisplaySize = viewport->Size; + draw_data->FramebufferScale = io.DisplayFramebufferScale; + for (int n = 0; n < draw_lists->Size; n++) { + ImDrawList* draw_list = draw_lists->Data[n]; + draw_list->_PopUnusedDrawCmd(); + draw_data->TotalVtxCount += draw_list->VtxBuffer.Size; + draw_data->TotalIdxCount += draw_list->IdxBuffer.Size; + } +} + +// 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); // Ensure ImDrawCmd are not merged + 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->PopClipRect(); + draw_list->AddDrawCmd(); // We need to create a command as + // CmdBuffer.back().IdxOffset won't be correct if + // we append to same command. + } +} + +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) + if (g.IO.SetPlatformImeDataFn && + memcmp(&g.PlatformImeData, &g.PlatformImeDataPrev, + sizeof(ImGuiPlatformImeData)) != 0) + g.IO.SetPlatformImeDataFn(GetMainViewport(), &g.PlatformImeData); + + // 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 (int i = 0; i != g.Windows.Size; i++) { + ImGuiWindow* window = g.Windows[i]; + 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.MouseWheel = g.IO.MouseWheelH = 0.0f; + g.IO.InputQueueCharacters.resize(0); + memset(g.IO.NavInputs, 0, sizeof(g.IO.NavInputs)); + + 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(); + const bool first_render_of_frame = (g.FrameCountRendered != g.FrameCount); + g.FrameCountRendered = g.FrameCount; + g.IO.MetricsRenderWindows = 0; + + CallContextHooks(&g, ImGuiContextHookType_RenderPre); + + // Add background ImDrawList (for each active viewport) + for (int n = 0; n != g.Viewports.Size; n++) { + ImGuiViewportP* viewport = g.Viewports[n]; + viewport->DrawDataBuilder.Clear(); + if (viewport->DrawLists[0] != NULL) + AddDrawListToDrawData(&viewport->DrawDataBuilder.Layers[0], + GetBackgroundDrawList(viewport)); + } + + // Draw modal/window whitening backgrounds + if (first_render_of_frame) RenderDimmedBackgrounds(); + + // Add ImDrawList to render + ImGuiWindow* windows_to_render_top_most[2]; + windows_to_render_top_most[0] = + (g.NavWindowingTarget && + !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) + ? g.NavWindowingTarget->RootWindow + : NULL; + windows_to_render_top_most[1] = + (g.NavWindowingTarget ? g.NavWindowingListWindow : NULL); + for (int n = 0; n != g.Windows.Size; n++) { + ImGuiWindow* window = g.Windows[n]; + 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 && first_render_of_frame && + 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 (int n = 0; n < g.Viewports.Size; n++) { + ImGuiViewportP* viewport = g.Viewports[n]; + viewport->DrawDataBuilder.FlattenIntoSingleLayer(); + + // Add foreground ImDrawList (for each active viewport) + if (viewport->DrawLists[1] != NULL) + AddDrawListToDrawData(&viewport->DrawDataBuilder.Layers[0], + GetForegroundDrawList(viewport)); + + SetupViewportDrawData(viewport, &viewport->DrawDataBuilder.Layers[0]); + ImDrawData* draw_data = &viewport->DrawDataP; + 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.CurrentWindow->ClipRect.Overlaps(g.LastItemData.Rect); +} + +bool ImGui::IsItemEdited() { + ImGuiContext& g = *GImGui; + return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Edited) != 0; +} + +// 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: Although this is exposed, its interaction and ideal idiom with using +// ImGuiButtonFlags_AllowItemOverlap flag are extremely confusing, need rework. +void ImGui::SetItemAllowOverlap() { + ImGuiContext& g = *GImGui; + ImGuiID id = g.LastItemData.ID; + if (g.HoveredId == id) g.HoveredIdAllowOverlap = true; + if (g.ActiveId == id) g.ActiveIdAllowOverlap = true; +} + +void ImGui::SetItemUsingMouseWheel() { + ImGuiContext& g = *GImGui; + ImGuiID id = g.LastItemData.ID; + if (g.HoveredId == id) g.HoveredIdUsingMouseWheel = true; + if (g.ActiveId == id) g.ActiveIdUsingMouseWheel = true; +} + +void ImGui::SetActiveIdUsingNavAndKeys() { + ImGuiContext& g = *GImGui; + IM_ASSERT(g.ActiveId != 0); + g.ActiveIdUsingNavDirMask = ~(ImU32)0; + g.ActiveIdUsingNavInputMask = ~(ImU32)0; + g.ActiveIdUsingKeyInputMask.SetAllBits(); + NavMoveRequestCancel(); +} + +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 much 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 + if (g.NavActivateId == id && !(flags & ImGuiWindowFlags_NavFlattened) && + (child_window->DC.NavLayersActiveMask != 0 || + child_window->DC.NavHasScroll)) { + FocusWindow(child_window); + NavInitWindow(child_window, false); + SetActiveID(id + 1, + child_window); // Steal ActiveId with another arbitrary id so + // that key-press won't activate child item + g.ActiveIdSource = ImGuiInputSource_Nav; + } + 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.NavHasScroll) && + !(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); + } + 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; + 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 ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags) { + ImGuiContext& g = *GImGui; + // IMGUI_DEBUG_PRINT("CreateNewWindow '%s', flags = 0x%08X\n", name, flags); + + // Create window the first time + ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name); + window->Flags = flags; + g.WindowsById.SetVoidPtr(window->ID, window); + + // 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); + + // User can disable loading and saving of settings. Tooltip and child windows + // also don't store settings. + if (!(flags & ImGuiWindowFlags_NoSavedSettings)) + if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID)) { + // Retrieve settings from .ini file + window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings); + 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 ((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); + } + + if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus) + g.Windows.push_front(window); // Quite slow but rare and only once + else + g.Windows.push_back(window); + UpdateWindowInFocusOrderList(window, true, window->Flags); + + 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; + const float decoration_up_height = window_for_height->TitleBarHeight() + + window_for_height->MenuBarHeight(); + new_size = ImMax(new_size, g.Style.WindowMinSize); + new_size.y = ImMax( + new_size.y, + decoration_up_height + + ImMax(0.0f, g.Style.WindowRounding - + 1.0f)); // 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_up_height = + window->TitleBarHeight() + window->MenuBarHeight(); + ImVec2 size_pad = window->WindowPadding * 2.0f; + ImVec2 size_desired = + size_contents + size_pad + ImVec2(0.0f, decoration_up_height); + 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)); + + // FIXME-VIEWPORT-WORKAREA: May want to use GetWorkSize() instead of Size + // depending on the type of windows? + ImVec2 avail_size = ImGui::GetMainViewport()->Size; + 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 - 0.0f < + 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_up_height < + 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. an 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; + + 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() + KeepAliveID(resize_grip_id); + 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 ? visibility_rect.Min.x : -FLT_MAX, + def.CornerPosN.y == 1.0f ? visibility_rect.Min.y : -FLT_MAX); + ImVec2 clamp_max = + ImVec2(def.CornerPosN.x == 0.0f ? visibility_rect.Max.x : +FLT_MAX, + def.CornerPosN.y == 0.0f ? visibility_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() + KeepAliveID(border_id); + 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 ? visibility_rect.Min.x : -FLT_MAX, + border_n == ImGuiDir_Down ? visibility_rect.Min.y : -FLT_MAX); + ImVec2 clamp_max( + border_n == ImGuiDir_Left ? visibility_rect.Max.x : +FLT_MAX, + border_n == ImGuiDir_Up ? visibility_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) + if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindow == window) { + ImVec2 nav_resize_delta; + if (g.NavInputSource == ImGuiInputSource_Keyboard && g.IO.KeyShift) + nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_RawKeyboard, + ImGuiNavReadMode_Down); + if (g.NavInputSource == ImGuiInputSource_Gamepad) + nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadDPad, + ImGuiNavReadMode_Down); + if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f) { + const float NAV_RESIZE_SPEED = 600.0f; + nav_resize_delta *= ImFloor(NAV_RESIZE_SPEED * g.IO.DeltaTime * + ImMin(g.IO.DisplayFramebufferScale.x, + g.IO.DisplayFramebufferScale.y)); + nav_resize_delta = ImMax( + nav_resize_delta, visibility_rect.Min - window->Pos - window->Size); + g.NavWindowingToggleLayer = false; + g.NavDisableMouseHover = true; + resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive); + // 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 + nav_resize_delta); + } + } + + // 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 ClampWindowRect(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, + 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 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 + 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 (!(flags & ImGuiWindowFlags_NoResize)) { + for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; + resize_grip_n++) { + 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(resize_grip_col[resize_grip_n]); + } + } + + // Borders + 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) { + pad_r += button_sz; + close_button_pos = + ImVec2(title_bar_rect.Max.x - pad_r - style.FramePadding.x, + title_bar_rect.Min.y); + } + if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Right) { + pad_r += button_sz; + collapse_button_pos = + ImVec2(title_bar_rect.Max.x - pad_r - style.FramePadding.x, + title_bar_rect.Min.y); + } + if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Left) { + collapse_button_pos = + ImVec2(title_bar_rect.Min.x + pad_l - style.FramePadding.x, + title_bar_rect.Min.y); + pad_l += button_sz; + } + + // 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. +// - Window // FindBlockingModal() returns Modal1 +// - Window // .. returns Modal1 +// - Modal1 // .. returns Modal2 +// - Window // .. returns Modal2 +// - Window // .. returns Modal2 +// - Modal2 // .. returns Modal2 +static 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 (int i = g.OpenPopupStack.Size - 1; i >= 0; i--) { + ImGuiWindow* popup_window = g.OpenPopupStack.Data[i].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 (IsWindowWithinBeginStackOf( + window, popup_window)) // Window is rendered over last modal, no + // render order change needed. + break; + for (ImGuiWindow* parent = + popup_window->ParentWindowInBeginStack->RootWindow; + parent != NULL; parent = parent->ParentWindowInBeginStack->RootWindow) + if (IsWindowWithinBeginStackOf(window, parent)) + return popup_window; // Place window above its begin stack parent. + } + 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); + else + UpdateWindowInFocusOrderList(window, window_just_created, 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) { + 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.SetToCurrentState(); + g.CurrentWindowStack.push_back(window_stack_data); + g.CurrentWindow = NULL; + if (flags & ImGuiWindowFlags_ChildMenu) g.BeginMenuCount++; + + 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; + } + + // 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; + } + + // 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; + + // 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; + MarkIniSettingsDirty(window); + } + } else { + window->Collapsed = false; + } + window->WantCollapseToggle = false; + + // SIZE + + // Calculate auto-fit size, handle automatic resize + const ImVec2 size_auto_fit = + CalcWindowAutoFitSize(window, window->ContentSizeIdeal); + bool use_current_size_for_scrollbar_x = window_just_created; + bool use_current_size_for_scrollbar_y = window_just_created; + 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; + + // Decoration size + const float decoration_up_height = + window->TitleBarHeight() + window->MenuBarHeight(); + + // 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) && + window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) + if (viewport_rect.GetWidth() > 0.0f && viewport_rect.GetHeight() > 0.0f) + ClampWindowRect(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; + + ImGuiWindow* modal = GetTopMostPopupModal(); + if (modal != NULL && !IsWindowWithinBeginStackOf(window, modal)) { + // Avoid focusing a window that is created outside of active modal. This + // will prevent active modal from being closed. Since window is not + // focused it would reappear at the same display position like the last + // time it was visible. In case of completely new windows it would go to + // the top (over current modal), but input to such window would still be + // blocked by modal. Position window behind a modal that is not a + // begin-parent of this window. + want_focus = false; + if (window == window->RootWindow) { + ImGuiWindow* blocking_modal = FindBlockingModal(window); + IM_ASSERT(blocking_modal != NULL); + BringWindowToDisplayBehind(window, blocking_modal); + } + } + } + + // [Test Engine] Register whole window in the item system +#ifdef IMGUI_ENABLE_TEST_ENGINE + if (g.TestEngineHookItems) { + IM_ASSERT(window->IDStack.Size == 1); + window->IDStack.Size = 0; + IMGUI_TEST_ENGINE_ITEM_ADD(window->Rect(), window->ID); + 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. When we use InnerRect here we are intentionally + // reading last frame size, same for ScrollbarSizes values before we set + // them again. + ImVec2 avail_size_from_current_frame = + ImVec2(window->SizeFull.x, window->SizeFull.y - decoration_up_height); + ImVec2 avail_size_from_last_frame = + window->InnerRect.GetSize() + window->ScrollbarSizes; + 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); + } + + // UPDATE RECTANGLES (1- THOSE NOT AFFECTED BY SCROLLING) + // Update various regions. Variables they depends 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->InnerRect.Min.y = window->Pos.y + decoration_up_height; + window->InnerRect.Max.x = + window->Pos.x + window->Size.x - window->ScrollbarSizes.x; + window->InnerRect.Max.y = + window->Pos.y + window->Size.y - window->ScrollbarSizes.y; + + // 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); + + // 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); + RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, + 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->ScrollbarSizes.x)); + 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 - + decoration_up_height - window->ScrollbarSizes.y)); + 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. + // Used by: + // - Mouse wheel scrolling + many other things + window->ContentRegionRect.Min.x = + window->Pos.x - window->Scroll.x + window->WindowPadding.x; + window->ContentRegionRect.Min.y = window->Pos.y - window->Scroll.y + + window->WindowPadding.y + + decoration_up_height; + 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->ScrollbarSizes.x)); + window->ContentRegionRect.Max.y = + window->ContentRegionRect.Min.y + + (window->ContentSizeExplicit.y != 0.0f + ? window->ContentSizeExplicit.y + : (window->Size.y - window->WindowPadding.y * 2.0f - + decoration_up_height - window->ScrollbarSizes.y)); + + // 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 = 0.0f + 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->DC.ColumnsOffset.x; + double start_pos_highp_y = (double)window->Pos.y + window->WindowPadding.y - + (double)window->Scroll.y + decoration_up_height; + 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 = false; + + window->DC.NavLayerCurrent = ImGuiNavLayer_Main; + window->DC.NavLayersActiveMask = window->DC.NavLayersActiveMaskNext; + window->DC.NavLayersActiveMaskNext = 0x00; + window->DC.NavHideHighlightOneFrame = false; + window->DC.NavHasScroll = (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) + if (want_focus) { + FocusWindow(window); + 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 && IsKeyPressedMap(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); + + // [Test Engine] Register title bar / tab + if (!(window->Flags & ImGuiWindowFlags_NoTitleBar)) + IMGUI_TEST_ENGINE_ITEM_ADD(g.LastItemData.Rect, g.LastItemData.ID); + } else { + // Append + SetCurrentWindow(window); + } + + // Pull/inherit current state + window->DC.NavFocusScopeIdCurrent = + (flags & ImGuiWindowFlags_ChildWindow) + ? parent_window->DC.NavFocusScopeIdCurrent + : window->GetID("#FOCUSSCOPE"); // Inherit from parent only // -V595 + + 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; + } + + 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 + + // Stop logging + if (!(window->Flags & + ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of + // logging + LogFinish(); + + // 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.CompareWithCurrentState(); + 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) { + ImGuiContext& g = *GImGui; + + 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 = 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) { + ImGuiContext& g = *GImGui; + 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) + if ((window->Flags & + (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) != + (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) { + ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(window); + FocusWindow(focus_window); + return; + } + } + FocusWindow(NULL); +} + +// 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(); +} + +// FIXME: Look into renaming this once we have settled the new +// Focus/Activation/TabStop system. +void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) { + PushItemFlag(ImGuiItemFlags_NoTabStop, !allow_keyboard_focus); +} + +void ImGui::PopAllowKeyboardFocus() { 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_AllowWhenOverlapped | + ImGuiHoveredFlags_AllowWhenDisabled)) == + 0); // Flags not supported by this function + 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; + 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; + 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 + if (size.x > 0.0f) { + window->AutoFitFramesX = 0; + window->SizeFull.x = IM_FLOOR(size.x); + } else { + window->AutoFitFramesX = 2; + window->AutoFitOnlyGrows = false; + } + if (size.y > 0.0f) { + window->AutoFitFramesY = 0; + window->SizeFull.y = IM_FLOOR(size.y); + } else { + window->AutoFitFramesY = 2; + window->AutoFitOnlyGrows = false; + } +} + +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::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::ActivateItem(ImGuiID id) { + ImGuiContext& g = *GImGui; + g.NavNextActivateId = id; + g.NavNextActivateFlags = ImGuiActivateFlags_None; +} + +void ImGui::PushFocusScope(ImGuiID id) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + g.FocusScopeStack.push_back(window->DC.NavFocusScopeIdCurrent); + window->DC.NavFocusScopeIdCurrent = id; +} + +void ImGui::PopFocusScope() { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(g.FocusScopeStack.Size > 0); // Too many PopFocusScope() ? + window->DC.NavFocusScopeIdCurrent = g.FocusScopeStack.back(); + g.FocusScopeStack.pop_back(); +} + +void ImGui::SetKeyboardFocusHere(int offset) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(offset >= -1); // -1 is allowed but not below + + SetNavWindow(window); + + ImGuiScrollFlags scroll_flags = + window->Appearing + ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY + : ImGuiScrollFlags_KeepVisibleEdgeX | + ImGuiScrollFlags_KeepVisibleEdgeY; + NavMoveRequestSubmit( + ImGuiDir_None, offset < 0 ? ImGuiDir_Up : ImGuiDir_Down, + ImGuiNavMoveFlags_Tabbing | ImGuiNavMoveFlags_FocusApi, + 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.NavInitResultId == 0) || + g.NavLayer != window->DC.NavLayerCurrent) + return; + + g.NavInitRequest = false; + g.NavInitResultId = g.LastItemData.ID; + g.NavInitResultRectRel = WindowRectAbsToRel(window, g.LastItemData.Rect); + NavUpdateAnyRequestFlag(); + + // Scroll could be done in NavInitRequestApplyResult() via a opt-in flag (we + // however don't want regular init requests to scroll) + if (!IsItemVisible()) + 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; +} + +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 +//----------------------------------------------------------------------------- + +// 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; +} + +ImGuiKeyData* ImGui::GetKeyData(ImGuiKey key) { + ImGuiContext& g = *GImGui; + int index; +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + IM_ASSERT(key >= ImGuiKey_LegacyNativeKey_BEGIN && + key < ImGuiKey_NamedKey_END); + if (IsLegacyKey(key)) + index = (g.IO.KeyMap[key] != -1) + ? g.IO.KeyMap[key] + : key; // Remap native->imgui or imgui->native + else + index = key; +#else + IM_ASSERT(IsNamedKey(key) && + "Support for user key indices was dropped in favor of ImGuiKey. " + "Please update backend & user code."); + index = key - ImGuiKey_NamedKey_BEGIN; +#endif + return &g.IO.KeysData[index]; +} + +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO +int ImGui::GetKeyIndex(ImGuiKey key) { + ImGuiContext& g = *GImGui; + IM_ASSERT(IsNamedKey(key)); + const ImGuiKeyData* key_data = GetKeyData(key); + return (int)(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", + "GamepadFaceUp", + "GamepadFaceDown", + "GamepadFaceLeft", + "GamepadFaceRight", + "GamepadDpadUp", + "GamepadDpadDown", + "GamepadDpadLeft", + "GamepadDpadRight", + "GamepadL1", + "GamepadR1", + "GamepadL2", + "GamepadR2", + "GamepadL3", + "GamepadR3", + "GamepadLStickUp", + "GamepadLStickDown", + "GamepadLStickLeft", + "GamepadLStickRight", + "GamepadRStickUp", + "GamepadRStickDown", + "GamepadRStickLeft", + "GamepadRStickRight", + "ModCtrl", + "ModShift", + "ModAlt", + "ModSuper"}; +IM_STATIC_ASSERT(ImGuiKey_NamedKey_COUNT == IM_ARRAYSIZE(GKeyNames)); + +const char* ImGui::GetKeyName(ImGuiKey key) { +#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO + IM_ASSERT((IsNamedKey(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)) { + ImGuiIO& io = GetIO(); + if (io.KeyMap[key] == -1) return "N/A"; + IM_ASSERT(IsNamedKey((ImGuiKey)io.KeyMap[key])); + key = (ImGuiKey)io.KeyMap[key]; + } +#endif + if (key == ImGuiKey_None) return "None"; + if (!IsNamedKey(key)) return "Unknown"; + + return GKeyNames[key - ImGuiKey_NamedKey_BEGIN]; +} + +// 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; +} + +int ImGui::GetKeyPressedAmount(ImGuiKey key, float repeat_delay, + float repeat_rate) { + ImGuiContext& g = *GImGui; + const ImGuiKeyData* key_data = GetKeyData(key); + const float t = key_data->DownDuration; + return CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, repeat_delay, + repeat_rate); +} + +// 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) { + const ImGuiKeyData* key_data = GetKeyData(key); + if (!key_data->Down) return false; + return true; +} + +bool ImGui::IsKeyPressed(ImGuiKey key, bool repeat) { + ImGuiContext& g = *GImGui; + const ImGuiKeyData* key_data = GetKeyData(key); + const float t = key_data->DownDuration; + if (t < 0.0f) return false; + const bool pressed = + (t == 0.0f) || + (repeat && t > g.IO.KeyRepeatDelay && + GetKeyPressedAmount(key, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0); + if (!pressed) return false; + return true; +} + +bool ImGui::IsKeyReleased(ImGuiKey key) { + const ImGuiKeyData* key_data = GetKeyData(key); + if (key_data->DownDurationPrev < 0.0f || key_data->Down) 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]; +} + +bool ImGui::IsMouseClicked(ImGuiMouseButton button, bool repeat) { + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + const float t = g.IO.MouseDownDuration[button]; + if (t == 0.0f) return true; + if (repeat && t > g.IO.KeyRepeatDelay) + return CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay, + g.IO.KeyRepeatRate) > 0; + return false; +} + +bool ImGui::IsMouseReleased(ImGuiMouseButton button) { + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseReleased[button]; +} + +bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button) { + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseClickedCount[button] == 2; +} + +int ImGui::GetMouseClickedCount(ImGuiMouseButton button) { + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseClickedCount[button]; +} + +// 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; +} + +ImGuiMouseCursor ImGui::GetMouseCursor() { + ImGuiContext& g = *GImGui; + return g.MouseCursor; +} + +void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type) { + ImGuiContext& g = *GImGui; + g.MouseCursor = cursor_type; +} + +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_METRICS_WINDOW +static const char* GetInputSourceName(ImGuiInputSource source) { + const char* input_source_names[] = {"None", "Mouse", "Keyboard", + "Gamepad", "Nav", "Clipboard"}; + IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT && + source >= 0 && source < ImGuiInputSource_COUNT); + return input_source_names[source]; +} +#endif + +/*static void DebugPrintInputEvent(const char* prefix, const ImGuiInputEvent* e) +{ + if (e->Type == ImGuiInputEventType_MousePos) { IMGUI_DEBUG_PRINT("%s: +MousePos (%.1f %.1f)\n", prefix, e->MousePos.PosX, e->MousePos.PosY); return; } + if (e->Type == ImGuiInputEventType_MouseButton) { IMGUI_DEBUG_PRINT("%s: +MouseButton %d %s\n", prefix, e->MouseButton.Button, e->MouseButton.Down ? +"Down" : "Up"); return; } if (e->Type == ImGuiInputEventType_MouseWheel) { +IMGUI_DEBUG_PRINT("%s: MouseWheel (%.1f %.1f)\n", prefix, e->MouseWheel.WheelX, +e->MouseWheel.WheelY); return; } if (e->Type == ImGuiInputEventType_Key) { +IMGUI_DEBUG_PRINT("%s: Key \"%s\" %s\n", prefix, ImGui::GetKeyName(e->Key.Key), +e->Key.Down ? "Down" : "Up"); return; } if (e->Type == ImGuiInputEventType_Text) +{ IMGUI_DEBUG_PRINT("%s: Text: %c (U+%08X)\n", prefix, e->Text.Char, +e->Text.Char); return; } if (e->Type == ImGuiInputEventType_Focus) { +IMGUI_DEBUG_PRINT("%s: AppFocused %d\n", prefix, e->AppFocused.Focused); return; +} +}*/ + +// 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++) { + const ImGuiInputEvent* e = &g.InputEventsQueue[event_n]; + if (e->Type == ImGuiInputEventType_MousePos) { + ImVec2 event_pos(e->MousePos.PosX, e->MousePos.PosY); + if (IsMousePosValid(&event_pos)) + event_pos = ImVec2( + ImFloorSigned(event_pos.x), + ImFloorSigned( + event_pos.y)); // Apply same flooring as UpdateMouseInputs() + if (io.MousePos.x != event_pos.x || io.MousePos.y != event_pos.y) { + // Trickling Rule: Stop processing queued events if we already handled a + // mouse button change + if (trickle_fast_inputs && + (mouse_button_changed != 0 || mouse_wheeled || key_changed || + text_inputted)) + break; + io.MousePos = event_pos; + mouse_moved = true; + } + } else if (e->Type == ImGuiInputEventType_MouseButton) { + const ImGuiMouseButton button = e->MouseButton.Button; + IM_ASSERT(button >= 0 && button < ImGuiMouseButton_COUNT); + if (io.MouseDown[button] != e->MouseButton.Down) { + // Trickling Rule: Stop processing queued events if we got multiple + // action on the same button + if (trickle_fast_inputs && + ((mouse_button_changed & (1 << button)) || mouse_wheeled)) + break; + io.MouseDown[button] = e->MouseButton.Down; + mouse_button_changed |= (1 << button); + } + } else if (e->Type == ImGuiInputEventType_MouseWheel) { + if (e->MouseWheel.WheelX != 0.0f || e->MouseWheel.WheelY != 0.0f) { + // 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; + mouse_wheeled = true; + } + } else if (e->Type == ImGuiInputEventType_Key) { + ImGuiKey key = e->Key.Key; + IM_ASSERT(key != ImGuiKey_None); + const int keydata_index = (key - ImGuiKey_KeysData_OFFSET); + ImGuiKeyData* keydata = &io.KeysData[keydata_index]; + if (keydata->Down != e->Key.Down || + keydata->AnalogValue != e->Key.AnalogValue) { + // Trickling Rule: Stop processing queued events if we got multiple + // action on the same button + if (trickle_fast_inputs && keydata->Down != e->Key.Down && + (key_changed_mask.TestBit(keydata_index) || text_inputted || + mouse_button_changed != 0)) + break; + keydata->Down = e->Key.Down; + keydata->AnalogValue = e->Key.AnalogValue; + key_changed = true; + key_changed_mask.SetBit(keydata_index); + + if (key == ImGuiKey_ModCtrl || key == ImGuiKey_ModShift || + key == ImGuiKey_ModAlt || key == ImGuiKey_ModSuper) { + if (key == ImGuiKey_ModCtrl) { + io.KeyCtrl = keydata->Down; + } + if (key == ImGuiKey_ModShift) { + io.KeyShift = keydata->Down; + } + if (key == ImGuiKey_ModAlt) { + io.KeyAlt = keydata->Down; + } + if (key == ImGuiKey_ModSuper) { + io.KeySuper = keydata->Down; + } + io.KeyMods = GetMergedModFlags(); + } + + // Allow legacy code using io.KeysDown[GetKeyIndex()] with new backends +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + io.KeysDown[key] = keydata->Down; + if (io.KeyMap[key] != -1) io.KeysDown[io.KeyMap[key]] = keydata->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 lower, in order to give a + // chance to multi-viewports backends to queue AddFocusEvent(false) + + // AddFocusEvent(true) in same frame. + io.AppFocusLost = !e->AppFocused.Focused; + } else { + IM_ASSERT(0 && "Unknown event!"); + } + } + + // Record trail (for domain-specific applications wanting to access a precise + // trail) + // if (event_n != 0) IMGUI_DEBUG_PRINT("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] + /*if (event_n != 0) + for (int n = 0; n < g.InputEventsQueue.Size; n++) + DebugPrintInputEvent(n < event_n ? "Processed" : "Remaining", + &g.InputEventsQueue[n]);*/ + + // 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) + if (g.IO.AppFocusLost) { + g.IO.ClearInputKeys(); + g.IO.AppFocusLost = false; + } +} + +//----------------------------------------------------------------------------- +// [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; +} + +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); + + // 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 ImGuiModFlags key_mods = GetMergedModFlags(); + 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) { + IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, + "Mismatched Begin/BeginChild vs End/EndChild calls: " + "did you forget to call End/EndChild?"); + 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.FocusScopeStack.Size > stack_sizes->SizeOfFocusScopeStack) //-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::SetToCurrentState() { + ImGuiContext& g = *GImGui; + 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::CompareWithCurrentState() { + ImGuiContext& g = *GImGui; + 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 = 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 | 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. + 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.org/faq + IM_ASSERT( + id != window->ID && + "Cannot have an empty ID at the root of a window. If you need an empty " + "label, use ## and read the FAQ about how the ID Stack works!"); + + // [DEBUG] Item Picker tool, when enabling the "extended" version we perform + // the check in ItemAdd() +#ifdef IMGUI_DEBUG_TOOL_ITEM_PICKER_EX + if (id == g.DebugItemPickerBreakId) { + IM_DEBUG_BREAK(); + g.DebugItemPickerBreakId = 0; + } +#endif + } + g.NextItemData.Flags = ImGuiNextItemDataFlags_None; + +#ifdef IMGUI_ENABLE_TEST_ENGINE + if (id != 0) IMGUI_TEST_ENGINE_ITEM_ADD(nav_bb_arg ? *nav_bb_arg : bb, id); +#endif + + // Clipping test + const bool is_clipped = IsClippedEx(bb, id); + if (is_clipped) return false; + // if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, + // IM_COL32(255,255,0,120)); // [DEBUG] + + // We need to calculate this now to take account of the current clipping + // rectangle (as items like Selectable may change them) + 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 +// pos_x == 0, no spacing if pos_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); +} + +// 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); +} + +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); +} + +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); +} + +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->ContentRegionRect.Max - window->Pos; + if (window->DC.CurrentColumns || g.CurrentTable) + mx.x = window->WorkRect.Max.x - window->Pos.x; + return mx; +} + +// [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->ContentRegionRect.Max; + if (window->DC.CurrentColumns || g.CurrentTable) + mx.x = window->WorkRect.Max.x; + 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? + + 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 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; + if (window->ScrollTarget.x < FLT_MAX) { + float decoration_total_width = window->ScrollbarSizes.x; + float center_x_ratio = window->ScrollTargetCenterRatio.x; + float scroll_target_x = window->ScrollTarget.x; + if (window->ScrollTargetEdgeSnapDist.x > 0.0f) { + float snap_x_min = 0.0f; + float snap_x_max = + window->ScrollMax.x + window->SizeFull.x - decoration_total_width; + scroll_target_x = CalcScrollEdgeSnap( + scroll_target_x, snap_x_min, snap_x_max, + window->ScrollTargetEdgeSnapDist.x, center_x_ratio); + } + scroll.x = scroll_target_x - + center_x_ratio * (window->SizeFull.x - decoration_total_width); + } + if (window->ScrollTarget.y < FLT_MAX) { + float decoration_total_height = window->TitleBarHeight() + + window->MenuBarHeight() + + window->ScrollbarSizes.y; + float center_y_ratio = window->ScrollTargetCenterRatio.y; + float scroll_target_y = window->ScrollTarget.y; + if (window->ScrollTargetEdgeSnapDist.y > 0.0f) { + float snap_y_min = 0.0f; + float snap_y_max = + window->ScrollMax.y + window->SizeFull.y - decoration_total_height; + scroll_target_y = CalcScrollEdgeSnap( + scroll_target_y, snap_y_min, snap_y_max, + window->ScrollTargetEdgeSnapDist.y, center_y_ratio); + } + scroll.y = scroll_target_y - + center_y_ratio * (window->SizeFull.y - decoration_total_height); + } + scroll.x = IM_FLOOR(ImMax(scroll.x, 0.0f)); + scroll.y = IM_FLOOR(ImMax(scroll.y, 0.0f)); + if (!window->Collapsed && !window->SkipItems) { + scroll.x = ImMin(scroll.x, window->ScrollMax.x); + scroll.y = ImMin(scroll.y, window->ScrollMax.y); + } + 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 window_rect(window->InnerRect.Min - ImVec2(1, 1), + window->InnerRect.Max + ImVec2(1, 1)); + // GetForegroundDrawList(window)->AddRect(window_rect.Min, window_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 >= window_rect.Min.x && + item_rect.Max.x <= window_rect.Max.x; + const bool fully_visible_y = item_rect.Min.y >= window_rect.Min.y && + item_rect.Max.y <= window_rect.Max.y; + const bool can_be_fully_visible_x = + (item_rect.GetWidth() + g.Style.ItemSpacing.x * 2.0f) <= + window_rect.GetWidth(); + const bool can_be_fully_visible_y = + (item_rect.GetHeight() + g.Style.ItemSpacing.y * 2.0f) <= + window_rect.GetHeight(); + + if ((flags & ImGuiScrollFlags_KeepVisibleEdgeX) && !fully_visible_x) { + if (item_rect.Min.x < window_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 >= window_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)) { + float target_x = can_be_fully_visible_x + ? ImFloor((item_rect.Min.x + item_rect.Max.x - + window->InnerRect.GetWidth()) * + 0.5f) + : item_rect.Min.x; + SetScrollFromPosX(window, target_x - window->Pos.x, 0.0f); + } + + if ((flags & ImGuiScrollFlags_KeepVisibleEdgeY) && !fully_visible_y) { + if (item_rect.Min.y < window_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 >= window_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)) { + float target_y = can_be_fully_visible_y + ? ImFloor((item_rect.Min.y + item_rect.Max.y - + window->InnerRect.GetHeight()) * + 0.5f) + : item_rect.Min.y; + SetScrollFromPosY(window, target_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 exists 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->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); + const float decoration_up_height = + window->TitleBarHeight() + + window->MenuBarHeight(); // FIXME: Would be nice to have a more + // standardized access to our scrollable/client + // rect; + local_y -= decoration_up_height; + window->ScrollTarget.y = IM_FLOOR( + local_y + 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 +//----------------------------------------------------------------------------- + +void ImGui::BeginTooltip() { + BeginTooltipEx(ImGuiTooltipFlags_None, ImGuiWindowFlags_None); +} + +void ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, + ImGuiWindowFlags extra_window_flags) { + ImGuiContext& g = *GImGui; + + if (g.DragDropWithinSource || g.DragDropWithinTarget) { + // The default tooltip position is a little offset to give space to see the + // context menu (it's also clamped within the current viewport/monitor) In + // the context of a dragging tooltip we try to reduce that offset and we + // enforce following the cursor. Whatever we do we want to call + // SetNextWindowPos() to enforce a tooltip position and disable clipping the + // tooltip without our display area, like regular tooltip do. + // ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - + // g.Style.WindowPadding; + ImVec2 tooltip_pos = g.IO.MousePos + ImVec2(16 * g.Style.MouseCursorScale, + 8 * 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_OverridePreviousTooltip; + } + + char window_name[16]; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", + g.TooltipOverrideCount); + if (tooltip_flags & ImGuiTooltipFlags_OverridePreviousTooltip) + 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. + window->Hidden = true; + window->HiddenFramesCanSkipItems = + 1; // FIXME: This may not be necessary? + 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); +} + +void ImGui::EndTooltip() { + IM_ASSERT(GetCurrentWindowRead()->Flags & + ImGuiWindowFlags_Tooltip); // Mismatched + // BeginTooltip()/EndTooltip() calls + End(); +} + +void ImGui::SetTooltipV(const char* fmt, va_list args) { + BeginTooltipEx(ImGuiTooltipFlags_OverridePreviousTooltip, + ImGuiWindowFlags_None); + TextV(fmt, args); + EndTooltip(); +} + +void ImGui::SetTooltip(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + SetTooltipV(fmt, args); + va_end(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); +} + +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; +} + +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(0u, 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.SourceWindow = g.NavWindow; + 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* focus_window = g.OpenPopupStack[remaining].SourceWindow; + ImGuiWindow* popup_window = g.OpenPopupStack[remaining].Window; + g.OpenPopupStack.resize(remaining); + + if (restore_focus_to_window_under_popup) { + if (focus_window && !focus_window->WasActive && popup_window) { + // Fallback + FocusTopMostWindowUnderOne(popup_window, NULL); + } else { + if (g.NavLayer == ImGuiNavLayer_Main && focus_window) + focus_window = NavRestoreLastChildNavWindow(focus_window); + FocusWindow(focus_window); + } + } +} + +// 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 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) + float sc = g.Style.MouseCursorScale; + ImVec2 ref_pos = NavCalcPreferredRefPos(); + 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 * sc, + ref_pos.y + + 24 * sc); // FIXME: Hard-coded based on mouse cursor shape + // expectation. Exact dimension not very important. + return FindBestWindowPosForPopupEx( + ref_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::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; +} + +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 window->DC.NavFocusScopeIdCurrent 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 = window->DC.NavFocusScopeIdCurrent; + window->NavLastIds[nav_layer] = id; + if (g.LastItemData.ID == id) + window->NavRectRel[nav_layer] = + WindowRectAbsToRel(window, g.LastItemData.NavRect); + + if (g.ActiveIdSource == ImGuiInputSource_Nav) + g.NavDisableMouseHover = true; + else + g.NavDisableHighlight = true; +} + +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 a0, float a1, float b0, + float b1) { + if (a1 < b0) return a1 - b0; + if (b1 < a0) return a0 - b1; + return 0.0f; +} + +static void inline NavClampRectToVisibleAreaForMoveDir( + ImGuiDir move_dir, ImRect& r, const ImRect& clip_rect) { + if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right) { + r.Min.y = ImClamp(r.Min.y, clip_rect.Min.y, clip_rect.Max.y); + r.Max.y = ImClamp(r.Max.y, clip_rect.Min.y, clip_rect.Max.y); + } else // FIXME: PageUp/PageDown are leaving move_dir == None + { + r.Min.x = ImClamp(r.Min.x, clip_rect.Min.x, clip_rect.Max.x); + r.Max.x = ImClamp(r.Max.x, clip_rect.Min.x, clip_rect.Max.x); + } +} + +// 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 + } + + // We perform scoring on items bounding box clipped by the current clipping + // rectangle on the other axis (clipping on our movement axis would give us + // equal scores for all clipped items) For example, this ensure that items in + // one column are not reached when moving vertically from items in another + // column. + NavClampRectToVisibleAreaForMoveDir(g.NavMoveClipDir, cand, window->ClipRect); + + // 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; + } + +#if IMGUI_DEBUG_NAV_SCORING + char buf[128]; + if (IsMouseHoveringRect(cand.Min, cand.Max)) { + ImFormatString(buf, IM_ARRAYSIZE(buf), + "dbox (%.2f,%.2f->%.4f)\ndcen (%.2f,%.2f->%.4f)\nd " + "(%.2f,%.2f->%.4f)\nnav %c, quadrant %c", + dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, + dist_axial, "WENS"[g.NavMoveDir], "WENS"[quadrant]); + 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, 150)); + draw_list->AddText(cand.Max, ~0U, buf); + } else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press + // C to rotate. + { + if (quadrant == g.NavMoveDir) { + 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, 200)); + draw_list->AddText(cand.Min, IM_COL32(255, 255, 255, 255), buf); + } + } +#endif + + // Is it in the quadrant we're interesting in moving to? + bool new_best = false; + const ImGuiDir move_dir = g.NavMoveDir; + 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 = window->DC.NavFocusScopeIdCurrent; + result->InFlags = g.LastItemData.InFlags; + result->RectRel = WindowRectAbsToRel(window, g.LastItemData.NavRect); +} + +// 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 ImRect nav_bb = g.LastItemData.NavRect; + const ImGuiItemFlags item_flags = g.LastItemData.InFlags; + + // 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.NavInitResultId == 0) { + g.NavInitResultId = id; + g.NavInitResultRectRel = WindowRectAbsToRel(window, nav_bb); + } + 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) { + const bool is_tab_stop = + (item_flags & ImGuiItemFlags_Inputable) && + (item_flags & (ImGuiItemFlags_NoTabStop | ImGuiItemFlags_Disabled)) == + 0; + const bool is_tabbing = (g.NavMoveFlags & ImGuiNavMoveFlags_Tabbing) != 0; + if (is_tabbing) { + if (is_tab_stop || (g.NavMoveFlags & ImGuiNavMoveFlags_FocusApi)) + NavProcessItemForTabbingRequest(id); + } else if ((g.NavId != id || + (g.NavMoveFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && + !(item_flags & + (ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNav))) { + ImGuiNavItemData* result = (window == g.NavWindow) + ? &g.NavMoveResultLocal + : &g.NavMoveResultOther; + if (!is_tabbing) { + 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 window-relative bounding box of 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 = window->DC.NavFocusScopeIdCurrent; + 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) { + ImGuiContext& g = *GImGui; + + // 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 (g.NavTabbingResultFirst.ID == 0) + NavApplyItemToResult(&g.NavTabbingResultFirst); + if (--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 { + NavApplyItemToResult(result); + } + } else if (g.NavTabbingDir == 0) { + // Tab Init + if (g.NavTabbingResultFirst.ID == 0) + NavMoveRequestResolveWithLastItem(&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_Tabbing) + 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(); +} + +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 != 0); // Call with _WrapX, _WrapY, _LoopX, _LoopY + // In theory we should test for NavMoveRequestButNoResultYet() but there's no + // point doing it, NavEndFrame() will do the same test + if (g.NavWindow == window && g.NavMoveScoringItems && + g.NavLayer == ImGuiNavLayer_Main) + g.NavMoveFlags |= 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 = g.NavFocusScopeId = 0; + 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, 0, ImRect()); + g.NavInitRequest = true; + g.NavInitRequestFromMove = false; + g.NavInitResultId = 0; + g.NavInitResultRectRel = ImRect(); + NavUpdateAnyRequestFlag(); + } else { + g.NavId = window->NavLastIds[0]; + g.NavFocusScopeId = 0; + } +} + +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. + } +} + +const char* ImGui::GetNavInputName(ImGuiNavInput n) { + static const char* names[] = { + "Activate", "Cancel", "Input", "Menu", "DpadLeft", + "DpadRight", "DpadUp", "DpadDown", "LStickLeft", "LStickRight", + "LStickUp", "LStickDown", "FocusPrev", "FocusNext", "TweakSlow", + "TweakFast", "KeyLeft", "KeyRight", "KeyUp", "KeyDown"}; + IM_ASSERT(IM_ARRAYSIZE(names) == ImGuiNavInput_COUNT); + IM_ASSERT(n >= 0 && n < ImGuiNavInput_COUNT); + return names[n]; +} + +float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiNavReadMode mode) { + ImGuiContext& g = *GImGui; + if (mode == ImGuiNavReadMode_Down) + return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as + // provided by user) + + const float t = g.IO.NavInputsDownDuration[n]; + if (t < 0.0f && + mode == ImGuiNavReadMode_Released) // Return 1.0f when just released, no + // repeat, ignore analog input. + return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f); + if (t < 0.0f) return 0.0f; + if (mode == ImGuiNavReadMode_Pressed) // Return 1.0f when just pressed, no + // repeat, ignore analog input. + return (t == 0.0f) ? 1.0f : 0.0f; + if (mode == ImGuiNavReadMode_Repeat) + return (float)CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, + g.IO.KeyRepeatDelay * 0.72f, + g.IO.KeyRepeatRate * 0.80f); + if (mode == ImGuiNavReadMode_RepeatSlow) + return (float)CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, + g.IO.KeyRepeatDelay * 1.25f, + g.IO.KeyRepeatRate * 2.00f); + if (mode == ImGuiNavReadMode_RepeatFast) + return (float)CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, + g.IO.KeyRepeatDelay * 0.72f, + g.IO.KeyRepeatRate * 0.30f); + return 0.0f; +} + +ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, + ImGuiNavReadMode mode, float slow_factor, + float fast_factor) { + ImVec2 delta(0.0f, 0.0f); + if (dir_sources & ImGuiNavDirSourceFlags_RawKeyboard) + delta += ImVec2((float)IsKeyDown(ImGuiKey_RightArrow) - + (float)IsKeyDown(ImGuiKey_LeftArrow), + (float)IsKeyDown(ImGuiKey_DownArrow) - + (float)IsKeyDown(ImGuiKey_UpArrow)); + if (dir_sources & ImGuiNavDirSourceFlags_Keyboard) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode) - + GetNavInputAmount(ImGuiNavInput_KeyLeft_, mode), + GetNavInputAmount(ImGuiNavInput_KeyDown_, mode) - + GetNavInputAmount(ImGuiNavInput_KeyUp_, mode)); + if (dir_sources & ImGuiNavDirSourceFlags_PadDPad) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode) - + GetNavInputAmount(ImGuiNavInput_DpadLeft, mode), + GetNavInputAmount(ImGuiNavInput_DpadDown, mode) - + GetNavInputAmount(ImGuiNavInput_DpadUp, mode)); + if (dir_sources & ImGuiNavDirSourceFlags_PadLStick) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - + GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), + GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - + GetNavInputAmount(ImGuiNavInput_LStickUp, mode)); + if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow)) + delta *= slow_factor; + if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast)) + delta *= fast_factor; + return delta; +} + +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); + + // Update Gamepad->Nav inputs mapping + // Set input source as Gamepad when buttons are pressed (as some features + // differs when used with Gamepad vs Keyboard) + const bool nav_gamepad_active = + (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && + (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; + if (nav_gamepad_active && g.IO.BackendUsingLegacyNavInputArray == false) { + for (int n = 0; n < ImGuiNavInput_COUNT; n++) + IM_ASSERT(io.NavInputs[n] == 0.0f && + "Backend needs to either only use " + "io.AddKeyEvent()/io.AddKeyAnalogEvent(), either only fill " + "legacy io.NavInputs[]. Not both!"); +#define NAV_MAP_KEY(_KEY, _NAV_INPUT, _ACTIVATE_NAV) \ + do { \ + io.NavInputs[_NAV_INPUT] = \ + io.KeysData[_KEY - ImGuiKey_KeysData_OFFSET].AnalogValue; \ + if (_ACTIVATE_NAV && io.NavInputs[_NAV_INPUT] > 0.0f) { \ + g.NavInputSource = ImGuiInputSource_Gamepad; \ + } \ + } while (0) + NAV_MAP_KEY(ImGuiKey_GamepadFaceDown, ImGuiNavInput_Activate, true); + NAV_MAP_KEY(ImGuiKey_GamepadFaceRight, ImGuiNavInput_Cancel, true); + NAV_MAP_KEY(ImGuiKey_GamepadFaceLeft, ImGuiNavInput_Menu, true); + NAV_MAP_KEY(ImGuiKey_GamepadFaceUp, ImGuiNavInput_Input, true); + NAV_MAP_KEY(ImGuiKey_GamepadDpadLeft, ImGuiNavInput_DpadLeft, true); + NAV_MAP_KEY(ImGuiKey_GamepadDpadRight, ImGuiNavInput_DpadRight, true); + NAV_MAP_KEY(ImGuiKey_GamepadDpadUp, ImGuiNavInput_DpadUp, true); + NAV_MAP_KEY(ImGuiKey_GamepadDpadDown, ImGuiNavInput_DpadDown, true); + NAV_MAP_KEY(ImGuiKey_GamepadL1, ImGuiNavInput_FocusPrev, false); + NAV_MAP_KEY(ImGuiKey_GamepadR1, ImGuiNavInput_FocusNext, false); + NAV_MAP_KEY(ImGuiKey_GamepadL1, ImGuiNavInput_TweakSlow, false); + NAV_MAP_KEY(ImGuiKey_GamepadR1, ImGuiNavInput_TweakFast, false); + NAV_MAP_KEY(ImGuiKey_GamepadLStickLeft, ImGuiNavInput_LStickLeft, false); + NAV_MAP_KEY(ImGuiKey_GamepadLStickRight, ImGuiNavInput_LStickRight, false); + NAV_MAP_KEY(ImGuiKey_GamepadLStickUp, ImGuiNavInput_LStickUp, false); + NAV_MAP_KEY(ImGuiKey_GamepadLStickDown, ImGuiNavInput_LStickDown, false); +#undef NAV_MAP_KEY + } + + // Update Keyboard->Nav inputs mapping + const bool nav_keyboard_active = + (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; + if (nav_keyboard_active) { +#define NAV_MAP_KEY(_KEY, _NAV_INPUT) \ + do { \ + if (IsKeyDown(_KEY)) { \ + io.NavInputs[_NAV_INPUT] = 1.0f; \ + g.NavInputSource = ImGuiInputSource_Keyboard; \ + } \ + } while (0) + NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate); + NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input); + NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel); + NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_); + NAV_MAP_KEY(ImGuiKey_RightArrow, ImGuiNavInput_KeyRight_); + NAV_MAP_KEY(ImGuiKey_UpArrow, ImGuiNavInput_KeyUp_); + NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_); + if (io.KeyCtrl) io.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f; + if (io.KeyShift) io.NavInputs[ImGuiNavInput_TweakFast] = 1.0f; +#undef NAV_MAP_KEY + } + memcpy(io.NavInputsDownDurationPrev, io.NavInputsDownDuration, + sizeof(io.NavInputsDownDuration)); + for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) + io.NavInputsDownDuration[i] = + (io.NavInputs[i] > 0.0f) + ? (io.NavInputsDownDuration[i] < 0.0f + ? 0.0f + : io.NavInputsDownDuration[i] + io.DeltaTime) + : -1.0f; + + // Process navigation init request (select first/default focus) + if (g.NavInitResultId != 0) NavInitRequestApplyResult(); + g.NavInitRequest = false; + g.NavInitRequestFromMove = false; + g.NavInitResultId = 0; + g.NavJustMovedToId = 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 = + g.NavActivateInputId = 0; + g.NavActivateFlags = ImGuiActivateFlags_None; + if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && + g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) { + bool activate_down = IsNavInputDown(ImGuiNavInput_Activate); + bool input_down = IsNavInputDown(ImGuiNavInput_Input); + bool activate_pressed = + activate_down && + IsNavInputTest(ImGuiNavInput_Activate, ImGuiNavReadMode_Pressed); + bool input_pressed = input_down && IsNavInputTest(ImGuiNavInput_Input, + ImGuiNavReadMode_Pressed); + 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.NavActivateInputId = g.NavId; + g.NavActivateFlags = ImGuiActivateFlags_PreferInput; + } + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down) + g.NavActivateDownId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_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) { + if (g.NavNextActivateFlags & ImGuiActivateFlags_PreferInput) + g.NavActivateInputId = g.NavNextActivateId; + else + 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.NavHasScroll && + 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 NavScrollXXX keys + // Next movement request will clamp the NavId reference rectangle to the + // visible area, so navigation will resume within those bounds. + ImVec2 scroll_dir = + GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, + ImGuiNavReadMode_Down, 1.0f / 10.0f, 10.0f); + if (scroll_dir.x != 0.0f && window->ScrollbarX) + SetScrollX(window, + ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed)); + if (scroll_dir.y != 0.0f) + SetScrollY(window, + ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed)); + } + + // 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_PRINT("SetMousePos: (%.1f,%.1f)\n", io.MousePos.x, + // io.MousePos.y); + } + + // [DEBUG] + g.NavScoringDebugCount = 0; +#if IMGUI_DEBUG_NAV_RECTS + if (g.NavWindow) { + ImDrawList* draw_list = GetForegroundDrawList(g.NavWindow); + if (1) { + for (int layer = 0; layer < 2; layer++) { + ImRect r = + WindowRectRelToAbs(g.NavWindow, g.NavWindow->NavRectRel[layer]); + draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 200, 0, 255)); + } + } // [DEBUG] + if (1) { + ImU32 col = (!g.NavWindow->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; + + // 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", + g.NavInitResultId, g.NavLayer, g.NavWindow->Name); + SetNavID(g.NavInitResultId, g.NavLayer, 0, g.NavInitResultRectRel); + g.NavIdIsAlive = + true; // Mark as alive from previous frame as we got a result + if (g.NavInitRequestFromMove) NavRestoreHighlightAfterMove(); +} + +void ImGui::NavUpdateCreateMoveRequest() { + ImGuiContext& g = *GImGui; + ImGuiIO& io = g.IO; + ImGuiWindow* window = g.NavWindow; + + 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 ImGuiNavReadMode read_mode = ImGuiNavReadMode_Repeat; + if (!IsActiveIdUsingNavDir(ImGuiDir_Left) && + (IsNavInputTest(ImGuiNavInput_DpadLeft, read_mode) || + IsNavInputTest(ImGuiNavInput_KeyLeft_, read_mode))) { + g.NavMoveDir = ImGuiDir_Left; + } + if (!IsActiveIdUsingNavDir(ImGuiDir_Right) && + (IsNavInputTest(ImGuiNavInput_DpadRight, read_mode) || + IsNavInputTest(ImGuiNavInput_KeyRight_, read_mode))) { + g.NavMoveDir = ImGuiDir_Right; + } + if (!IsActiveIdUsingNavDir(ImGuiDir_Up) && + (IsNavInputTest(ImGuiNavInput_DpadUp, read_mode) || + IsNavInputTest(ImGuiNavInput_KeyUp_, read_mode))) { + g.NavMoveDir = ImGuiDir_Up; + } + if (!IsActiveIdUsingNavDir(ImGuiDir_Down) && + (IsNavInputTest(ImGuiNavInput_DpadDown, read_mode) || + IsNavInputTest(ImGuiNavInput_KeyDown_, read_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? + const bool nav_keyboard_active = + (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; + 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 +#if IMGUI_DEBUG_NAV_SCORING + if (io.KeyCtrl && IsKeyPressed(ImGuiKey_C)) + g.NavMoveDirForDebug = (ImGuiDir)((g.NavMoveDirForDebug + 1) & 3); + if (io.KeyCtrl && g.NavMoveDir == ImGuiDir_None) { + g.NavMoveDir = g.NavMoveDirForDebug; + 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 a 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.NavInitResultId = 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 every 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))); + 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 = g.NavFocusScopeId = 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); + scoring_rect.Min.x = ImMin(scoring_rect.Min.x + 1.0f, scoring_rect.Max.x); + scoring_rect.Max.x = scoring_rect.Min.x; + IM_ASSERT( + !scoring_rect + .IsInverted()); // Ensure if we have a finite, non-inverted + // bounding box here will allows 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, true) && + !IsActiveIdUsingKey(ImGuiKey_Tab) && !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. + //// FIXME: We use (g.ActiveId == 0) but (g.NavDisableHighlight == false) + /// might be righter once we can tab through anything + g.NavTabbingDir = g.IO.KeyShift ? -1 : (g.ActiveId == 0) ? 0 : +1; + 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, ImGuiNavMoveFlags_Tabbing, + 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_Tabbing) + if ((g.NavTabbingCounter == 1 || g.NavTabbingDir == 0) && + g.NavTabbingResultFirst.ID) + result = &g.NavTabbingResultFirst; + + // In a situation when there is no results but NavId != 0, re-enable the + // Navigation highlight (because g.NavId is not considered as a possible + // result) + if (result == NULL) { + if (g.NavMoveFlags & ImGuiNavMoveFlags_Tabbing) + g.NavMoveFlags |= ImGuiNavMoveFlags_DontSetNavHighlight; + if (g.NavId != 0 && + (g.NavMoveFlags & ImGuiNavMoveFlags_DontSetNavHighlight) == 0) + NavRestoreHighlightAfterMove(); + 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) { + if (g.NavMoveFlags & ImGuiNavMoveFlags_ScrollToEdgeY) { + // FIXME: Should remove this + float scroll_target = + (g.NavMoveDir == ImGuiDir_Up) ? result->Window->ScrollMax.y : 0.0f; + SetScrollY(result->Window, scroll_target); + } else { + ImRect rect_abs = WindowRectRelToAbs(result->Window, result->RectRel); + ScrollToRectEx(result->Window, rect_abs, g.NavMoveScrollFlags); + } + } + + 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(); + if (g.NavId != result->ID) { + // Don't set NavJustMovedToId if just landed on the same spot (which may + // happen with ImGuiNavMoveFlags_AllowCurrentNavId) + g.NavJustMovedToId = result->ID; + g.NavJustMovedToFocusScopeId = result->FocusScopeId; + g.NavJustMovedToKeyMods = g.NavMoveKeyMods; + } + + // Focus + IMGUI_DEBUG_LOG_NAV( + "[nav] NavMoveRequest: result 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); + + // Tabbing: Activates Inputable or Focus non-Inputable + if ((g.NavMoveFlags & ImGuiNavMoveFlags_Tabbing) && + (result->InFlags & ImGuiItemFlags_Inputable)) { + g.NavNextActivateId = result->ID; + g.NavNextActivateFlags = + ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_TryToPreserveState; + g.NavMoveFlags |= ImGuiNavMoveFlags_DontSetNavHighlight; + } + + // Activate + if (g.NavMoveFlags & ImGuiNavMoveFlags_Activate) { + g.NavNextActivateId = result->ID; + g.NavNextActivateFlags = ImGuiActivateFlags_None; + } + + // Enable nav highlight + if ((g.NavMoveFlags & ImGuiNavMoveFlags_DontSetNavHighlight) == 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; + if (!IsNavInputTest(ImGuiNavInput_Cancel, ImGuiNavReadMode_Pressed)) return; + + IMGUI_DEBUG_LOG_NAV("[nav] ImGuiNavInput_Cancel\n"); + if (g.ActiveId != 0) { + if (!IsActiveIdUsingNavInput(ImGuiNavInput_Cancel)) 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) { + // Close open popup/menu + if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) + 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 = g.NavFocusScopeId = 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) && !IsActiveIdUsingKey(ImGuiKey_PageUp); + const bool page_down_held = + IsKeyDown(ImGuiKey_PageDown) && !IsActiveIdUsingKey(ImGuiKey_PageDown); + const bool home_pressed = + IsKeyPressed(ImGuiKey_Home) && !IsActiveIdUsingKey(ImGuiKey_Home); + const bool end_pressed = + IsKeyPressed(ImGuiKey_End) && !IsActiveIdUsingKey(ImGuiKey_End); + 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.NavHasScroll) { + // Fallback manual-scroll when window has no navigable item + if (IsKeyPressed(ImGuiKey_PageUp, true)) + SetScrollY(window, window->Scroll.y - window->InnerRect.GetHeight()); + else if (IsKeyPressed(ImGuiKey_PageDown, true)) + 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; + } 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; + } 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. + const ImGuiNavMoveFlags wanted_flags = + ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX | + ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY; + if (g.NavWindow && NavMoveRequestButNoResultYet() && + (g.NavMoveFlags & wanted_flags) && + (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; + 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; + 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.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); + 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 bool start_windowing_with_gamepad = + allow_windowing && !g.NavWindowingTarget && + IsNavInputTest(ImGuiNavInput_Menu, ImGuiNavReadMode_Pressed); + const bool start_windowing_with_keyboard = + allow_windowing && !g.NavWindowingTarget && io.KeyCtrl && + IsKeyPressed(ImGuiKey_Tab); + 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.NavWindowingToggleLayer = start_windowing_with_gamepad + ? true + : false; // Gamepad starts toggling layer + g.NavInputSource = start_windowing_with_keyboard + ? ImGuiInputSource_Keyboard + : ImGuiInputSource_Gamepad; + } + + // 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)IsNavInputTest(ImGuiNavInput_FocusPrev, + ImGuiNavReadMode_RepeatSlow) - + (int)IsNavInputTest(ImGuiNavInput_FocusNext, + ImGuiNavReadMode_RepeatSlow); + 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 (!IsNavInputDown(ImGuiNavInput_Menu)) { + 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 + g.NavWindowingHighlightAlpha = + ImMax(g.NavWindowingHighlightAlpha, + ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / + 0.05f)); // 1.0f + if (IsKeyPressed(ImGuiKey_Tab, true)) + NavUpdateWindowingHighlightWindow(io.KeyShift ? +1 : -1); + if (!io.KeyCtrl) 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. + const bool nav_keyboard_active = + (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; + if (nav_keyboard_active && IsKeyPressed(ImGuiKey_ModAlt)) { + 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) + if (io.InputQueueCharacters.Size > 0 || io.KeyCtrl || io.KeyShift || + io.KeySuper) + 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(ImGuiKey_ModAlt) && g.NavWindowingToggleLayer) + if (g.ActiveId == 0 || g.ActiveIdAllowOverlap) + if (IsMousePosValid(&io.MousePos) == IsMousePosValid(&io.MousePosPrev)) + apply_toggle_layer = true; + if (!IsKeyDown(ImGuiKey_ModAlt)) g.NavWindowingToggleLayer = false; + } + + // Move window + if (g.NavWindowingTarget && + !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove)) { + ImVec2 move_delta; + if (g.NavInputSource == ImGuiInputSource_Keyboard && !io.KeyShift) + move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_RawKeyboard, + ImGuiNavReadMode_Down); + if (g.NavInputSource == ImGuiInputSource_Gamepad) + move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, + ImGuiNavReadMode_Down); + if (move_delta.x != 0.0f || move_delta.y != 0.0f) { + const float NAV_MOVE_SPEED = 800.0f; + const float move_speed = ImFloor( + NAV_MOVE_SPEED * io.DeltaTime * + ImMin(io.DisplayFramebufferScale.x, + io.DisplayFramebufferScale + .y)); // FIXME: Doesn't handle variable framerate very well + ImGuiWindow* moving_window = g.NavWindowingTarget->RootWindow; + SetWindowPos(moving_window, moving_window->Pos + move_delta * move_speed, + ImGuiCond_Always); + MarkIniSettingsDirty(moving_window); + g.NavDisableMouseHover = true; + } + } + + // Apply final focus + if (apply_focus_window && + (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow)) { + ClearActiveID(); + NavRestoreHighlightAfterMove(); + apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window); + ClosePopupsOverWindow(apply_focus_window, false); + FocusWindow(apply_focus_window); + 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 "(Popup)"; + if ((window->Flags & ImGuiWindowFlags_MenuBar) && + strcmp(window->Name, "##MainMenuBar") == 0) + return "(Main menu bar)"; + return "(Untitled)"; +} + +// 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 +//----------------------------------------------------------------------------- + +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); + 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 + SetActiveIdUsingNavAndKeys(); + } 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. + BeginTooltip(); + if (g.DragDropAcceptIdPrev && + (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip)) { + ImGuiWindow* tooltip_window = g.CurrentWindow; + tooltip_window->Hidden = tooltip_window->SkipItems = true; + tooltip_window->HiddenFramesCanSkipItems = 1; + } + } + + 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 pushes 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) { + g.DragDropAcceptFlags = flags; + g.DragDropAcceptIdCurr = g.DragDropTargetId; + g.DragDropAcceptIdCurrRectSurface = r_surface; + } + + // Render default drop visuals + // FIXME-DRAGDROP: Settle on a proper default visuals for drop target. + payload.Preview = was_accepted_previously; + flags |= + (g.DragDropSourceFlags & + ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit + // the preview (useful for + // external sources that + // lives 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; + + return &payload; +} + +const ImGuiPayload* ImGui::GetDragDropPayload() { + ImGuiContext& g = *GImGui; + return g.DragDropActive ? &g.DragDropPayload : NULL; +} + +// We don't really use/need this now, but added it for the sake of consistency +// and because we might need it later. +void ImGui::EndDragDropTarget() { + ImGuiContext& g = *GImGui; + IM_ASSERT(g.DragDropActive); + IM_ASSERT(g.DragDropWithinTarget); + g.DragDropWithinTarget = false; +} + +//----------------------------------------------------------------------------- +// [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(); + PushAllowKeyboardFocus(false); + SetNextItemWidth(80.0f); + SliderInt("Default Depth", &g.LogDepthToExpandDefault, 0, 9, NULL); + PopAllowKeyboardFocus(); + 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] +// - CreateNewWindowSettings() [Internal] +// - FindWindowSettings() [Internal] +// - FindOrCreateWindowSettings() [Internal] +// - FindSettingsHandler() [Internal] +// - ClearIniSettings() [Internal] +// - LoadIniSettingsFromDisk() +// - LoadIniSettingsFromMemory() +// - SaveIniSettingsToDisk() +// - SaveIniSettingsToMemory() +// - 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; +} + +ImGuiWindowSettings* ImGui::CreateNewWindowSettings(const char* name) { + ImGuiContext& g = *GImGui; + +#if !IMGUI_DEBUG_INI_SETTINGS + // Skip to the "###" marker if any. We don't skip past to match the behavior + // of GetID() Preserve the full string when IMGUI_DEBUG_INI_SETTINGS is set to + // make .ini inspection easier. + if (const char* p = strstr(name, "###")) name = p; +#endif + 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; +} + +ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id) { + ImGuiContext& g = *GImGui; + for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); + settings != NULL; settings = g.SettingsWindows.next_chunk(settings)) + if (settings->ID == id) return settings; + return NULL; +} + +ImGuiWindowSettings* ImGui::FindOrCreateWindowSettings(const char* name) { + if (ImGuiWindowSettings* settings = FindWindowSettings(ImHashStr(name))) + return settings; + return CreateNewWindowSettings(name); +} + +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 (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + if (g.SettingsHandlers[handler_n].TypeHash == type_hash) + return &g.SettingsHandlers[handler_n]; + return NULL; +} + +void ImGui::ClearIniSettings() { + ImGuiContext& g = *GImGui; + g.SettingsIniData.clear(); + for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + if (g.SettingsHandlers[handler_n].ClearAllFn) + g.SettingsHandlers[handler_n].ClearAllFn(&g, + &g.SettingsHandlers[handler_n]); +} + +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 +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 (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + if (g.SettingsHandlers[handler_n].ReadInitFn) + g.SettingsHandlers[handler_n].ReadInitFn(&g, + &g.SettingsHandlers[handler_n]); + + 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 (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) + if (g.SettingsHandlers[handler_n].ApplyAllFn) + g.SettingsHandlers[handler_n].ApplyAllFn(&g, + &g.SettingsHandlers[handler_n]); +} + +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 (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) { + ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n]; + handler->WriteAllFn(&g, handler, &g.SettingsIniData); + } + if (out_size) *out_size = (size_t)g.SettingsIniData.size(); + return g.SettingsIniData.c_str(); +} + +static void WindowSettingsHandler_ClearAll(ImGuiContext* ctx, + ImGuiSettingsHandler*) { + ImGuiContext& g = *ctx; + for (int i = 0; i != g.Windows.Size; i++) g.Windows[i]->SettingsOffset = -1; + g.SettingsWindows.clear(); +} + +static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, + ImGuiSettingsHandler*, + const char* name) { + ImGuiWindowSettings* settings = ImGui::FindOrCreateWindowSettings(name); + ImGuiID id = settings->ID; + *settings = + ImGuiWindowSettings(); // Clear existing if recycling previous entry + 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 (int i = 0; i != g.Windows.Size; i++) { + ImGuiWindow* window = g.Windows[i]; + if (window->Flags & ImGuiWindowFlags_NoSavedSettings) continue; + + ImGuiWindowSettings* settings = + (window->SettingsOffset != -1) + ? g.SettingsWindows.ptr_from_offset(window->SettingsOffset) + : ImGui::FindWindowSettings(window->ID); + 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; + } + + // 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)) { + 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] 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 (int n = 0; n < g.Viewports.Size; n++) { + ImGuiViewportP* viewport = g.Viewports[n]; + + // 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*) { + ImGuiContext& g = *GImGui; + 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*) { + 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) { + ImGuiContext& g = *GImGui; + 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*) { + ImGuiContext& g = *GImGui; + return g.ClipboardHandlerData.empty() ? NULL : g.ClipboardHandlerData.begin(); +} + +static void SetClipboardTextFn_DefaultImpl(void*, const char* text) { + ImGuiContext& g = *GImGui; + 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; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (hwnd == 0) hwnd = (HWND)ImGui::GetIO().ImeWindowHandle; +#endif + 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_METRICS_WINDOW + +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 (int i = 0; i != g.Windows.Size; i++) { + ImGuiWindow* thumb_window = g.Windows[i]; + 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 (int n = 0; n < g.Viewports.Size; n++) + bb_full.Add(g.Viewports[n]->GetMainRect()); + ImVec2 p = window->DC.CursorPos; + ImVec2 off = p - bb_full.Min * SCALE; + for (int n = 0; n < g.Viewports.Size; n++) { + ImGuiViewportP* viewport = g.Viewports[n]; + 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); +} + +// 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("list", 4, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | + ImGuiTableFlags_SizingFixedFit)) + 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::IsItemHovered()) { + ImGui::BeginTooltip(); + 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 (int i = 0; i < atlas->Fonts.Size; i++) { + ImFont* font = atlas->Fonts[i]; + PushID(font); + DebugNodeFont(font); + PopID(); + } + if (TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, + atlas->TexHeight)) { + ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + ImVec4 border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); + 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->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->LastFirstRowHeight); + } else if (rect_type == TRT_ColumnsContentUnfrozen) { + ImGuiTableColumn* c = &table->Columns[n]; + return ImRect( + c->WorkMinX, + table->InnerClipRect.Min.y + table_instance->LastFirstRowHeight, + 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(); + } + } + + 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 (int i = 0; i < g.Windows.Size; i++) + if (g.Windows[i]->LastFrameActive + 1 >= g.FrameCount) + temp_buffer.push_back(g.Windows[i]); + 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 (int viewport_i = 0; viewport_i < g.Viewports.Size; viewport_i++) + drawlist_count += + g.Viewports[viewport_i]->DrawDataBuilder.GetDrawListCount(); + 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 (int viewport_i = 0; viewport_i < g.Viewports.Size; viewport_i++) { + ImGuiViewportP* viewport = g.Viewports[viewport_i]; + for (int layer_i = 0; + layer_i < IM_ARRAYSIZE(viewport->DrawDataBuilder.Layers); layer_i++) + for (int draw_list_i = 0; + draw_list_i < viewport->DrawDataBuilder.Layers[layer_i].Size; + draw_list_i++) + DebugNodeDrawList( + NULL, viewport->DrawDataBuilder.Layers[layer_i][draw_list_i], + "DrawList"); + } + TreePop(); + } + + // Viewports + if (TreeNode("Viewports", "Viewports (%d)", g.Viewports.Size)) { + Indent(GetTreeNodeToLabelSpacing()); + RenderViewportsThumbnails(); + Unindent(GetTreeNodeToLabelSpacing()); + for (int i = 0; i < g.Viewports.Size; i++) + DebugNodeViewport(g.Viewports[i]); + TreePop(); + } + + // Details for Popups + if (TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size)) { + for (int i = 0; i < g.OpenPopupStack.Size; i++) { + ImGuiWindow* window = g.OpenPopupStack[i].Window; + BulletText("PopupID: %08x, Window: '%s'%s%s", g.OpenPopupStack[i].PopupId, + window ? window->Name : "NULL", + window && (window->Flags & ImGuiWindowFlags_ChildWindow) + ? " ChildWindow" + : "", + window && (window->Flags & ImGuiWindowFlags_ChildMenu) + ? " ChildMenu" + : ""); + } + 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(""); + Text("SettingsDirtyTimer %.2f", g.SettingsDirtyTimer); + if (TreeNode("SettingsHandlers", "Settings handlers: (%d)", + g.SettingsHandlers.Size)) { + for (int n = 0; n < g.SettingsHandlers.Size; n++) + BulletText("%s", g.SettingsHandlers[n].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(); + } + + // Misc Details + 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)); + Text("ActiveIdWindow: '%s'", + g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL"); + + int active_id_using_key_input_count = 0; + for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++) + active_id_using_key_input_count += g.ActiveIdUsingKeyInputMask[n] ? 1 : 0; + Text( + "ActiveIdUsing: Wheel: %d, NavDirMask: %X, NavInputMask: %X, " + "KeyInputMask: %d key(s)", + g.ActiveIdUsingMouseWheel, g.ActiveIdUsingNavDirMask, + g.ActiveIdUsingNavInputMask, active_id_using_key_input_count); + 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("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", + g.DragDropActive, g.DragDropPayload.SourceId, + g.DragDropPayload.DataType, g.DragDropPayload.DataSize); + Unindent(); + + Text("NAV,FOCUS"); + Indent(); + Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL"); + Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer); + Text("NavInputSource: %s", GetInputSourceName(g.NavInputSource)); + Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible); + Text("NavActivateId/DownId/PressedId/InputId: %08X/%08X/%08X/%08X", + g.NavActivateId, g.NavActivateDownId, g.NavActivatePressedId, + g.NavActivateInputId); + 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 (int n = 0; n < g.Windows.Size; n++) { + ImGuiWindow* window = g.Windows[n]; + 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 (int column_n = 0; column_n < columns->Columns.Size; column_n++) + BulletText("Column %02d: OffsetNorm %.3f (= %.1f px)", column_n, + columns->Columns[column_n].OffsetNorm, + GetColumnOffsetFromNorm(columns, + columns->Columns[column_n].OffsetNorm)); + TreePop(); +} + +// [DEBUG] Display contents of ImDrawList +void ImGui::DebugNodeDrawList(ImGuiWindow* window, const ImDrawList* draw_list, + const char* label) { + ImGuiContext& g = *GImGui; + 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 (int n = 0; n < storage->Data.Size; n++) { + const ImGuiStorage::ImGuiStoragePair& p = storage->Data[n]; + 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*"); + p += ImFormatString(p, buf_end - p, " { "); + 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 ? ", " : "", + (tab->NameOffset != -1) ? tab_bar->GetTabName(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++) { + const 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: %.1f, Width: %.1f/%.1f", tab_n, + (tab->ID == tab_bar->SelectedTabId) ? '*' : ' ', tab->ID, + (tab->NameOffset != -1) ? tab_bar->GetTabName(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 (int layer_i = 0; + layer_i < IM_ARRAYSIZE(viewport->DrawDataBuilder.Layers); layer_i++) + for (int draw_list_i = 0; + draw_list_i < viewport->DrawDataBuilder.Layers[layer_i].Size; + draw_list_i++) + DebugNodeDrawList( + NULL, viewport->DrawDataBuilder.Layers[layer_i][draw_list_i], + "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->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]); + continue; + } + 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); + if (IsItemHovered()) + GetForegroundDrawList(window)->AddRect( + r.Min + window->Pos, r.Max + window->Pos, IM_COL32(255, 255, 0, 255)); + } + 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 (int n = 0; n < window->ColumnsStorage.Size; n++) + DebugNodeColumns(&window->ColumnsStorage[n]); + TreePop(); + } + DebugNodeStorage(&window->StateStorage, "Storage"); + TreePop(); +} + +void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings* settings) { + 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); +} + +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 +//----------------------------------------------------------------------------- + +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; + g.DebugLogBuf.appendf("[%05d] ", g.FrameCount); + g.DebugLogBuf.appendfv(fmt, args); +} + +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; + } + + AlignTextToFramePadding(); + Text("Log events:"); + SameLine(); + 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); + + if (SmallButton("Clear")) g.DebugLogBuf.clear(); + SameLine(); + if (SmallButton("Copy")) SetClipboardText(g.DebugLogBuf.c_str()); + BeginChild("##log", ImVec2(0.0f, 0.0f), true, + ImGuiWindowFlags_AlwaysVerticalScrollbar | + ImGuiWindowFlags_AlwaysHorizontalScrollbar); + TextUnformatted( + g.DebugLogBuf.begin(), + g.DebugLogBuf + .end()); // FIXME-OPT: Could use a line index, but TextUnformatted() + // has a semi-decent fast path for large text. + if (GetScrollY() >= GetScrollMaxY()) SetScrollHereY(1.0f); + EndChild(); + + End(); +} + +//----------------------------------------------------------------------------- +// [SECTION] OTHER DEBUG TOOLS (ITEM PICKER, STACK TOOL) +//----------------------------------------------------------------------------- + +// [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; + if (IsMouseClicked(0) && hovered_id) { + g.DebugItemPickerBreakId = hovered_id; + g.DebugItemPickerActive = false; + } + SetNextWindowBgAlpha(0.60f); + BeginTooltip(); + Text("HoveredId: 0x%08X", hovered_id); + Text("Press ESC to abort picking."); + TextColored( + GetStyleColorVec4(hovered_id ? ImGuiCol_Text : ImGuiCol_TextDisabled), + "Click to break in debugger!"); + 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 assume 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(ImGuiKey_ModCtrl) && + 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*, 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_METRICS_WINDOW + +//----------------------------------------------------------------------------- + +// 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/customchar-ui/libs/imgui/imgui_draw.cpp b/customchar-ui/libs/imgui/imgui_draw.cpp new file mode 100644 index 0000000..d8eb9fe --- /dev/null +++ b/customchar-ui/libs/imgui/imgui_draw.cpp @@ -0,0 +1,5157 @@ +// dear imgui, v1.88 WIP +// (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 + +#include "imgui.h" +#ifndef IMGUI_DISABLE + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif + +#include "imgui_internal.h" +#ifdef IMGUI_ENABLE_FREETYPE +#include "misc/freetype/imgui_freetype.h" +#endif + +#include // vsnprintf, sscanf, printf +#if !defined(alloca) +#if defined(__GLIBC__) || defined(__sun) || defined(__APPLE__) || \ + defined(__NEWLIB__) +#include // alloca (glibc uses . Note that Cygwin may have _WIN32 defined, so the order matters here) +#elif defined(_WIN32) +#include // alloca +#if !defined(alloca) +#define alloca _alloca // for clang with MS Codegen +#endif +#else +#include // alloca +#endif +#endif + +// 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 : 6255) // [Static Analyzer] _alloca indicates failure + // by raising a stack overflow exception. + // Consider using _malloca instead. +#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 +#if __has_warning("-Walloca") +#pragma clang diagnostic ignored \ + "-Walloca" // warning: use of function '__builtin_alloca' is discouraged +#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 +#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 "imstb_rectpack.h" +#endif +#endif + +#ifdef IMGUI_ENABLE_STB_TRUETYPE +#ifndef STB_TRUETYPE_IMPLEMENTATION // in case the user already have an + // implementation in the _same_ compilation + // unit (e.g. unity builds) +#ifndef IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION // in case the user already + // have an implementation in + // another compilation unit +#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 "imstb_truetype.h" +#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)); + + 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() { + if (CmdBuffer.Size == 0) return; + ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + if (curr_cmd->ElemCount == 0 && curr_cmd->UserCallback == NULL) + 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 < 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) 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 + ImVec2* temp_normals = + (ImVec2*)alloca(points_count * ((use_texture || !thick_line) ? 3 : 5) * + sizeof(ImVec2)); //-V630 + 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) 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 + ImVec2* temp_normals = + (ImVec2*)alloca(points_count * sizeof(ImVec2)); //-V630 + 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& center, 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& center, 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& center, 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& center, 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) { + 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) { + 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 + // 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) { + 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& center, float radius, ImU32 col, + int num_segments, float thickness) { + if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f) return; + + if (num_segments <= 0) { + // Use arc with automatic segment count + _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& center, float radius, ImU32 col, + int num_segments) { + if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f) return; + + if (num_segments <= 0) { + // Use arc with automatic segment count + _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& center, float radius, ImU32 col, + int num_segments, float thickness) { + if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2) return; + + // Because we are filling a closed shape we remove 1 from the count of + // segments/points + const float a_max = + (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; + PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1); + PathStroke(col, ImDrawFlags_Closed, thickness); +} + +// Guaranteed to honor 'num_segments' +void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, + int num_segments) { + if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2) return; + + // Because we are filling a closed shape we remove 1 from the count of + // segments/points + const float a_max = + (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; + PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); + PathFillConvex(col); +} + +// 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 +//----------------------------------------------------------------------------- + +// 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 (int i = 0; i < CmdListsCount; i++) { + ImDrawList* cmd_list = CmdLists[i]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { + ImDrawCmd* cmd = &cmd_list->CmdBuffer[cmd_i]; + 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 = 3; // FIXME: 2 may be a better default? + 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 (int i = 0; i < ConfigData.Size; i++) + if (ConfigData[i].FontData && ConfigData[i].FontDataOwnedByAtlas) { + IM_FREE(ConfigData[i].FontData); + ConfigData[i].FontData = NULL; + } + + // When clearing this we lose access to the font name and other information + // used to build the font. + for (int i = 0; i < Fonts.Size; i++) + if (Fonts[i]->ConfigData >= ConfigData.Data && + Fonts[i]->ConfigData < ConfigData.Data + ConfigData.Size) { + Fonts[i]->ConfigData = NULL; + Fonts[i]->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) { + unsigned char* data = pixels + x + y * stride; + for (int j = h; j > 0; j--, data += stride) + for (int i = 0; i < w; i++) data[i] = table[data[i]]; +} + +#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 GlyphsMap) +}; + +// 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) + 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++) { + ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; + if (src_tmp.GlyphsCount == 0) continue; + + // 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. + 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. + + 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 (int i = 0; i < atlas->Fonts.Size; i++) + if (atlas->Fonts[i]->DirtyLookupTables) atlas->Fonts[i]->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::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 the character information database of the + // Information-technology Promotion Agency, Japan + // - https://mojikiban.ipa.go.jp/mji/ + // - Available under the terms of the Creative Commons + // Attribution-ShareAlike 2.1 Japan (CC BY-SA 2.1 JP). + // - https://creativecommons.org/licenses/by-sa/2.1/jp/deed.en + // - https://creativecommons.org/licenses/by-sa/2.1/jp/legalcode + // - You can generate this code by the script at: + // - https://github.com/vaiorabbit/everyday_use_kanji + // - References: + // - List of Joyo Kanji + // - (Official list by the Agency for Cultural Affairs) + // https://www.bunka.go.jp/kokugo_nihongo/sisaku/joho/joho/kakuki/14/tosin02/index.html + // - (Wikipedia) + // https://en.wikipedia.org/wiki/List_of_j%C5%8Dy%C5%8D_kanji + // - List of Jinmeiyo Kanji + // - (Official list by the Ministry of Justice) + // http://www.moj.go.jp/MINJI/minji86.html + // - (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; + DotChar = (ImWchar)-1; + 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); + + // Ellipsis character 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)); + if (DotChar == (ImWchar)-1) + DotChar = + FindFirstExistingGlyph(this, dots_chars, IM_ARRAYSIZE(dots_chars)); + + // 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; +} + +// 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]; +} + +const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, + const char* text_end, + float wrap_width) const { + // Simple word-wrapping for English, not full-featured. Please submit failing + // cases! + // 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.) + + // 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; + 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 == 0) break; + + 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; + } + + 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 (word_wrap_eol == + s) // Wrap_width is too small to fit anything. Force displaying 1 + // character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 + // but it's ok because we use s >= word_wrap_eol + // below + } + + 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; + + // Wrapping skips upcoming blanks + while (s < text_end) { + const char c = *s; + if (ImCharIsBlankA(c)) { + s++; + } else if (c == '\n') { + s++; + break; + } else { + break; + } + } + 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 == 0) // Malformed UTF-8? + break; + } + + 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); + const char* word_wrap_eol = NULL; + + // Fast-forward to first visible line + const char* s = text_begin; + if (y + line_height < clip_rect.y && !word_wrap_enabled) + while (y + line_height < clip_rect.y && s < text_end) { + s = (const char*)memchr(s, '\n', text_end - s); + s = s ? s + 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_current_idx = draw_list->_VtxCurrentIdx; + + const ImU32 col_untinted = col | ~IM_COL32_A_MASK; + + 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 (word_wrap_eol == + s) // Wrap_width is too small to fit anything. Force displaying 1 + // character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 + // but it's ok because we use s >= word_wrap_eol + // below + } + + if (s >= word_wrap_eol) { + x = start_x; + y += line_height; + word_wrap_eol = NULL; + + // Wrapping skips upcoming blanks + while (s < text_end) { + const char c = *s; + if (ImCharIsBlankA(c)) { + s++; + } else if (c == '\n') { + s++; + break; + } else { + break; + } + } + 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 == 0) // Malformed UTF-8? + break; + } + + 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: + { + idx_write[0] = (ImDrawIdx)(vtx_current_idx); + idx_write[1] = (ImDrawIdx)(vtx_current_idx + 1); + idx_write[2] = (ImDrawIdx)(vtx_current_idx + 2); + idx_write[3] = (ImDrawIdx)(vtx_current_idx); + idx_write[4] = (ImDrawIdx)(vtx_current_idx + 2); + idx_write[5] = (ImDrawIdx)(vtx_current_idx + 3); + 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; + vtx_write += 4; + vtx_current_idx += 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_current_idx; +} + +//----------------------------------------------------------------------------- +// [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) { + 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'wYmTFmL<" + "LD)F^%[tC'8;+9E#C$g%#5Y>q9wI>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&" + "#3^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/customchar-ui/libs/imgui/imgui_tables.cpp b/customchar-ui/libs/imgui/imgui_tables.cpp new file mode 100644 index 0000000..03cafa3 --- /dev/null +++ b/customchar-ui/libs/imgui/imgui_tables.cpp @@ -0,0 +1,4824 @@ +// dear imgui, v1.88 WIP +// (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 is 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 _NoHostExtenY is set) Y with ScrollX/ScrollY enabled: using a child +// window for scrolling +// - outer_size.y < 0.0f -> Bottom-align. Not meaningful is 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 that 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 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 +//----------------------------------------------------------------------------- +// About overriding column sizing policy and width/weight with +// TableSetupColumn(): We use a default parameter of 'init_width_or_weight == +// -1'. +// - 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 full padding. +//----------------------------------------------------------------------------- +// 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 weight/widths. +//----------------------------------------------------------------------------- +// About using column width: +// If a column is manual 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 +// only submit 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() return 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 + +#include "imgui.h" +#ifndef IMGUI_DISABLE + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui_internal.h" + +// System includes +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#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 + // 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 && + "Only 1..64 columns allowed!"); + 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 rules 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 int instance_no = + (table->LastFrameActive != g.FrameCount) ? 0 : table->InstanceCurrent + 1; + const ImGuiID instance_id = id + instance_no; + const ImGuiTableFlags table_last_flags = table->Flags; + if (instance_no > 0) + IM_ASSERT(table->ColumnsCount == columns_count && + "BeginTable(): Cannot change columns count mid-frame while " + "preserving same ID"); + + // 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 + table->ID = id; + table->Flags = flags; + table->InstanceCurrent = (ImS16)instance_no; + 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; + if (instance_no > 0 && table->InstanceDataExtra.Size < instance_no) + table->InstanceDataExtra.push_back(ImGuiTableInstanceData()); + + // 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); + } 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(instance_id); + + // 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; + table->CellPaddingY = g.Style.CellPadding.y; + + 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->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 elements of the grid + table->BorderColorStrong = GetColorU32(ImGuiCol_TableBorderStrong); + table->BorderColorLight = GetColorU32(ImGuiCol_TableBorderLight); + + // Make table current + g.CurrentTable = table; + 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 + 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 per number of nested tables +// + 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 + ImSpanAllocator<3> 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); + 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); +} + +// Apply queued resizing/reordering/hiding requests +void ImGui::TableBeginApplyRequests(ImGuiTable* table) { + // Handle resizing request + // (We process this at the first TableBegin of the frame) + // 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 the 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(). Runs on the first call to TableNextRow(), to give a chance for +// TableSetupColumn() 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; + table->EnabledMaskByIndex = 0x00; + table->EnabledMaskByDisplayOrder = 0x00; + 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++; + table->EnabledMaskByIndex |= (ImU64)1 << column_n; + table->EnabledMaskByDisplayOrder |= (ImU64)1 << 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 (!(table->EnabledMaskByIndex & ((ImU64)1 << 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 && + (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n))) + 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_avail = + ((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) + ? table->InnerClipRect.GetWidth() + : work_rect.GetWidth(); + 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 (!(table->EnabledMaskByIndex & ((ImU64)1 << 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 (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << 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; + } + + ImGuiTableInstanceData* table_instance = + TableGetInstanceData(table, table->InstanceCurrent); + 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 bool is_hovering_table = ItemHoverable(mouse_hit_rect, 0); + + // [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; + table->VisibleMaskByIndex = 0x00; + table->RequestOutputMaskByIndex = 0x00; + 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 || + column_n < table->FreezeColumnsCount) + ? ImGuiNavLayer_Menu + : ImGuiNavLayer_Main); + + 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 ((table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)) == 0) { + // 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) table->VisibleMaskByIndex |= ((ImU64)1 << 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; + if (column->IsRequestOutput) + table->RequestOutputMaskByIndex |= ((ImU64)1 << column_n); + + // 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); + + // [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 (table->IsContextPopupOpen && + table->InstanceCurrent == table->InstanceInteracted) { + const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID); + if (BeginPopupEx(context_menu_id, ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoSavedSettings)) { + TableDrawContextMenu(table); + EndPopup(); + } else { + table->IsContextPopupOpen = false; + } + } + + // [Part 13] Sanitize and build sort specs before we have a change 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); + + // 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 feedback +// noise +// - Submit ahead of table contents and header, use +// ImGuiButtonFlags_AllowItemOverlap to prioritize widgets +// overlapping the same area. +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 (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << 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); + // GetForegroundDrawList()->AddRect(hit_rect.Min, hit_rect.Max, + // IM_COL32(255, 0, 0, 100)); + KeepAliveID(column_id); + + bool hovered = false, held = false; + bool pressed = ButtonBehavior(hit_rect, column_id, &hovered, &held, + ImGuiButtonFlags_FlattenChildren | + ImGuiButtonFlags_AllowItemOverlap | + 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 (table->EnabledMaskByIndex & ((ImU64)1 << 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->ID + table->InstanceCurrent, + "Mismatching PushID/PopID!"); + IM_ASSERT_USER_ERROR(outer_window->DC.ItemWidthStack.Size >= + temp_data->HostBackupItemWidthStackSize, + "Too many PopItemWidth!"); + 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; +} + +// 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] +// - 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(const ImGuiTable* table, int column_n, + int instance_no) { + IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); + ImGuiID id = table->ID + 1 + (instance_no * table->ColumnsCount) + column_n; + return id; +} + +// Return -1 when table is not hovered. return columns_count if the unused space +// at the right of visible columns is hovered. +int ImGui::TableGetHoveredColumn() { + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + if (!table) return -1; + return (int)table->HoveredColumnBody; +} + +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 ((table->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0) 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->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->CellPaddingY * 2.0f; + table->RowPosY2 = ImMax(table->RowPosY2, table->RowPosY1 + row_min_height); + + // Disable output until user calls TableNextColumn() + table->InnerWindow->SkipItems = true; +} + +// [Internal] 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.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) { + // 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++) { + ImGuiTableColumn* column = &table->Columns[column_n]; + column->NavLayerCurrent = + (ImS8)((column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu + : ImGuiNavLayer_Main); + } + if (unfreeze_rows_actual) { + IM_ASSERT(table->IsUnfrozenRows == false); + table->IsUnfrozenRows = true; + + // BgClipRect starts as table->InnerClipRect, reduce it now and make + // BgClipRectForDrawCmd == BgClipRect + float y0 = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y); + 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->RequestOutputMaskByIndex & ((ImU64)1 << column_n)) != 0; +} + +// [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. + int column_n = table->CurrentColumn; + return (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)) != 0; +} + +// [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) { + 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->CellPaddingY; + 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.CurrLineTextBaseOffset = table->RowTextBaseline; + window->DC.NavLayerCurrent = (ImGuiNavLayer)column->NavLayerCurrent; + + 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; + + // To allow ImGuiListClipper to function we propagate our row height + if (!column->IsEnabled) + window->DC.CursorPos.y = ImMax(window->DC.CursorPos.y, table->RowPosY2); + + window->SkipItems = column->IsSkipItems; + if (column->IsSkipItems) { + ImGuiContext& g = *GImGui; + 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 + ImGuiContext& g = *GImGui; + 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; + + // 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); + table->RowPosY2 = + ImMax(table->RowPosY2, window->DC.CursorMaxPos.y + table->CellPaddingY); + 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 support a single _trailing_ stretch + // column. + + // 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 quantity + 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 || + table->VisibleMaskByIndex != table->EnabledMaskByIndex) + ? +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; + ImBitArray ChannelsMask; + + MergeGroup() { ChannelsCount = 0; } + }; + int merge_group_mask = 0x00; + MergeGroup merge_groups[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 ((table->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0) 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 < IMGUI_TABLE_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); + merge_group->ChannelsMask.SetBit(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; + ImBitArray + remaining_mask; // We need 132-bit of storage + remaining_mask.SetBitRange(LEADING_DRAW_CHANNELS, splitter->_Count); + remaining_mask.ClearBit(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); +#if 0 + GetOverlayDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0f, 0, 1.0f); + GetOverlayDrawList()->AddLine(merge_group->ClipRect.Min, merge_clip_rect.Min, IM_COL32(255, 100, 0, 200)); + GetOverlayDrawList()->AddLine(merge_group->ClipRect.Max, merge_clip_rect.Max, IM_COL32(255, 100, 0, 200)); +#endif + remaining_count -= merge_group->ChannelsCount; + for (int n = 0; n < IM_ARRAYSIZE(remaining_mask.Storage); n++) + remaining_mask.Storage[n] &= ~merge_group->ChannelsMask.Storage[n]; + for (int n = 0; n < splitter->_Count && merge_channels_count != 0; + n++) { + // Copy + overwrite new clip rect + if (!merge_group->ChannelsMask.TestBit(n)) continue; + merge_group->ChannelsMask.ClearBit(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 (!remaining_mask.TestBit(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 (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << 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) You can sort your data again when 'SpecsChanged == true'. It will be +// true with sorting specs have changed since last call, or the first time. +// 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 had 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 + // - table->InstanceCurrent is only >0 when we use multiple + // BeginTable/EndTable calls with same identifier. + const char* name = + (TableGetColumnFlags(column_n) & ImGuiTableColumnFlags_NoHeaderLabel) + ? "" + : TableGetColumnName(column_n); + PushID(table->InstanceCurrent * table->ColumnsCount + 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->CellPaddingY * 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 AllowItemOverlap 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_AllowItemOverlap); + if (g.ActiveId != id) SetItemAllowOverlap(); + 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 = cell_r.Max.x - w_arrow - w_sort_text; + 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.HoveredIdNotActiveTimer > g.TooltipSlowDelay) + SetTooltip("%.*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); + } +} + +// 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("Size column to fit###SizeOne", NULL, false, can_resize)) + TableSetColumnWidthAutoSingle(table, column_n); + } + + const char* size_all_desc; + if (table->ColumnsEnabledFixedCount == table->ColumnsEnabledCount && + (table->Flags & ImGuiTableFlags_SizingMask_) != + ImGuiTableFlags_SizingFixedSame) + size_all_desc = "Size all columns to fit###SizeAll"; // All fixed + else + size_all_desc = + "Size all columns to default###SizeAll"; // All stretch or mixed + if (MenuItem(size_all_desc, NULL)) TableSetColumnWidthAutoAll(table); + want_separator = true; + } + + // Ordering + if (table->Flags & ImGuiTableFlags_Reorderable) { + if (MenuItem("Reset order", 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: shouldn't have to leak into user performing a sort + 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_METRICS_WINDOW + +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) { + char buf[512]; + char* p = buf; + const char* buf_end = buf + IM_ARRAYSIZE(buf); + const bool is_active = + (table->LastFrameActive >= + ImGui::GetFrameCount() - + 2); // Note that fully clipped early out scrolling tables will + // appear as inactive here. + ImFormatString(p, buf_end - p, "Table 0x%08X (%d columns, in '%s')%s", + table->ID, table->ColumnsCount, table->OuterWindow->Name, + is_active ? "" : " *Inactive*"); + if (!is_active) { + PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); + } + bool open = TreeNode(table, "%s", buf); + 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) + ImGui::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); + // 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); + 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_METRICS_WINDOW + +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; + + 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; +} + +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); + 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)); + KeepAliveID(column_id); + if (IsClippedEx(column_hit_rect, + column_id)) // FIXME: Can be removed or replaced with a + // lower-level test + 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); +} + +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/customchar-ui/libs/imgui/imgui_widgets.cpp b/customchar-ui/libs/imgui/imgui_widgets.cpp new file mode 100644 index 0000000..d4764f7 --- /dev/null +++ b/customchar-ui/libs/imgui/imgui_widgets.cpp @@ -0,0 +1,10087 @@ +// dear imgui, v1.88 WIP +// (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 + +#include "imgui.h" +#ifndef IMGUI_DISABLE + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui_internal.h" + +// System includes +#include // toupper +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif + +//------------------------------------------------------------------------- +// 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(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(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; + + // FIXME-OPT: Handle the %s shortcut? + 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); + if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) + TextEx(va_arg(args, const char*), NULL, + ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting + else + 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]); + if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) + TextEx(va_arg(args, const char*), NULL, + ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting + else + 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; + bool need_backup = + (g.CurrentWindow->DC.TextWrapPos < + 0.0f); // Keep existing wrap position if one is already set + if (need_backup) PushTextWrapPos(0.0f); + if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0) + TextEx(va_arg(args, const char*), NULL, + ImGuiTextFlags_NoWidthForLargeClippedText); // Skip formatting + else + 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_; + + 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 + if (id != 0 && g.LastItemData.ID != id) IMGUI_TEST_ENGINE_ITEM_ADD(bb, id); +#endif + + bool pressed = false; + bool hovered = ItemHoverable(bb, id); + + // Drag source doesn't report as hovered + if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && + !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover)) + hovered = false; + + // 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; + + // 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. + if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && + (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0)) + hovered = false; + + // Mouse handling + if (hovered) { + if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || + (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) { + // Poll buttons + int mouse_button_clicked = -1; + if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseClicked[0]) { + mouse_button_clicked = 0; + } else if ((flags & ImGuiButtonFlags_MouseButtonRight) && + g.IO.MouseClicked[1]) { + mouse_button_clicked = 1; + } else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && + g.IO.MouseClicked[2]) { + mouse_button_clicked = 2; + } + + if (mouse_button_clicked != -1 && g.ActiveId != 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) { + int mouse_button_released = -1; + if ((flags & ImGuiButtonFlags_MouseButtonLeft) && + g.IO.MouseReleased[0]) { + mouse_button_released = 0; + } else if ((flags & ImGuiButtonFlags_MouseButtonRight) && + g.IO.MouseReleased[1]) { + mouse_button_released = 1; + } else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && + g.IO.MouseReleased[2]) { + mouse_button_released = 2; + } + if (mouse_button_released != -1) { + const bool has_repeated_at_least_once = + (flags & ImGuiButtonFlags_Repeat) && + 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 && (flags & ImGuiButtonFlags_Repeat)) + if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && + IsMouseClicked(g.ActiveIdMouseButton, true)) + 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 = + IsNavInputTest(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) + ? ImGuiNavReadMode_Repeat + : ImGuiNavReadMode_Pressed); + 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 = ImGuiInputSource_Nav; + 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; + IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT); + if (g.IO.MouseDown[mouse_button]) { + 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 = + (flags & ImGuiButtonFlags_Repeat) && + g.IO.MouseDownDurationPrev[mouse_button] >= + g.IO.KeyRepeatDelay; // Repeat mode trumps + if (!is_double_click_release && !is_repeating_already) pressed = true; + } + ClearActiveID(); + } + if (!(flags & ImGuiButtonFlags_NoNavFocus)) g.NavDisableHighlight = true; + } else if (g.ActiveIdSource == ImGuiInputSource_Nav) { + // 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; + + if (g.LastItemData.InFlags & ImGuiItemFlags_ButtonRepeat) + flags |= ImGuiButtonFlags_Repeat; + + 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; + + if (g.LastItemData.InFlags & ImGuiItemFlags_ButtonRepeat) + flags |= ImGuiButtonFlags_Repeat; + + 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) + g.Style.FramePadding * 2.0f); + 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 regular behavior of buttons, but it doesn't affect the user much + // because navigation tends to keep items visible). + 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, 12); + + 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) + g.Style.FramePadding * 2.0f); + ItemAdd(bb, id); + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None); + + // 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, 12); + RenderArrow(window->DrawList, bb.Min + g.Style.FramePadding, 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, outer_rect.Max.y); + else + return ImRect(ImMax(outer_rect.Min.x, + outer_rect.Max.x - border_size - scrollbar_size), + inner_rect.Min.y, outer_rect.Max.x, inner_rect.Max.y); +} + +void ImGui::Scrollbar(ImGuiAxis axis) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const ImGuiID id = GetWindowScrollbarID(window, axis); + KeepAliveID(id); + + // 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; + 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 ImVec2& padding, + const ImVec4& bg_col, const ImVec4& tint_col) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) return false; + + const ImRect bb(window->DC.CursorPos, + window->DC.CursorPos + size + padding * 2); + ItemSize(bb); + if (!ItemAdd(bb, id)) return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + // 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; +} + +// frame_padding < 0: uses FramePadding from style (default) +// frame_padding = 0: no framing +// frame_padding > 0: set framing size +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(); + + const ImVec2 padding = + (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) + : g.Style.FramePadding; + return ImageButtonEx(id, user_texture_id, size, uv0, uv1, padding, bg_col, + tint_col); +} + +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; + ImGuiItemFlags backup_item_flags = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_MixedValue; + pressed = Checkbox(label, &all_on); + g.CurrentItemFlags = backup_item_flags; + } 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); + window->DrawList->AddCircleFilled( + center, radius, + GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive + : hovered ? ImGuiCol_FrameBgHovered + : ImGuiCol_FrameBg), + 16); + if (active) { + const float pad = ImMax(1.0f, IM_FLOOR(square_sz / 6.0f)); + window->DrawList->AddCircleFilled(center, radius - pad, + GetColorU32(ImGuiCol_CheckMark), 16); + } + + if (style.FrameBorderSize > 0.0f) { + window->DrawList->AddCircle(center + ImVec2(1, 1), radius, + GetColorU32(ImGuiCol_BorderShadow), 16, + style.FrameBorderSize); + window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), + 16, 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 +void ImGui::SeparatorEx(ImGuiSeparatorFlags flags) { + 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 + + float thickness_draw = 1.0f; + float thickness_layout = 0.0f; + if (flags & ImGuiSeparatorFlags_Vertical) { + // Vertical separator, for menu bars (use current line height). Not exposed + // because it is misleading and it doesn't have an effect on regular layout. + 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_draw, y2)); + ItemSize(ImVec2(thickness_layout, 0.0f)); + if (!ItemAdd(bb, 0)) return; + + // Draw + window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), + ImVec2(bb.Min.x, bb.Max.y), + 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; + } + + 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 ImRect bb(ImVec2(x1, window->DC.CursorPos.y), + ImVec2(x2, window->DC.CursorPos.y + thickness_draw)); + ItemSize(ImVec2(0.0f, thickness_layout)); + const bool item_visible = ItemAdd(bb, 0); + if (item_visible) { + // Draw + window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x, bb.Min.y), + 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 overridable by the user + 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); +} + +// 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) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus; + bool item_add = ItemAdd(bb, id); + g.CurrentItemFlags = item_flags_backup; + if (!item_add) return false; + + 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, + ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap); + if (hovered) + g.LastItemData.StatusFlags |= + ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because + // bb_interact is larger than bb + if (g.ActiveId != id) SetItemAllowOverlap(); + + 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 + 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 left-to-right (could make it an + // option of the function?) 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; + } + if (width_excess > 0.0f) + for (int n = 0; n < count; n++) + if (items[n].Index < (int)(width_excess + 0.01f)) items[n].Width += 1.0f; +} + +//------------------------------------------------------------------------- +// [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; + SetNextWindowSizeConstraints( + ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount( + popup_max_height_in_items))); + } + + // 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 || + !window->ClipRect.Overlaps( + g.LastItemData.Rect)) // FIXME: Because we don't have a + // ImGuiItemStatusFlags_Visible flag to test + // last ItemAdd() result + 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.Contains( + 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)) { + 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] +//------------------------------------------------------------------------- +// - PatchFormatStringFloatToInt() +// - DataTypeGetInfo() +// - DataTypeFormatString() +// - DataTypeApplyOp() +// - DataTypeApplyOpFromText() +// - 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); + +// FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and +// because of this the compile-time default value for format was "%.0f". Even +// though we changed the compile-time default, we expect users to have carried +// %f around, which would break the display of DragInt() calls. To honor +// backward compatibility we are rewriting the format string, unless +// IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?! +static const char* PatchFormatStringFloatToInt(const char* fmt) { + if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && + fmt[4] == 0) // Fast legacy path for "%.0f" which is expected to be the + // most common case. + return "%d"; + const char* fmt_start = + ImParseFormatFindStart(fmt); // Find % (if any, and ignore %%) + const char* fmt_end = ImParseFormatFindEnd( + fmt_start); // Find end of format specifier, which itself is an exercise + // of confidence/recklessness (because snprintf is dependent + // on libc or user). + if (fmt_end > fmt_start && fmt_end[-1] == 'f') { +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (fmt_start == fmt && fmt_end[0] == 0) return "%d"; + const char* tmp_format; + ImFormatStringToTempBuffer( + &tmp_format, NULL, "%.*s%%d%s", (int)(fmt_start - fmt), fmt, + fmt_end); // Honor leading and trailing decorations, but lose + // alignment/precision. + return tmp_format; +#else + IM_ASSERT( + 0 && + "DragInt(): Invalid format string!"); // Old versions used a default + // parameter of "%.0f", please + // replace with e.g. "%d" +#endif + } + return fmt; +} + +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 + 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_Nav) { + const int decimal_precision = + is_floating_point ? ImParseFormatPrecision(format, 3) : 0; + adjust_delta = GetNavInputAmount2d( + ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, + ImGuiNavReadMode_RepeatFast, 1.0f / 10.0f, 10.0f)[axis]; + 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) { + if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) + ClearActiveID(); + else if (g.ActiveIdSource == ImGuiInputSource_Nav && + 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; + else if (data_type == ImGuiDataType_S32 && + strcmp(format, "%d") != + 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", + // read function more details.) + format = PatchFormatStringFloatToInt(format); + + // Tabbing or CTRL-clicking on Drag turns it into an InputText + const bool hovered = ItemHoverable(frame_bb, id); + bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); + if (!temp_input_is_active) { + const bool input_requested_by_tabbing = + temp_input_allowed && (g.LastItemData.StatusFlags & + ImGuiItemStatusFlags_FocusedByTabbing) != 0; + const bool clicked = (hovered && g.IO.MouseClicked[0]); + const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2); + if (input_requested_by_tabbing || clicked || double_clicked || + g.NavActivateId == id || g.NavActivateInputId == id) { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + if (temp_input_allowed) + if (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || + double_clicked || g.NavActivateInputId == id) + temp_input_is_active = true; + } + + // Experimental: 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 = g.NavActivateInputId = id; + g.NavActivateFlags = ImGuiActivateFlags_PreferInput; + temp_input_is_active = true; + } + } + + 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); + 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; +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +// Obsolete versions with power parameter. See +// https://github.com/ocornut/imgui/issues/3361 for details. +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, float power) { + ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None; + if (power != 1.0f) { + IM_ASSERT(power == 1.0f && + "Call function with ImGuiSliderFlags_Logarithmic flags instead " + "of using the old 'float power' function!"); + IM_ASSERT(p_min != NULL && + p_max != NULL); // When using a power curve the drag needs to + // have known bounds + drag_flags |= + ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths + } + return DragScalar(label, data_type, p_data, v_speed, p_min, p_max, format, + drag_flags); +} + +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, float power) { + ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None; + if (power != 1.0f) { + IM_ASSERT(power == 1.0f && + "Call function with ImGuiSliderFlags_Logarithmic flags instead " + "of using the old 'float power' function!"); + IM_ASSERT(p_min != NULL && + p_max != NULL); // When using a power curve the drag needs to + // have known bounds + drag_flags |= + ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths + } + return DragScalarN(label, data_type, p_data, components, v_speed, p_min, + p_max, format, drag_flags); +} + +#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +//------------------------------------------------------------------------- +// [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; + } + + // 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) { + if (v_min == v_max) return v_min; + const bool is_floating_point = + (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); + + TYPE result; + if (is_logarithmic) { + // We special-case the extents because otherwise our fudging can lead to + // "mathematically correct" but non-intuitive behaviors like a fully-left + // slider not actually reaching the minimum value + if (t <= 0.0f) + result = v_min; + else if (t >= 1.0f) + result = v_max; + else { + bool flipped = v_max < v_min; // Check if range is "backwards" + + // 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; + + 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 + if (is_floating_point) { + result = ImLerp(v_min, v_max, t); + } else { + // - 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. + if (t < 1.0) { + 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))); + } else { + result = v_max; + } + } + } + + return result; +} + +// FIXME: Move more of the code into 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 grab_padding = 2.0f; + const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f; + float grab_sz = style.GrabMinSize; + SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max); + if (!is_floating_point && + v_range >= 0) // v_range < 0 may happen on integer overflows + grab_sz = ImMax((float)(slider_sz / (v_range + 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]; + clicked_t = (slider_usable_sz > 0.0f) + ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / + slider_usable_sz, + 0.0f, 1.0f) + : 0.0f; + if (axis == ImGuiAxis_Y) clicked_t = 1.0f - clicked_t; + set_new_value = true; + } + } else if (g.ActiveIdSource == ImGuiInputSource_Nav) { + if (g.ActiveIdIsJustActivated) { + g.SliderCurrentAccum = + 0.0f; // Reset any stored nav delta upon activation + g.SliderCurrentAccumDirty = false; + } + + const ImVec2 input_delta2 = GetNavInputAmount2d( + ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, + ImGuiNavReadMode_RepeatFast, 0.0f, 0.0f); + float input_delta = + (axis == ImGuiAxis_X) ? input_delta2.x : -input_delta2.y; + if (input_delta != 0.0f) { + 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 (IsNavInputDown(ImGuiNavInput_TweakSlow)) input_delta /= 10.0f; + } else { + if ((v_range >= -100.0f && v_range <= 100.0f) || + IsNavInputDown(ImGuiNavInput_TweakSlow)) + input_delta = + ((input_delta < 0.0f) ? -1.0f : +1.0f) / + (float) + v_range; // Gamepad/keyboard tweak speeds in integer steps + else + input_delta /= 100.0f; + } + if (IsNavInputDown(ImGuiNavInput_TweakFast)) 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) { + 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."); + + ImGuiContext& g = *GImGui; + 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 = 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; + else if (data_type == ImGuiDataType_S32 && + strcmp(format, "%d") != + 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", + // read function more details.) + format = PatchFormatStringFloatToInt(format); + + // Tabbing or CTRL-clicking on Slider turns it into an input box + const bool hovered = ItemHoverable(frame_bb, id); + bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); + if (!temp_input_is_active) { + const bool input_requested_by_tabbing = + temp_input_allowed && (g.LastItemData.StatusFlags & + ImGuiItemStatusFlags_FocusedByTabbing) != 0; + const bool clicked = (hovered && g.IO.MouseClicked[0]); + if (input_requested_by_tabbing || clicked || g.NavActivateId == id || + g.NavActivateInputId == id) { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + if (temp_input_allowed && + (input_requested_by_tabbing || (clicked && g.IO.KeyCtrl) || + g.NavActivateInputId == id)) + temp_input_is_active = true; + } + } + + 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); + 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; + else if (data_type == ImGuiDataType_S32 && + strcmp(format, "%d") != + 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", + // read function more details.) + format = PatchFormatStringFloatToInt(format); + + const bool hovered = ItemHoverable(frame_bb, id); + if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || + g.NavActivateInputId == 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); +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +// Obsolete versions with power parameter. See +// https://github.com/ocornut/imgui/issues/3361 for details. +bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, + void* p_data, const void* p_min, const void* p_max, + const char* format, float power) { + ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None; + if (power != 1.0f) { + IM_ASSERT(power == 1.0f && + "Call function with ImGuiSliderFlags_Logarithmic flags instead " + "of using the old 'float power' function!"); + slider_flags |= + ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths + } + return SliderScalar(label, data_type, p_data, p_min, p_max, format, + slider_flags); +} + +bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, + int components, const void* v_min, const void* v_max, + const char* format, float power) { + ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None; + if (power != 1.0f) { + IM_ASSERT(power == 1.0f && + "Call function with ImGuiSliderFlags_Logarithmic flags instead " + "of using the old 'float power' function!"); + slider_flags |= + ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths + } + return SliderScalarN(label, data_type, v, components, v_min, v_max, format, + slider_flags); +} + +#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +//------------------------------------------------------------------------- +// [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 +// 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 fmt; + 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 "%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 == '.')) 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) { + char fmt_buf[32]; + char data_buf[32]; + format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf)); + 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 = DataTypeGetInfo(data_type)->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) { + 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); + + // 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(); + } else { + if (InputText(label, buf, IM_ARRAYSIZE(buf), flags)) + value_changed = DataTypeApplyFromText(buf, data_type, p_data, format); + } + 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() + 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(const ImWchar* text_begin, + const ImWchar* text_end, + const ImWchar** remaining, + ImVec2* out_offset, + bool stop_on_new_line) { + ImGuiContext& g = *GImGui; + 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 = *GImGui; + 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( + 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)); +} + +// 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. +static bool is_separator(unsigned int c) { + return ImCharIsBlankW(c) || c == ',' || c == ';' || c == '(' || c == ')' || + c == '{' || c == '}' || c == '[' || c == ']' || c == '|' || + c == '\n' || c == '\r'; +} +static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx) { + if (obj->Flags & ImGuiInputTextFlags_Password) return 0; + return idx > 0 ? (is_separator(obj->TextW[idx - 1]) && + !is_separator(obj->TextW[idx])) + : 1; +} +static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) { + if (obj->Flags & ImGuiInputTextFlags_Password) return 0; + return idx > 0 ? (!is_separator(obj->TextW[idx - 1]) && + is_separator(obj->TextW[idx])) + : 1; +} +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; +} +#define STB_TEXTEDIT_MOVEWORDLEFT \ + STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h +#ifdef __APPLE__ // FIXME: Move setting to IO structure +#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_MAC +#else +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; +} +#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_WIN +#endif + +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 +#include "imstb_textedit.h" + +// stb_textedit internally allows for a single undo record to do addition and +// deletion, but somehow, calling the stb_textedit_paste() function creates two +// separate records, so we perform it manually. (FIXME: Report to nothings/stb?) +static void stb_textedit_replace(ImGuiInputTextState* str, + STB_TexteditState* state, + const 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); + if (text_len <= 0) return; + if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len)) { + state->cursor = 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) { + 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 = *GImGui; + 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(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::GetCurrentContext()->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 = *GImGui; + const unsigned c_decimal_point = (unsigned int)g.PlatformLocaleDecimalPoint; + + // 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') *p_char = (c += (unsigned int)('A' - 'a')); + + if (flags & ImGuiInputTextFlags_CharsNoBlank) + if (ImCharIsBlankW(c)) return false; + } + + // Custom callback filter + if (flags & ImGuiInputTextFlags_CallbackCharFilter) { + ImGuiInputTextCallbackData callback_data; + memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData)); + 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); +} + +// 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); + 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.NavActivateInputId == id) || + (g.NavActivateId == id && + 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); + 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(); + + // 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) + } + + if (g.ActiveId != id && init_make_active) { + IM_ASSERT(state && state->ID == id); + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + + // Declare our inputs + IM_ASSERT(ImGuiNavInput_COUNT < 32); + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory)) + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel); + SetActiveIdUsingKey(ImGuiKey_Home); + SetActiveIdUsingKey(ImGuiKey_End); + if (is_multiline) { + SetActiveIdUsingKey(ImGuiKey_PageUp); + SetActiveIdUsingKey(ImGuiKey_PageDown); + } + if (flags & + (ImGuiInputTextFlags_CallbackCompletion | + ImGuiInputTextFlags_AllowTabInput)) // Disable keyboard tabbing out as + // we will use the \t character. + { + SetActiveIdUsingKey(ImGuiKey_Tab); + } + } + + // 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 + const 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 enter_pressed = 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]; + g.WantTextInputNextFrame = 1; + + // 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)); + + const bool is_osx = io.ConfigMacOSXBehaviors; + 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) { + // FIXME: unselect on late click could be done release? + if (hovered) { + 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 except 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) + const bool ignore_char_inputs = + (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper); + if ((flags & ImGuiInputTextFlags_AllowTabInput) && + IsKeyPressed(ImGuiKey_Tab) && !ignore_char_inputs && !io.KeyShift && + !is_readonly) { + unsigned int c = '\t'; // Insert TAB + if (InputTextFilterCharacter(&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. + 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(&c, flags, callback, callback_user_data, + ImGuiInputSource_Keyboard)) + state->OnKeyPressed((int)c); + } + + // Consume characters + io.InputQueueCharacters.resize(0); + } + } + + // Process other shortcuts/key-presses + bool cancel_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_osx = io.ConfigMacOSXBehaviors; + const bool is_osx_shift_shortcut = + is_osx && (io.KeyMods == (ImGuiModFlags_Super | ImGuiModFlags_Shift)); + 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 + const bool is_ctrl_key_only = (io.KeyMods == ImGuiModFlags_Ctrl); + const bool is_shift_key_only = (io.KeyMods == ImGuiModFlags_Shift); + const bool is_shortcut_key = g.IO.ConfigMacOSXBehaviors + ? (io.KeyMods == ImGuiModFlags_Super) + : (io.KeyMods == ImGuiModFlags_Ctrl); + + const bool is_cut = + ((is_shortcut_key && IsKeyPressed(ImGuiKey_X)) || + (is_shift_key_only && IsKeyPressed(ImGuiKey_Delete))) && + !is_readonly && !is_password && + (!is_multiline || state->HasSelection()); + const bool is_copy = + ((is_shortcut_key && IsKeyPressed(ImGuiKey_C)) || + (is_ctrl_key_only && IsKeyPressed(ImGuiKey_Insert))) && + !is_password && (!is_multiline || state->HasSelection()); + const bool is_paste = + ((is_shortcut_key && IsKeyPressed(ImGuiKey_V)) || + (is_shift_key_only && IsKeyPressed(ImGuiKey_Insert))) && + !is_readonly; + const bool is_undo = ((is_shortcut_key && IsKeyPressed(ImGuiKey_Z)) && + !is_readonly && is_undoable); + const bool is_redo = + ((is_shortcut_key && IsKeyPressed(ImGuiKey_Y)) || + (is_osx_shift_shortcut && IsKeyPressed(ImGuiKey_Z))) && + !is_readonly && is_undoable; + + // 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 is_validate_enter = + IsKeyPressed(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_KeypadEnter); + const bool is_validate_nav = + (IsNavInputTest(ImGuiNavInput_Activate, ImGuiNavReadMode_Pressed) && + !IsKeyPressed(ImGuiKey_Space)) || + IsNavInputTest(ImGuiNavInput_Input, ImGuiNavReadMode_Pressed); + const bool is_cancel = + IsKeyPressed(ImGuiKey_Escape) || + IsNavInputTest(ImGuiNavInput_Cancel, ImGuiNavReadMode_Pressed); + + 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) { + 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_validate_enter) { + bool ctrl_enter_for_new_line = + (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; + if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || + (!ctrl_enter_for_new_line && io.KeyCtrl)) { + enter_pressed = clear_active_id = true; + } else if (!is_readonly) { + unsigned int c = '\n'; // Insert new line + if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, + ImGuiInputSource_Keyboard)) + state->OnKeyPressed((int)c); + } + } else if (is_validate_nav) { + IM_ASSERT(!is_validate_enter); + enter_pressed = clear_active_id = true; + } else if (is_cancel) { + clear_active_id = cancel_edit = true; + } else if (is_undo || is_redo) { + state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO); + state->ClearSelection(); + } else if (is_shortcut_key && IsKeyPressed(ImGuiKey_A)) { + 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;) { + unsigned int c; + s += ImTextCharFromUtf8(&c, s, NULL); + if (c == 0) break; + if (!InputTextFilterCharacter(&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 (cancel_edit) { + // Restore initial value. Only return true if restoring to the initial + // value changes the current buffer contents. + if (!is_readonly && strcmp(buf, state->InitialTextA.Data) != 0) { + // 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; + 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 = + !cancel_edit || + (enter_pressed && (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 && + IsKeyPressed(ImGuiKey_Tab)) { + 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; + memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData)); + 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( + 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; + } + } + + // Clear temporary user storage + state->Flags = ImGuiInputTextFlags_None; + } + + // 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.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)); + value_changed = true; + } + + // Release active ID at the end of the function (so e.g. pressing Return still + // does a final application of the value) + if (clear_active_id && g.ActiveId == id) ClearActiveID(); + + // 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(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(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(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)); + ImGuiItemFlags backup_item_flags = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop; + EndChild(); + item_data_backup.StatusFlags |= + (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow); + g.CurrentItemFlags = backup_item_flags; + + // ...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); + if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0) + return enter_pressed; + else + return value_changed; +} + +void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) { +#ifndef IMGUI_DISABLE_METRICS_WINDOW + 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); + Text("CurLenW: %d, CurLenA: %d, Cursor: %d, Selection: %d..%d", + state->CurLenA, state->CurLenW, stb_state->cursor, + stb_state->select_start, stb_state->select_end); + 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); +} + +// 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) { + // This check is optional. Suppose we have two color widgets side by side, + // both widgets display different colors, but both colors have hue and/or + // saturation undefined. With color check: hue/saturation is preserved in one + // widget. Editing color in one widget would reset hue/saturation in another + // one. Without color check: common hue/saturation would be displayed in all + // widgets that have hue/saturation undefined. g.ColorEditLastColor is stored + // as ImU32 RGB value: this essentially gives us color equality check with + // reduced precision. Tiny external color changes would not be detected and + // this check would still pass. This is OK, since we only restore + // hue/saturation _only_ if they are undefined, therefore this change flipping + // hue/saturation from undefined to a very tiny value would still be + // represented in color picker. + ImGuiContext& g = *GImGui; + if (g.ColorEditLastColor != + 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.ColorEditLastHue == 1)) + *H = g.ColorEditLastHue; + + // When V == 0, S is undefined. + if (*V == 0.0f) *S = g.ColorEditLastSat; +} + +// 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); + + // 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 greyscale 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")) { + 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)) { + SameLine(0.0f, 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.ColorEditLastHue = f[0]; + g.ColorEditLastSat = f[1]; + ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); + g.ColorEditLastColor = + 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]; + } + + 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 + 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) 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); + 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 greyscale 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)); + + // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is + // rapidly modified using SV square. + if (g.ColorEditLastColor == + ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0))) + H = g.ColorEditLastHue; + 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.ColorEditLastHue = H; + g.ColorEditLastSat = S; + g.ColorEditLastColor = + 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 = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32); + 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(6, 6); + draw_list->PrimVtx(tra, uv_white, hue_color32); + draw_list->PrimVtx(trb, uv_white, hue_color32); + draw_list->PrimVtx(trc, uv_white, col_white); + draw_list->PrimVtx(tra, uv_white, 0); + draw_list->PrimVtx(trb, uv_white, col_black); + draw_list->PrimVtx(trc, uv_white, 0); + 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; + draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, + user_col32_striped_of_alpha, 12); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, col_midgrey, 12); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, 12); + + // 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) MarkItemEdited(g.LastItemData.ID); + + 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) + 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; + + BeginTooltipEx(ImGuiTooltipFlags_OverridePreviousTooltip, + ImGuiWindowFlags_None); + 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; + 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(); +} + +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; + 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(); +} + +//------------------------------------------------------------------------- +// [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); +} + +bool ImGui::TreeNodeBehaviorIsOpen(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; + storage->SetInt(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; + storage->SetInt(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; + + // Store a flag for the current depth to tell if we will allow closing this + // node when navigating one of its child. For this purpose we essentially + // compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and + // TreePop(). This is currently only support 32 level deep and we are fine + // with (1 << Depth) overflowing into a zero. + const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; + bool is_open = TreeNodeBehaviorIsOpen(id, flags); + if (is_open && !g.NavIdIsAlive && + (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && + !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth); + + bool item_add = ItemAdd(interact_bb, id); + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; + g.LastItemData.DisplayRect = frame_bb; + + 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_AllowItemOverlap) + button_flags |= ImGuiButtonFlags_AllowItemOverlap; + 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; + 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; + NavMoveRequestCancel(); + } + + if (toggled) { + is_open = !is_open; + window->DC.StateStorage->SetInt(id, is_open); + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen; + } + } + if (flags & ImGuiTreeNodeFlags_AllowItemOverlap) SetItemAllowOverlap(); + + // 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 ? ImGuiDir_Down : ImGuiDir_Right, 1.0f); + else // Leaf without bullet, left-adjusted text + text_pos.x -= text_offset_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 ? 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 (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && + NavMoveRequestButNoResultYet()) + if (g.NavIdIsAlive && + (window->DC.TreeJumpToParentOnPopMask & tree_depth_mask)) { + SetNavID(window->IDStack.back(), g.NavLayer, 0, ImRect()); + NavMoveRequestCancel(); + } + 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_AllowItemOverlap | + 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 * 2.0f - button_size); + float button_y = g.LastItemData.Rect.Min.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_AllowItemOverlap +// 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_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_AllowItemOverlap) { + button_flags |= ImGuiButtonFlags_AllowItemOverlap; + } + + 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 == window->DC.NavFocusScopeIdCurrent) + 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, + window->DC.NavFocusScopeIdCurrent, + WindowRectAbsToRel(window, bb)); // (bb == NavRect) + g.NavDisableHighlight = true; + } + } + if (pressed) MarkItemEdited(id); + + if (flags & ImGuiSelectableFlags_AllowItemOverlap) SetItemAllowOverlap(); + + // 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 (held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld)) + hovered = true; + 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); + } + 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; +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +// OBSOLETED in 1.81 (from February 2021) +bool ImGui::ListBoxHeader(const char* label, int items_count, + int height_in_items) { + // If height_in_items == -1, default height is maximum 7. + ImGuiContext& g = *GImGui; + float height_in_items_f = + (height_in_items < 0 ? ImMin(items_count, 7) : height_in_items) + 0.25f; + ImVec2 size; + size.x = 0.0f; + size.y = GetTextLineHeightWithSpacing() * height_in_items_f + + g.Style.FramePadding.y * 2.0f; + return BeginListBox(label, size); +} +#endif + +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, ImVec2 frame_size) { + 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); + if (frame_size.x == 0.0f) frame_size.x = CalcItemWidth(); + if (frame_size.y == 0.0f) + frame_size.y = label_size.y + (style.FramePadding.y * 2); + + 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); + + // 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 + 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); + + 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. PuhsID() 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]; + return (window->DC.NavLayerCurrent == upper_popup->ParentNavLayer && + upper_popup->Window && + (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu)); +} + +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 flags = + ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus; + if (window->Flags & ImGuiWindowFlags_ChildMenu) + 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, 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) + const bool menuset_is_open = IsRootOfOpenMenuSet(); + ImGuiWindow* backed_nav_window = g.NavWindow; + if (menuset_is_open) g.NavWindow = window; + + // 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; + const ImGuiSelectableFlags selectable_flags = + ImGuiSelectableFlags_NoHoldingActiveID | + 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, 0.0f)); + 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, 0.0f)); + 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) g.NavWindow = backed_nav_window; + + 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; + ImGuiWindow* child_menu_window = + (g.BeginPopupStack.Size < g.OpenPopupStack.Size && + g.OpenPopupStack[g.BeginPopupStack.Size].SourceWindow == window) + ? g.OpenPopupStack[g.BeginPopupStack.Size].Window + : NULL; + if (g.HoveredWindow == window && child_menu_window != NULL && + !(window->Flags & ImGuiWindowFlags_MenuBar)) { + float ref_unit = g.FontSize; // FIXME-DPI + ImRect next_window_rect = child_menu_window->Rect(); + ImVec2 ta = (g.IO.MousePos - g.IO.MouseDelta); + ImVec2 tb = (window->Pos.x < child_menu_window->Pos.x) + ? next_window_rect.GetTL() + : next_window_rect.GetTR(); + ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) + ? 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 += (window->Pos.x < child_menu_window->Pos.x) + ? -0.5f + : +0.5f; // to avoid numerical issues (FIXME: ??) + 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_other_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) + 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 (!menu_is_open && want_open && + g.OpenPopupStack.Size > g.BeginPopupStack.Size) { + // Don't recycle same menu level in the same frame, first close the other + // menu and yield for a frame. + OpenPopup(label); + return false; + } + + menu_is_open |= want_open; + if (want_open) OpenPopup(label); + + if (menu_is_open) { + SetNextWindowPos( + popup_pos, + ImGuiCond_Always); // Note: this is super 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, flags); // menu_is_open can be 'false' when the popup is completely + // clipped (e.g. zero size display) + PopStyleVar(); + } 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 _within our child menu_ failed, close + // ourselves (the _parent_ menu). A menu doesn't close itself because + // EndMenuBar() wants the catch the last Left<>Right inputs. However, it means + // that with the current code, a BeginMenu() from outside another menu or a + // menu-bar won't be closable with the Left direction. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && + window->DC.LayoutType == ImGuiLayoutType_Vertical) + if (g.NavWindow && + (g.NavWindow->RootWindowForNav->Flags & ImGuiWindowFlags_Popup) && + g.NavWindow->RootWindowForNav->ParentWindow == window) { + ClosePopupToLevel(g.BeginPopupStack.Size, 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); + + const bool menuset_is_open = IsRootOfOpenMenuSet(); + ImGuiWindow* backed_nav_window = g.NavWindow; + if (menuset_is_open) g.NavWindow = window; + + // 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(); + + const ImGuiSelectableFlags selectable_flags = + ImGuiSelectableFlags_SelectOnRelease | + 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(); + 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, 0.0f)); + 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) g.NavWindow = backed_nav_window; + + 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] +// - TabBarRemoveTab() [Internal] +// - TabBarCloseTab() [Internal] +// - TabBarScrollClamp() [Internal] +// - TabBarScrollToTab() [Internal] +// - TabBarQueueChangeTabOrder() [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); +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); +} // namespace ImGui + +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->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; + + if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) PopID(); + + g.CurrentTabBarStack.pop_back(); + g.CurrentTabBar = g.CurrentTabBarStack.empty() + ? NULL + : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back()); +} + +// 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 = tab_bar->GetTabName(tab); + const bool has_close_button = + (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true; + tab->ContentWidth = TabItemCalcSize(tab_name, has_close_button).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); + int shrink_buffer_index = shrink_buffer_indexes[section_n]++; + g.ShrinkWidthBuffer[shrink_buffer_index].Index = tab_n; + g.ShrinkWidthBuffer[shrink_buffer_index].Width = tab->ContentWidth; + + IM_ASSERT(tab->ContentWidth > 0.0f); + tab->Width = tab->ContentWidth; + } + + // 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 > 0.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; + + 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_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; + } + + // 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; + + // Update scrolling + if (scroll_to_tab_id != 0) + TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections); + 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; + + // Clear name buffers + if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) + tab_bar->TabsNames.Buf.resize(0); + + // 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); +} + +// Dockables uses Name/ID in the global namespace. Non-dockable items use the ID +// stack. +static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label) { + 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; +} + +// The *TabId fields be 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) { + IM_ASSERT(!(tab->Flags & ImGuiTabItemFlags_Button)); + 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) tab_bar->NextSelectedTabId = tab->ID; + } +} + +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 = tab_bar->GetTabOrder(tab); + + // Scrolling happens only in the central section (leading/trailing sections + // are not scrolling) + // FIXME: This is all confusing. + float scrollable_width = tab_bar->BarRect.GetWidth() - sections[0].Width - + sections[2].Width - sections[1].Spacing; + + // 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::TabBarQueueReorder(ImGuiTabBar* tab_bar, const 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, + const 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 = tab_bar->GetTabOrder(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 = tab_bar->GetTabOrder(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 = tab_bar->GetTabName(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); + 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); +} + +bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, + ImGuiTabItemFlags flags) { + // Layout whole tab bar if not already done + if (tab_bar->WantLayout) TabBarLayout(tab_bar); + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) return false; + + const ImGuiStyle& style = g.Style; + const ImGuiID id = TabBarCalcTabID(tab_bar, label); + + // 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 | ImGuiItemFlags_NoNavDefaultFocus); + 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; + + // Calculate tab contents size + ImVec2 size = TabItemCalcSize(label, p_open != NULL); + + // 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->Width = size.x; + tab_bar->TabsAddedNew = true; + tab_is_new = true; + } + tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab); + 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 is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0; + tab->LastFrameVisible = g.FrameCount; + tab->Flags = flags; + + // Append name with zero-terminator + tab->NameOffset = (ImS32)tab_bar->TabsNames.size(); + tab_bar->TabsNames.append(label, label + strlen(label) + 1); + + // Update selected tab + if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && + tab_bar->NextSelectedTabId == 0) + if (!tab_bar_appearing || tab_bar->SelectedTabId == 0) + if (!is_tab_button) + tab_bar->NextSelectedTabId = id; // New tabs gets activated + if ((flags & ImGuiTabItemFlags_SetSelected) && + (tab_bar->SelectedTabId != + id)) // SetSelected can only be passed on explicit tab bar + if (!is_tab_button) tab_bar->NextSelectedTabId = id; + + // 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 | ImGuiItemFlags_NoNavDefaultFocus); + 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 + ImGuiButtonFlags button_flags = + ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease + : ImGuiButtonFlags_PressedOnClick) | + ImGuiButtonFlags_AllowItemOverlap); + if (g.DragDropActive) button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + if (pressed && !is_tab_button) tab_bar->NextSelectedTabId = id; + + // Allow the close button to overlap unless we are dragging (in which case we + // don't want any overlapping tabs to be hovered) + if (g.ActiveId != id) SetItemAllowOverlap(); + + // 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))) + if (!is_tab_button) tab_bar->NextSelectedTabId = id; + + 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, 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 && + g.HoveredIdNotActiveTimer > g.TooltipSlowDelay && IsItemHovered()) + if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && + !(tab->Flags & ImGuiTabItemFlags_NoTooltip)) + SetTooltip("%.*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); + 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) { + 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) + 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); +} + +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 * 2.0f - button_sz), bb.Min.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; + PushStyleVar(ImGuiStyleVar_FramePadding, frame_padding); + if (CloseButton(close_button_id, button_pos)) close_button_pressed = true; + PopStyleVar(); + 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/customchar-ui/libs/imgui/include/imconfig.h b/customchar-ui/libs/imgui/include/imconfig.h new file mode 100644 index 0000000..bd6c08e --- /dev/null +++ b/customchar-ui/libs/imgui/include/imconfig.h @@ -0,0 +1,180 @@ +//----------------------------------------------------------------------------- +// COMPILE-TIME OPTIONS FOR DEAR IMGUI +// 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 files 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 avoid using soon-to-be 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. +// It is very strongly recommended to NOT disable the demo windows during +// development. 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. Not recommended. #define +// IMGUI_DISABLE_METRICS_WINDOW // Disable metrics/debugger +// and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and +// ShowStackToolWindow() will be empty. + +//---- Don't implement some functions to reduce linkage requirements. +//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't +// implement default clipboard handler. Won't use and link with +// OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, +// kernel32.lib/.a) #define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // +//[Win32] [Default with Visual Studio] Implement default IME handler (require +// imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW) +//#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 enabled #define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION #define +// IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION + +//---- 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 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); } +*/ + +//---- 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 +// (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: Have the Item Picker break in the ItemAdd() function instead +// of ItemHoverable(), +// (which comes earlier in the code, will catch a few extra items, allow picking +// items other than Hovered one.) This adds a small runtime cost which is why it +// is not enabled by default. +//#define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX + +//---- Debug Tools: Enable slower asserts +//#define IMGUI_DEBUG_PARANOID + +//---- Tip: You can add extra functions within the ImGui:: namespace, here or in +// your own headers files. +/* +namespace ImGui +{ + void MyFunction(const char* name, const MyMatrix44& v); +} +*/ diff --git a/customchar-ui/libs/imgui/include/imgui.h b/customchar-ui/libs/imgui/include/imgui.h new file mode 100644 index 0000000..4b685dd --- /dev/null +++ b/customchar-ui/libs/imgui/include/imgui.h @@ -0,0 +1,6105 @@ +// dear imgui, v1.88 WIP +// (headers) + +// Help: +// - Read FAQ at http://dearimgui.org/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.org/faq +// - Homepage & latest https://github.com/ocornut/imgui +// - Releases & changelog https://github.com/ocornut/imgui/releases +// - Gallery https://github.com/ocornut/imgui/issues/5243 (please +// post your screenshots/video there!) +// - Wiki https://github.com/ocornut/imgui/wiki (lots of good +// stuff there) +// - Glossary https://github.com/ocornut/imgui/wiki/Glossary +// - Issues & support https://github.com/ocornut/imgui/issues + +// Getting Started? +// - For first-time users having issues compiling/linking/running or issues +// loading fonts: +// please post in https://github.com/ocornut/imgui/discussions if you cannot +// find a solution in resources above. + +/* + +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, 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 +#if !defined(IMGUI_DISABLE_INCLUDE_IMCONFIG_H) || \ + defined(IMGUI_INCLUDE_IMCONFIG_H) +#include "imconfig.h" +#endif + +#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 + +// Version +// (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in +// progress versions typically starts at XYY99 then bounce up to XYY00, XYY01 +// etc. when release tagging happens) +#define IMGUI_VERSION "1.88 WIP" +#define IMGUI_VERSION_NUM 18729 +#define IMGUI_CHECKVERSION() \ + ImGui::DebugCheckVersionAndDataLayout( \ + IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), \ + sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) +#define IMGUI_HAS_TABLE + +// 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 + +// 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 +#pragma clang diagnostic ignored "-Wold-style-cast" +#if __has_warning("-Wzero-as-null-pointer-constant") +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif +#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 + +// Enums/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 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 + ImGuiKey; // -> enum ImGuiKey_ // Enum: A key identifier +typedef int ImGuiNavInput; // -> enum ImGuiNavInput_ // Enum: An input + // identifier for navigation +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 identifier +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() +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 ImGuiModFlags; // -> enum ImGuiModFlags_ // Flags: for + // io.KeyMods (Ctrl/Shift/Alt/Super) +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) const { + IM_ASSERT(idx <= 1); + return (&x)[idx]; + } // We very rarely use this [] operator, the assert overhead is fine. + float& operator[](size_t idx) { + IM_ASSERT(idx <= 1); + return (&x)[idx]; + } // We very rarely use this [] operator, the assert overhead is fine. +#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 a 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 (useful if you + // want to do your own drawing via the DrawList API) +IMGUI_API ImVec2 GetWindowSize(); // get current window size +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 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 + // override with SetNextWindowContentSize(), in + // window coordinates + +// Windows Scrolling +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 PushAllowKeyboardFocus( + bool allow_keyboard_focus); // == tab stop enable. Allow focusing using + // TAB/Shift-TAB, enabled by default but you + // can disable it for certain widgets +IMGUI_API void PopAllowKeyboardFocus(); +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 style editor (ShowStyleEditor() function) to interactively see what +// the colors are) +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 an + // 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.org/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); + +// 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 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( + 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)); // <0 frame_padding uses default frame + // padding settings. 0 for no padding +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: Combo Box +// - 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 +// functions, 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 takes 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 takes 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 = NULL); // " +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 +// - Tooltip are windows following the mouse. They do not take focus away. +IMGUI_API void +BeginTooltip(); // begin/append a tooltip window. to create full-featured + // tooltip (with any kind of items). +IMGUI_API void EndTooltip(); +IMGUI_API void SetTooltip(const char* fmt, ...) IM_FMTARGS( + 1); // set a text-only tooltip, typically use with ImGui::IsItemHovered(). + // override any previous call to SetTooltip(). +IMGUI_API void SetTooltipV(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 interactions 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 columns 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 +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. + +// 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 it 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 requires 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 requires 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 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 +IMGUI_API void +SetItemAllowOverlap(); // allow last item to be overlapped by a subsequent + // item. sometimes useful with invisible buttons, + // selectables, etc. to catch unused area. + +// 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 +// Without IMGUI_DISABLE_OBSOLETE_KEYIO: (legacy support) +// - For 'ImGuiKey key' you can still use your legacy native/user indices +// according to how your backend/engine stored them in io.KeysDown[]. +// With IMGUI_DISABLE_OBSOLETE_KEYIO: (this is the way forward) +// - Any use of 'ImGuiKey' will assert when key < 512 will be passed, +// previously reserved as native/user keys indices +// - 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 +// - 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 cursor type, 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 cursor type +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() +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() + // ImGuiWindowFlags_ResizeFromAnySide = 1 << 17, // [Obsolete] --> Set + // io.ConfigWindowsResizeFromEdges=true and make sure mouse cursors are + // supported by backend (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) +}; + +// Flags for ImGui::InputText() +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) + +// Obsolete names (will be removed soon) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , + ImGuiInputTextFlags_AlwaysInsertMode = + ImGuiInputTextFlags_AlwaysOverwrite // [renamed in 1.82] name was not + // matching behavior +#endif +}; + +// 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_AllowItemOverlap = + 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 + 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 +}; + +// 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 another another flag, you need to pass in the +// ImGuiPopupFlags_MouseButtonRight flag. +// - 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 don'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_AllowItemOverlap = + 1 + << 4 // (WIP) Hit testing to allow subsequent widgets to overlap this one +}; + +// 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 an 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 appears in Headers). -> May move to style + ImGuiTableFlags_NoBordersInBodyUntilResize = + 1 << 12, // [ALPHA] Disable vertical borders in columns Body until + // hovered for resize (borders will always appears 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 outer-most padding. + // Generally desirable if you have headers. + ImGuiTableFlags_NoPadOuterX = + 1 << 22, // Default if BordersOuterV is off. Disable outer-most 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 create 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 + +// Obsolete names (will be removed soon) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +//, ImGuiTableFlags_ColumnsWidthFixed = ImGuiTableFlags_SizingFixedFit, +// ImGuiTableFlags_ColumnsWidthStretch = ImGuiTableFlags_SizingStretchSame // +// WIP Tables 2020/12 , ImGuiTableFlags_SizingPolicyFixed = +// ImGuiTableFlags_SizingFixedFit, ImGuiTableFlags_SizingPolicyStretch = +// ImGuiTableFlags_SizingStretchSame // WIP Tables 2021/01 +#endif +}; + +// 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) + +// Obsolete names (will be removed soon) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +// ImGuiTableColumnFlags_WidthAuto = +// ImGuiTableColumnFlags_WidthFixed | +// ImGuiTableColumnFlags_NoResize, // Column will not stretch and +// keep resizing based on submitted contents. +#endif +}; + +// 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 changes 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_AllowWhenOverlapped = + 1 << 8, // IsItemHovered() only: Return true even if the position is + // obstructed or overlapped by another window + ImGuiHoveredFlags_AllowWhenDisabled = + 1 << 9, // IsItemHovered() only: Return true even if the item is disabled + ImGuiHoveredFlags_NoNavOverride = + 1 << 10, // Disable using gamepad/keyboard navigation state when active, + // always query mouse. + ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | + ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | + ImGuiHoveredFlags_AllowWhenOverlapped, + ImGuiHoveredFlags_RootAndChildWindows = + ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows +}; + +// Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload() +enum ImGuiDragDropFlags_ { + ImGuiDragDropFlags_None = 0, + // BeginDragDropSource() flags + ImGuiDragDropFlags_SourceNoPreviewTooltip = + 1 << 0, // By default, a successful call to BeginDragDropSource opens a + // tooltip so you can display a preview or description of the + // source contents. This flag disable 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 disable 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. +}; + +// Keys value 0 to 511 are left unused as legacy native/opaque key values +// (< 1.87) Keys value >= 512 are named keys (>= 1.87) +enum ImGuiKey_ { + // 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 + ImGuiKey_GamepadStart, // Menu (Xbox) + (Switch) Start/Options + // (PS) // -- + ImGuiKey_GamepadBack, // View (Xbox) - (Switch) Share (PS) // -- + ImGuiKey_GamepadFaceUp, // Y (Xbox) X (Switch) Triangle (PS) // + // -> ImGuiNavInput_Input + ImGuiKey_GamepadFaceDown, // A (Xbox) B (Switch) Cross (PS) // + // -> ImGuiNavInput_Activate + ImGuiKey_GamepadFaceLeft, // X (Xbox) Y (Switch) Square (PS) // + // -> ImGuiNavInput_Menu + ImGuiKey_GamepadFaceRight, // B (Xbox) A (Switch) Circle (PS) + // // -> ImGuiNavInput_Cancel + ImGuiKey_GamepadDpadUp, // D-pad Up // -> ImGuiNavInput_DpadUp + ImGuiKey_GamepadDpadDown, // D-pad Down // -> ImGuiNavInput_DpadDown + ImGuiKey_GamepadDpadLeft, // D-pad Left // -> ImGuiNavInput_DpadLeft + ImGuiKey_GamepadDpadRight, // D-pad Right // -> ImGuiNavInput_DpadRight + ImGuiKey_GamepadL1, // L Bumper (Xbox) L (Switch) L1 (PS) // -> + // ImGuiNavInput_FocusPrev + ImGuiNavInput_TweakSlow + ImGuiKey_GamepadR1, // R Bumper (Xbox) R (Switch) R1 (PS) // -> + // ImGuiNavInput_FocusNext + ImGuiNavInput_TweakFast + ImGuiKey_GamepadL2, // L Trigger (Xbox) ZL (Switch) L2 (PS) [Analog] + ImGuiKey_GamepadR2, // R Trigger (Xbox) ZR (Switch) R2 (PS) [Analog] + ImGuiKey_GamepadL3, // L Thumbstick (Xbox) L3 (Switch) L3 (PS) + ImGuiKey_GamepadR3, // R Thumbstick (Xbox) R3 (Switch) R3 (PS) + ImGuiKey_GamepadLStickUp, // [Analog] // -> ImGuiNavInput_LStickUp + ImGuiKey_GamepadLStickDown, // [Analog] // -> ImGuiNavInput_LStickDown + ImGuiKey_GamepadLStickLeft, // [Analog] // -> ImGuiNavInput_LStickLeft + ImGuiKey_GamepadLStickRight, // [Analog] // -> ImGuiNavInput_LStickRight + ImGuiKey_GamepadRStickUp, // [Analog] + ImGuiKey_GamepadRStickDown, // [Analog] + ImGuiKey_GamepadRStickLeft, // [Analog] + ImGuiKey_GamepadRStickRight, // [Analog] + + // 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 keys (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 ImGuiKey_ModCtrl). + // - 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... + ImGuiKey_ModCtrl, + ImGuiKey_ModShift, + ImGuiKey_ModAlt, + ImGuiKey_ModSuper, + + // End of list + ImGuiKey_COUNT, // No valid ImGuiKey is ever greater than this value + + // [Internal] Prior to 1.87 we required user to fill io.KeysDown[512] using + // their own native index + a io.KeyMap[] array. We are ditching this method + // but keeping a legacy path for user code doing e.g. + // IsKeyPressed(MY_NATIVE_KEY_CODE) + 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 // First key stored in io.KeysData[0]. Accesses + // to io.KeysData[] must use (key - + // ImGuiKey_KeysData_OFFSET). +#else + ImGuiKey_KeysData_SIZE = ImGuiKey_COUNT, // Size of KeysData[]: hold legacy + // 0..512 keycodes + named keys + ImGuiKey_KeysData_OFFSET = + 0 // First key stored in io.KeysData[0]. Accesses to io.KeysData[] must + // use (key - ImGuiKey_KeysData_OFFSET). +#endif + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , + ImGuiKey_KeyPadEnter = ImGuiKey_KeypadEnter // Renamed in 1.87 +#endif +}; + +// Helper "flags" version of key-mods to store and compare multiple key-mods +// easily. Sometimes used for storage (e.g. io.KeyMods) but otherwise not much +// used in public API. +enum ImGuiModFlags_ { + ImGuiModFlags_None = 0, + ImGuiModFlags_Ctrl = 1 << 0, + ImGuiModFlags_Shift = 1 << 1, + ImGuiModFlags_Alt = 1 << 2, // Menu + ImGuiModFlags_Super = 1 << 3 // Cmd/Super/Windows key +}; + +// Gamepad/Keyboard navigation +// Since >= 1.87 backends you generally don't need to care about this enum since +// io.NavInputs[] is setup automatically. This might become private/internal +// some day. Keyboard: Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard +// to enable. NewFrame() will automatically fill io.NavInputs[] based on your +// io.AddKeyEvent() calls. Gamepad: Set io.ConfigFlags |= +// ImGuiConfigFlags_NavEnableGamepad to enable. Backend: set +// ImGuiBackendFlags_HasGamepad and fill the io.NavInputs[] fields before +// calling NewFrame(). Note that io.NavInputs[] is cleared by EndFrame(). Read +// instructions in imgui.cpp for more details. Download PNG/PSD at +// http://dearimgui.org/controls_sheets. +enum ImGuiNavInput_ { + // Gamepad Mapping + ImGuiNavInput_Activate, // Activate / Open / Toggle / Tweak value // + // e.g. Cross (PS4), A (Xbox), A (Switch), Space + // (Keyboard) + ImGuiNavInput_Cancel, // Cancel / Close / Exit // e.g. + // Circle (PS4), B (Xbox), B (Switch), Escape + // (Keyboard) + ImGuiNavInput_Input, // Text input / On-Screen keyboard // e.g. + // Triang.(PS4), Y (Xbox), X (Switch), Return (Keyboard) + ImGuiNavInput_Menu, // Tap: Toggle menu / Hold: Focus, Move, Resize // e.g. + // Square (PS4), X (Xbox), Y (Switch), Alt (Keyboard) + ImGuiNavInput_DpadLeft, // Move / Tweak / Resize window (w/ PadMenu) // + // e.g. D-pad Left/Right/Up/Down (Gamepads), Arrow + // keys (Keyboard) + ImGuiNavInput_DpadRight, // + ImGuiNavInput_DpadUp, // + ImGuiNavInput_DpadDown, // + ImGuiNavInput_LStickLeft, // Scroll / Move window (w/ PadMenu) // + // e.g. Left Analog Stick Left/Right/Up/Down + ImGuiNavInput_LStickRight, // + ImGuiNavInput_LStickUp, // + ImGuiNavInput_LStickDown, // + ImGuiNavInput_FocusPrev, // Focus Next window (w/ PadMenu) // + // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL + // (Switch) + ImGuiNavInput_FocusNext, // Focus Prev window (w/ PadMenu) // + // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL + // (Switch) + ImGuiNavInput_TweakSlow, // Slower tweaks // + // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL + // (Switch) + ImGuiNavInput_TweakFast, // Faster tweaks // + // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL + // (Switch) + + // [Internal] Don't use directly! This is used internally to differentiate + // keyboard from gamepad inputs for behaviors that require to differentiate + // them. Keyboard behavior that have no corresponding gamepad mapping (e.g. + // CTRL+TAB) will be directly reading from keyboard keys instead of + // io.NavInputs[]. + ImGuiNavInput_KeyLeft_, // Move left // = + // Arrow keys + ImGuiNavInput_KeyRight_, // Move right + ImGuiNavInput_KeyUp_, // Move up + ImGuiNavInput_KeyDown_, // Move down + ImGuiNavInput_COUNT +}; + +// Configuration flags stored in io.ConfigFlags. Set by user/application. +enum ImGuiConfigFlags_ { + ImGuiConfigFlags_None = 0, + ImGuiConfigFlags_NavEnableKeyboard = + 1 << 0, // Master keyboard navigation enable flag. NewFrame() will + // automatically fill io.NavInputs[] based on io.AddKeyEvent() + // calls + ImGuiConfigFlags_NavEnableGamepad = + 1 << 1, // Master gamepad navigation enable flag. This is mostly to + // instruct your imgui backend to fill io.NavInputs[]. 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_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 (will be removed) + // 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. +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 (will be removed) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + , + ImGuiSliderFlags_ClampOnInput = + ImGuiSliderFlags_AlwaysClamp // [renamed in 1.79] +#endif +}; + +// 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 an 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 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) + 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); + 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 constraint 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 + 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 appears + // 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. + 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]; + + 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). + 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.250f // 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. + void* UserData; // = NULL // Store your own data for retrieval by + // callbacks. + + 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 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. + + //------------------------------------------------------------------ + // 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 + + //------------------------------------------------------------------ + // 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 wh_x, + float wh_y); // Queue a mouse wheel update + 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 an UTF-16 character, it + // can be a surrogate + IMGUI_API void AddInputCharactersUTF8( + const char* str); // Queue a new characters input from an 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 + ClearInputCharacters(); // [Internal] Clear the text input buffer manually + IMGUI_API void ClearInputKeys(); // [Internal] Release all keys + + //------------------------------------------------------------------ + // 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; // Rough estimate of application framerate, in frame per + // second. Solely for convenience. Rolling average + // estimation based on io.DeltaTime over 120 frames. + 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(). +#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. +#endif + + //------------------------------------------------------------------ + // [Internal] Dear ImGui will maintain those fields. Forward compatibility not + // guaranteed! + //------------------------------------------------------------------ + + // 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. Others buttons allows 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. + float MouseWheelH; // Mouse wheel Horizontal. Most users don't have a mouse + // with an horizontal wheel, may not be filled by all + // backends. + 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 + float NavInputs[ImGuiNavInput_COUNT]; // Gamepad inputs. Cleared back to zero + // by EndFrame(). Keyboard keys will be + // auto-mapped and be written here by + // NewFrame(). + + // Other state maintained from data above + IO function calls + ImGuiModFlags + KeyMods; // Key mods flags (same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper + // but merged into flags), 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. + 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 NavInputsDownDuration[ImGuiNavInput_COUNT]; + float NavInputsDownDurationPrev[ImGuiNavInput_COUNT]; + 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 { + 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() + 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, 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 an 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 a 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 { + 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 ForceDisplayRangeByIndices() before first call to Step() if you need a + // range of items to be displayed regardless of visibility. + IMGUI_API void ForceDisplayRangeByIndices( + int item_min, + int item_max); // item_max is exclusive e.g. use (42, 42+1) to make item + // 42 always visible BUT due to alignment/padding of + // certain items it is likely that an extra item may be + // included on either end of the display range. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + 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 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) {} + ImColor(int r, int g, int b, int a = 255) { + float sc = 1.0f / 255.0f; + Value.x = (float)r * sc; + Value.y = (float)g * sc; + Value.z = (float)b * sc; + Value.w = (float)a * sc; + } + ImColor(ImU32 rgba) { + float sc = 1.0f / 255.0f; + Value.x = (float)((rgba >> IM_COL32_R_SHIFT) & 0xFF) * sc; + Value.y = (float)((rgba >> IM_COL32_G_SHIFT) & 0xFF) * sc; + Value.z = (float)((rgba >> IM_COL32_B_SHIFT) & 0xFF) * sc; + Value.w = (float)((rgba >> IM_COL32_A_SHIFT) & 0xFF) * sc; + } + 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 a 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. + const 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(const 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 guaranteed 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& center, float radius, ImU32 col, + int num_segments = 0, float thickness = 1.0f); + IMGUI_API void AddCircleFilled(const ImVec2& center, float radius, ImU32 col, + int num_segments = 0); + IMGUI_API void AddNgon(const ImVec2& center, float radius, ImU32 col, + int num_segments, float thickness = 1.0f); + IMGUI_API void AddNgonFilled(const ImVec2& center, 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& center, float radius, float a_min, + float a_max, int num_segments = 0); + IMGUI_API void PathArcToFast( + const ImVec2& center, 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) + // - FIXME-OBSOLETE: 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 + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + 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) +#endif + + // [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& center, float radius, + int a_min_sample, int a_max_sample, + int a_step); + IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, + float a_max, int num_segments); +}; + +// All draw data to render a Dear ImGui frame +// (NB: the style and the naming convention here is a little inconsistent, we +// currently preserve them for backward compatibility purpose, as this is one of +// the oldest structure exposed by the library! Basically, ImDrawList == +// CmdList) +struct ImDrawData { + bool Valid; // Only valid after Render() is called and before the next + // NewFrame() is called. + int CmdListsCount; // Number of ImDrawList* to render + int TotalIdxCount; // For convenience, sum of all ImDrawList's IdxBuffer.Size + int TotalVtxCount; // For convenience, sum of all ImDrawList's VtxBuffer.Size + ImDrawList** + CmdLists; // Array of ImDrawList* to render. The ImDrawList 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. + + // Functions + ImDrawData() { Clear(); } + void Clear() { + memset(this, 0, sizeof(*this)); + } // The ImDrawList are owned by ImGuiContext! + 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; // 3 // Rasterize at higher quality for sub-pixel + // positioning. Note the difference between 2 and 3 is + // minimal so you can reduce this to 2 to 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 // Pointer to a user-provided list of Unicode + // range (2 value per range, values are inclusive, + // zero-terminated list). THE ARRAY DATA NEEDS TO PERSIST AS + // LONG AS THE FONT IS ALIVE. + 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 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 built 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. In C++11, you can create UTF-8 + // string literal using the u8"Hello world" syntax. See FAQ 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* + 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. + + // [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. + ImWchar DotChar; // 2 // out // = '.' // Character used for ellipsis + // rendering (if a single '...' character isn't found) + 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 int GetKeyIndex(ImGuiKey key); // map ImGuiKey_* values into legacy + // native key index. == io.KeyMap[key] +#else +static inline int 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 +} // namespace ImGui + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +namespace ImGui { +// 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; +} +// OBSOLETED in 1.81 (from February 2021) +IMGUI_API bool ListBoxHeader( + const char* label, int items_count, + int height_in_items = + -1); // Helper to calculate size from items_count and height_in_items +static inline bool ListBoxHeader(const char* label, + const ImVec2& size = ImVec2(0, 0)) { + return BeginListBox(label, size); +} +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.0' argument instead +// of flags. For shared code, you can version check at compile-time with `#if +// IMGUI_VERSION_NUM >= 17704`. +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); +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); +static inline bool DragFloat(const char* label, float* v, float v_speed, + float v_min, float v_max, const char* format, + float power) { + return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, + format, power); +} +static inline bool DragFloat2(const char* label, float v[2], float v_speed, + float v_min, float v_max, const char* format, + float power) { + return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, + format, power); +} +static inline bool DragFloat3(const char* label, float v[3], float v_speed, + float v_min, float v_max, const char* format, + float power) { + return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, + format, power); +} +static inline bool DragFloat4(const char* label, float v[4], float v_speed, + float v_min, float v_max, const char* format, + float power) { + return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, + format, power); +} +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); +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); +static inline bool SliderFloat(const char* label, float* v, float v_min, + float v_max, const char* format, float power) { + return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, + power); +} +static inline bool SliderFloat2(const char* label, float v[2], float v_min, + float v_max, const char* format, float power) { + return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, + power); +} +static inline bool SliderFloat3(const char* label, float v[3], float v_min, + float v_max, const char* format, float power) { + return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, + power); +} +static inline bool SliderFloat4(const char* label, float v[4], float v_min, + float v_max, const char* format, float power) { + return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, + power); +} +// OBSOLETED in 1.77 (from June 2020) +static inline bool BeginPopupContextWindow(const char* str_id, + ImGuiMouseButton mb, + bool over_items) { + return BeginPopupContextWindow( + str_id, mb | (over_items ? 0 : ImGuiPopupFlags_NoOpenOverItems)); +} + +// Some of the older obsolete names along with their replacement (commented out +// so they are not reported in IDE) +// 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) 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) +} // namespace ImGui + +// 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 ImGuiKeyModFlags -> ImGuiModFlags in 1.88 (from April 2022) +typedef int ImGuiKeyModFlags; +enum ImGuiKeyModFlags_ { + ImGuiKeyModFlags_None = ImGuiModFlags_None, + ImGuiKeyModFlags_Ctrl = ImGuiModFlags_Ctrl, + ImGuiKeyModFlags_Shift = ImGuiModFlags_Shift, + ImGuiKeyModFlags_Alt = ImGuiModFlags_Alt, + ImGuiKeyModFlags_Super = ImGuiModFlags_Super +}; + +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +//----------------------------------------------------------------------------- + +#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/customchar-ui/libs/imgui/include/imgui_impl_glfw.h b/customchar-ui/libs/imgui/include/imgui_impl_glfw.h new file mode 100644 index 0000000..0dbd5c6 --- /dev/null +++ b/customchar-ui/libs/imgui/include/imgui_impl_glfw.h @@ -0,0 +1,74 @@ +// dear imgui: Platform Backend for GLFW +// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan, WebGPU..) +// (Info: GLFW is a cross-platform general purpose library for handling windows, +// inputs, OpenGL/Vulkan graphics context creation, etc.) + +// Implemented features: +// [X] Platform: Clipboard support. +// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() +// function. Pass ImGuiKey values to all key functions e.g. +// ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy GLFW_KEY_* values will also be +// supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set] [X] Platform: Gamepad +// support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. +// [X] Platform: Mouse cursor shape and visibility. Disable with +// 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange' (note: the resizing +// cursors requires GLFW 3.4+). + +// You can use unmodified imgui_impl_* files in your project. See examples/ +// folder for examples of using this. Prefer including the entire imgui/ +// repository into your project (either as a copy or as a submodule), and only +// build the backends you need. If you are new to Dear ImGui, read documentation +// from the docs/ folder + read the top of imgui.cpp. Read online: +// https://github.com/ocornut/imgui/tree/master/docs + +// About GLSL version: +// The 'glsl_version' initialization parameter defaults to "#version 150" if +// NULL. Only override if your GL version doesn't handle this GLSL version. Keep +// NULL if unsure! + +#pragma once +#include "imgui.h" // IMGUI_IMPL_API + +struct GLFWwindow; +struct GLFWmonitor; + +IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, + bool install_callbacks); +IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, + bool install_callbacks); +IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOther(GLFWwindow* window, + bool install_callbacks); +IMGUI_IMPL_API void ImGui_ImplGlfw_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplGlfw_NewFrame(); + +// GLFW callbacks (installer) +// - When calling Init with 'install_callbacks=true': +// ImGui_ImplGlfw_InstallCallbacks() is called. GLFW callbacks will be installed +// for you. They will chain-call user's previously installed callbacks, if any. +// - When calling Init with 'install_callbacks=false': GLFW callbacks won't be +// installed. You will need to call individual function yourself from your own +// GLFW callbacks. +IMGUI_IMPL_API void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window); +IMGUI_IMPL_API void ImGui_ImplGlfw_RestoreCallbacks(GLFWwindow* window); + +// GLFW callbacks (individual callbacks to call if you didn't install callbacks) +IMGUI_IMPL_API void ImGui_ImplGlfw_WindowFocusCallback( + GLFWwindow* window, int focused); // Since 1.84 +IMGUI_IMPL_API void ImGui_ImplGlfw_CursorEnterCallback( + GLFWwindow* window, int entered); // Since 1.84 +IMGUI_IMPL_API void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, + double x, + double y); // Since 1.87 +IMGUI_IMPL_API void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, + int button, int action, + int mods); +IMGUI_IMPL_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, + double xoffset, + double yoffset); +IMGUI_IMPL_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, + int scancode, int action, + int mods); +IMGUI_IMPL_API void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, + unsigned int c); +IMGUI_IMPL_API void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor* monitor, + int event); diff --git a/customchar-ui/libs/imgui/include/imgui_impl_opengl3.h b/customchar-ui/libs/imgui/include/imgui_impl_opengl3.h new file mode 100644 index 0000000..738bf7c --- /dev/null +++ b/customchar-ui/libs/imgui/include/imgui_impl_opengl3.h @@ -0,0 +1,63 @@ +// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic +// pipeline +// - Desktop GL: 2.x 3.x 4.x +// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) +// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, +// custom..) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier +// as void*/ImTextureID. Read the FAQ about ImTextureID! [x] Renderer: Desktop +// GL only: Support for large meshes (64k+ vertices) with 16-bit indices. + +// You can use unmodified imgui_impl_* files in your project. See examples/ +// folder for examples of using this. Prefer including the entire imgui/ +// repository into your project (either as a copy or as a submodule), and only +// build the backends you need. If you are new to Dear ImGui, read documentation +// from the docs/ folder + read the top of imgui.cpp. Read online: +// https://github.com/ocornut/imgui/tree/master/docs + +// About GLSL version: +// The 'glsl_version' initialization parameter should be NULL (default) or a +// "#version XXX" string. On computer platform the GLSL version default to +// "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es" +// Only override if your GL version doesn't handle this GLSL version. See GLSL +// version table at the top of imgui_impl_opengl3.cpp. + +#pragma once +#include "imgui.h" // IMGUI_IMPL_API + +// Backend API +IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = NULL); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data); + +// (Optional) Called by Init/NewFrame/Shutdown +IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture(); +IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects(); + +// Specific OpenGL ES versions +//#define IMGUI_IMPL_OPENGL_ES2 // Auto-detected on Emscripten +//#define IMGUI_IMPL_OPENGL_ES3 // Auto-detected on iOS/Android + +// You can explicitly select GLES2 or GLES3 API by using one of the '#define +// IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) + +// Try to detect GLES on matching platforms +#if defined(__APPLE__) +#include +#endif +#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || \ + (defined(__ANDROID__)) +#define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es" +#elif defined(__EMSCRIPTEN__) || defined(__amigaos4__) +#define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100" +#else +// Otherwise imgui_impl_opengl3_loader.h will be used. +#endif + +#endif diff --git a/customchar-ui/libs/imgui/include/imgui_impl_opengl3_loader.h b/customchar-ui/libs/imgui/include/imgui_impl_opengl3_loader.h new file mode 100644 index 0000000..9c2f414 --- /dev/null +++ b/customchar-ui/libs/imgui/include/imgui_impl_opengl3_loader.h @@ -0,0 +1,868 @@ +//----------------------------------------------------------------------------- +// About imgui_impl_opengl3_loader.h: +// +// We embed our own OpenGL loader to not require user to provide their own or to +// have to use ours, which proved to be endless problems for users. Our loader +// is custom-generated, based on gl3w but automatically filtered to only include +// enums/functions that we use in our imgui_impl_opengl3.cpp source file in +// order to be small. +// +// YOU SHOULD NOT NEED TO INCLUDE/USE THIS DIRECTLY. THIS IS USED BY +// imgui_impl_opengl3.cpp ONLY. THE REST OF YOUR APP SHOULD USE A DIFFERENT GL +// LOADER: ANY GL LOADER OF YOUR CHOICE. +// +// Regenerate with: +// python gl3w_gen.py --output ../imgui/backends/imgui_impl_opengl3_loader.h +// --ref ../imgui/backends/imgui_impl_opengl3.cpp ./extra_symbols.txt +// +// More info: +// https://github.com/dearimgui/gl3w_stripped +// https://github.com/ocornut/imgui/issues/4445 +//----------------------------------------------------------------------------- + +/* + * This file was generated with gl3w_gen.py, part of imgl3w + * (hosted at https://github.com/dearimgui/gl3w_stripped) + * + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __gl3w_h_ +#define __gl3w_h_ + +// Adapted from KHR/khrplatform.h to avoid including entire file. +#ifndef __khrplatform_h_ +typedef float khronos_float_t; +typedef signed char khronos_int8_t; +typedef unsigned char khronos_uint8_t; +typedef signed short int khronos_int16_t; +typedef unsigned short int khronos_uint16_t; +#ifdef _WIN64 +typedef signed long long int khronos_intptr_t; +typedef signed long long int khronos_ssize_t; +#else +typedef signed long int khronos_intptr_t; +typedef signed long int khronos_ssize_t; +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +typedef signed __int64 khronos_int64_t; +typedef unsigned __int64 khronos_uint64_t; +#elif (defined(__clang__) || defined(__GNUC__)) && (__cplusplus < 201100) +#include +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#else +typedef signed long long khronos_int64_t; +typedef unsigned long long khronos_uint64_t; +#endif +#endif // __khrplatform_h_ + +#ifndef __gl_glcorearb_h_ +#define __gl_glcorearb_h_ 1 +#ifdef __cplusplus +extern "C" { +#endif +/* +** Copyright 2013-2020 The Khronos Group Inc. +** SPDX-License-Identifier: MIT +** +** This header is generated from the Khronos OpenGL / OpenGL ES XML +** API Registry. The current version of the Registry, generator scripts +** used to make the header, and the header can be found at +** https://github.com/KhronosGroup/OpenGL-Registry +*/ +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && \ + !defined(__SCITECH_SNAP__) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#endif +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY* +#endif +#ifndef GLAPI +#define GLAPI extern +#endif +/* glcorearb.h is for use with OpenGL core profile implementations. +** It should should be placed in the same directory as gl.h and +** included as . +** +** glcorearb.h includes only APIs in the latest OpenGL core profile +** implementation together with APIs in newer ARB extensions which +** can be supported by the core profile. It does not, and never will +** include functionality removed from the core profile, such as +** fixed-function vertex and fragment processing. +** +** Do not #include both and either of or +** in the same source file. +*/ +/* Generated C header for: + * API: gl + * Profile: core + * Versions considered: .* + * Versions emitted: .* + * Default extensions included: glcore + * Additional extensions included: _nomatch_^ + * Extensions removed: _nomatch_^ + */ +#ifndef GL_VERSION_1_0 +typedef void GLvoid; +typedef unsigned int GLenum; + +typedef khronos_float_t GLfloat; +typedef int GLint; +typedef int GLsizei; +typedef unsigned int GLbitfield; +typedef double GLdouble; +typedef unsigned int GLuint; +typedef unsigned char GLboolean; +typedef khronos_uint8_t GLubyte; +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_FALSE 0 +#define GL_TRUE 1 +#define GL_TRIANGLES 0x0004 +#define GL_ONE 1 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_POLYGON_MODE 0x0B40 +#define GL_CULL_FACE 0x0B44 +#define GL_DEPTH_TEST 0x0B71 +#define GL_STENCIL_TEST 0x0B90 +#define GL_VIEWPORT 0x0BA2 +#define GL_BLEND 0x0BE2 +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_RGBA 0x1908 +#define GL_FILL 0x1B02 +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#define GL_LINEAR 0x2601 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +typedef void(APIENTRYP PFNGLPOLYGONMODEPROC)(GLenum face, GLenum mode); +typedef void(APIENTRYP PFNGLSCISSORPROC)(GLint x, GLint y, GLsizei width, + GLsizei height); +typedef void(APIENTRYP PFNGLTEXPARAMETERIPROC)(GLenum target, GLenum pname, + GLint param); +typedef void(APIENTRYP PFNGLTEXIMAGE2DPROC)(GLenum target, GLint level, + GLint internalformat, GLsizei width, + GLsizei height, GLint border, + GLenum format, GLenum type, + const void* pixels); +typedef void(APIENTRYP PFNGLCLEARPROC)(GLbitfield mask); +typedef void(APIENTRYP PFNGLCLEARCOLORPROC)(GLfloat red, GLfloat green, + GLfloat blue, GLfloat alpha); +typedef void(APIENTRYP PFNGLDISABLEPROC)(GLenum cap); +typedef void(APIENTRYP PFNGLENABLEPROC)(GLenum cap); +typedef void(APIENTRYP PFNGLFLUSHPROC)(void); +typedef void(APIENTRYP PFNGLPIXELSTOREIPROC)(GLenum pname, GLint param); +typedef void(APIENTRYP PFNGLREADPIXELSPROC)(GLint x, GLint y, GLsizei width, + GLsizei height, GLenum format, + GLenum type, void* pixels); +typedef GLenum(APIENTRYP PFNGLGETERRORPROC)(void); +typedef void(APIENTRYP PFNGLGETINTEGERVPROC)(GLenum pname, GLint* data); +typedef const GLubyte*(APIENTRYP PFNGLGETSTRINGPROC)(GLenum name); +typedef GLboolean(APIENTRYP PFNGLISENABLEDPROC)(GLenum cap); +typedef void(APIENTRYP PFNGLVIEWPORTPROC)(GLint x, GLint y, GLsizei width, + GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPolygonMode(GLenum face, GLenum mode); +GLAPI void APIENTRY glScissor(GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTexParameteri(GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glTexImage2D(GLenum target, GLint level, + GLint internalformat, GLsizei width, + GLsizei height, GLint border, GLenum format, + GLenum type, const void* pixels); +GLAPI void APIENTRY glClear(GLbitfield mask); +GLAPI void APIENTRY glClearColor(GLfloat red, GLfloat green, GLfloat blue, + GLfloat alpha); +GLAPI void APIENTRY glDisable(GLenum cap); +GLAPI void APIENTRY glEnable(GLenum cap); +GLAPI void APIENTRY glFlush(void); +GLAPI void APIENTRY glPixelStorei(GLenum pname, GLint param); +GLAPI void APIENTRY glReadPixels(GLint x, GLint y, GLsizei width, + GLsizei height, GLenum format, GLenum type, + void* pixels); +GLAPI GLenum APIENTRY glGetError(void); +GLAPI void APIENTRY glGetIntegerv(GLenum pname, GLint* data); +GLAPI const GLubyte* APIENTRY glGetString(GLenum name); +GLAPI GLboolean APIENTRY glIsEnabled(GLenum cap); +GLAPI void APIENTRY glViewport(GLint x, GLint y, GLsizei width, GLsizei height); +#endif +#endif /* GL_VERSION_1_0 */ +#ifndef GL_VERSION_1_1 +typedef khronos_float_t GLclampf; +typedef double GLclampd; +#define GL_TEXTURE_BINDING_2D 0x8069 +typedef void(APIENTRYP PFNGLDRAWELEMENTSPROC)(GLenum mode, GLsizei count, + GLenum type, const void* indices); +typedef void(APIENTRYP PFNGLBINDTEXTUREPROC)(GLenum target, GLuint texture); +typedef void(APIENTRYP PFNGLDELETETEXTURESPROC)(GLsizei n, + const GLuint* textures); +typedef void(APIENTRYP PFNGLGENTEXTURESPROC)(GLsizei n, GLuint* textures); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawElements(GLenum mode, GLsizei count, GLenum type, + const void* indices); +GLAPI void APIENTRY glBindTexture(GLenum target, GLuint texture); +GLAPI void APIENTRY glDeleteTextures(GLsizei n, const GLuint* textures); +GLAPI void APIENTRY glGenTextures(GLsizei n, GLuint* textures); +#endif +#endif /* GL_VERSION_1_1 */ +#ifndef GL_VERSION_1_3 +#define GL_TEXTURE0 0x84C0 +#define GL_ACTIVE_TEXTURE 0x84E0 +typedef void(APIENTRYP PFNGLACTIVETEXTUREPROC)(GLenum texture); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTexture(GLenum texture); +#endif +#endif /* GL_VERSION_1_3 */ +#ifndef GL_VERSION_1_4 +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_FUNC_ADD 0x8006 +typedef void(APIENTRYP PFNGLBLENDFUNCSEPARATEPROC)(GLenum sfactorRGB, + GLenum dfactorRGB, + GLenum sfactorAlpha, + GLenum dfactorAlpha); +typedef void(APIENTRYP PFNGLBLENDEQUATIONPROC)(GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparate(GLenum sfactorRGB, GLenum dfactorRGB, + GLenum sfactorAlpha, + GLenum dfactorAlpha); +GLAPI void APIENTRY glBlendEquation(GLenum mode); +#endif +#endif /* GL_VERSION_1_4 */ +#ifndef GL_VERSION_1_5 +typedef khronos_ssize_t GLsizeiptr; +typedef khronos_intptr_t GLintptr; +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_STREAM_DRAW 0x88E0 +typedef void(APIENTRYP PFNGLBINDBUFFERPROC)(GLenum target, GLuint buffer); +typedef void(APIENTRYP PFNGLDELETEBUFFERSPROC)(GLsizei n, + const GLuint* buffers); +typedef void(APIENTRYP PFNGLGENBUFFERSPROC)(GLsizei n, GLuint* buffers); +typedef void(APIENTRYP PFNGLBUFFERDATAPROC)(GLenum target, GLsizeiptr size, + const void* data, GLenum usage); +typedef void(APIENTRYP PFNGLBUFFERSUBDATAPROC)(GLenum target, GLintptr offset, + GLsizeiptr size, + const void* data); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindBuffer(GLenum target, GLuint buffer); +GLAPI void APIENTRY glDeleteBuffers(GLsizei n, const GLuint* buffers); +GLAPI void APIENTRY glGenBuffers(GLsizei n, GLuint* buffers); +GLAPI void APIENTRY glBufferData(GLenum target, GLsizeiptr size, + const void* data, GLenum usage); +GLAPI void APIENTRY glBufferSubData(GLenum target, GLintptr offset, + GLsizeiptr size, const void* data); +#endif +#endif /* GL_VERSION_1_5 */ +#ifndef GL_VERSION_2_0 +typedef char GLchar; +typedef khronos_int16_t GLshort; +typedef khronos_int8_t GLbyte; +typedef khronos_uint16_t GLushort; +#define GL_BLEND_EQUATION_RGB 0x8009 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_UPPER_LEFT 0x8CA2 +typedef void(APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC)(GLenum modeRGB, + GLenum modeAlpha); +typedef void(APIENTRYP PFNGLATTACHSHADERPROC)(GLuint program, GLuint shader); +typedef void(APIENTRYP PFNGLCOMPILESHADERPROC)(GLuint shader); +typedef GLuint(APIENTRYP PFNGLCREATEPROGRAMPROC)(void); +typedef GLuint(APIENTRYP PFNGLCREATESHADERPROC)(GLenum type); +typedef void(APIENTRYP PFNGLDELETEPROGRAMPROC)(GLuint program); +typedef void(APIENTRYP PFNGLDELETESHADERPROC)(GLuint shader); +typedef void(APIENTRYP PFNGLDETACHSHADERPROC)(GLuint program, GLuint shader); +typedef void(APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC)(GLuint index); +typedef void(APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC)(GLuint index); +typedef GLint(APIENTRYP PFNGLGETATTRIBLOCATIONPROC)(GLuint program, + const GLchar* name); +typedef void(APIENTRYP PFNGLGETPROGRAMIVPROC)(GLuint program, GLenum pname, + GLint* params); +typedef void(APIENTRYP PFNGLGETPROGRAMINFOLOGPROC)(GLuint program, + GLsizei bufSize, + GLsizei* length, + GLchar* infoLog); +typedef void(APIENTRYP PFNGLGETSHADERIVPROC)(GLuint shader, GLenum pname, + GLint* params); +typedef void(APIENTRYP PFNGLGETSHADERINFOLOGPROC)(GLuint shader, + GLsizei bufSize, + GLsizei* length, + GLchar* infoLog); +typedef GLint(APIENTRYP PFNGLGETUNIFORMLOCATIONPROC)(GLuint program, + const GLchar* name); +typedef void(APIENTRYP PFNGLGETVERTEXATTRIBIVPROC)(GLuint index, GLenum pname, + GLint* params); +typedef void(APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC)(GLuint index, + GLenum pname, + void** pointer); +typedef void(APIENTRYP PFNGLLINKPROGRAMPROC)(GLuint program); +typedef void(APIENTRYP PFNGLSHADERSOURCEPROC)(GLuint shader, GLsizei count, + const GLchar* const* string, + const GLint* length); +typedef void(APIENTRYP PFNGLUSEPROGRAMPROC)(GLuint program); +typedef void(APIENTRYP PFNGLUNIFORM1IPROC)(GLint location, GLint v0); +typedef void(APIENTRYP PFNGLUNIFORMMATRIX4FVPROC)(GLint location, GLsizei count, + GLboolean transpose, + const GLfloat* value); +typedef void(APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC)(GLuint index, GLint size, + GLenum type, + GLboolean normalized, + GLsizei stride, + const void* pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparate(GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glAttachShader(GLuint program, GLuint shader); +GLAPI void APIENTRY glCompileShader(GLuint shader); +GLAPI GLuint APIENTRY glCreateProgram(void); +GLAPI GLuint APIENTRY glCreateShader(GLenum type); +GLAPI void APIENTRY glDeleteProgram(GLuint program); +GLAPI void APIENTRY glDeleteShader(GLuint shader); +GLAPI void APIENTRY glDetachShader(GLuint program, GLuint shader); +GLAPI void APIENTRY glDisableVertexAttribArray(GLuint index); +GLAPI void APIENTRY glEnableVertexAttribArray(GLuint index); +GLAPI GLint APIENTRY glGetAttribLocation(GLuint program, const GLchar* name); +GLAPI void APIENTRY glGetProgramiv(GLuint program, GLenum pname, GLint* params); +GLAPI void APIENTRY glGetProgramInfoLog(GLuint program, GLsizei bufSize, + GLsizei* length, GLchar* infoLog); +GLAPI void APIENTRY glGetShaderiv(GLuint shader, GLenum pname, GLint* params); +GLAPI void APIENTRY glGetShaderInfoLog(GLuint shader, GLsizei bufSize, + GLsizei* length, GLchar* infoLog); +GLAPI GLint APIENTRY glGetUniformLocation(GLuint program, const GLchar* name); +GLAPI void APIENTRY glGetVertexAttribiv(GLuint index, GLenum pname, + GLint* params); +GLAPI void APIENTRY glGetVertexAttribPointerv(GLuint index, GLenum pname, + void** pointer); +GLAPI void APIENTRY glLinkProgram(GLuint program); +GLAPI void APIENTRY glShaderSource(GLuint shader, GLsizei count, + const GLchar* const* string, + const GLint* length); +GLAPI void APIENTRY glUseProgram(GLuint program); +GLAPI void APIENTRY glUniform1i(GLint location, GLint v0); +GLAPI void APIENTRY glUniformMatrix4fv(GLint location, GLsizei count, + GLboolean transpose, + const GLfloat* value); +GLAPI void APIENTRY glVertexAttribPointer(GLuint index, GLint size, GLenum type, + GLboolean normalized, GLsizei stride, + const void* pointer); +#endif +#endif /* GL_VERSION_2_0 */ +#ifndef GL_VERSION_3_0 +typedef khronos_uint16_t GLhalf; +#define GL_MAJOR_VERSION 0x821B +#define GL_MINOR_VERSION 0x821C +#define GL_NUM_EXTENSIONS 0x821D +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#define GL_VERTEX_ARRAY_BINDING 0x85B5 +typedef void(APIENTRYP PFNGLGETBOOLEANI_VPROC)(GLenum target, GLuint index, + GLboolean* data); +typedef void(APIENTRYP PFNGLGETINTEGERI_VPROC)(GLenum target, GLuint index, + GLint* data); +typedef const GLubyte*(APIENTRYP PFNGLGETSTRINGIPROC)(GLenum name, + GLuint index); +typedef void(APIENTRYP PFNGLBINDVERTEXARRAYPROC)(GLuint array); +typedef void(APIENTRYP PFNGLDELETEVERTEXARRAYSPROC)(GLsizei n, + const GLuint* arrays); +typedef void(APIENTRYP PFNGLGENVERTEXARRAYSPROC)(GLsizei n, GLuint* arrays); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI const GLubyte* APIENTRY glGetStringi(GLenum name, GLuint index); +GLAPI void APIENTRY glBindVertexArray(GLuint array); +GLAPI void APIENTRY glDeleteVertexArrays(GLsizei n, const GLuint* arrays); +GLAPI void APIENTRY glGenVertexArrays(GLsizei n, GLuint* arrays); +#endif +#endif /* GL_VERSION_3_0 */ +#ifndef GL_VERSION_3_1 +#define GL_VERSION_3_1 1 +#define GL_PRIMITIVE_RESTART 0x8F9D +#endif /* GL_VERSION_3_1 */ +#ifndef GL_VERSION_3_2 +#define GL_VERSION_3_2 1 +typedef struct __GLsync* GLsync; +typedef khronos_uint64_t GLuint64; +typedef khronos_int64_t GLint64; +typedef void(APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXPROC)(GLenum mode, + GLsizei count, + GLenum type, + const void* indices, + GLint basevertex); +typedef void(APIENTRYP PFNGLGETINTEGER64I_VPROC)(GLenum target, GLuint index, + GLint64* data); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawElementsBaseVertex(GLenum mode, GLsizei count, + GLenum type, const void* indices, + GLint basevertex); +#endif +#endif /* GL_VERSION_3_2 */ +#ifndef GL_VERSION_3_3 +#define GL_VERSION_3_3 1 +#define GL_SAMPLER_BINDING 0x8919 +typedef void(APIENTRYP PFNGLBINDSAMPLERPROC)(GLuint unit, GLuint sampler); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindSampler(GLuint unit, GLuint sampler); +#endif +#endif /* GL_VERSION_3_3 */ +#ifndef GL_VERSION_4_1 +typedef void(APIENTRYP PFNGLGETFLOATI_VPROC)(GLenum target, GLuint index, + GLfloat* data); +typedef void(APIENTRYP PFNGLGETDOUBLEI_VPROC)(GLenum target, GLuint index, + GLdouble* data); +#endif /* GL_VERSION_4_1 */ +#ifndef GL_VERSION_4_3 +typedef void(APIENTRY* GLDEBUGPROC)(GLenum source, GLenum type, GLuint id, + GLenum severity, GLsizei length, + const GLchar* message, + const void* userParam); +#endif /* GL_VERSION_4_3 */ +#ifndef GL_VERSION_4_5 +#define GL_CLIP_ORIGIN 0x935C +typedef void(APIENTRYP PFNGLGETTRANSFORMFEEDBACKI_VPROC)(GLuint xfb, + GLenum pname, + GLuint index, + GLint* param); +typedef void(APIENTRYP PFNGLGETTRANSFORMFEEDBACKI64_VPROC)(GLuint xfb, + GLenum pname, + GLuint index, + GLint64* param); +#endif /* GL_VERSION_4_5 */ +#ifndef GL_ARB_bindless_texture +typedef khronos_uint64_t GLuint64EXT; +#endif /* GL_ARB_bindless_texture */ +#ifndef GL_ARB_cl_event +struct _cl_context; +struct _cl_event; +#endif /* GL_ARB_cl_event */ +#ifndef GL_ARB_clip_control +#define GL_ARB_clip_control 1 +#endif /* GL_ARB_clip_control */ +#ifndef GL_ARB_debug_output +typedef void(APIENTRY* GLDEBUGPROCARB)(GLenum source, GLenum type, GLuint id, + GLenum severity, GLsizei length, + const GLchar* message, + const void* userParam); +#endif /* GL_ARB_debug_output */ +#ifndef GL_EXT_EGL_image_storage +typedef void* GLeglImageOES; +#endif /* GL_EXT_EGL_image_storage */ +#ifndef GL_EXT_direct_state_access +typedef void(APIENTRYP PFNGLGETFLOATI_VEXTPROC)(GLenum pname, GLuint index, + GLfloat* params); +typedef void(APIENTRYP PFNGLGETDOUBLEI_VEXTPROC)(GLenum pname, GLuint index, + GLdouble* params); +typedef void(APIENTRYP PFNGLGETPOINTERI_VEXTPROC)(GLenum pname, GLuint index, + void** params); +typedef void(APIENTRYP PFNGLGETVERTEXARRAYINTEGERI_VEXTPROC)(GLuint vaobj, + GLuint index, + GLenum pname, + GLint* param); +typedef void(APIENTRYP PFNGLGETVERTEXARRAYPOINTERI_VEXTPROC)(GLuint vaobj, + GLuint index, + GLenum pname, + void** param); +#endif /* GL_EXT_direct_state_access */ +#ifndef GL_NV_draw_vulkan_image +typedef void(APIENTRY* GLVULKANPROCNV)(void); +#endif /* GL_NV_draw_vulkan_image */ +#ifndef GL_NV_gpu_shader5 +typedef khronos_int64_t GLint64EXT; +#endif /* GL_NV_gpu_shader5 */ +#ifndef GL_NV_vertex_buffer_unified_memory +typedef void(APIENTRYP PFNGLGETINTEGERUI64I_VNVPROC)(GLenum value, GLuint index, + GLuint64EXT* result); +#endif /* GL_NV_vertex_buffer_unified_memory */ +#ifdef __cplusplus +} +#endif +#endif + +#ifndef GL3W_API +#define GL3W_API +#endif + +#ifndef __gl_h_ +#define __gl_h_ +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define GL3W_OK 0 +#define GL3W_ERROR_INIT -1 +#define GL3W_ERROR_LIBRARY_OPEN -2 +#define GL3W_ERROR_OPENGL_VERSION -3 + +typedef void (*GL3WglProc)(void); +typedef GL3WglProc (*GL3WGetProcAddressProc)(const char* proc); + +/* gl3w api */ +GL3W_API int imgl3wInit(void); +GL3W_API int imgl3wInit2(GL3WGetProcAddressProc proc); +GL3W_API int imgl3wIsSupported(int major, int minor); +GL3W_API GL3WglProc imgl3wGetProcAddress(const char* proc); + +/* gl3w internal state */ +union GL3WProcs { + GL3WglProc ptr[58]; + struct { + PFNGLACTIVETEXTUREPROC ActiveTexture; + PFNGLATTACHSHADERPROC AttachShader; + PFNGLBINDBUFFERPROC BindBuffer; + PFNGLBINDSAMPLERPROC BindSampler; + PFNGLBINDTEXTUREPROC BindTexture; + PFNGLBINDVERTEXARRAYPROC BindVertexArray; + PFNGLBLENDEQUATIONPROC BlendEquation; + PFNGLBLENDEQUATIONSEPARATEPROC BlendEquationSeparate; + PFNGLBLENDFUNCSEPARATEPROC BlendFuncSeparate; + PFNGLBUFFERDATAPROC BufferData; + PFNGLBUFFERSUBDATAPROC BufferSubData; + PFNGLCLEARPROC Clear; + PFNGLCLEARCOLORPROC ClearColor; + PFNGLCOMPILESHADERPROC CompileShader; + PFNGLCREATEPROGRAMPROC CreateProgram; + PFNGLCREATESHADERPROC CreateShader; + PFNGLDELETEBUFFERSPROC DeleteBuffers; + PFNGLDELETEPROGRAMPROC DeleteProgram; + PFNGLDELETESHADERPROC DeleteShader; + PFNGLDELETETEXTURESPROC DeleteTextures; + PFNGLDELETEVERTEXARRAYSPROC DeleteVertexArrays; + PFNGLDETACHSHADERPROC DetachShader; + PFNGLDISABLEPROC Disable; + PFNGLDISABLEVERTEXATTRIBARRAYPROC DisableVertexAttribArray; + PFNGLDRAWELEMENTSPROC DrawElements; + PFNGLDRAWELEMENTSBASEVERTEXPROC DrawElementsBaseVertex; + PFNGLENABLEPROC Enable; + PFNGLENABLEVERTEXATTRIBARRAYPROC EnableVertexAttribArray; + PFNGLFLUSHPROC Flush; + PFNGLGENBUFFERSPROC GenBuffers; + PFNGLGENTEXTURESPROC GenTextures; + PFNGLGENVERTEXARRAYSPROC GenVertexArrays; + PFNGLGETATTRIBLOCATIONPROC GetAttribLocation; + PFNGLGETERRORPROC GetError; + PFNGLGETINTEGERVPROC GetIntegerv; + PFNGLGETPROGRAMINFOLOGPROC GetProgramInfoLog; + PFNGLGETPROGRAMIVPROC GetProgramiv; + PFNGLGETSHADERINFOLOGPROC GetShaderInfoLog; + PFNGLGETSHADERIVPROC GetShaderiv; + PFNGLGETSTRINGPROC GetString; + PFNGLGETSTRINGIPROC GetStringi; + PFNGLGETUNIFORMLOCATIONPROC GetUniformLocation; + PFNGLGETVERTEXATTRIBPOINTERVPROC GetVertexAttribPointerv; + PFNGLGETVERTEXATTRIBIVPROC GetVertexAttribiv; + PFNGLISENABLEDPROC IsEnabled; + PFNGLLINKPROGRAMPROC LinkProgram; + PFNGLPIXELSTOREIPROC PixelStorei; + PFNGLPOLYGONMODEPROC PolygonMode; + PFNGLREADPIXELSPROC ReadPixels; + PFNGLSCISSORPROC Scissor; + PFNGLSHADERSOURCEPROC ShaderSource; + PFNGLTEXIMAGE2DPROC TexImage2D; + PFNGLTEXPARAMETERIPROC TexParameteri; + PFNGLUNIFORM1IPROC Uniform1i; + PFNGLUNIFORMMATRIX4FVPROC UniformMatrix4fv; + PFNGLUSEPROGRAMPROC UseProgram; + PFNGLVERTEXATTRIBPOINTERPROC VertexAttribPointer; + PFNGLVIEWPORTPROC Viewport; + } gl; +}; + +GL3W_API extern union GL3WProcs imgl3wProcs; + +/* OpenGL functions */ +#define glActiveTexture imgl3wProcs.gl.ActiveTexture +#define glAttachShader imgl3wProcs.gl.AttachShader +#define glBindBuffer imgl3wProcs.gl.BindBuffer +#define glBindSampler imgl3wProcs.gl.BindSampler +#define glBindTexture imgl3wProcs.gl.BindTexture +#define glBindVertexArray imgl3wProcs.gl.BindVertexArray +#define glBlendEquation imgl3wProcs.gl.BlendEquation +#define glBlendEquationSeparate imgl3wProcs.gl.BlendEquationSeparate +#define glBlendFuncSeparate imgl3wProcs.gl.BlendFuncSeparate +#define glBufferData imgl3wProcs.gl.BufferData +#define glBufferSubData imgl3wProcs.gl.BufferSubData +#define glClear imgl3wProcs.gl.Clear +#define glClearColor imgl3wProcs.gl.ClearColor +#define glCompileShader imgl3wProcs.gl.CompileShader +#define glCreateProgram imgl3wProcs.gl.CreateProgram +#define glCreateShader imgl3wProcs.gl.CreateShader +#define glDeleteBuffers imgl3wProcs.gl.DeleteBuffers +#define glDeleteProgram imgl3wProcs.gl.DeleteProgram +#define glDeleteShader imgl3wProcs.gl.DeleteShader +#define glDeleteTextures imgl3wProcs.gl.DeleteTextures +#define glDeleteVertexArrays imgl3wProcs.gl.DeleteVertexArrays +#define glDetachShader imgl3wProcs.gl.DetachShader +#define glDisable imgl3wProcs.gl.Disable +#define glDisableVertexAttribArray imgl3wProcs.gl.DisableVertexAttribArray +#define glDrawElements imgl3wProcs.gl.DrawElements +#define glDrawElementsBaseVertex imgl3wProcs.gl.DrawElementsBaseVertex +#define glEnable imgl3wProcs.gl.Enable +#define glEnableVertexAttribArray imgl3wProcs.gl.EnableVertexAttribArray +#define glFlush imgl3wProcs.gl.Flush +#define glGenBuffers imgl3wProcs.gl.GenBuffers +#define glGenTextures imgl3wProcs.gl.GenTextures +#define glGenVertexArrays imgl3wProcs.gl.GenVertexArrays +#define glGetAttribLocation imgl3wProcs.gl.GetAttribLocation +#define glGetError imgl3wProcs.gl.GetError +#define glGetIntegerv imgl3wProcs.gl.GetIntegerv +#define glGetProgramInfoLog imgl3wProcs.gl.GetProgramInfoLog +#define glGetProgramiv imgl3wProcs.gl.GetProgramiv +#define glGetShaderInfoLog imgl3wProcs.gl.GetShaderInfoLog +#define glGetShaderiv imgl3wProcs.gl.GetShaderiv +#define glGetString imgl3wProcs.gl.GetString +#define glGetStringi imgl3wProcs.gl.GetStringi +#define glGetUniformLocation imgl3wProcs.gl.GetUniformLocation +#define glGetVertexAttribPointerv imgl3wProcs.gl.GetVertexAttribPointerv +#define glGetVertexAttribiv imgl3wProcs.gl.GetVertexAttribiv +#define glIsEnabled imgl3wProcs.gl.IsEnabled +#define glLinkProgram imgl3wProcs.gl.LinkProgram +#define glPixelStorei imgl3wProcs.gl.PixelStorei +#define glPolygonMode imgl3wProcs.gl.PolygonMode +#define glReadPixels imgl3wProcs.gl.ReadPixels +#define glScissor imgl3wProcs.gl.Scissor +#define glShaderSource imgl3wProcs.gl.ShaderSource +#define glTexImage2D imgl3wProcs.gl.TexImage2D +#define glTexParameteri imgl3wProcs.gl.TexParameteri +#define glUniform1i imgl3wProcs.gl.Uniform1i +#define glUniformMatrix4fv imgl3wProcs.gl.UniformMatrix4fv +#define glUseProgram imgl3wProcs.gl.UseProgram +#define glVertexAttribPointer imgl3wProcs.gl.VertexAttribPointer +#define glViewport imgl3wProcs.gl.Viewport + +#ifdef __cplusplus +} +#endif + +#endif + +#ifdef IMGL3W_IMPL +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include + +static HMODULE libgl; +typedef PROC(__stdcall* GL3WglGetProcAddr)(LPCSTR); +static GL3WglGetProcAddr wgl_get_proc_address; + +static int open_libgl(void) { + libgl = LoadLibraryA("opengl32.dll"); + if (!libgl) return GL3W_ERROR_LIBRARY_OPEN; + wgl_get_proc_address = + (GL3WglGetProcAddr)GetProcAddress(libgl, "wglGetProcAddress"); + return GL3W_OK; +} + +static void close_libgl(void) { FreeLibrary(libgl); } +static GL3WglProc get_proc(const char* proc) { + GL3WglProc res; + res = (GL3WglProc)wgl_get_proc_address(proc); + if (!res) res = (GL3WglProc)GetProcAddress(libgl, proc); + return res; +} +#elif defined(__APPLE__) +#include + +static void* libgl; +static int open_libgl(void) { + libgl = dlopen("/System/Library/Frameworks/OpenGL.framework/OpenGL", + RTLD_LAZY | RTLD_LOCAL); + if (!libgl) return GL3W_ERROR_LIBRARY_OPEN; + return GL3W_OK; +} + +static void close_libgl(void) { dlclose(libgl); } + +static GL3WglProc get_proc(const char* proc) { + GL3WglProc res; + *(void**)(&res) = dlsym(libgl, proc); + return res; +} +#else +#include + +static void* libgl; +static GL3WglProc (*glx_get_proc_address)(const GLubyte*); + +static int open_libgl(void) { + libgl = dlopen("libGL.so.1", RTLD_LAZY | RTLD_LOCAL); + if (!libgl) return GL3W_ERROR_LIBRARY_OPEN; + *(void**)(&glx_get_proc_address) = dlsym(libgl, "glXGetProcAddressARB"); + return GL3W_OK; +} + +static void close_libgl(void) { dlclose(libgl); } + +static GL3WglProc get_proc(const char* proc) { + GL3WglProc res; + res = glx_get_proc_address((const GLubyte*)proc); + if (!res) *(void**)(&res) = dlsym(libgl, proc); + return res; +} +#endif + +static struct { int major, minor; } version; + +static int parse_version(void) { + if (!glGetIntegerv) return GL3W_ERROR_INIT; + glGetIntegerv(GL_MAJOR_VERSION, &version.major); + glGetIntegerv(GL_MINOR_VERSION, &version.minor); + if (version.major < 3) return GL3W_ERROR_OPENGL_VERSION; + return GL3W_OK; +} + +static void load_procs(GL3WGetProcAddressProc proc); + +int imgl3wInit(void) { + int res = open_libgl(); + if (res) return res; + atexit(close_libgl); + return imgl3wInit2(get_proc); +} + +int imgl3wInit2(GL3WGetProcAddressProc proc) { + load_procs(proc); + return parse_version(); +} + +int imgl3wIsSupported(int major, int minor) { + if (major < 3) return 0; + if (version.major == major) return version.minor >= minor; + return version.major >= major; +} + +GL3WglProc imgl3wGetProcAddress(const char* proc) { return get_proc(proc); } + +static const char* proc_names[] = { + "glActiveTexture", + "glAttachShader", + "glBindBuffer", + "glBindSampler", + "glBindTexture", + "glBindVertexArray", + "glBlendEquation", + "glBlendEquationSeparate", + "glBlendFuncSeparate", + "glBufferData", + "glBufferSubData", + "glClear", + "glClearColor", + "glCompileShader", + "glCreateProgram", + "glCreateShader", + "glDeleteBuffers", + "glDeleteProgram", + "glDeleteShader", + "glDeleteTextures", + "glDeleteVertexArrays", + "glDetachShader", + "glDisable", + "glDisableVertexAttribArray", + "glDrawElements", + "glDrawElementsBaseVertex", + "glEnable", + "glEnableVertexAttribArray", + "glFlush", + "glGenBuffers", + "glGenTextures", + "glGenVertexArrays", + "glGetAttribLocation", + "glGetError", + "glGetIntegerv", + "glGetProgramInfoLog", + "glGetProgramiv", + "glGetShaderInfoLog", + "glGetShaderiv", + "glGetString", + "glGetStringi", + "glGetUniformLocation", + "glGetVertexAttribPointerv", + "glGetVertexAttribiv", + "glIsEnabled", + "glLinkProgram", + "glPixelStorei", + "glPolygonMode", + "glReadPixels", + "glScissor", + "glShaderSource", + "glTexImage2D", + "glTexParameteri", + "glUniform1i", + "glUniformMatrix4fv", + "glUseProgram", + "glVertexAttribPointer", + "glViewport", +}; + +GL3W_API union GL3WProcs imgl3wProcs; + +static void load_procs(GL3WGetProcAddressProc proc) { + size_t i; + for (i = 0; i < ARRAY_SIZE(proc_names); i++) + imgl3wProcs.ptr[i] = proc(proc_names[i]); +} + +#ifdef __cplusplus +} +#endif +#endif diff --git a/customchar-ui/libs/imgui/include/imgui_internal.h b/customchar-ui/libs/imgui/include/imgui_internal.h new file mode 100644 index 0000000..61f0000 --- /dev/null +++ b/customchar-ui/libs/imgui/include/imgui_internal.h @@ -0,0 +1,4692 @@ +// dear imgui, v1.88 WIP +// (internal structures/api) + +// You may use this file to debug, understand or extend ImGui features but we +// don't provide any guarantee of forward compatibility! Set: +// #define IMGUI_DEFINE_MATH_OPERATORS +// To implement maths operators for ImVec2 (disabled by default to not collide +// with using IM_VEC2_CLASS_EXTRA along with your own math types+operators) + +/* + +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] 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(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 + +// 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 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 ImGuiLastItemData; // Status storage for last submitted items +struct ImGuiMenuColumns; // Simple column measurement, currently used for + // MenuItem() only +struct ImGuiNavItemData; // Result of a gamepad/keyboard directional navigation + // move query result +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) + +// Use your programming IDE "Go to definition" facility on the names of the +// center columns to find the actual flags/enum lists. +typedef int ImGuiLayoutType; // -> enum ImGuiLayoutType_ // Enum: + // Horizontal or vertical +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 ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for + // PushItemFlag() +typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: + // for DC.LastItemStatusFlags +typedef int ImGuiOldColumnFlags; // -> enum ImGuiOldColumnFlags_ // Flags: + // for BeginColumns() +typedef int ImGuiNavHighlightFlags; // -> enum ImGuiNavHighlightFlags_ // + // Flags: for RenderNavHighlight() +typedef int ImGuiNavDirSourceFlags; // -> enum ImGuiNavDirSourceFlags_ // + // Flags: for GetNavInputAmount2d() +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 "imstb_textedit.h" + +} // namespace ImStb + +//----------------------------------------------------------------------------- +// [SECTION] Macros +//----------------------------------------------------------------------------- + +// Debug Printing Into TTY +#ifndef IMGUI_DEBUG_PRINT +#define IMGUI_DEBUG_PRINT(_FMT, ...) \ + printf("[%05d] " _FMT, g.FrameCount, __VA_ARGS__) +#endif + +// Debug Logging for ShowDebugLogWindow(). This is designed for relatively rare +// events so please don't spam. +#define IMGUI_DEBUG_LOG(...) ImGui::DebugLog(__VA_ARGS__); +#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) + +// 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 +#define IM_TABSIZE (4) +#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)) // + +// 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<> +//----------------------------------------------------------------------------- + +// Helpers: Hashing +IMGUI_API ImGuiID ImHashData(const void* data, size_t data_size, + ImU32 seed = 0); +IMGUI_API ImGuiID ImHashStr(const char* data, size_t data_size = 0, + ImU32 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); +static inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } +static inline bool ImCharIsBlankW(unsigned int c) { + return c == ' ' || c == '\t' || c == 0x3000; +} + +// 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: ImVec2/ImVec4 operators +// We are keeping those disabled by default so they don't leak in user space, to +// allow user enabling implicit cast operators between ImVec2 and their own +// types (using IM_VEC2_CLASS_EXTRA etc.) We unfortunately don't have a unary- +// operator for ImVec2 because this would needs to be defined inside the class +// itself. +#ifdef IMGUI_DEFINE_MATH_OPERATORS +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*=(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: 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; +} +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; +} +IMGUI_API ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy); + +// 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); } +}; +IM_MSVC_RUNTIME_CHECKS_RESTORE + +// Helper: ImBitArray +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; + } +} + +// 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 ImBitArrayTestBit(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 ImBitArrayTestBit(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 ImBitArrayTestBit(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); + } +}; + +// 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); } +}; + +//----------------------------------------------------------------------------- +// [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] 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]; // Global layers for: regular, tooltip + + void Clear() { + for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) Layers[n].resize(0); + } + void ClearFreeMemory() { + for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) Layers[n].clear(); + } + int GetDrawListCount() const { + int count = 0; + for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) count += Layers[n].Size; + return count; + } + IMGUI_API void FlattenIntoSingleLayer(); +}; + +//----------------------------------------------------------------------------- +// [SECTION] Widgets support: flags, enums, data structures +//----------------------------------------------------------------------------- + +// Transient per-window flags, reset at the beginning of the frame. For child +// window, inherited from parent on first Begin(). This is going to be exposed +// in imgui.h when stabilized enough. +enum ImGuiItemFlags_ { + ImGuiItemFlags_None = 0, + ImGuiItemFlags_NoTabStop = 1 << 0, // false // Disable keyboard tabbing + // (FIXME: should merge with _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 keyboard/gamepad directional navigation + // (FIXME: should merge with _NoTabStop) + 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_Inputable = + 1 << 8 // false // [WIP] Auto-activate input mode when tab focused. + // Currently only used and supported by a few items before it + // becomes a generic feature. +}; + +// Storage for LastItem data +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) + +#ifdef IMGUI_ENABLE_TEST_ENGINE + , // [imgui_tests only] + ImGuiItemStatusFlags_Openable = 1 + << 20, // Item is an openable (e.g. TreeNode) + ImGuiItemStatusFlags_Opened = 1 << 21, // + ImGuiItemStatusFlags_Checkable = + 1 << 22, // Item is a checkable (e.g. CheckBox, MenuItem) + ImGuiItemStatusFlags_Checked = 1 << 23 // +#endif +}; + +// 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_AllowItemOverlap = + 1 + << 12, // require previous frame HoveredId to either match id or be null + // before being usable, use along with SetItemAllowOverlap() + 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 + ImGuiButtonFlags_NoHoveredOnFocus = + 1 << 19, // don't report as hovered when nav focus is on this item + 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 +}; + +// 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_DrawHoveredWhenHeld = + 1 << 25, // Always show active when held, even is not hovered. This + // concept could probably be renamed/formalized somehow. + ImGuiSelectableFlags_SetNavIdOnHover = + 1 << 26, // Set Nav/Focus ID on mouse hover (used by MenuItem) + ImGuiSelectableFlags_NoPadWithHalfSpacing = + 1 << 27 // Disable padding each side with ItemSpacing * 0.5f +}; + +// Extend ImGuiTreeNodeFlags_ +enum ImGuiTreeNodeFlagsPrivate_ { + ImGuiTreeNodeFlags_ClipLabelForTrailingButton = 1 << 20 +}; + +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 +}; + +enum ImGuiTextFlags_ { + ImGuiTextFlags_None = 0, + ImGuiTextFlags_NoWidthForLargeClippedText = 1 << 0 +}; + +enum ImGuiTooltipFlags_ { + ImGuiTooltipFlags_None = 0, + ImGuiTooltipFlags_OverridePreviousTooltip = + 1 << 0 // Override will 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 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 state of the currently focused/edited text input box +// For a given item ID, access with ImGui::GetInputTextState() +struct IMGUI_API ImGuiInputTextState { + 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 + + 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* SourceWindow; // Set on OpenPopup() copy of NavWindow at the + // time of opening the popup + 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; + 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; + } // 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)); } +}; + +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 SetToCurrentState(); + void CompareWithCurrentState(); +}; + +// 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; +}; + +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 +//----------------------------------------------------------------------------- + +typedef ImBitArray + ImBitArrayForNamedKeys; + +enum ImGuiKeyPrivate_ { + ImGuiKey_LegacyNativeKey_BEGIN = 0, + ImGuiKey_LegacyNativeKey_END = 512, + ImGuiKey_Gamepad_BEGIN = ImGuiKey_GamepadStart, + ImGuiKey_Gamepad_END = ImGuiKey_GamepadRStickRight + 1 +}; + +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, + ImGuiInputSource_Keyboard, + ImGuiInputSource_Gamepad, + ImGuiInputSource_Clipboard, // Currently only used by InputText() + ImGuiInputSource_Nav, // Stored in g.ActiveIdSource only + 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; +}; +struct ImGuiInputEventMouseWheel { + float WheelX, WheelY; +}; +struct ImGuiInputEventMouseButton { + int Button; + bool Down; +}; +struct ImGuiInputEventKey { + ImGuiKey Key; + bool Down; + float AnalogValue; +}; +struct ImGuiInputEventText { + unsigned int Char; +}; +struct ImGuiInputEventAppFocused { + bool Focused; +}; + +struct ImGuiInputEvent { + ImGuiInputEventType Type; + ImGuiInputSource Source; + 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)); } +}; + +// FIXME-NAV: Clarify/expose various repeat delay/rate +enum ImGuiNavReadMode { + ImGuiNavReadMode_Down, + ImGuiNavReadMode_Pressed, + ImGuiNavReadMode_Released, + ImGuiNavReadMode_Repeat, + ImGuiNavReadMode_RepeatSlow, + ImGuiNavReadMode_RepeatFast +}; + +//----------------------------------------------------------------------------- +// [SECTION] Clipper support +//----------------------------------------------------------------------------- + +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 if keyboard is available. + ImGuiActivateFlags_PreferTweak = + 1 << 1, // Favor activation for tweaking with arrows or gamepad (e.g. for + // Slider/Drag). Default if keyboard is not available. + 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 ImGuiNavDirSourceFlags_ { + ImGuiNavDirSourceFlags_None = 0, + ImGuiNavDirSourceFlags_RawKeyboard = + 1 << 0, // Raw keyboard (not pulled from nav), facilitate use of some + // functions before we can unify nav and keys + ImGuiNavDirSourceFlags_Keyboard = 1 << 1, + ImGuiNavDirSourceFlags_PadDPad = 1 << 2, + ImGuiNavDirSourceFlags_PadLStick = 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_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, + ImGuiNavMoveFlags_Tabbing = 1 << 10, // == Focus + Activate if item is + // Inputable + DontChangeNavHighlight + ImGuiNavMoveFlags_Activate = 1 << 11, + ImGuiNavMoveFlags_DontSetNavHighlight = + 1 << 12 // 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/ImGuiNavInput_Menu) + 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 DrawListsLastFrame[2]; // Last frame number the background (0) and + // foreground (1) draw lists were used + ImDrawList* DrawLists[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; + + 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() { + DrawListsLastFrame[0] = DrawListsLastFrame[1] = -1; + DrawLists[0] = DrawLists[1] = NULL; + } + ~ImGuiViewportP() { + if (DrawLists[0]) IM_DELETE(DrawLists[0]); + if (DrawLists[1]) IM_DELETE(DrawLists[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) + + 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] 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_EventMask_ = + ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | + ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav +}; + +struct ImGuiMetricsConfig { + bool ShowDebugLog; + bool ShowStackTool; + bool ShowWindowsRects; + bool ShowWindowsBeginOrder; + bool ShowTablesRects; + bool ShowDrawCmdMesh; + bool ShowDrawCmdBoundingBoxes; + int ShowWindowsRectsType; + int ShowTablesRectsType; + + ImGuiMetricsConfig() { + ShowDebugLog = ShowStackTool = ShowWindowsRects = ShowWindowsBeginOrder = + ShowTablesRects = false; + ShowDrawCmdMesh = true; + ShowDrawCmdBoundingBoxes = true; + ShowWindowsRectsType = 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; + ImVector + InputEventsQueue; // Input events which will be tricked/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. + 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 + + // 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; + float WheelingWindowTimer; + + // 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 HoveredIdUsingMouseWheel; // Hovered widget will use mouse wheel. Blocks + // scrolling the underlying window. + bool HoveredIdPreviousFrameUsingMouseWheel; + 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 with mouse or nav (gamepad/keyboard) + 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. + + // Input Ownership + bool ActiveIdUsingMouseWheel; // Active widget will want to read mouse wheel. + // Blocks scrolling the underlying window. + ImU32 ActiveIdUsingNavDirMask; // Active widget will want to read those nav + // move requests (e.g. can activate a button + // and move away from it) + ImU32 ActiveIdUsingNavInputMask; // Active widget will want to read those nav + // inputs. + ImBitArrayForNamedKeys + ActiveIdUsingKeyInputMask; // Active widget will want to read those key + // inputs. When we grow the ImGuiKey enum + // we'll need to either to order the enum to + // make useful keys come first, either + // redesign this into e.g. a small array. + + // Next window/item data + ImGuiItemFlags CurrentItemFlags; // == g.ItemFlagsStack.back() + 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() - not + // inherited by Begin(), unless child window + 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) + 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) && + // IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : + // 0, also set when calling ActivateItem() + ImGuiID NavActivateDownId; // ~~ IsNavInputDown(ImGuiNavInput_Activate) ? + // NavId : 0 + ImGuiID NavActivatePressedId; // ~~ IsNavInputPressed(ImGuiNavInput_Activate) + // ? NavId : 0 + ImGuiID NavActivateInputId; // ~~ IsNavInputPressed(ImGuiNavInput_Input) ? + // NavId : 0; ImGuiActivateFlags_PreferInput will + // be set and NavActivateId will be 0. + 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). + ImGuiModFlags NavJustMovedToKeyMods; + ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame. + ImGuiActivateFlags NavNextActivateFlags; + ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS WILL ONLY + // BE None or NavGamepad or NavKeyboard. + 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; + ImGuiID NavInitResultId; // Init request result (first item of the window, or + // one for which SetItemDefaultFocus() was called) + ImRect NavInitResultRectRel; // Init request result rectangle (relative to + // parent window) + 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; + ImGuiModFlags 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) + 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; + + // Render + float DimBgRatio; // 0.0..1.0 animation when fading in a dimming background + // (for modal window and CTRL+TAB list) + ImGuiMouseCursor MouseCursor; + + // 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; + + // Widget state + ImVec2 MouseLastValidPos; + ImGuiInputTextState InputTextState; + ImFont InputTextPasswordFont; + ImGuiID + TempInputId; // Temporary text input when CTRL+clicking on a slider, etc. + ImGuiColorEditFlags + ColorEditOptions; // Store user options for color edit widgets + float ColorEditLastHue; // Backup of last Hue associated to LastColor, so we + // can restore Hue in lossy RGB<>HSV round trips + float ColorEditLastSat; // Backup of last Saturation associated to LastColor, + // so we can restore Saturation in lossy RGB<>HSV + // round trips + ImU32 ColorEditLastColor; // RGB value with alpha set to 0. + ImVec4 ColorPickerRef; // Initial/reference color at the time of opening the + // color picker. + ImGuiComboPreviewData ComboPreviewData; + 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 TooltipOverrideCount; + float TooltipSlowDelay; // Time before slow tooltips appears (FIXME: This is + // temporary until we merge in tooltip timer+priority + // work) + 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 + char PlatformLocaleDecimalPoint; // '.' or *localeconv()->decimal_point + + // 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 + + // 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; + bool DebugItemPickerActive; // Item picker is active (started with + // DebugStartItemPicker()) + ImGuiID DebugItemPickerBreakId; // Will call IM_DEBUG_BREAK() when + // encountering this ID + ImGuiMetricsConfig DebugMetricsConfig; + ImGuiStackTool DebugStackTool; + + // Misc + float FramerateSecPerFrame[120]; // Calculate estimate of framerate for user + // over the last 2 seconds. + 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) { + 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; + + WindowsActiveCount = 0; + CurrentWindow = NULL; + HoveredWindow = NULL; + HoveredWindowUnderMovingWindow = NULL; + MovingWindow = NULL; + WheelingWindow = NULL; + WheelingWindowTimer = 0.0f; + + DebugHookIdInfo = 0; + HoveredId = HoveredIdPreviousFrame = 0; + HoveredIdAllowOverlap = false; + HoveredIdUsingMouseWheel = HoveredIdPreviousFrameUsingMouseWheel = 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; + + ActiveIdUsingMouseWheel = false; + ActiveIdUsingNavDirMask = 0x00; + ActiveIdUsingNavInputMask = 0x00; + ActiveIdUsingKeyInputMask.ClearAllBits(); + + CurrentItemFlags = ImGuiItemFlags_None; + BeginMenuCount = 0; + + NavWindow = NULL; + NavId = NavFocusScopeId = NavActivateId = NavActivateDownId = + NavActivatePressedId = NavActivateInputId = 0; + NavJustMovedToId = NavJustMovedToFocusScopeId = NavNextActivateId = 0; + NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None; + NavJustMovedToKeyMods = ImGuiModFlags_None; + NavInputSource = ImGuiInputSource_None; + NavLayer = ImGuiNavLayer_Main; + NavIdIsAlive = false; + NavMousePosDirty = false; + NavDisableHighlight = true; + NavDisableMouseHover = false; + NavAnyRequest = false; + NavInitRequest = false; + NavInitRequestFromMove = false; + NavInitResultId = 0; + NavMoveSubmitted = false; + NavMoveScoringItems = false; + NavMoveForwardToNextFrame = false; + NavMoveFlags = ImGuiNavMoveFlags_None; + NavMoveScrollFlags = ImGuiScrollFlags_None; + NavMoveKeyMods = ImGuiModFlags_None; + NavMoveDir = NavMoveDirForDebug = NavMoveClipDir = ImGuiDir_None; + NavScoringDebugCount = 0; + NavTabbingDir = 0; + NavTabbingCounter = 0; + + NavWindowingTarget = NavWindowingTargetAnim = NavWindowingListWindow = NULL; + NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f; + NavWindowingToggleLayer = false; + + DimBgRatio = 0.0f; + MouseCursor = ImGuiMouseCursor_Arrow; + + 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; + + TempInputId = 0; + ColorEditOptions = ImGuiColorEditFlags_DefaultOptions_; + ColorEditLastHue = ColorEditLastSat = 0.0f; + ColorEditLastColor = 0; + SliderCurrentAccum = 0.0f; + SliderCurrentAccumDirty = false; + DragCurrentAccumDirty = false; + DragCurrentAccum = 0.0f; + DragSpeedDefaultRatio = 1.0f / 100.0f; + DisabledAlphaBackup = 0.0f; + DisabledStackSize = 0; + ScrollbarClickDeltaToGrabCenter = 0.0f; + TooltipOverrideCount = 0; + TooltipSlowDelay = 0.50f; + + PlatformImeData.InputPos = ImVec2(0.0f, 0.0f); + PlatformImeDataPrev.InputPos = + ImVec2(-1.0f, -1.0f); // Different to ensure initial submission + PlatformLocaleDecimalPoint = '.'; + + SettingsLoaded = false; + SettingsDirtyTimer = 0.0f; + HookIdNext = 0; + + LogEnabled = false; + LogType = ImGuiLogType_None; + LogNextPrefix = LogNextSuffix = NULL; + LogFile = NULL; + LogLinePosY = FLT_MAX; + LogLineFirstItem = false; + LogDepthRef = 0; + LogDepthToExpand = LogDepthToExpandDefault = 2; + + DebugLogFlags = ImGuiDebugLogFlags_None; + DebugItemPickerActive = false; + 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; + 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 compensentate 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) + ImGuiID NavFocusScopeIdCurrent; // Current focus scope ID while appending + bool NavHideHighlightOneFrame; + bool NavHasScroll; // Set when scrolling can be used (ScrollMax > 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 { + 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(). + 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 + 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 + + 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.CurrentWidow. + ImRect Rect() const { + return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); + } + float CalcFontSize() const { + ImGuiContext& g = *GImGui; + float scale = g.FontBaseSize * FontWindowScale; + if (ParentWindow) scale *= ParentWindow->FontWindowScale; + return scale; + } + float TitleBarHeight() const { + ImGuiContext& g = *GImGui; + 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 = *GImGui; + 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 + 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() + bool WantClose; // Marked as closed by SetTabItemClosed() + + ImGuiTabItem() { + memset(this, 0, sizeof(*this)); + LastFrameVisible = LastFrameSelected = -1; + 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(); + int GetTabOrder(const ImGuiTabItem* tab) const { + return Tabs.index_from_ptr(tab); + } + const char* GetTabName(const ImGuiTabItem* tab) const { + IM_ASSERT(tab->NameOffset != -1 && tab->NameOffset < TabsNames.Buf.Size); + return TabsNames.Buf.Data + tab->NameOffset; + } +}; + +//----------------------------------------------------------------------------- +// [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 \ + 64 // sizeof(ImU64) * 8. This is solely because we frequently encode columns + // set in a ImU64. +#define IMGUI_TABLE_MAX_DRAW_CHANNELS \ + (4 + 64 * 2) // See TableSetupDrawChannels() + +// Our current column maximum is 64 but we may raise that in the future. +typedef ImS8 ImGuiTableColumnIdx; +typedef ImU8 ImGuiTableDrawChannelIdx; + +// [Internal] sizeof() ~ 104 +// 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 of available sort directions (2-bits + // each) + + 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 needs they +// could be moved to ImGuiTableTempData ?) +struct ImGuiTableInstanceData { + float LastOuterHeight; // Outer height from last frame // FIXME: + // multi-instance issue (#3955) + float LastFirstRowHeight; // Height of first row from last frame // FIXME: + // possible multi-instance issue? + + ImGuiTableInstanceData() { LastOuterHeight = LastFirstRowHeight = 0.0f; } +}; + +// FIXME-TABLE: more transient data could be stored in a per-stacked table +// structure: DrawSplitter, SortSpecs, incoming RowData +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. + ImU64 EnabledMaskByDisplayOrder; // Column DisplayOrder -> IsEnabled map + ImU64 EnabledMaskByIndex; // Column Index -> IsEnabled map (== not hidden by + // user/api) in a format adequate for iterating + // column without touching cold data + ImU64 VisibleMaskByIndex; // Column Index -> IsVisibleX|IsVisibleY map (== + // not hidden by user/api && not hidden by + // scrolling/cliprect) + ImU64 RequestOutputMaskByIndex; // Column Index -> IsVisible || AutoFit (== + // expect user to submit items) + 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 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 + float CellPaddingY; + float CellSpacingX1; // Spacing between non-bordered cells + 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 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. +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); +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); +} + +// Windows: Display Order and Focus Order +IMGUI_API void FocusWindow(ImGuiWindow* window); +IMGUI_API void FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, + ImGuiWindow* ignore_window); +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. + +// 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 ImGuiWindowSettings* CreateNewWindowSettings(const char* name); +IMGUI_API ImGuiWindowSettings* FindWindowSettings(ImGuiID id); +IMGUI_API ImGuiWindowSettings* FindOrCreateWindowSettings(const char* name); +IMGUI_API void AddSettingsHandler(const ImGuiSettingsHandler* handler); +IMGUI_API void RemoveSettingsHandler(const char* type_name); +IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name); + +// Scrolling +IMGUI_API void SetNextWindowScroll( + const ImVec2& scroll); // Use -1.0f on one axis to leave as-is +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 ImGuiID GetItemID() { + ImGuiContext& g = *GImGui; + return g.LastItemData.ID; +} // Get ID of last item (~~ often same ImGui::GetID(label) beforehand) +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); + +// 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); +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 +IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); +IMGUI_API void PopItemFlag(); + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +// Currently refactoring focus/nav/tabbing system +// 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.NavActivateInputId == id' +// (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 + +// 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 void BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, + ImGuiWindowFlags extra_window_flags); +IMGUI_API ImRect GetPopupAllowedExtentRect(ImGuiWindow* window); +IMGUI_API ImGuiWindow* GetTopMostPopupModal(); +IMGUI_API ImGuiWindow* GetTopMostAndVisiblePopupModal(); +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 NavMoveRequestCancel(); +IMGUI_API void NavMoveRequestApplyResult(); +IMGUI_API void NavMoveRequestTryWrapping(ImGuiWindow* window, + ImGuiNavMoveFlags move_flags); +IMGUI_API const char* GetNavInputName(ImGuiNavInput n); +IMGUI_API float GetNavInputAmount(ImGuiNavInput n, ImGuiNavReadMode mode); +IMGUI_API ImVec2 GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, + ImGuiNavReadMode mode, + float slow_factor = 0.0f, + float fast_factor = 0.0f); +IMGUI_API int CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, + float repeat_rate); +IMGUI_API void ActivateItem( + ImGuiID id); // Remotely activate a button, checkbox, tree node etc. given + // its unique ID. activation is queued and processed on the + // next frame when the item is encountered again. +IMGUI_API void SetNavWindow(ImGuiWindow* window); +IMGUI_API void SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, + ImGuiID focus_scope_id, const ImRect& rect_rel); + +// Focus Scope (WIP) +// This is generally used to identify a selection set (multiple of which may be +// in the same window), as selection patterns generally need to react (e.g. +// clear selection) when landing on an item of the set. +IMGUI_API void PushFocusScope(ImGuiID id); +IMGUI_API void PopFocusScope(); +inline ImGuiID GetFocusedFocusScope() { + ImGuiContext& g = *GImGui; + return g.NavFocusScopeId; +} // Focus scope which is actually active +inline ImGuiID GetFocusScope() { + ImGuiContext& g = *GImGui; + return g.CurrentWindow->DC.NavFocusScopeIdCurrent; +} // Focus scope we are outputting into, set by PushFocusScope() + +// 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 IsLegacyKey(ImGuiKey key) { + return key >= ImGuiKey_LegacyNativeKey_BEGIN && + key < ImGuiKey_LegacyNativeKey_END; +} +inline bool IsGamepadKey(ImGuiKey key) { + return key >= ImGuiKey_Gamepad_BEGIN && key < ImGuiKey_Gamepad_END; +} +IMGUI_API ImGuiKeyData* GetKeyData(ImGuiKey key); +IMGUI_API void SetItemUsingMouseWheel(); +IMGUI_API void SetActiveIdUsingNavAndKeys(); +inline bool IsActiveIdUsingNavDir(ImGuiDir dir) { + ImGuiContext& g = *GImGui; + return (g.ActiveIdUsingNavDirMask & (1 << dir)) != 0; +} +inline bool IsActiveIdUsingNavInput(ImGuiNavInput input) { + ImGuiContext& g = *GImGui; + return (g.ActiveIdUsingNavInputMask & (1 << input)) != 0; +} +inline bool IsActiveIdUsingKey(ImGuiKey key) { + ImGuiContext& g = *GImGui; + return g.ActiveIdUsingKeyInputMask[key]; +} +inline void SetActiveIdUsingKey(ImGuiKey key) { + ImGuiContext& g = *GImGui; + g.ActiveIdUsingKeyInputMask.SetBit(key); +} +IMGUI_API bool IsMouseDragPastThreshold(ImGuiMouseButton button, + float lock_threshold = -1.0f); +inline bool IsNavInputDown(ImGuiNavInput n) { + ImGuiContext& g = *GImGui; + return g.IO.NavInputs[n] > 0.0f; +} +inline bool IsNavInputTest(ImGuiNavInput n, ImGuiNavReadMode rm) { + return (GetNavInputAmount(n, rm) > 0.0f); +} +IMGUI_API ImGuiModFlags GetMergedModFlags(); +#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] +#endif + +// Drag and Drop +IMGUI_API bool BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id); +IMGUI_API void ClearDragDrop(); +IMGUI_API bool IsDragDropPayloadBeingAccepted(); + +// 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 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 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]; +} +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(const 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 +IMGUI_API bool BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& bb, + ImGuiTabBarFlags flags); +IMGUI_API ImGuiTabItem* TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id); +IMGUI_API void TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id); +IMGUI_API void TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); +IMGUI_API void TabBarQueueReorder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, + int offset); +IMGUI_API void TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, + const 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); +IMGUI_API ImVec2 TabItemCalcSize(const char* label, bool has_close_button); +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 CloseButton(ImGuiID id, const ImVec2& pos); +IMGUI_API bool CollapseButton(ImGuiID id, const ImVec2& pos); +IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, + ImGuiButtonFlags flags = 0); +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 bool ImageButtonEx(ImGuiID id, ImTextureID texture_id, + const ImVec2& size, const ImVec2& uv0, + const ImVec2& uv1, const ImVec2& padding, + const ImVec4& bg_col, const ImVec4& tint_col); +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); +IMGUI_API void SeparatorEx(ImGuiSeparatorFlags flags); +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 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); +IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, + const char* label, + const char* label_end = NULL); +IMGUI_API bool TreeNodeBehaviorIsOpen( + ImGuiID id, + ImGuiTreeNodeFlags flags = 0); // Consume previous SetNextItemOpen() data, + // if any. May return true when logging +IMGUI_API void TreePushOverrideID(ImGuiID id); + +// 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 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 (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, + ImVec2 frame_size); + +// 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); +inline void DebugDrawItemRect(ImU32 col = IM_COL32(255, 0, 0, 255)) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + GetForegroundDrawList(window)->AddRect(g.LastItemData.Rect.Min, + g.LastItemData.Rect.Max, col); +} +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, + 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 DebugRenderViewportThumbnail(ImDrawList* draw_list, + ImGuiViewportP* viewport, + const ImRect& bb); + +} // 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, const ImRect& bb, + ImGuiID id); +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); + +#define IMGUI_TEST_ENGINE_ITEM_ADD(_BB, _ID) \ + if (g.TestEngineHookItems) \ + ImGuiTestEngineHook_ItemAdd(&g, _BB, _ID) // 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/customchar-ui/libs/imgui/include/imstb_rectpack.h b/customchar-ui/libs/imgui/include/imstb_rectpack.h new file mode 100644 index 0000000..052627a --- /dev/null +++ b/customchar-ui/libs/imgui/include/imstb_rectpack.h @@ -0,0 +1,626 @@ +// [DEAR IMGUI] +// This is a slightly modified version of stb_rect_pack.h 1.01. +// Grep for [DEAR IMGUI] to find the changes. +// +// stb_rect_pack.h - v1.01 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Before #including, +// +// #define STB_RECT_PACK_IMPLEMENTATION +// +// in the file that you want to have the implementation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// github:IntellectualKitty +// +// Bugfixes / warning fixes +// Jeremy Jaussaud +// Fabian Giesen +// +// Version history: +// +// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in +// public section 1.00 (2019-02-25) avoid small space waste; gracefully +// fail too-wide rectangles 0.99 (2019-02-07) warning fixes 0.11 +// (2017-03-03) return packing success/fail result 0.10 (2016-10-25) +// remove cast-away-const to avoid warnings 0.09 (2016-08-27) fix compiler +// warnings 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// See end of file for license information. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +typedef int stbrp_coord; + +#define STBRP__MAXVAL 0x7fffffff +// Mostly for internal use, but this is the maximum supported coordinate value. + +STBRP_DEF int stbrp_pack_rects(stbrp_context* context, stbrp_rect* rects, + int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. +// +// The function returns 1 if all of the rectangles were successfully +// packed and 0 otherwise. + +struct stbrp_rect { + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + +STBRP_DEF void stbrp_init_target(stbrp_context* context, int width, int height, + stbrp_node* nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is +// 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = +// 1' +// +// If you don't do either of the above things, widths will be quantized to +// multiples of small integers to guarantee the algorithm doesn't run out of +// temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the +// algorithm may run out of temporary storage and be unable to pack some +// rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context* context, + int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context* context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum { + STBRP_HEURISTIC_Skyline_default = 0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node { + stbrp_coord x, y; + stbrp_node* next; +}; + +struct stbrp_context { + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node* active_head; + stbrp_node* free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal + // user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#define STBRP__CDECL __cdecl +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#define STBRP__CDECL +#endif + +enum { STBRP__INIT_skyline = 1 }; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context* context, int heuristic) { + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || + heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context* context, + int allow_out_of_mem) { + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = + (context->width + context->num_nodes - 1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context* context, int width, int height, + stbrp_node* nodes, int num_nodes) { + int i; + + for (i = 0; i < num_nodes - 1; ++i) nodes[i].next = &nodes[i + 1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width + // explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord)width; + context->extra[1].y = (1 << 30); + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context* c, stbrp_node* first, + int x0, int width, int* pwaste) { + stbrp_node* node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + +#if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; +#else + STBRP_ASSERT(node->next->x > + x0); // we ended up handling this in the caller for efficiency +#endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct { + int x, y; + stbrp_node** prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context* c, + int width, int height) { + int best_waste = (1 << 30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + // if it can't possibly fit, bail immediately + if (width > c->width || height > c->height) { + fr.prev_link = NULL; + fr.x = fr.y = 0; + return fr; + } + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y, waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == + STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test + // BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each + // node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses + // left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y, waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height <= c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || + (waste == best_waste && xpos < best_x)) { + best_x = xpos; + // STBRP_ASSERT(y <= best_y); [DEAR IMGUI] + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context* context, + int width, int height) { + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || + context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord)res.x; + node->y = (stbrp_coord)(res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node* next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node* next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) cur->x = (stbrp_coord)(res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + int count = 0; + cur = context->active_head; + while (cur) { + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes + 2); + } +#endif + + return res; +} + +static int STBRP__CDECL rect_height_compare(const void* a, const void* b) { + const stbrp_rect* p = (const stbrp_rect*)a; + const stbrp_rect* q = (const stbrp_rect*)b; + if (p->h > q->h) return -1; + if (p->h < q->h) return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int STBRP__CDECL rect_original_order(const void* a, const void* b) { + const stbrp_rect* p = (const stbrp_rect*)a; + const stbrp_rect* q = (const stbrp_rect*)b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +STBRP_DEF int stbrp_pack_rects(stbrp_context* context, stbrp_rect* rects, + int num_rects) { + int i, all_rects_packed = 1; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i = 0; i < num_rects; ++i) { + rects[i].was_packed = i; + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i = 0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } else { + stbrp__findresult fr = + stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord)fr.x; + rects[i].y = (stbrp_coord)fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags and all_rects_packed status + for (i = 0; i < num_rects; ++i) { + rects[i].was_packed = + !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if (!rects[i].was_packed) all_rects_packed = 0; + } + + // return the all_rects_packed status + return all_rects_packed; +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/customchar-ui/libs/imgui/include/imstb_textedit.h b/customchar-ui/libs/imgui/include/imstb_textedit.h new file mode 100644 index 0000000..46ffd76 --- /dev/null +++ b/customchar-ui/libs/imgui/include/imstb_textedit.h @@ -0,0 +1,1486 @@ +// [DEAR IMGUI] +// This is a slightly modified version of stb_textedit.h 1.14. +// Those changes would need to be pushed into nothings/stb: +// - Fix in stb_textedit_discard_redo (see +// https://github.com/nothings/stb/issues/321) Grep for [DEAR IMGUI] to find the +// changes. + +// stb_textedit.h - v1.14 - public domain - Sean Barrett +// Development of this library was sponsored by RAD Game Tools +// +// This C header file implements the guts of a multi-line text-editing +// widget; you implement display, word-wrapping, and low-level string +// insertion/deletion, and stb_textedit will map user inputs into +// insertions & deletions, plus updates to the cursor position, +// selection state, and undo state. +// +// It is intended for use in games and other systems that need to build +// their own custom widgets and which do not have heavy text-editing +// requirements (this library is not recommended for use for editing large +// texts, as its performance does not scale and it has limited undo). +// +// Non-trivial behaviors are modelled after Windows text controls. +// +// +// LICENSE +// +// See end of file for license information. +// +// +// DEPENDENCIES +// +// Uses the C runtime function 'memmove', which you can override +// by defining STB_TEXTEDIT_memmove before the implementation. +// Uses no other functions. Performs no runtime allocations. +// +// +// VERSION HISTORY +// +// 1.14 (2021-07-11) page up/down, various fixes +// 1.13 (2019-02-07) fix bug in undo size management +// 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid +// crash 1.11 (2017-03-03) fix HOME on last line, dragging off single-line +// textfield 1.10 (2016-10-25) supress warnings about casting away const with +// -Wcast-qual 1.9 (2016-08-27) customizable move-by-word 1.8 (2016-04-02) +// better keyboard handling when mouse button is down 1.7 (2015-09-13) change +// y range handling in case baseline is non-0 1.6 (2015-04-15) allow +// STB_TEXTEDIT_memmove 1.5 (2014-09-10) add support for secondary keys for +// OS X 1.4 (2014-08-17) fix signed/unsigned warnings 1.3 (2014-06-19) fix +// mouse clicking to round to nearest char boundary 1.2 (2014-05-27) fix some +// RAD types that had crept into the new code 1.1 (2013-12-15) move-by-word +// (requires STB_TEXTEDIT_IS_SPACE ) 1.0 (2012-07-26) improve documentation, +// initial public release 0.3 (2012-02-24) bugfixes, single-line mode; insert +// mode 0.2 (2011-11-28) fixes to undo/redo 0.1 (2010-07-08) initial version +// +// ADDITIONAL CONTRIBUTORS +// +// Ulf Winklemann: move-by-word in 1.1 +// Fabian Giesen: secondary key inputs in 1.5 +// Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6 +// Louis Schnellbach: page up/down in 1.14 +// +// Bugfixes: +// Scott Graham +// Daniel Keller +// Omar Cornut +// Dan Thompson +// +// USAGE +// +// This file behaves differently depending on what symbols you define +// before including it. +// +// +// Header-file mode: +// +// If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, +// it will operate in "header file" mode. In this mode, it declares a +// single public symbol, STB_TexteditState, which encapsulates the current +// state of a text widget (except for the string, which you will store +// separately). +// +// To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a +// primitive type that defines a single character (e.g. char, wchar_t, etc). +// +// To save space or increase undo-ability, you can optionally define the +// following things that are used by the undo system: +// +// STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor +// position STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to +// allow STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to +// store in the undo buffer +// +// If you don't define these, they are set to permissive types and +// moderate sizes. The undo system does no memory allocations, so +// it grows STB_TexteditState by the worst-case storage which is (in bytes): +// +// [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * +// STB_TEXTEDIT_UNDOSTATECOUNT +// + sizeof(STB_TEXTEDIT_CHARTYPE) * +// STB_TEXTEDIT_UNDOCHARCOUNT +// +// +// Implementation mode: +// +// If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it +// will compile the implementation of the text edit widget, depending +// on a large number of symbols which must be defined before the include. +// +// The implementation is defined only as static functions. You will then +// need to provide your own APIs in the same file which will access the +// static functions. +// +// The basic concept is that you provide a "string" object which +// behaves like an array of characters. stb_textedit uses indices to +// refer to positions in the string, implicitly representing positions +// in the displayed textedit. This is true for both plain text and +// rich text; even with rich text stb_truetype interacts with your +// code as if there was an array of all the displayed characters. +// +// Symbols that must be the same in header-file and implementation mode: +// +// STB_TEXTEDIT_CHARTYPE the character type +// STB_TEXTEDIT_POSITIONTYPE small type that is a valid cursor +// position STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to +// allow STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store +// in the undo buffer +// +// Symbols you must define for implementation mode: +// +// STB_TEXTEDIT_STRING the type of object representing a string +// being edited, +// typically this is a wrapper object with +// other data you need +// +// STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) +// STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line +// of characters +// starting from character #n (see +// discussion below) +// STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of +// the i'th character +// to the xpos of the i+1'th char for a +// line of characters starting at +// character #n (i.e. accounts for +// kerning with previous char) +// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable +// character +// (return type is int, -1 means not +// valid to insert) +// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, +// 0-based STB_TEXTEDIT_NEWLINE the character returned by +// _GETCHAR() we recognize +// as manually wordwrapping for +// end-of-line positioning +// +// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i +// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed +// to by STB_TEXTEDIT_CHARTYPE*) +// +// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard +// input to represent the shift key +// +// STB_TEXTEDIT_K_LEFT keyboard input to move cursor left +// STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right +// STB_TEXTEDIT_K_UP keyboard input to move cursor up +// STB_TEXTEDIT_K_DOWN keyboard input to move cursor down +// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page +// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page +// STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line +// // e.g. HOME STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to +// end of line // e.g. END STB_TEXTEDIT_K_TEXTSTART keyboard input to +// move cursor to start of text // e.g. ctrl-HOME STB_TEXTEDIT_K_TEXTEND +// keyboard input to move cursor to end of text // e.g. ctrl-END +// STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character +// under cursor STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection +// or character left of cursor STB_TEXTEDIT_K_UNDO keyboard input to +// perform undo STB_TEXTEDIT_K_REDO keyboard input to perform redo +// +// Optional: +// STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode +// STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. +// 'isspace'), +// required for default +// WORDLEFT/WORDRIGHT handlers +// STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns +// index to move cursor to STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler +// for WORDRIGHT, returns index to move cursor to STB_TEXTEDIT_K_WORDLEFT +// keyboard input to move cursor left one word // e.g. ctrl-LEFT +// STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one +// word // e.g. ctrl-RIGHT STB_TEXTEDIT_K_LINESTART2 secondary +// keyboard input to move cursor to start of line STB_TEXTEDIT_K_LINEEND2 +// secondary keyboard input to move cursor to end of line +// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor +// to start of text STB_TEXTEDIT_K_TEXTEND2 secondary keyboard +// input to move cursor to end of text +// +// Keyboard input must be encoded as a single integer value; e.g. a character +// code and some bitflags that represent shift states. to simplify the +// interface, SHIFT must be a bitflag, so we can test the shifted state of +// cursor movements to allow selection, i.e. +// (STB_TEXTEDIT_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. +// +// You can encode other things, such as CONTROL or ALT, in additional bits, and +// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, +// my Windows implementations add an additional CONTROL bit, and an additional +// KEYDOWN bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN +// bit, and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in +// the API below. The control keys will only match WM_KEYDOWN events because of +// the keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN +// bit so it only decodes WM_CHAR events. +// +// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed +// row of characters assuming they start on the i'th character--the width and +// the height and the number of characters consumed. This allows this library +// to traverse the entire layout incrementally. You need to compute +// word-wrapping here. +// +// Each textfield keeps its own insert mode state, which is not how normal +// applications work. To keep an app-wide insert mode, update/copy the +// "insert_mode" field of STB_TexteditState before/after calling API functions. +// +// API +// +// void stb_textedit_initialize_state(STB_TexteditState *state, int +// is_single_line) +// +// void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState +// *state, float x, float y) void stb_textedit_drag(STB_TEXTEDIT_STRING *str, +// STB_TexteditState *state, float x, float y) int +// stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) int +// stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, +// STB_TEXTEDIT_CHARTYPE *text, int len) void +// stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, +// STB_TEXEDIT_KEYTYPE key) +// +// Each of these functions potentially updates the string and updates the +// state. +// +// initialize_state: +// set the textedit state to a known good default state when initially +// constructing the textedit. +// +// click: +// call this with the mouse x,y on a mouse down; it will update the +// cursor and reset the selection start/end to the cursor point. the +// x,y must be relative to the text widget, with (0,0) being the top +// left. +// +// drag: +// call this with the mouse x,y on a mouse drag/up; it will update the +// cursor and the selection end point +// +// cut: +// call this to delete the current selection; returns true if there was +// one. you should FIRST copy the current selection to the system paste +// buffer. (To copy, just copy the current selection out of the string +// yourself.) +// +// paste: +// call this to paste text at the current cursor point or over the +// current selection if there is one. +// +// key: +// call this for keyboard inputs sent to the textfield. you can use it +// for "key down" events or for "translated" key events. if you need to +// do both (as in Win32), or distinguish Unicode characters from +// control inputs, set a high bit to distinguish the two; then you can +// define the various definitions like STB_TEXTEDIT_K_LEFT have the +// is-key-event bit set, and make STB_TEXTEDIT_KEYTOCHAR check that the +// is-key-event bit is clear. STB_TEXTEDIT_KEYTYPE defaults to int, but +// you can #define it to anything other type you wante before +// including. +// +// +// When rendering, you can read the cursor position and selection state from +// the STB_TexteditState. +// +// +// Notes: +// +// This is designed to be usable in IMGUI, so it allows for the possibility of +// running in an IMGUI that has NOT cached the multi-line layout. For this +// reason, it provides an interface that is compatible with computing the +// layout incrementally--we try to make sure we make as few passes through +// as possible. (For example, to locate the mouse pointer in the text, we +// could define functions that return the X and Y positions of characters +// and binary search Y and then X, but if we're doing dynamic layout this +// will run the layout algorithm many times, so instead we manually search +// forward in one pass. Similar logic applies to e.g. up-arrow and +// down-arrow movement.) +// +// If it's run in a widget that *has* cached the layout, then this is less +// efficient, but it's not horrible on modern computers. But you wouldn't +// want to edit million-line files with it. + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Header-file mode +//// +//// + +#ifndef INCLUDE_STB_TEXTEDIT_H +#define INCLUDE_STB_TEXTEDIT_H + +//////////////////////////////////////////////////////////////////////// +// +// STB_TexteditState +// +// Definition of STB_TexteditState which you should store +// per-textfield; it includes cursor position, selection state, +// and undo state. +// + +#ifndef STB_TEXTEDIT_UNDOSTATECOUNT +#define STB_TEXTEDIT_UNDOSTATECOUNT 99 +#endif +#ifndef STB_TEXTEDIT_UNDOCHARCOUNT +#define STB_TEXTEDIT_UNDOCHARCOUNT 999 +#endif +#ifndef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_CHARTYPE int +#endif +#ifndef STB_TEXTEDIT_POSITIONTYPE +#define STB_TEXTEDIT_POSITIONTYPE int +#endif + +typedef struct { + // private data + STB_TEXTEDIT_POSITIONTYPE where; + STB_TEXTEDIT_POSITIONTYPE insert_length; + STB_TEXTEDIT_POSITIONTYPE delete_length; + int char_storage; +} StbUndoRecord; + +typedef struct { + // private data + StbUndoRecord undo_rec[STB_TEXTEDIT_UNDOSTATECOUNT]; + STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT]; + short undo_point, redo_point; + int undo_char_point, redo_char_point; +} StbUndoState; + +typedef struct { + ///////////////////// + // + // public data + // + + int cursor; + // position of the text cursor within the string + + int select_start; // selection start point + int select_end; + // selection start and end point in characters; if equal, no selection. + // note that start may be less than or greater than end (e.g. when + // dragging the mouse, start is where the initial click was, and you + // can drag in either direction) + + unsigned char insert_mode; + // each textfield keeps its own insert mode state. to keep an app-wide + // insert mode, copy this value in/out of the app state + + int row_count_per_page; + // page size in number of row. + // this value MUST be set to >0 for pageup or pagedown in multilines + // documents. + + ///////////////////// + // + // private data + // + unsigned char cursor_at_end_of_line; // not implemented yet + unsigned char initialized; + unsigned char has_preferred_x; + unsigned char single_line; + unsigned char padding1, padding2, padding3; + float preferred_x; // this determines where the cursor up/down tries to seek + // to along x + StbUndoState undostate; +} STB_TexteditState; + +//////////////////////////////////////////////////////////////////////// +// +// StbTexteditRow +// +// Result of layout query, used by stb_textedit to determine where +// the text in each row is. + +// result of layout query +typedef struct { + float x0, + x1; // starting x location, end x location (allows for align=right, etc) + float baseline_y_delta; // position of baseline relative to previous row's + // baseline + float ymin, ymax; // height of row above and below baseline + int num_chars; +} StbTexteditRow; +#endif // INCLUDE_STB_TEXTEDIT_H + +//////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////// +//// +//// Implementation mode +//// +//// + +// implementation isn't include-guarded, since it might have indirectly +// included just the "header" portion +#ifdef STB_TEXTEDIT_IMPLEMENTATION + +#ifndef STB_TEXTEDIT_memmove +#include +#define STB_TEXTEDIT_memmove memmove +#endif + +///////////////////////////////////////////////////////////////////////////// +// +// Mouse input handling +// + +// traverse the layout to locate the nearest character to a display position +static int stb_text_locate_coord(STB_TEXTEDIT_STRING* str, float x, float y) { + StbTexteditRow r; + int n = STB_TEXTEDIT_STRINGLEN(str); + float base_y = 0, prev_x; + int i = 0, k; + + r.x0 = r.x1 = 0; + r.ymin = r.ymax = 0; + r.num_chars = 0; + + // search rows to find one that straddles 'y' + while (i < n) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (r.num_chars <= 0) return n; + + if (i == 0 && y < base_y + r.ymin) return 0; + + if (y < base_y + r.ymax) break; + + i += r.num_chars; + base_y += r.baseline_y_delta; + } + + // below all text, return 'after' last character + if (i >= n) return n; + + // check if it's before the beginning of the line + if (x < r.x0) return i; + + // check if it's before the end of the line + if (x < r.x1) { + // search characters in row for one that straddles 'x' + prev_x = r.x0; + for (k = 0; k < r.num_chars; ++k) { + float w = STB_TEXTEDIT_GETWIDTH(str, i, k); + if (x < prev_x + w) { + if (x < prev_x + w / 2) + return k + i; + else + return k + i + 1; + } + prev_x += w; + } + // shouldn't happen, but if it does, fall through to end-of-line case + } + + // if the last character is a newline, return that. otherwise return 'after' + // the last character + if (STB_TEXTEDIT_GETCHAR(str, i + r.num_chars - 1) == STB_TEXTEDIT_NEWLINE) + return i + r.num_chars - 1; + else + return i + r.num_chars; +} + +// API click: on mouse down, move the cursor to the clicked location, and reset +// the selection +static void stb_textedit_click(STB_TEXTEDIT_STRING* str, + STB_TexteditState* state, float x, float y) { + // In single-line mode, just always make y = 0. This lets the drag keep + // working if the mouse goes off the top or bottom of the text + if (state->single_line) { + StbTexteditRow r; + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + y = r.ymin; + } + + state->cursor = stb_text_locate_coord(str, x, y); + state->select_start = state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; +} + +// API drag: on mouse drag, move the cursor and selection endpoint to the +// clicked location +static void stb_textedit_drag(STB_TEXTEDIT_STRING* str, + STB_TexteditState* state, float x, float y) { + int p = 0; + + // In single-line mode, just always make y = 0. This lets the drag keep + // working if the mouse goes off the top or bottom of the text + if (state->single_line) { + StbTexteditRow r; + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + y = r.ymin; + } + + if (state->select_start == state->select_end) + state->select_start = state->cursor; + + p = stb_text_locate_coord(str, x, y); + state->cursor = state->select_end = p; +} + +///////////////////////////////////////////////////////////////////////////// +// +// Keyboard input handling +// + +// forward declarations +static void stb_text_undo(STB_TEXTEDIT_STRING* str, STB_TexteditState* state); +static void stb_text_redo(STB_TEXTEDIT_STRING* str, STB_TexteditState* state); +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING* str, + STB_TexteditState* state, int where, + int length); +static void stb_text_makeundo_insert(STB_TexteditState* state, int where, + int length); +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING* str, + STB_TexteditState* state, int where, + int old_length, int new_length); + +typedef struct { + float x, y; // position of n'th character + float height; // height of line + int first_char, length; // first char of row, and length + int prev_first; // first char of previous row +} StbFindState; + +// find the x/y location of a character, and remember info about the previous +// row in case we get a move-up event (for page up, we'll have to rescan) +static void stb_textedit_find_charpos(StbFindState* find, + STB_TEXTEDIT_STRING* str, int n, + int single_line) { + StbTexteditRow r; + int prev_start = 0; + int z = STB_TEXTEDIT_STRINGLEN(str); + int i = 0, first; + + if (n == z) { + // if it's at the end, then find the last line -- simpler than trying to + // explicitly handle this case in the regular code + if (single_line) { + STB_TEXTEDIT_LAYOUTROW(&r, str, 0); + find->y = 0; + find->first_char = 0; + find->length = z; + find->height = r.ymax - r.ymin; + find->x = r.x1; + } else { + find->y = 0; + find->x = 0; + find->height = 1; + while (i < z) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + prev_start = i; + i += r.num_chars; + } + find->first_char = i; + find->length = 0; + find->prev_first = prev_start; + } + return; + } + + // search rows to find the one that straddles character n + find->y = 0; + + for (;;) { + STB_TEXTEDIT_LAYOUTROW(&r, str, i); + if (n < i + r.num_chars) break; + prev_start = i; + i += r.num_chars; + find->y += r.baseline_y_delta; + } + + find->first_char = first = i; + find->length = r.num_chars; + find->height = r.ymax - r.ymin; + find->prev_first = prev_start; + + // now scan to find xpos + find->x = r.x0; + for (i = 0; first + i < n; ++i) + find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); +} + +#define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) + +// make the selection/cursor state valid if client altered the string +static void stb_textedit_clamp(STB_TEXTEDIT_STRING* str, + STB_TexteditState* state) { + int n = STB_TEXTEDIT_STRINGLEN(str); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start > n) state->select_start = n; + if (state->select_end > n) state->select_end = n; + // if clamping forced them to be equal, move the cursor to match + if (state->select_start == state->select_end) + state->cursor = state->select_start; + } + if (state->cursor > n) state->cursor = n; +} + +// delete characters while updating undo +static void stb_textedit_delete(STB_TEXTEDIT_STRING* str, + STB_TexteditState* state, int where, int len) { + stb_text_makeundo_delete(str, state, where, len); + STB_TEXTEDIT_DELETECHARS(str, where, len); + state->has_preferred_x = 0; +} + +// delete the section +static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING* str, + STB_TexteditState* state) { + stb_textedit_clamp(str, state); + if (STB_TEXT_HAS_SELECTION(state)) { + if (state->select_start < state->select_end) { + stb_textedit_delete(str, state, state->select_start, + state->select_end - state->select_start); + state->select_end = state->cursor = state->select_start; + } else { + stb_textedit_delete(str, state, state->select_end, + state->select_start - state->select_end); + state->select_start = state->cursor = state->select_end; + } + state->has_preferred_x = 0; + } +} + +// canoncialize the selection so start <= end +static void stb_textedit_sortselection(STB_TexteditState* state) { + if (state->select_end < state->select_start) { + int temp = state->select_end; + state->select_end = state->select_start; + state->select_start = temp; + } +} + +// move cursor to first character of selection +static void stb_textedit_move_to_first(STB_TexteditState* state) { + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + state->cursor = state->select_start; + state->select_end = state->select_start; + state->has_preferred_x = 0; + } +} + +// move cursor to last character of selection +static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING* str, + STB_TexteditState* state) { + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_sortselection(state); + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->select_start = state->select_end; + state->has_preferred_x = 0; + } +} + +#ifdef STB_TEXTEDIT_IS_SPACE +static int is_word_boundary(STB_TEXTEDIT_STRING* str, int idx) { + return idx > 0 ? (STB_TEXTEDIT_IS_SPACE(STB_TEXTEDIT_GETCHAR(str, idx - 1)) && + !STB_TEXTEDIT_IS_SPACE(STB_TEXTEDIT_GETCHAR(str, idx))) + : 1; +} + +#ifndef STB_TEXTEDIT_MOVEWORDLEFT +static int stb_textedit_move_to_word_previous(STB_TEXTEDIT_STRING* str, int c) { + --c; // always move at least one character + while (c >= 0 && !is_word_boundary(str, c)) --c; + + if (c < 0) c = 0; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous +#endif + +#ifndef STB_TEXTEDIT_MOVEWORDRIGHT +static int stb_textedit_move_to_word_next(STB_TEXTEDIT_STRING* str, int c) { + const int len = STB_TEXTEDIT_STRINGLEN(str); + ++c; // always move at least one character + while (c < len && !is_word_boundary(str, c)) ++c; + + if (c > len) c = len; + + return c; +} +#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next +#endif + +#endif + +// update selection and cursor to match each other +static void stb_textedit_prep_selection_at_cursor(STB_TexteditState* state) { + if (!STB_TEXT_HAS_SELECTION(state)) + state->select_start = state->select_end = state->cursor; + else + state->cursor = state->select_end; +} + +// API cut: delete selection +static int stb_textedit_cut(STB_TEXTEDIT_STRING* str, + STB_TexteditState* state) { + if (STB_TEXT_HAS_SELECTION(state)) { + stb_textedit_delete_selection(str, state); // implicitly clamps + state->has_preferred_x = 0; + return 1; + } + return 0; +} + +// API paste: replace existing selection with passed-in text +static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING* str, + STB_TexteditState* state, + STB_TEXTEDIT_CHARTYPE* text, int len) { + // if there's a selection, the paste should delete it + stb_textedit_clamp(str, state); + stb_textedit_delete_selection(str, state); + // try to insert the characters + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { + stb_text_makeundo_insert(state, state->cursor, len); + state->cursor += len; + state->has_preferred_x = 0; + return 1; + } + // note: paste failure will leave deleted selection, may be restored with an + // undo (see https://github.com/nothings/stb/issues/734 for details) + return 0; +} + +#ifndef STB_TEXTEDIT_KEYTYPE +#define STB_TEXTEDIT_KEYTYPE int +#endif + +// API key: process a keyboard input +static void stb_textedit_key(STB_TEXTEDIT_STRING* str, STB_TexteditState* state, + STB_TEXTEDIT_KEYTYPE key) { +retry: + switch (key) { + default: { + int c = STB_TEXTEDIT_KEYTOTEXT(key); + if (c > 0) { + STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE)c; + + // can't add newline in single-line mode + if (c == '\n' && state->single_line) break; + + if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && + state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { + stb_text_makeundo_replace(str, state, state->cursor, 1, 1); + STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + ++state->cursor; + state->has_preferred_x = 0; + } + } else { + stb_textedit_delete_selection(str, state); // implicitly clamps + if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { + stb_text_makeundo_insert(state, state->cursor, 1); + ++state->cursor; + state->has_preferred_x = 0; + } + } + } + break; + } + +#ifdef STB_TEXTEDIT_K_INSERT + case STB_TEXTEDIT_K_INSERT: + state->insert_mode = !state->insert_mode; + break; +#endif + + case STB_TEXTEDIT_K_UNDO: + stb_text_undo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_REDO: + stb_text_redo(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT: + // if currently there's a selection, move cursor to start of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else if (state->cursor > 0) + --state->cursor; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_RIGHT: + // if currently there's a selection, move cursor to end of selection + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else + ++state->cursor; + stb_textedit_clamp(str, state); + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + // move selection left + if (state->select_end > 0) --state->select_end; + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_MOVEWORDLEFT + case STB_TEXTEDIT_K_WORDLEFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + stb_textedit_clamp(str, state); + } + break; + + case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: + if (!STB_TEXT_HAS_SELECTION(state)) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp(str, state); + break; +#endif + +#ifdef STB_TEXTEDIT_MOVEWORDRIGHT + case STB_TEXTEDIT_K_WORDRIGHT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + else { + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + stb_textedit_clamp(str, state); + } + break; + + case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: + if (!STB_TEXT_HAS_SELECTION(state)) + stb_textedit_prep_selection_at_cursor(state); + + state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); + state->select_end = state->cursor; + + stb_textedit_clamp(str, state); + break; +#endif + + case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + // move selection right + ++state->select_end; + stb_textedit_clamp(str, state); + state->cursor = state->select_end; + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_DOWN: + case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: + case STB_TEXTEDIT_K_PGDOWN: + case STB_TEXTEDIT_K_PGDOWN | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, j, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGDOWN; + int row_count = is_page ? state->row_count_per_page : 1; + + if (!is_page && state->single_line) { + // on windows, up&down in single-line behave like left&right + key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_last(str, state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + for (j = 0; j < row_count; ++j) { + float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; + int start = find.first_char + find.length; + + if (find.length == 0) break; + + // [DEAR IMGUI] + // going down while being on the last line shouldn't bring us to that + // line end + if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != + STB_TEXTEDIT_NEWLINE) + break; + + // now find character position down a row + state->cursor = start; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i = 0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); +#ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) break; +#endif + x += dx; + if (x > goal_x) break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) state->select_end = state->cursor; + + // go to next line + find.first_char = find.first_char + find.length; + find.length = row.num_chars; + } + break; + } + + case STB_TEXTEDIT_K_UP: + case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: + case STB_TEXTEDIT_K_PGUP: + case STB_TEXTEDIT_K_PGUP | STB_TEXTEDIT_K_SHIFT: { + StbFindState find; + StbTexteditRow row; + int i, j, prev_scan, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; + int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGUP; + int row_count = is_page ? state->row_count_per_page : 1; + + if (!is_page && state->single_line) { + // on windows, up&down become left&right + key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); + goto retry; + } + + if (sel) + stb_textedit_prep_selection_at_cursor(state); + else if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_move_to_first(state); + + // compute current position of cursor point + stb_textedit_clamp(str, state); + stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); + + for (j = 0; j < row_count; ++j) { + float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; + + // can only go up if there's a previous row + if (find.prev_first == find.first_char) break; + + // now find character position up a row + state->cursor = find.prev_first; + STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); + x = row.x0; + for (i = 0; i < row.num_chars; ++i) { + float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); +#ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE + if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) break; +#endif + x += dx; + if (x > goal_x) break; + ++state->cursor; + } + stb_textedit_clamp(str, state); + + state->has_preferred_x = 1; + state->preferred_x = goal_x; + + if (sel) state->select_end = state->cursor; + + // go to previous line + // (we need to scan previous line the hard way. maybe we could expose + // this as a new API function?) + prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0; + while (prev_scan > 0 && + STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE) + --prev_scan; + find.first_char = find.prev_first; + find.prev_first = prev_scan; + } + break; + } + + case STB_TEXTEDIT_K_DELETE: + case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + int n = STB_TEXTEDIT_STRINGLEN(str); + if (state->cursor < n) + stb_textedit_delete(str, state, state->cursor, 1); + } + state->has_preferred_x = 0; + break; + + case STB_TEXTEDIT_K_BACKSPACE: + case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: + if (STB_TEXT_HAS_SELECTION(state)) + stb_textedit_delete_selection(str, state); + else { + stb_textedit_clamp(str, state); + if (state->cursor > 0) { + stb_textedit_delete(str, state, state->cursor - 1, 1); + --state->cursor; + } + } + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2: +#endif + case STB_TEXTEDIT_K_TEXTSTART: + state->cursor = state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2: +#endif + case STB_TEXTEDIT_K_TEXTEND: + state->cursor = STB_TEXTEDIT_STRINGLEN(str); + state->select_start = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTSTART2 + case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = 0; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_TEXTEND2 + case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: + stb_textedit_prep_selection_at_cursor(state); + state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2: +#endif + case STB_TEXTEDIT_K_LINESTART: + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = 0; + else + while (state->cursor > 0 && + STB_TEXTEDIT_GETCHAR(str, state->cursor - 1) != + STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2: +#endif + case STB_TEXTEDIT_K_LINEEND: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_move_to_first(state); + if (state->single_line) + state->cursor = n; + else + while (state->cursor < n && + STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->has_preferred_x = 0; + break; + } + +#ifdef STB_TEXTEDIT_K_LINESTART2 + case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = 0; + else + while (state->cursor > 0 && + STB_TEXTEDIT_GETCHAR(str, state->cursor - 1) != + STB_TEXTEDIT_NEWLINE) + --state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + +#ifdef STB_TEXTEDIT_K_LINEEND2 + case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: +#endif + case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { + int n = STB_TEXTEDIT_STRINGLEN(str); + stb_textedit_clamp(str, state); + stb_textedit_prep_selection_at_cursor(state); + if (state->single_line) + state->cursor = n; + else + while (state->cursor < n && + STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) + ++state->cursor; + state->select_end = state->cursor; + state->has_preferred_x = 0; + break; + } + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Undo processing +// +// @OPTIMIZE: the undo/redo buffer should be circular + +static void stb_textedit_flush_redo(StbUndoState* state) { + state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; +} + +// discard the oldest entry in the undo list +static void stb_textedit_discard_undo(StbUndoState* state) { + if (state->undo_point > 0) { + // if the 0th undo state has characters, clean those up + if (state->undo_rec[0].char_storage >= 0) { + int n = state->undo_rec[0].insert_length, i; + // delete n characters from all other records + state->undo_char_point -= n; + STB_TEXTEDIT_memmove( + state->undo_char, state->undo_char + n, + (size_t)(state->undo_char_point * sizeof(STB_TEXTEDIT_CHARTYPE))); + for (i = 0; i < state->undo_point; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage -= + n; // @OPTIMIZE: get rid of char_storage and infer it + } + --state->undo_point; + STB_TEXTEDIT_memmove( + state->undo_rec, state->undo_rec + 1, + (size_t)(state->undo_point * sizeof(state->undo_rec[0]))); + } +} + +// discard the oldest entry in the redo list--it's bad if this +// ever happens, but because undo & redo have to store the actual +// characters in different cases, the redo character buffer can +// fill up even though the undo buffer didn't +static void stb_textedit_discard_redo(StbUndoState* state) { + int k = STB_TEXTEDIT_UNDOSTATECOUNT - 1; + + if (state->redo_point <= k) { + // if the k'th undo state has characters, clean those up + if (state->undo_rec[k].char_storage >= 0) { + int n = state->undo_rec[k].insert_length, i; + // move the remaining redo character data to the end of the buffer + state->redo_char_point += n; + STB_TEXTEDIT_memmove( + state->undo_char + state->redo_char_point, + state->undo_char + state->redo_char_point - n, + (size_t)((STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point) * + sizeof(STB_TEXTEDIT_CHARTYPE))); + // adjust the position of all the other records to account for above + // memmove + for (i = state->redo_point; i < k; ++i) + if (state->undo_rec[i].char_storage >= 0) + state->undo_rec[i].char_storage += n; + } + // now move all the redo records towards the end of the buffer; the first + // one is at 'redo_point' [DEAR IMGUI] + size_t move_size = + (size_t)((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * + sizeof(state->undo_rec[0])); + const char* buf_begin = (char*)state->undo_rec; + (void)buf_begin; + const char* buf_end = (char*)state->undo_rec + sizeof(state->undo_rec); + (void)buf_end; + IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin); + IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= + buf_end); + STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point + 1, + state->undo_rec + state->redo_point, move_size); + + // now move redo_point to point to the new one + ++state->redo_point; + } +} + +static StbUndoRecord* stb_text_create_undo_record(StbUndoState* state, + int numchars) { + // any time we create a new undo record, we discard redo + stb_textedit_flush_redo(state); + + // if we have no free records, we have to make room, by sliding the + // existing records down + if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT) + stb_textedit_discard_undo(state); + + // if the characters to store won't possibly fit in the buffer, we can't undo + if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) { + state->undo_point = 0; + state->undo_char_point = 0; + return NULL; + } + + // if we don't have enough free characters in the buffer, we have to make room + while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT) + stb_textedit_discard_undo(state); + + return &state->undo_rec[state->undo_point++]; +} + +static STB_TEXTEDIT_CHARTYPE* stb_text_createundo(StbUndoState* state, int pos, + int insert_len, + int delete_len) { + StbUndoRecord* r = stb_text_create_undo_record(state, insert_len); + if (r == NULL) return NULL; + + r->where = pos; + r->insert_length = (STB_TEXTEDIT_POSITIONTYPE)insert_len; + r->delete_length = (STB_TEXTEDIT_POSITIONTYPE)delete_len; + + if (insert_len == 0) { + r->char_storage = -1; + return NULL; + } else { + r->char_storage = state->undo_char_point; + state->undo_char_point += insert_len; + return &state->undo_char[r->char_storage]; + } +} + +static void stb_text_undo(STB_TEXTEDIT_STRING* str, STB_TexteditState* state) { + StbUndoState* s = &state->undostate; + StbUndoRecord u, *r; + if (s->undo_point == 0) return; + + // we need to do two things: apply the undo record, and create a redo record + u = s->undo_rec[s->undo_point - 1]; + r = &s->undo_rec[s->redo_point - 1]; + r->char_storage = -1; + + r->insert_length = u.delete_length; + r->delete_length = u.insert_length; + r->where = u.where; + + if (u.delete_length) { + // if the undo record says to delete characters, then the redo record will + // need to re-insert the characters that get deleted, so we need to store + // them. + + // there are three cases: + // there's enough room to store the characters + // characters stored for *redoing* don't leave room for redo + // characters stored for *undoing* don't leave room for redo + // if the last is true, we have to bail + + if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) { + // the undo records take up too much character space; there's no space to + // store the redo characters + r->insert_length = 0; + } else { + int i; + + // there's definitely room to store the characters eventually + while (s->undo_char_point + u.delete_length > s->redo_char_point) { + // should never happen: + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) return; + // there's currently not enough room, so discard a redo record + stb_textedit_discard_redo(s); + } + r = &s->undo_rec[s->redo_point - 1]; + + r->char_storage = s->redo_char_point - u.delete_length; + s->redo_char_point = s->redo_char_point - u.delete_length; + + // now save the characters + for (i = 0; i < u.delete_length; ++i) + s->undo_char[r->char_storage + i] = + STB_TEXTEDIT_GETCHAR(str, u.where + i); + } + + // now we can carry out the deletion + STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); + } + + // check type of recorded action: + if (u.insert_length) { + // easy case: was a deletion, so we need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], + u.insert_length); + s->undo_char_point -= u.insert_length; + } + + state->cursor = u.where + u.insert_length; + + s->undo_point--; + s->redo_point--; +} + +static void stb_text_redo(STB_TEXTEDIT_STRING* str, STB_TexteditState* state) { + StbUndoState* s = &state->undostate; + StbUndoRecord *u, r; + if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) return; + + // we need to do two things: apply the redo record, and create an undo record + u = &s->undo_rec[s->undo_point]; + r = s->undo_rec[s->redo_point]; + + // we KNOW there must be room for the undo record, because the redo record + // was derived from an undo record + + u->delete_length = r.insert_length; + u->insert_length = r.delete_length; + u->where = r.where; + u->char_storage = -1; + + if (r.delete_length) { + // the redo record requires us to delete characters, so the undo record + // needs to store the characters + + if (s->undo_char_point + u->insert_length > s->redo_char_point) { + u->insert_length = 0; + u->delete_length = 0; + } else { + int i; + u->char_storage = s->undo_char_point; + s->undo_char_point = s->undo_char_point + u->insert_length; + + // now save the characters + for (i = 0; i < u->insert_length; ++i) + s->undo_char[u->char_storage + i] = + STB_TEXTEDIT_GETCHAR(str, u->where + i); + } + + STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); + } + + if (r.insert_length) { + // easy case: need to insert n characters + STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], + r.insert_length); + s->redo_char_point += r.insert_length; + } + + state->cursor = r.where + r.insert_length; + + s->undo_point++; + s->redo_point++; +} + +static void stb_text_makeundo_insert(STB_TexteditState* state, int where, + int length) { + stb_text_createundo(&state->undostate, where, 0, length); +} + +static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING* str, + STB_TexteditState* state, int where, + int length) { + int i; + STB_TEXTEDIT_CHARTYPE* p = + stb_text_createundo(&state->undostate, where, length, 0); + if (p) { + for (i = 0; i < length; ++i) p[i] = STB_TEXTEDIT_GETCHAR(str, where + i); + } +} + +static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING* str, + STB_TexteditState* state, int where, + int old_length, int new_length) { + int i; + STB_TEXTEDIT_CHARTYPE* p = + stb_text_createundo(&state->undostate, where, old_length, new_length); + if (p) { + for (i = 0; i < old_length; ++i) + p[i] = STB_TEXTEDIT_GETCHAR(str, where + i); + } +} + +// reset the state to default +static void stb_textedit_clear_state(STB_TexteditState* state, + int is_single_line) { + state->undostate.undo_point = 0; + state->undostate.undo_char_point = 0; + state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; + state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; + state->select_end = state->select_start = 0; + state->cursor = 0; + state->has_preferred_x = 0; + state->preferred_x = 0; + state->cursor_at_end_of_line = 0; + state->initialized = 1; + state->single_line = (unsigned char)is_single_line; + state->insert_mode = 0; + state->row_count_per_page = 0; +} + +// API initialize +static void stb_textedit_initialize_state(STB_TexteditState* state, + int is_single_line) { + stb_textedit_clear_state(state, is_single_line); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +static int stb_textedit_paste(STB_TEXTEDIT_STRING* str, + STB_TexteditState* state, + STB_TEXTEDIT_CHARTYPE const* ctext, int len) { + return stb_textedit_paste_internal(str, state, (STB_TEXTEDIT_CHARTYPE*)ctext, + len); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TEXTEDIT_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/customchar-ui/libs/imgui/include/imstb_truetype.h b/customchar-ui/libs/imgui/include/imstb_truetype.h new file mode 100644 index 0000000..460b584 --- /dev/null +++ b/customchar-ui/libs/imgui/include/imstb_truetype.h @@ -0,0 +1,5456 @@ +// [DEAR IMGUI] +// This is a slightly modified version of stb_truetype.h 1.26. +// Mostly fixing for compiler and static analyzer warnings. +// Grep for [DEAR IMGUI] to find the changes. + +// stb_truetype.h - v1.26 - public domain +// authored from 2009-2021 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance +// field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) +// +// VERSION HISTORY +// +// 1.26 (2021-08-28) fix broken rasterizer +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but +// only kern not GPOS) 1.22 (2019-08-11) minimize missing-glyph duplication; +// fix kerning if both 'GPOS' and 'kern' are defined 1.21 (2019-02-25) fix +// warning 1.20 (2019-02-07) PackFontRange skips missing codepoints; +// GetScaleFontVMetrics() 1.19 (2018-02-11) GPOS kerning, STBTT_fmod 1.18 +// (2018-01-29) add missing function 1.17 (2017-07-23) make more arguments +// const; doc fix 1.16 (2017-07-12) SDF support 1.15 (2017-03-03) make more +// arguments const 1.14 (2017-01-16) num-fonts-in-TTC function 1.13 +// (2017-01-02) support OpenType fonts, certain Apple fonts 1.12 (2016-10-25) +// suppress warnings about casting away const with -Wcast-qual 1.11 +// (2016-04-02) fix unused-variable warning 1.10 (2016-04-02) user-defined +// fabs(); rare memory leak; remove duplicate typedef 1.09 (2016-01-16) +// warning fix; avoid crash on outofmem; use allocation userdata properly 1.08 +// (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal +// edges 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse +// codepoints; +// variant PackFontRanges to pack and render in separate +// phases; fix stbtt_GetFontOFfsetForIndex (never worked for +// non-0 input?); fixed an assert() bug in the new +// rasterizer replace assert() with STBTT_assert() in new +// rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the +// implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for +// use as texture stbtt_GetBakedQuad() -- compute quad +// to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really +// want it stbtt_PackBegin() stbtt_PackSetOversampling() -- +// for improved quality on small fonts stbtt_PackFontRanges() -- pack +// and renders stbtt_PackEnd() stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer +// loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font +// collections stbtt_GetNumberOfFonts() -- number of fonts +// for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a +// bitmap stbtt_MakeCodepointBitmap() -- renders into bitmap +// you provide stbtt_GetCodepointBitmapBox() -- how big the +// bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in +// pixels. If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the +// baseline-relative bounding box for all characters. SF*-y0 will be the +// distance in pixels that the worst-case character could extend above the +// baseline, so if you want the top edge of characters to appear at the top +// of the screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to +// = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate + // implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION +// #define your own (u)stbtt_int8/16/32 before including to override this +#ifndef stbtt_uint8 +typedef unsigned char stbtt_uint8; +typedef signed char stbtt_int8; +typedef unsigned short stbtt_uint16; +typedef signed short stbtt_int16; +typedef unsigned int stbtt_uint32; +typedef signed int stbtt_int32; +#endif + +typedef char stbtt__check_size32[sizeof(stbtt_int32) == 4 ? 1 : -1]; +typedef char stbtt__check_size16[sizeof(stbtt_int16) == 2 ? 1 : -1]; + +// e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h +#ifndef STBTT_ifloor +#include +#define STBTT_ifloor(x) ((int)floor(x)) +#define STBTT_iceil(x) ((int)ceil(x)) +#endif + +#ifndef STBTT_sqrt +#include +#define STBTT_sqrt(x) sqrt(x) +#define STBTT_pow(x, y) pow(x, y) +#endif + +#ifndef STBTT_fmod +#include +#define STBTT_fmod(x, y) fmod(x, y) +#endif + +#ifndef STBTT_cos +#include +#define STBTT_cos(x) cos(x) +#define STBTT_acos(x) acos(x) +#endif + +#ifndef STBTT_fabs +#include +#define STBTT_fabs(x) fabs(x) +#endif + +// #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h +#ifndef STBTT_malloc +#include +#define STBTT_malloc(x, u) ((void)(u), malloc(x)) +#define STBTT_free(x, u) ((void)(u), free(x)) +#endif + +#ifndef STBTT_assert +#include +#define STBTT_assert(x) assert(x) +#endif + +#ifndef STBTT_strlen +#include +#define STBTT_strlen(x) strlen(x) +#endif + +#ifndef STBTT_memcpy +#include +#define STBTT_memcpy memcpy +#define STBTT_memset memset +#endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct { + unsigned char* data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct { + unsigned short x0, y0, x1, y1; // coordinates of bbox in bitmap + float xoff, yoff, xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap( + const unsigned char* data, + int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char* pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar* chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that +// fit if return is 0, no characters fit and no rows were used This uses a very +// crappy packing. + +typedef struct { + float x0, y0, s0, t0; // top-left + float x1, y1, s1, t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad( + const stbtt_bakedchar* chardata, int pw, int ph, // same data as above + int char_index, // character to display + float* xpos, + float* ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad* q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char* fontdata, + int index, float size, float* ascent, + float* descent, float* lineGap); +// Query the font vertical metrics without having to create a font first. + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct { + unsigned short x0, y0, x1, y1; // coordinates of bbox in bitmap + float xoff, yoff, xadvance; + float xoff2, yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context* spc, unsigned char* pixels, + int width, int height, int stride_in_bytes, + int padding, void* alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd(stbtt_pack_context* spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context* spc, + const unsigned char* fontdata, int font_index, + float font_size, + int first_unicode_char_in_range, + int num_chars_in_range, + stbtt_packedchar* chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at +// first_unicode_char_in_range and increasing. Data for how to render them is +// stored in chardata_for_range; pass these to stbtt_GetPackedQuad to get back +// renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels +// tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct { + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are + // continuous, and this is the first + // codepoint + int* array_of_unicode_codepoints; // if non-zero, then this is an array of + // unicode codepoints + int num_chars; + stbtt_packedchar* chardata_for_range; // output + unsigned char h_oversample, + v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context* spc, + const unsigned char* fontdata, + int font_index, stbtt_pack_range* ranges, + int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context* spc, + unsigned int h_oversample, + unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context* spc, + int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad( + const stbtt_packedchar* chardata, int pw, int ph, // same data as above + int char_index, // character to display + float* xpos, + float* ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad* q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context* spc, + const stbtt_fontinfo* info, + stbtt_pack_range* ranges, + int num_ranges, + stbrp_rect* rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context* spc, + stbrp_rect* rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context* spc, + const stbtt_fontinfo* info, + stbtt_pack_range* ranges, + int num_ranges, + stbrp_rect* rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void* user_allocator_context; + void* pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char* pixels; + void* nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char* data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char* data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo { + void* userdata; + unsigned char* data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca, head, glyf, hhea, hmtx, kern, gpos, + svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo* info, const unsigned char* data, + int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo* info, + int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo* info, + float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar +// calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo* info, + float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo* info, int* ascent, + int* descent, int* lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically +// negative) lineGap is the spacing between one row's descent and the next row's +// ascent... so you should advance the vertical position by "*ascent - *descent +// + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo* info, + int* typoAscent, int* typoDescent, + int* typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the +// OS/2 table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo* info, int* x0, + int* y0, int* x1, int* y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo* info, + int codepoint, int* advanceWidth, + int* leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the +// left edge of the character advanceWidth is the offset from the current +// horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo* info, int ch1, + int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo* info, int codepoint, + int* x0, int* y0, int* x1, int* y1); +// Gets the bounding box of the visible part of the glyph, in unscaled +// coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo* info, + int glyph_index, int* advanceWidth, + int* leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo* info, int glyph1, + int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo* info, int glyph_index, + int* x0, int* y0, int* x1, int* y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry { + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo* info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo* info, + stbtt_kerningentry* table, + int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns +// how many entries it did write. The table will be sorted by (a.glyph1 == +// b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but + // why?) +enum { STBTT_vmove = 1, STBTT_vline, STBTT_vcurve, STBTT_vcubic }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) +#define stbtt_vertex_type \ + short // can't use stbtt_int16 because that's not visible in the header file +typedef struct { + stbtt_vertex_type x, y, cx, cy, cx1, cy1; + unsigned char type, padding; +} stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo* info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo* info, + int unicode_codepoint, + stbtt_vertex** vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo* info, int glyph_index, + stbtt_vertex** vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo* info, + stbtt_vertex* vertices); +// frees the data allocated above + +STBTT_DEF unsigned char* stbtt_FindSVGDoc(const stbtt_fontinfo* info, int gl); +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo* info, + int unicode_codepoint, const char** svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo* info, int gl, + const char** svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char* bitmap, void* userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char* stbtt_GetCodepointBitmap(const stbtt_fontinfo* info, + float scale_x, float scale_y, + int codepoint, int* width, + int* height, int* xoff, + int* yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left +// of the bitmap + +STBTT_DEF unsigned char* stbtt_GetCodepointBitmapSubpixel( + const stbtt_fontinfo* info, float scale_x, float scale_y, float shift_x, + float shift_y, int codepoint, int* width, int* height, int* xoff, + int* yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo* info, + unsigned char* output, int out_w, + int out_h, int out_stride, + float scale_x, float scale_y, + int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo* info, + unsigned char* output, + int out_w, int out_h, + int out_stride, float scale_x, + float scale_y, float shift_x, + float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter( + const stbtt_fontinfo* info, unsigned char* output, int out_w, int out_h, + int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, + int oversample_x, int oversample_y, float* sub_x, float* sub_y, + int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo* font, + int codepoint, float scale_x, + float scale_y, int* ix0, int* iy0, + int* ix1, int* iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel( + const stbtt_fontinfo* font, int codepoint, float scale_x, float scale_y, + float shift_x, float shift_y, int* ix0, int* iy0, int* ix1, int* iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char* stbtt_GetGlyphBitmap(const stbtt_fontinfo* info, + float scale_x, float scale_y, + int glyph, int* width, + int* height, int* xoff, + int* yoff); +STBTT_DEF unsigned char* stbtt_GetGlyphBitmapSubpixel( + const stbtt_fontinfo* info, float scale_x, float scale_y, float shift_x, + float shift_y, int glyph, int* width, int* height, int* xoff, int* yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo* info, + unsigned char* output, int out_w, + int out_h, int out_stride, float scale_x, + float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo* info, + unsigned char* output, int out_w, + int out_h, int out_stride, + float scale_x, float scale_y, + float shift_x, float shift_y, + int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter( + const stbtt_fontinfo* info, unsigned char* output, int out_w, int out_h, + int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, + int oversample_x, int oversample_y, float* sub_x, float* sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo* font, int glyph, + float scale_x, float scale_y, int* ix0, + int* iy0, int* ix1, int* iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo* font, + int glyph, float scale_x, + float scale_y, float shift_x, + float shift_y, int* ix0, + int* iy0, int* ix1, int* iy1); + +// @TODO: don't expose this structure +typedef struct { + int w, h, stride; + unsigned char* pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize( + stbtt__bitmap* result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex* vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void* userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char* bitmap, void* userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char* stbtt_GetGlyphSDF(const stbtt_fontinfo* info, + float scale, int glyph, int padding, + unsigned char onedge_value, + float pixel_dist_scale, int* width, + int* height, int* xoff, int* yoff); +STBTT_DEF unsigned char* stbtt_GetCodepointSDF( + const stbtt_fontinfo* info, float scale, int codepoint, int padding, + unsigned char onedge_value, float pixel_dist_scale, int* width, int* height, + int* xoff, int* yoff); +// These functions compute a discretized SDF field for a single character, +// suitable for storing in a single-channel texture, sampling with bilinear +// filtering, and testing against larger than some threshold to produce scalable +// fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, +// same as it would be creating a regular bitmap glyph/codepoint -- the +// character to generate the SDF for padding -- extra "pixels" +// around the character which are filled with the distance to the +// character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to +// reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when +// moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if +// negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap +// (including padding) xoff,yoff -- output origin of the +// character return value -- a 2D array of bytes 0..255, +// width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to +// 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a +// filled shape, sample the SDF at each pixel and fill the pixel if the SDF +// value is greater than or equal to 180/255. (You'll actually want to +// antialias, which is beyond the scope of this example.) Additionally, you +// can compute offset outlines (e.g. to stroke the character border inside +// & outside, or only outside). For example, to fill outside the character +// up to 3 SDF pixels, you would compare against (180-36.0*3)/255 = 72/255. +// The above choice of variables maps a range from 5 pixels outside the +// shape to 2 pixels inside the shape to 0..255; this is intended primarily +// for apply outside effects only (the interior range is needed to allow +// proper antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char* fontdata, + const char* name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE \ + 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char* s1, int len1, + const char* s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from +// next func + +STBTT_DEF const char* stbtt_GetFontNameString(const stbtt_fontinfo* font, + int* length, int platformID, + int encodingID, int languageID, + int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE = 0, + STBTT_PLATFORM_ID_MAC = 1, + STBTT_PLATFORM_ID_ISO = 2, + STBTT_PLATFORM_ID_MICROSOFT = 3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 = 0, + STBTT_UNICODE_EID_UNICODE_1_1 = 1, + STBTT_UNICODE_EID_ISO_10646 = 2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP = 3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL = 4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL = 0, + STBTT_MS_EID_UNICODE_BMP = 1, + STBTT_MS_EID_SHIFTJIS = 2, + STBTT_MS_EID_UNICODE_FULL = 10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN = 0, + STBTT_MAC_EID_ARABIC = 4, + STBTT_MAC_EID_JAPANESE = 1, + STBTT_MAC_EID_HEBREW = 5, + STBTT_MAC_EID_CHINESE_TRAD = 2, + STBTT_MAC_EID_GREEK = 6, + STBTT_MAC_EID_KOREAN = 3, + STBTT_MAC_EID_RUSSIAN = 7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic + // LCIDs + STBTT_MS_LANG_ENGLISH = 0x0409, + STBTT_MS_LANG_ITALIAN = 0x0410, + STBTT_MS_LANG_CHINESE = 0x0804, + STBTT_MS_LANG_JAPANESE = 0x0411, + STBTT_MS_LANG_DUTCH = 0x0413, + STBTT_MS_LANG_KOREAN = 0x0412, + STBTT_MS_LANG_FRENCH = 0x040c, + STBTT_MS_LANG_RUSSIAN = 0x0419, + STBTT_MS_LANG_GERMAN = 0x0407, + STBTT_MS_LANG_SPANISH = 0x0409, + STBTT_MS_LANG_HEBREW = 0x040d, + STBTT_MS_LANG_SWEDISH = 0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH = 0, + STBTT_MAC_LANG_JAPANESE = 11, + STBTT_MAC_LANG_ARABIC = 12, + STBTT_MAC_LANG_KOREAN = 23, + STBTT_MAC_LANG_DUTCH = 4, + STBTT_MAC_LANG_RUSSIAN = 32, + STBTT_MAC_LANG_FRENCH = 1, + STBTT_MAC_LANG_SPANISH = 6, + STBTT_MAC_LANG_GERMAN = 2, + STBTT_MAC_LANG_SWEDISH = 5, + STBTT_MAC_LANG_HEBREW = 10, + STBTT_MAC_LANG_CHINESE_SIMPLIFIED = 33, + STBTT_MAC_LANG_ITALIAN = 3, + STBTT_MAC_LANG_CHINESE_TRAD = 19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2 + [(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE - 1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf* b) { + if (b->cursor >= b->size) return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf* b) { + if (b->cursor >= b->size) return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf* b, int o) { + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf* b, int o) { + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf* b, int n) { + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void* p, size_t size) { + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*)p; + r.size = (int)size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf* b, int o, int s) { + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf* b) { + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf* b) { + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) + return b0 - 139; + else if (b0 >= 247 && b0 <= 250) + return (b0 - 247) * 256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) + return -(b0 - 251) * 256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) + return stbtt__buf_get16(b); + else if (b0 == 29) + return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf* b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf* b, int key) { + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end - start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf* b, int key, int outcount, + stbtt_uint32* out) { + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf* b) { + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) { + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i * offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2 + (count + 1) * offsize + start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define +// ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (*(stbtt_uint8*)(p)) +#define ttCHAR(p) (*(stbtt_int8*)(p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8* p) { return p[0] * 256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8* p) { return p[0] * 256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8* p) { + return (p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3]; +} +static stbtt_int32 ttLONG(stbtt_uint8* p) { + return (p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3]; +} + +#define stbtt_tag4(p, c0, c1, c2, c3) \ + ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p, str) stbtt_tag4(p, str[0], str[1], str[2], str[3]) + +static int stbtt__isfont(stbtt_uint8* font) { + // check the version number + if (stbtt_tag4(font, '1', 0, 0, 0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) + return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0, 1, 0, 0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) + return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8* data, stbtt_uint32 fontstart, + const char* tag) { + stbtt_int32 num_tables = ttUSHORT(data + fontstart + 4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i = 0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16 * i; + if (stbtt_tag(data + loc + 0, tag)) return ttULONG(data + loc + 8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char* font_collection, + int index) { + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection + 4) == 0x00010000 || + ttULONG(font_collection + 4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection + 8); + if (index >= n) return -1; + return ttULONG(font_collection + 12 + index * 4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char* font_collection) { + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection + 4) == 0x00010000 || + ttULONG(font_collection + 4) == 0x00020000) { + return ttLONG(font_collection + 8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) { + stbtt_uint32 subrsoff = 0, private_loc[2] = {0, 0}; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1] + subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo* info) { + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo* info, unsigned char* data, + int fontstart) { + stbtt_uint32 cmap, t; + stbtt_int32 i, numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data + cff, 512 * 1024 * 1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size - fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data + t + 4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i = 0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch (ttUSHORT(data + encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data + encoding_record + 2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data + encoding_record + 4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data + encoding_record + 4); + break; + } + } + if (info->index_map == 0) return 0; + + info->indexToLocFormat = ttUSHORT(data + info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo* info, + int unicode_codepoint) { + stbtt_uint8* data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes - 6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32)unicode_codepoint >= first && + (stbtt_uint32)unicode_codepoint < first + count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first) * 2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary + // search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data + index_map + 6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data + index_map + 8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data + index_map + 10); + stbtt_uint16 rangeShift = ttUSHORT(data + index_map + 12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift * 2)) + search += rangeShift * 2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange * 2); + if (unicode_codepoint > end) search += searchRange * 2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start, last; + stbtt_uint16 item = (stbtt_uint16)((search - endCount) >> 1); + + start = ttUSHORT(data + index_map + 14 + segcount * 2 + 2 + 2 * item); + last = ttUSHORT(data + endCount + 2 * item); + if (unicode_codepoint < start || unicode_codepoint > last) return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount * 6 + 2 + 2 * item); + if (offset == 0) + return (stbtt_uint16)(unicode_codepoint + + ttSHORT(data + index_map + 14 + segcount * 4 + 2 + + 2 * item)); + + return ttUSHORT(data + offset + (unicode_codepoint - start) * 2 + + index_map + 14 + segcount * 6 + 2 + 2 * item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data + index_map + 12); + stbtt_int32 low, high; + low = 0; + high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = + low + ((high - low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data + index_map + 16 + mid * 12); + stbtt_uint32 end_char = ttULONG(data + index_map + 16 + mid * 12 + 4); + if ((stbtt_uint32)unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32)unicode_codepoint > end_char) + low = mid + 1; + else { + stbtt_uint32 start_glyph = + ttULONG(data + index_map + 16 + mid * 12 + 8); + if (format == 12) + return start_glyph + unicode_codepoint - start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo* info, + int unicode_codepoint, + stbtt_vertex** vertices) { + return stbtt_GetGlyphShape( + info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex* v, stbtt_uint8 type, stbtt_int32 x, + stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) { + v->type = type; + v->x = (stbtt_int16)x; + v->y = (stbtt_int16)y; + v->cx = (stbtt_int16)cx; + v->cy = (stbtt_int16)cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo* info, int glyph_index) { + int g1, g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) + return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG(info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG(info->data + info->loca + glyph_index * 4 + 4); + } + + return g1 == g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo* info, int glyph_index, + int* x0, int* y0, int* x1, int* y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo* info, int glyph_index, + int* x0, int* y0, int* x1, int* y1) { + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo* info, int codepoint, + int* x0, int* y0, int* x1, int* y1) { + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info, codepoint), x0, y0, + x1, y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo* info, int glyph_index) { + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == + 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex* vertices, int num_vertices, + int was_off, int start_off, stbtt_int32 sx, + stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, + stbtt_int32 cx, stbtt_int32 cy) { + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx + scx) >> 1, + (cy + scy) >> 1, cx, cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx, sy, scx, scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx, sy, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, sx, sy, 0, 0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo* info, int glyph_index, + stbtt_vertex** pvertices) { + stbtt_int16 numberOfContours; + stbtt_uint8* endPtsOfContours; + stbtt_uint8* data = info->data; + stbtt_vertex* vertices = 0; + int num_vertices = 0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags = 0, flagcount; + stbtt_int32 ins, i, j = 0, m, n, next_move, was_off = 0, off, start_off = 0; + stbtt_int32 x, y, cx, cy, sx, sy, scx, scy; + stbtt_uint8* points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1 + ttUSHORT(endPtsOfContours + numberOfContours * 2 - 2); + + m = n + 2 * numberOfContours; // a loose bound on how many vertices we + // might need + vertices = + (stbtt_vertex*)STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) return 0; + + next_move = 0; + flagcount = 0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m + // ends up being calculated + + // first load flags + + for (i = 0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) flagcount = *points++; + } else + --flagcount; + vertices[off + i].type = flags; + } + + // now load x coordinates + x = 0; + for (i = 0; i < n; ++i) { + flags = vertices[off + i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16)(points[0] * 256 + points[1]); + points += 2; + } + } + vertices[off + i].x = (stbtt_int16)x; + } + + // now load y coordinates + y = 0; + for (i = 0; i < n; ++i) { + flags = vertices[off + i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16)(points[0] * 256 + points[1]); + points += 2; + } + } + vertices[off + i].y = (stbtt_int16)y; + } + + // now convert them to our format + num_vertices = 0; + sx = sy = cx = cy = scx = scy = 0; + for (i = 0; i < n; ++i) { + flags = vertices[off + i].type; + x = (stbtt_int16)vertices[off + i].x; + y = (stbtt_int16)vertices[off + i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = + stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx, + sy, scx, scy, cx, cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find + // a point on the curve where we can start, and we need to save some + // state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off + i + 1].type & 1)) { + // next point is also a curve point, so interpolate an on-point + // curve + sx = (x + (stbtt_int32)vertices[off + i + 1].x) >> 1; + sy = (y + (stbtt_int32)vertices[off + i + 1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32)vertices[off + i + 1].x; + sy = (stbtt_int32)vertices[off + i + 1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove, sx, sy, 0, 0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours + j * 2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means + // interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, + (cx + x) >> 1, (cy + y) >> 1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x, y, cx, + cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x, y, 0, 0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, + start_off, sx, sy, scx, scy, cx, cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8* comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1, 0, 0, 1, 0, 0}, m, n; + + flags = ttSHORT(comp); + comp += 2; + gidx = ttSHORT(comp); + comp += 2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); + comp += 2; + mtx[5] = ttSHORT(comp); + comp += 2; + } else { + mtx[4] = ttCHAR(comp); + comp += 1; + mtx[5] = ttCHAR(comp); + comp += 1; + } + } else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1 << 3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp) / 16384.0f; + comp += 2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1 << 6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp) / 16384.0f; + comp += 2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp) / 16384.0f; + comp += 2; + } else if (flags & (1 << 7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp) / 16384.0f; + comp += 2; + mtx[1] = ttSHORT(comp) / 16384.0f; + comp += 2; + mtx[2] = ttSHORT(comp) / 16384.0f; + comp += 2; + mtx[3] = ttSHORT(comp) / 16384.0f; + comp += 2; + } + + // Find transformation scales. + m = (float)STBTT_sqrt(mtx[0] * mtx[0] + mtx[1] * mtx[1]); + n = (float)STBTT_sqrt(mtx[2] * mtx[2] + mtx[3] * mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x, y; + x = v->x; + y = v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0] * x + mtx[2] * y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1] * x + mtx[3] * y + mtx[5])); + x = v->cx; + y = v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0] * x + mtx[2] * y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1] * x + mtx[3] * y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc( + (num_vertices + comp_num_verts) * sizeof(stbtt_vertex), + info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0 && vertices) + STBTT_memcpy(tmp, vertices, num_vertices * sizeof(stbtt_vertex)); + STBTT_memcpy(tmp + num_vertices, comp_verts, + comp_num_verts * sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1 << 5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct { + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex* pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) \ + { bounds, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, 0 } + +static void stbtt__track_vertex(stbtt__csctx* c, stbtt_int32 x, stbtt_int32 y) { + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx* c, stbtt_uint8 type, stbtt_int32 x, + stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, + stbtt_int32 cx1, stbtt_int32 cy1) { + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16)cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16)cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx* ctx) { + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, + 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx* ctx, float dx, float dy) { + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx* ctx, float dx, float dy) { + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx* ctx, float dx1, float dy1, + float dx2, float dy2, float dx3, + float dy3) { + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, + (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) { + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo* info, + int glyph_index) { + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, + stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo* info, int glyph_index, + stbtt__csctx* c) { + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we + // have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp - 2], s[sp - 1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp - 1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp - 1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) stbtt__csctx_rline_to(c, s[i], s[i + 1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and + // vertical starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i + 1], s[i + 2], s[i + 3], + (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i + 1], s[i + 2], + (sp - i == 5) ? s[i + 4] : 0.0f, s[i + 3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i + 1], s[i + 2], s[i + 3], + s[i + 4], s[i + 5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i + 1], s[i + 2], s[i + 3], + s[i + 4], s[i + 5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i + 1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) stbtt__csctx_rline_to(c, s[i], s[i + 1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i + 1], s[i + 2], s[i + 3], s[i + 4], + s[i + 5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { + f = s[i]; + i++; + } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i + 1], s[i + 2], s[i + 3], + 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i + 1], s[i + 2], 0.0, + s[i + 3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // FALLTHROUGH + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int)s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and + // resolution, and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + // fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, + -(dy1 + dy2 + dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1 + dx2 + dx3 + dx4 + dx5; + dy = dy1 + dy2 + dy3 + dy4 + dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && b0 < 32) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo* info, int glyph_index, + stbtt_vertex** pvertices) { + // runs the charstring twice, once to count and once to output (to avoid + // realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc( + count_ctx.num_vertices * sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo* info, int glyph_index, + int* x0, int* y0, int* x1, int* y1) { + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo* info, int glyph_index, + stbtt_vertex** pvertices) { + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo* info, + int glyph_index, int* advanceWidth, + int* leftSideBearing) { + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data + info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) + *advanceWidth = ttSHORT(info->data + info->hmtx + 4 * glyph_index); + if (leftSideBearing) + *leftSideBearing = ttSHORT(info->data + info->hmtx + 4 * glyph_index + 2); + } else { + if (advanceWidth) + *advanceWidth = + ttSHORT(info->data + info->hmtx + 4 * (numOfLongHorMetrics - 1)); + if (leftSideBearing) + *leftSideBearing = + ttSHORT(info->data + info->hmtx + 4 * numOfLongHorMetrics + + 2 * (glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo* info) { + stbtt_uint8* data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) return 0; + if (ttUSHORT(data + 2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data + 8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data + 10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo* info, + stbtt_kerningentry* table, + int table_length) { + stbtt_uint8* data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) return 0; + if (ttUSHORT(data + 2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data + 8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data + 10); + if (table_length < length) length = table_length; + + for (k = 0; k < length; k++) { + table[k].glyph1 = ttUSHORT(data + 18 + (k * 6)); + table[k].glyph2 = ttUSHORT(data + 20 + (k * 6)); + table[k].advance = ttSHORT(data + 22 + (k * 6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo* info, + int glyph1, int glyph2) { + stbtt_uint8* data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) return 0; + if (ttUSHORT(data + 2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data + 8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data + 10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data + 18 + (m * 6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data + 22 + (m * 6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8* coverageTable, + int glyph) { + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch (coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l = 0, r = glyphCount - 1, m; + int straw, needle = glyph; + while (l <= r) { + stbtt_uint8* glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + break; + } + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8* rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l = 0, r = rangeCount - 1, m; + int strawStart, strawEnd, needle = glyph; + while (l <= r) { + stbtt_uint8* rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + break; + } + + default: + return -1; // unsupported + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8* classDefTable, int glyph) { + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch (classDefFormat) { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8* classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + + 2 * (glyph - startGlyphID)); + break; + } + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8* classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l = 0, r = classRangeCount - 1, m; + int strawStart, strawEnd, needle = glyph; + while (l <= r) { + stbtt_uint8* classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + break; + } + + default: + return -1; // Unsupported definition type, return an error. + } + + // "All glyphs not assigned to a class fall into class 0". (OpenType spec) + return 0; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo* info, + int glyph1, int glyph2) { + stbtt_uint16 lookupListOffset; + stbtt_uint8* lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8* data; + stbtt_int32 i, sti; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data + 0) != 1) return 0; // Major version 1 + if (ttUSHORT(data + 2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data + 8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i = 0; i < lookupCount; ++i) { + stbtt_uint16 lookupOffset = ttUSHORT(lookupList + 2 + 2 * i); + stbtt_uint8* lookupTable = lookupList + lookupOffset; + + stbtt_uint16 lookupType = ttUSHORT(lookupTable); + stbtt_uint16 subTableCount = ttUSHORT(lookupTable + 4); + stbtt_uint8* subTableOffsets = lookupTable + 6; + if (lookupType != 2) // Pair Adjustment Positioning Subtable + continue; + + for (sti = 0; sti < subTableCount; sti++) { + stbtt_uint16 subtableOffset = ttUSHORT(subTableOffsets + 2 * sti); + stbtt_uint8* table = lookupTable + subtableOffset; + stbtt_uint16 posFormat = ttUSHORT(table); + stbtt_uint16 coverageOffset = ttUSHORT(table + 2); + stbtt_int32 coverageIndex = + stbtt__GetCoverageIndex(table + coverageOffset, glyph1); + if (coverageIndex == -1) continue; + + switch (posFormat) { + case 1: { + stbtt_int32 l, r, m; + int straw, needle; + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + if (valueFormat1 == 4 && + valueFormat2 == 0) { // Support more formats? + stbtt_int32 valueRecordPairSizeInBytes = 2; + stbtt_uint16 pairSetCount = ttUSHORT(table + 8); + stbtt_uint16 pairPosOffset = + ttUSHORT(table + 10 + 2 * coverageIndex); + stbtt_uint8* pairValueTable = table + pairPosOffset; + stbtt_uint16 pairValueCount = ttUSHORT(pairValueTable); + stbtt_uint8* pairValueArray = pairValueTable + 2; + + if (coverageIndex >= pairSetCount) return 0; + + needle = glyph2; + r = pairValueCount - 1; + l = 0; + + // Binary search. + while (l <= r) { + stbtt_uint16 secondGlyph; + stbtt_uint8* pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } else + return 0; + break; + } + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + if (valueFormat1 == 4 && + valueFormat2 == 0) { // Support more formats? + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = + stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = + stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + stbtt_uint8 *class1Records, *class2Records; + stbtt_int16 xAdvance; + + if (glyph1class < 0 || glyph1class >= class1Count) + return 0; // malformed + if (glyph2class < 0 || glyph2class >= class2Count) + return 0; // malformed + + class1Records = table + 16; + class2Records = class1Records + 2 * (glyph1class * class2Count); + xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } else + return 0; + break; + } + + default: + return 0; // Unsupported position format + } + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo* info, int g1, + int g2) { + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo* info, int ch1, + int ch2) { + if (!info->kern && !info->gpos) // if no kerning table, don't waste time + // looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info, ch1), + stbtt_FindGlyphIndex(info, ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo* info, + int codepoint, int* advanceWidth, + int* leftSideBearing) { + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info, codepoint), + advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo* info, int* ascent, + int* descent, int* lineGap) { + if (ascent) *ascent = ttSHORT(info->data + info->hhea + 4); + if (descent) *descent = ttSHORT(info->data + info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data + info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo* info, + int* typoAscent, int* typoDescent, + int* typoLineGap) { + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) return 0; + if (typoAscent) *typoAscent = ttSHORT(info->data + tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data + tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data + tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo* info, int* x0, + int* y0, int* x1, int* y1) { + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo* info, + float height) { + int fheight = ttSHORT(info->data + info->hhea + 4) - + ttSHORT(info->data + info->hhea + 6); + return (float)height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo* info, + float pixels) { + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo* info, stbtt_vertex* v) { + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8* stbtt_FindSVGDoc(const stbtt_fontinfo* info, int gl) { + int i; + stbtt_uint8* data = info->data; + stbtt_uint8* svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo*)info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8* svg_docs = svg_doc_list + 2; + + for (i = 0; i < numEntries; i++) { + stbtt_uint8* svg_doc = svg_docs + (12 * i); + if ((gl >= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo* info, int gl, + const char** svg) { + stbtt_uint8* data = info->data; + stbtt_uint8* svg_doc; + + if (info->svg == 0) return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char*)data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo* info, + int unicode_codepoint, const char** svg) { + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), + svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo* font, + int glyph, float scale_x, + float scale_y, float shift_x, + float shift_y, int* ix0, + int* iy0, int* ix1, int* iy1) { + int x0 = 0, y0 = 0, x1, y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0, &y0, &x1, &y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels + // get touched)? + if (ix0) *ix0 = STBTT_ifloor(x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil(x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil(-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo* font, int glyph, + float scale_x, float scale_y, int* ix0, + int* iy0, int* ix1, int* iy1) { + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y, 0.0f, 0.0f, + ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel( + const stbtt_fontinfo* font, int codepoint, float scale_x, float scale_y, + float shift_x, float shift_y, int* ix0, int* iy0, int* ix1, int* iy1) { + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font, codepoint), + scale_x, scale_y, shift_x, shift_y, ix0, iy0, + ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo* font, + int codepoint, float scale_x, + float scale_y, int* ix0, int* iy0, + int* ix1, int* iy1) { + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y, 0.0f, + 0.0f, ix0, iy0, ix1, iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk { + struct stbtt__hheap_chunk* next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap { + struct stbtt__hheap_chunk* head; + void* first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void* stbtt__hheap_alloc(stbtt__hheap* hh, size_t size, void* userdata) { + if (hh->first_free) { + void* p = hh->first_free; + hh->first_free = *(void**)p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk* c = (stbtt__hheap_chunk*)STBTT_malloc( + sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char*)(hh->head) + sizeof(stbtt__hheap_chunk) + + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap* hh, void* p) { + *(void**)p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap* hh, void* userdata) { + stbtt__hheap_chunk* c = hh->head; + while (c) { + stbtt__hheap_chunk* n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0, y0, x1, y1; + int invert; +} stbtt__edge; + +typedef struct stbtt__active_edge { + struct stbtt__active_edge* next; +#if STBTT_RASTERIZER_VERSION == 1 + int x, dx; + float ey; + int direction; +#elif STBTT_RASTERIZER_VERSION == 2 + float fx, fdx, fdy; + float direction; + float sy; + float ey; +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX - 1) + +static stbtt__active_edge* stbtt__new_active(stbtt__hheap* hh, stbtt__edge* e, + int off_x, float start_point, + void* userdata) { + stbtt__active_edge* z = + (stbtt__active_edge*)stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor( + STBTT_FIX * e->x0 + + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's + // by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge* stbtt__new_active(stbtt__hheap* hh, stbtt__edge* e, + int off_x, float start_point, + void* userdata) { + stbtt__active_edge* z = + (stbtt__active_edge*)stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + // STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f / dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char* scanline, int len, + stbtt__active_edge* e, int max_weight) { + // non-zero winding fill + int x0 = 0, w = 0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; + w += e->direction; + } else { + int x1 = e->x; + w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8)((x1 - x0) * max_weight >> + STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + + (stbtt_uint8)(((STBTT_FIX - (x0 & STBTT_FIXMASK)) * + max_weight) >> + STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + + (stbtt_uint8)(((x1 & STBTT_FIXMASK) * max_weight) >> + STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8)max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap* result, stbtt__edge* e, + int n, int vsubsample, int off_x, + int off_y, void* userdata) { + stbtt__hheap hh = {0, 0, 0}; + stbtt__active_edge* active = NULL; + int y, j = 0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char*)STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float)vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s = 0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge** step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this + // scanline + while (*step) { + stbtt__active_edge* z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for (;;) { + int changed = 0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge* t = *step; + stbtt__active_edge* q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit + // ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge* z = + stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge* p = active; + while (p->next && p->next->x < z->x) p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical +// line at x+1 (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float* scanline, int x, + stbtt__active_edge* e, float x0, + float y0, float x1, float y1) { + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1 - x0) * (e->sy - y0) / (y1 - y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1 - x0) * (e->ey - y1) / (y1 - y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x + 1); + else if (x0 == x + 1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x + 1) + STBTT_assert(x1 >= x + 1); + else + STBTT_assert(x1 >= x && x1 <= x + 1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1 - y0); + else if (x0 >= x + 1 && x1 >= x + 1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x + 1 && x1 >= x && x1 <= x + 1); + scanline[x] += + e->direction * (y1 - y0) * + (1 - ((x0 - x) + (x1 - x)) / 2); // coverage = 1 - average x position + } +} + +static float stbtt__sized_trapezoid_area(float height, float top_width, + float bottom_width) { + STBTT_assert(top_width >= 0); + STBTT_assert(bottom_width >= 0); + return (top_width + bottom_width) / 2.0f * height; +} + +static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, + float bx0, float bx1) { + return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); +} + +static float stbtt__sized_triangle_area(float height, float width) { + return height * width / 2; +} + +static void stbtt__fill_active_edges_new(float* scanline, float* scanline_fill, + int len, stbtt__active_edge* e, + float y_top) { + float y_bottom = y_top + 1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline, (int)x0, e, x0, y_top, x0, + y_bottom); + stbtt__handle_clipped_edge(scanline_fill - 1, (int)x0 + 1, e, x0, + y_top, x0, y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill - 1, 0, e, x0, y_top, x0, + y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0, sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int)x_top == (int)x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int)x_top; + height = (sy1 - sy0) * e->direction; + STBTT_assert(x >= 0 && x < len); + scanline[x] += stbtt__position_trapezoid_area(height, x_top, x + 1.0f, + x_bottom, x + 1.0f); + scanline_fill[x] += + height; // everything right of this pixel is filled + } else { + int x, x1, x2; + float y_crossing, y_final, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + STBTT_assert(dy >= 0); + STBTT_assert(dx >= 0); + + x1 = (int)x_top; + x2 = (int)x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = y_top + dy * (x1 + 1 - x0); + + // compute intersection with y axis at x2 + y_final = y_top + dy * (x2 - x0); + + // x1 x_top x2 x_bottom + // y_top + // +------|-----+------------+------------+--------|---+------------+ + // | | | | | | + // | | | | | | + // sy0 | + // Txxxxx|............|............|............|............| + // y_crossing | *xxxxx.......|............|............|............| + // | | + // xxxxx..|............|............|............| | | /- + // xx*xxxx........|............|............| | | dy < | + // xxxxxx..|............|............| + // y_final | | \- | + // xx*xxx.........|............| + // sy1 | | | | + // xxxxxB...|............| + // | | | | | | + // | | | | | | + // y_bottom + // +------------+------------+------------+------------+------------+ + // + // goal is to measure the area covered by '.' in each pixel + + // if x2 is right at the right edge of x1, y_crossing can blow up, + // github #1057 + // @TODO: maybe test against sy1 rather than y_bottom? + if (y_crossing > y_bottom) y_crossing = y_bottom; + + sign = e->direction; + + // area of the rectangle covered from sy0..y_crossing + area = sign * (y_crossing - sy0); + + // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) + scanline[x1] += stbtt__sized_triangle_area(area, x1 + 1 - x_top); + + // check if final y_crossing is blown up; no test case for this + if (y_final > y_bottom) { + int denom = (x2 - (x1 + 1)); + y_final = y_bottom; + if (denom != 0) { // [DEAR IMGUI] Avoid div by zero + // (https://github.com/nothings/stb/issues/1316) + dy = (y_final - y_crossing) / + denom; // if denom=0, y_final = y_crossing, so y_final <= + // y_bottom + } + } + + // in second pixel, area covered by line segment found in first pixel + // is always a rectangle 1 wide * the height of that line segment; + // this is exactly what the variable 'area' stores. it also gets a + // contribution from the line segment within it. the THIRD pixel will + // get the first pixel's rectangle contribution, the second pixel's + // rectangle contribution, and its own contribution. the 'own + // contribution' is the same in every pixel except the leftmost and + // rightmost, a trapezoid that slides down in each pixel. the second + // pixel's contribution to the third pixel will be the rectangle 1 + // wide times the height change in the second pixel, which is dy. + + step = sign * dy * + 1; // dy is dy/dx, change in y for every 1 change in x, + // which multiplied by 1-pixel-width is how much pixel area changes + // for each step in x so the area advances by 'step' every time + + for (x = x1 + 1; x < x2; ++x) { + scanline[x] += area + step / 2; // area of trapezoid is 1*step/2 + area += step; + } + STBTT_assert(STBTT_fabs(area) <= + 1.01f); // accumulated error from area += step unless we + // round step down + STBTT_assert(sy1 > y_final - 0.01f); + + // area covered in the last pixel is the rectangle from all the pixels + // to the left, plus the trapezoid filled by the line segment in this + // pixel all the way to the right edge + scanline[x2] += area + sign * stbtt__position_trapezoid_area( + sy1 - y_final, (float)x2, x2 + 1.0f, + x_bottom, x2 + 1.0f); + + // the rest of the line is filled based on the total height of the + // line segment in this pixel + scanline_fill[x2] += sign * (sy1 - sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + // note though that this does happen some of the time because + // x_top and x_bottom can be extrapolated at the top & bottom of + // the shape and actually lie outside the bounding box + int x; + for (x = 0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any + // intersection with left or right edges can be handled by splitting + // into two (or three) regions. intersections with top & bottom do not + // necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & + // right edges, then used some simple logic to produce up to three + // segments in sorted order from top-to-bottom. however, this had a + // problem: if an x edge was epsilon across the x border, then the + // corresponding y position might not be distinct from the other y + // segment, and it might ignored as an empty segment. to avoid that, + // we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float)(x); + float x2 = (float)(x + 1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x + 1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x1, y1); + stbtt__handle_clipped_edge(scanline, x, e, x1, y1, x2, y2); + stbtt__handle_clipped_edge(scanline, x, e, x2, y2, x3, y3); + } else if (x3 < x1 && + x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x2, y2); + stbtt__handle_clipped_edge(scanline, x, e, x2, y2, x1, y1); + stbtt__handle_clipped_edge(scanline, x, e, x1, y1, x3, y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x1, y1); + stbtt__handle_clipped_edge(scanline, x, e, x1, y1, x3, y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x1, y1); + stbtt__handle_clipped_edge(scanline, x, e, x1, y1, x3, y3); + } else if (x0 < x2 && + x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x2, y2); + stbtt__handle_clipped_edge(scanline, x, e, x2, y2, x3, y3); + } else if (x3 < x2 && + x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x2, y2); + stbtt__handle_clipped_edge(scanline, x, e, x2, y2, x3, y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x3, y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap* result, stbtt__edge* e, + int n, int vsubsample, int off_x, + int off_y, void* userdata) { + stbtt__hheap hh = {0, 0, 0}; + stbtt__active_edge* active = NULL; + int y, j = 0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = + (float*)STBTT_malloc((result->w * 2 + 1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float)(off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge** step = &active; + + STBTT_memset(scanline, 0, result->w * sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w + 1) * sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge* z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge* z = + stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp + // rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= + scan_y_top); // if we get really unlucky a tiny bit of + // an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2 + 1, result->w, active, + scan_y_top); + + { + float sum = 0; + for (i = 0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float)STBTT_fabs(k) * 255 + 0.5f; + m = (int)k; + if (m > 255) m = 255; + result->pixels[j * result->stride + i] = (unsigned char)m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge* z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a, b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge* p, int n) { + int i, j; + for (i = 1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge* b = &p[j - 1]; + int c = STBTT__COMPARE(a, b); + if (!c) break; + p[j] = p[j - 1]; + --j; + } + if (i != j) p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge* p, int n) { + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01, c12, c, m, i, j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0], &p[m]); + c12 = STBTT__COMPARE(&p[m], &p[n - 1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0], &p[n - 1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n - 1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i = 1; + j = n - 1; + for (;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;; ++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;; --j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n - i)) { + stbtt__sort_edges_quicksort(p, j); + p = p + i; + n = n - i; + } else { + stbtt__sort_edges_quicksort(p + i, n - i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge* p, int n) { + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct { + float x, y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap* result, stbtt__point* pts, + int* wcount, int windings, float scale_x, + float scale_y, float shift_x, float shift_y, + int off_x, int off_y, int invert, void* userdata) { + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge* e; + int n, i, j, k, m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i = 0; i < windings; ++i) n += wcount[i]; + + e = (stbtt__edge*)STBTT_malloc(sizeof(*e) * (n + 1), + userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m = 0; + for (i = 0; i < windings; ++i) { + stbtt__point* p = pts + m; + m += wcount[i]; + j = wcount[i] - 1; + for (k = 0; k < wcount[i]; j = k++) { + int a = k, b = j; + // skip the edge if horizontal + if (p[j].y == p[k].y) continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a = j, b = k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then + // by x) + // STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, + // use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, + userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point* points, int n, float x, float y) { + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for +// non-linear stretching +static int stbtt__tesselate_curve(stbtt__point* points, int* num_points, + float x0, float y0, float x1, float y1, + float x2, float y2, + float objspace_flatness_squared, int n) { + // midpoint + float mx = (x0 + 2 * x1 + x2) / 4; + float my = (y0 + 2 * y1 + y2) / 4; + // versus directly drawn line + float dx = (x0 + x2) / 2 - mx; + float dy = (y0 + y2) / 2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx * dx + dy * dy > + objspace_flatness_squared) { // half-pixel error allowed... need to be + // smaller if AA + stbtt__tesselate_curve(points, num_points, x0, y0, (x0 + x1) / 2.0f, + (y0 + y1) / 2.0f, mx, my, objspace_flatness_squared, + n + 1); + stbtt__tesselate_curve(points, num_points, mx, my, (x1 + x2) / 2.0f, + (y1 + y2) / 2.0f, x2, y2, objspace_flatness_squared, + n + 1); + } else { + stbtt__add_point(points, *num_points, x2, y2); + *num_points = *num_points + 1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point* points, int* num_points, + float x0, float y0, float x1, float y1, + float x2, float y2, float x3, float y3, + float objspace_flatness_squared, int n) { + // @TODO this "flatness" calculation is just made-up nonsense that seems to + // work well enough + float dx0 = x1 - x0; + float dy0 = y1 - y0; + float dx1 = x2 - x1; + float dy1 = y2 - y1; + float dx2 = x3 - x2; + float dy2 = y3 - y2; + float dx = x3 - x0; + float dy = y3 - y0; + float longlen = (float)(STBTT_sqrt(dx0 * dx0 + dy0 * dy0) + + STBTT_sqrt(dx1 * dx1 + dy1 * dy1) + + STBTT_sqrt(dx2 * dx2 + dy2 * dy2)); + float shortlen = (float)STBTT_sqrt(dx * dx + dy * dy); + float flatness_squared = longlen * longlen - shortlen * shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0 + x1) / 2; + float y01 = (y0 + y1) / 2; + float x12 = (x1 + x2) / 2; + float y12 = (y1 + y2) / 2; + float x23 = (x2 + x3) / 2; + float y23 = (y2 + y3) / 2; + + float xa = (x01 + x12) / 2; + float ya = (y01 + y12) / 2; + float xb = (x12 + x23) / 2; + float yb = (y12 + y23) / 2; + + float mx = (xa + xb) / 2; + float my = (ya + yb) / 2; + + stbtt__tesselate_cubic(points, num_points, x0, y0, x01, y01, xa, ya, mx, my, + objspace_flatness_squared, n + 1); + stbtt__tesselate_cubic(points, num_points, mx, my, xb, yb, x23, y23, x3, y3, + objspace_flatness_squared, n + 1); + } else { + stbtt__add_point(points, *num_points, x3, y3); + *num_points = *num_points + 1; + } +} + +// returns number of contours +static stbtt__point* stbtt_FlattenCurves(stbtt_vertex* vertices, int num_verts, + float objspace_flatness, + int** contour_lengths, + int* num_contours, void* userdata) { + stbtt__point* points = 0; + int num_points = 0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i, n = 0, start = 0, pass; + + // count how many "moves" there are to get the contour count + for (i = 0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = + (int*)STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass = 0; pass < 2; ++pass) { + float x = 0, y = 0; + if (pass == 1) { + points = + (stbtt__point*)STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n = -1; + for (i = 0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x, y, vertices[i].cx, + vertices[i].cy, vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x, y, vertices[i].cx, + vertices[i].cy, vertices[i].cx1, + vertices[i].cy1, vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap* result, float flatness_in_pixels, + stbtt_vertex* vertices, int num_verts, + float scale_x, float scale_y, float shift_x, + float shift_y, int x_off, int y_off, int invert, + void* userdata) { + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int* winding_lengths = NULL; + stbtt__point* windings = + stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, + &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, + scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char* bitmap, void* userdata) { + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char* stbtt_GetGlyphBitmapSubpixel( + const stbtt_fontinfo* info, float scale_x, float scale_y, float shift_x, + float shift_y, int glyph, int* width, int* height, int* xoff, int* yoff) { + int ix0, iy0, ix1, iy1; + stbtt__bitmap gbm; + stbtt_vertex* vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, + shift_y, &ix0, &iy0, &ix1, &iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff) *xoff = ix0; + if (yoff) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char*)STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, + shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char* stbtt_GetGlyphBitmap(const stbtt_fontinfo* info, + float scale_x, float scale_y, + int glyph, int* width, + int* height, int* xoff, + int* yoff) { + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, + width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo* info, + unsigned char* output, int out_w, + int out_h, int out_stride, + float scale_x, float scale_y, + float shift_x, float shift_y, + int glyph) { + int ix0, iy0; + stbtt_vertex* vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, + shift_y, &ix0, &iy0, 0, 0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, + shift_y, ix0, iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo* info, + unsigned char* output, int out_w, + int out_h, int out_stride, float scale_x, + float scale_y, int glyph) { + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, + scale_y, 0.0f, 0.0f, glyph); +} + +STBTT_DEF unsigned char* stbtt_GetCodepointBitmapSubpixel( + const stbtt_fontinfo* info, float scale_x, float scale_y, float shift_x, + float shift_y, int codepoint, int* width, int* height, int* xoff, + int* yoff) { + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, shift_x, shift_y, + stbtt_FindGlyphIndex(info, codepoint), + width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter( + const stbtt_fontinfo* info, unsigned char* output, int out_w, int out_h, + int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, + int oversample_x, int oversample_y, float* sub_x, float* sub_y, + int codepoint) { + stbtt_MakeGlyphBitmapSubpixelPrefilter( + info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, + shift_y, oversample_x, oversample_y, sub_x, sub_y, + stbtt_FindGlyphIndex(info, codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo* info, + unsigned char* output, + int out_w, int out_h, + int out_stride, float scale_x, + float scale_y, float shift_x, + float shift_y, int codepoint) { + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, + scale_y, shift_x, shift_y, + stbtt_FindGlyphIndex(info, codepoint)); +} + +STBTT_DEF unsigned char* stbtt_GetCodepointBitmap(const stbtt_fontinfo* info, + float scale_x, float scale_y, + int codepoint, int* width, + int* height, int* xoff, + int* yoff) { + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, + codepoint, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo* info, + unsigned char* output, int out_w, + int out_h, int out_stride, + float scale_x, float scale_y, + int codepoint) { + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, + scale_x, scale_y, 0.0f, 0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal( + unsigned char* data, + int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char* pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar* chardata) { + float scale; + int x, y, bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) return -1; + STBTT_memset(pixels, 0, pw * ph); // background of 0 around pixels + x = y = 1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i = 0; i < num_chars; ++i) { + int advance, lsb, x0, y0, x1, y1, gw, gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale, scale, &x0, &y0, &x1, &y1); + gw = x1 - x0; + gh = y1 - y0; + if (x + gw + 1 >= pw) y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= + ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x + gw < pw); + STBTT_assert(y + gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels + x + y * pw, gw, gh, pw, scale, scale, g); + chardata[i].x0 = (stbtt_int16)x; + chardata[i].y0 = (stbtt_int16)y; + chardata[i].x1 = (stbtt_int16)(x + gw); + chardata[i].y1 = (stbtt_int16)(y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float)x0; + chardata[i].yoff = (float)y0; + x = x + gw + 1; + if (y + gh + 1 > bottom_y) bottom_y = y + gh + 1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar* chardata, int pw, + int ph, int char_index, float* xpos, + float* ypos, stbtt_aligned_quad* q, + int opengl_fillrule) { + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar* b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct { + int width, height; + int x, y, bottom_y; +} stbrp_context; + +typedef struct { + unsigned char x; +} stbrp_node; + +struct stbrp_rect { + stbrp_coord x, y; + int id, w, h, was_packed; +}; + +static void stbrp_init_target(stbrp_context* con, int pw, int ph, + stbrp_node* nodes, int num_nodes) { + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context* con, stbrp_rect* rects, + int num_rects) { + int i; + for (i = 0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for (; i < num_rects; ++i) rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context* spc, unsigned char* pixels, + int pw, int ph, int stride_in_bytes, int padding, + void* alloc_context) { + stbrp_context* context = + (stbrp_context*)STBTT_malloc(sizeof(*context), alloc_context); + int num_nodes = pw - padding; + stbrp_node* nodes = + (stbrp_node*)STBTT_malloc(sizeof(*nodes) * num_nodes, alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes, alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw - padding, ph - padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw * ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd(stbtt_pack_context* spc) { + STBTT_free(spc->nodes, spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context* spc, + unsigned int h_oversample, + unsigned int v_oversample) { + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context* spc, + int skip) { + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE - 1) + +static void stbtt__h_prefilter(unsigned char* pixels, int w, int h, + int stride_in_bytes, unsigned int kernel_width) { + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset( + buffer, 0, + STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j = 0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out + // the divide + switch (kernel_width) { + case 2: + for (i = 0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char)(total / 2); + } + break; + case 3: + for (i = 0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char)(total / 3); + } + break; + case 4: + for (i = 0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char)(total / 4); + } + break; + case 5: + for (i = 0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char)(total / 5); + } + break; + default: + for (i = 0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char)(total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char)(total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char* pixels, int w, int h, + int stride_in_bytes, unsigned int kernel_width) { + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset( + buffer, 0, + STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j = 0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out + // the divide + switch (kernel_width) { + case 2: + for (i = 0; i <= safe_h; ++i) { + total += pixels[i * stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = + pixels[i * stride_in_bytes]; + pixels[i * stride_in_bytes] = (unsigned char)(total / 2); + } + break; + case 3: + for (i = 0; i <= safe_h; ++i) { + total += pixels[i * stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = + pixels[i * stride_in_bytes]; + pixels[i * stride_in_bytes] = (unsigned char)(total / 3); + } + break; + case 4: + for (i = 0; i <= safe_h; ++i) { + total += pixels[i * stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = + pixels[i * stride_in_bytes]; + pixels[i * stride_in_bytes] = (unsigned char)(total / 4); + } + break; + case 5: + for (i = 0; i <= safe_h; ++i) { + total += pixels[i * stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = + pixels[i * stride_in_bytes]; + pixels[i * stride_in_bytes] = (unsigned char)(total / 5); + } + break; + default: + for (i = 0; i <= safe_h; ++i) { + total += pixels[i * stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i + kernel_width) & STBTT__OVER_MASK] = + pixels[i * stride_in_bytes]; + pixels[i * stride_in_bytes] = (unsigned char)(total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i * stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i * stride_in_bytes] = (unsigned char)(total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) { + if (!oversample) return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given +// ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context* spc, + const stbtt_fontinfo* info, + stbtt_pack_range* ranges, + int num_ranges, + stbrp_rect* rects) { + int i, j, k; + int missing_glyph_added = 0; + + k = 0; + for (i = 0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) + : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char)spc->h_oversample; + ranges[i].v_oversample = (unsigned char)spc->v_oversample; + for (j = 0; j < ranges[i].num_chars; ++j) { + int x0, y0, x1, y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL + ? ranges[i].first_unicode_codepoint_in_range + j + : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale * spc->h_oversample, + scale * spc->v_oversample, 0, 0, &x0, + &y0, &x1, &y1); + rects[k].w = + (stbrp_coord)(x1 - x0 + spc->padding + spc->h_oversample - 1); + rects[k].h = + (stbrp_coord)(y1 - y0 + spc->padding + spc->v_oversample - 1); + if (glyph == 0) missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter( + const stbtt_fontinfo* info, unsigned char* output, int out_w, int out_h, + int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, + int prefilter_x, int prefilter_y, float* sub_x, float* sub_y, int glyph) { + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), out_stride, scale_x, + scale_y, shift_x, shift_y, glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given +// ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context* spc, + const stbtt_fontinfo* info, + stbtt_pack_range* ranges, + int num_ranges, + stbrp_rect* rects) { + int i, j, k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i = 0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) + : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h, recip_v, sub_x, sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j = 0; j < ranges[i].num_chars; ++j) { + stbrp_rect* r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar* bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0, y0, x1, y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL + ? ranges[i].first_unicode_codepoint_in_range + j + : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord)spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, scale * spc->h_oversample, + scale * spc->v_oversample, &x0, &y0, &x1, &y1); + stbtt_MakeGlyphBitmapSubpixel( + info, spc->pixels + r->x + r->y * spc->stride_in_bytes, + r->w - spc->h_oversample + 1, r->h - spc->v_oversample + 1, + spc->stride_in_bytes, scale * spc->h_oversample, + scale * spc->v_oversample, 0, 0, glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y * spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y * spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16)r->x; + bc->y0 = (stbtt_int16)r->y; + bc->x1 = (stbtt_int16)(r->x + r->w); + bc->y1 = (stbtt_int16)(r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float)x0 * recip_h + sub_x; + bc->yoff = (float)y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && + missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = + ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context* spc, + stbrp_rect* rects, int num_rects) { + stbrp_pack_rects((stbrp_context*)spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context* spc, + const unsigned char* fontdata, + int font_index, stbtt_pack_range* ranges, + int num_ranges) { + stbtt_fontinfo info; + int i, j, n, return_value; // [DEAR IMGUI] removed = 1; + // stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect* rects; + + // flag all characters as NOT packed + for (i = 0; i < num_ranges; ++i) + for (j = 0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i = 0; i < num_ranges; ++i) n += ranges[i].num_chars; + + rects = (stbrp_rect*)STBTT_malloc(sizeof(*rects) * n, + spc->user_allocator_context); + if (rects == NULL) return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, + stbtt_GetFontOffsetForIndex(fontdata, font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, + num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context* spc, + const unsigned char* fontdata, int font_index, + float font_size, + int first_unicode_codepoint_in_range, + int num_chars_in_range, + stbtt_packedchar* chardata_for_range) { + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char* fontdata, + int index, float size, float* ascent, + float* descent, float* lineGap) { + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) + : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float)i_ascent * scale; + *descent = (float)i_descent * scale; + *lineGap = (float)i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar* chardata, int pw, + int ph, int char_index, float* xpos, + float* ypos, stbtt_aligned_quad* q, + int align_to_integer) { + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar* b = chardata + char_index; + + if (align_to_integer) { + float x = (float)STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float)STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a, b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a, b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], + float q1[2], float q2[2], + float hits[2][2]) { + float q0perp = q0[1] * ray[0] - q0[0] * ray[1]; + float q1perp = q1[1] * ray[0] - q1[0] * ray[1]; + float q2perp = q2[1] * ray[0] - q2[0] * ray[1]; + float roperp = orig[1] * ray[0] - orig[0] * ray[1]; + + float a = q0perp - 2 * q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b * b - a * c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float)STBTT_sqrt(discr); + s0 = (b + d) * rcpna; + s1 = (b - d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0] * ray[0] + ray[1] * ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0] * rayn_x + q0[1] * rayn_y; + float q1d = q1[0] * rayn_x + q1[1] * rayn_y; + float q2d = q2[0] * rayn_x + q2[1] * rayn_y; + float rod = orig[0] * rayn_x + orig[1] * rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0 * (2.0f - 2.0f * s0) * q10d + s0 * s0 * q20d; + hits[0][1] = a * s0 + b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1 * (2.0f - 2.0f * s1) * q10d + s1 * s1 * q20d; + hits[1][1] = a * s1 + b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float* a, float* b) { return (a[0] == b[0] && a[1] == b[1]); } + +static int stbtt__compute_crossings_x(float x, float y, int nverts, + stbtt_vertex* verts) { + int i; + float orig[2], ray[2] = {1, 0}; + float y_frac; + int winding = 0; + + // make sure y never passes through a vertex of the shape + y_frac = (float)STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + + orig[0] = x; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i = 0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int)verts[i - 1].x, y0 = (int)verts[i - 1].y; + int x1 = (int)verts[i].x, y1 = (int)verts[i].y; + if (y > STBTT_min(y0, y1) && y < STBTT_max(y0, y1) && + x > STBTT_min(x0, x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1 - x0) + x0; + if (x_inter < x) winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int)verts[i - 1].x, y0 = (int)verts[i - 1].y; + int x1 = (int)verts[i].cx, y1 = (int)verts[i].cy; + int x2 = (int)verts[i].x, y2 = (int)verts[i].y; + int ax = STBTT_min(x0, STBTT_min(x1, x2)), + ay = STBTT_min(y0, STBTT_min(y1, y2)); + int by = STBTT_max(y0, STBTT_max(y1, y2)); + if (y > ay && y < by && x > ax) { + float q0[2], q1[2], q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0, q1) || equal(q1, q2)) { + x0 = (int)verts[i - 1].x; + y0 = (int)verts[i - 1].y; + x1 = (int)verts[i].x; + y1 = (int)verts[i].y; + if (y > STBTT_min(y0, y1) && y < STBTT_max(y0, y1) && + x > STBTT_min(x0, x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1 - x0) + x0; + if (x_inter < x) winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = + stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot(float x) { + if (x < 0) + return -(float)STBTT_pow(-x, 1.0f / 3.0f); + else + return (float)STBTT_pow(x, 1.0f / 3.0f); +} + +// x^3 + a*x^2 + b*x + c = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) { + float s = -a / 3; + float p = b - a * a / 3; + float q = a * (2 * a * a - 9 * b) / 27 + c; + float p3 = p * p * p; + float d = q * q + 4 * p3 / 27; + if (d >= 0) { + float z = (float)STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float)STBTT_sqrt(-p / 3); + float v = (float)STBTT_acos(-STBTT_sqrt(-27 / p3) * q / 2) / + 3; // p3 must be negative, since d is negative + float m = (float)STBTT_cos(v); + float n = (float)STBTT_cos(v - 3.141592 / 2) * 1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + // STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these + // asserts may not be safe at all scales, though they're in bezier t + // parameter units so maybe? STBTT_assert( + // STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); STBTT_assert( + // STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char* stbtt_GetGlyphSDF(const stbtt_fontinfo* info, + float scale, int glyph, int padding, + unsigned char onedge_value, + float pixel_dist_scale, int* width, + int* height, int* xoff, int* yoff) { + float scale_x = scale, scale_y = scale; + int ix0, iy0, ix1, iy1; + int w, h; + unsigned char* data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f, 0.0f, &ix0, + &iy0, &ix1, &iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width) *width = w; + if (height) *height = h; + if (xoff) *xoff = ix0; + if (yoff) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x, y, i, j; + float* precompute; + stbtt_vertex* verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char*)STBTT_malloc(w * h, info->userdata); + precompute = + (float*)STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i = 0, j = num_verts - 1; i < num_verts; j = i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x * scale_x, y0 = verts[i].y * scale_y; + float x1 = verts[j].x * scale_x, y1 = verts[j].y * scale_y; + float dist = + (float)STBTT_sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x * scale_x, y2 = verts[j].y * scale_y; + float x1 = verts[i].cx * scale_x, y1 = verts[i].cy * scale_y; + float x0 = verts[i].x * scale_x, y0 = verts[i].y * scale_y; + float bx = x0 - 2 * x1 + x2, by = y0 - 2 * y1 + y2; + float len2 = bx * bx + by * by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx * bx + by * by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y = iy0; y < iy1; ++y) { + for (x = ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float)x + 0.5f; + float sy = (float)y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x( + x_gspace, y_gspace, num_verts, + verts); // @OPTIMIZE: this could just be a rasterization, but needs + // to be line vs. non-tesselated curves so a new path + + for (i = 0; i < num_verts; ++i) { + float x0 = verts[i].x * scale_x, y0 = verts[i].y * scale_y; + + if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { + float x1 = verts[i - 1].x * scale_x, y1 = verts[i - 1].y * scale_y; + + float dist, dist2 = (x0 - sx) * (x0 - sx) + (y0 - sy) * (y0 - sy); + if (dist2 < min_dist * min_dist) + min_dist = (float)STBTT_sqrt(dist2); + + // coarse culling against bbox + // if (sx > STBTT_min(x0,x1)-min_dist && sx < + // STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < + // STBTT_max(y0,y1)+min_dist) + dist = (float)STBTT_fabs((x1 - x0) * (y0 - sy) - + (y1 - y0) * (x0 - sx)) * + precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1 - x0, dy = y1 - y0; + float px = x0 - sx, py = y0 - sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + + // t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy derivative: 2*px*dx + + // 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px * dx + py * dy) / (dx * dx + dy * dy); + if (t >= 0.0f && t <= 1.0f) min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i - 1].x * scale_x, y2 = verts[i - 1].y * scale_y; + float x1 = verts[i].cx * scale_x, y1 = verts[i].cy * scale_y; + float box_x0 = STBTT_min(STBTT_min(x0, x1), x2); + float box_y0 = STBTT_min(STBTT_min(y0, y1), y2); + float box_x1 = STBTT_max(STBTT_max(x0, x1), x2); + float box_y1 = STBTT_max(STBTT_max(y0, y1), y2); + // coarse culling against bbox to avoid computing cubic + // unnecessarily + if (sx > box_x0 - min_dist && sx < box_x1 + min_dist && + sy > box_y0 - min_dist && sy < box_y1 + min_dist) { + int num = 0; + float ax = x1 - x0, ay = y1 - y0; + float bx = x0 - 2 * x1 + x2, by = y0 - 2 * y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3] = {0.f, 0.f, 0.f}; + float px, py, t, it, dist2; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use + // quadratic formula + float a = 3 * (ax * bx + ay * by); + float b = 2 * (ax * ax + ay * ay) + (mx * bx + my * by); + float c = mx * ax + my * ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c / b; + } + } else { + float discriminant = b * b - 4 * a * c; + if (discriminant < 0) + num = 0; + else { + float root = (float)STBTT_sqrt(discriminant); + res[0] = (-b - root) / (2 * a); + res[1] = (-b + root) / (2 * a); + num = 2; // don't bother distinguishing 1-solution case, as + // code below will still work + } + } + } else { + float b = 3 * (ax * bx + ay * by) * + a_inv; // could precompute this as it doesn't depend + // on sample point + float c = + (2 * (ax * ax + ay * ay) + (mx * bx + my * by)) * a_inv; + float d = (mx * ax + my * ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + dist2 = (x0 - sx) * (x0 - sx) + (y0 - sy) * (y0 - sy); + if (dist2 < min_dist * min_dist) + min_dist = (float)STBTT_sqrt(dist2); + + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it * it * x0 + 2 * t * it * x1 + t * t * x2; + py = it * it * y0 + 2 * t * it * y1 + t * t * y2; + dist2 = (px - sx) * (px - sx) + (py - sy) * (py - sy); + if (dist2 < min_dist * min_dist) + min_dist = (float)STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it * it * x0 + 2 * t * it * x1 + t * t * x2; + py = it * it * y0 + 2 * t * it * y1 + t * t * y2; + dist2 = (px - sx) * (px - sx) + (py - sy) * (py - sy); + if (dist2 < min_dist * min_dist) + min_dist = (float)STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it * it * x0 + 2 * t * it * x1 + t * t * x2; + py = it * it * y0 + 2 * t * it * y1 + t * t * y2; + dist2 = (px - sx) * (px - sx) + (py - sy) * (py - sy); + if (dist2 < min_dist * min_dist) + min_dist = (float)STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y - iy0) * w + (x - ix0)] = (unsigned char)val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char* stbtt_GetCodepointSDF( + const stbtt_fontinfo* info, float scale, int codepoint, int padding, + unsigned char onedge_value, float pixel_dist_scale, int* width, int* height, + int* xoff, int* yoff) { + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), + padding, onedge_value, pixel_dist_scale, width, + height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char* bitmap, void* userdata) { + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so +// return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix( + stbtt_uint8* s1, stbtt_int32 len1, stbtt_uint8* s2, stbtt_int32 len2) { + stbtt_int32 i = 0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0] * 256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i + 1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2] * 256 + s2[3]; + if (i + 3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c)&0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i + 2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch)&0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char* s1, int len1, + char* s2, int len2) { + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix( + (stbtt_uint8*)s1, len1, (stbtt_uint8*)s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte +// encodings will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to +// compare +STBTT_DEF const char* stbtt_GetFontNameString(const stbtt_fontinfo* font, + int* length, int platformID, + int encodingID, int languageID, + int nameID) { + stbtt_int32 i, count, stringOffset; + stbtt_uint8* fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc + nm + 2); + stringOffset = nm + ttUSHORT(fc + nm + 4); + for (i = 0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc + loc + 0) && + encodingID == ttUSHORT(fc + loc + 2) && + languageID == ttUSHORT(fc + loc + 4) && + nameID == ttUSHORT(fc + loc + 6)) { + *length = ttUSHORT(fc + loc + 8); + return (const char*)(fc + stringOffset + ttUSHORT(fc + loc + 10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8* fc, stbtt_uint32 nm, stbtt_uint8* name, + stbtt_int32 nlen, stbtt_int32 target_id, + stbtt_int32 next_id) { + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc + nm + 2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc + nm + 4); + + for (i = 0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc + loc + 6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc + loc + 0), + encoding = ttUSHORT(fc + loc + 2), + language = ttUSHORT(fc + loc + 4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || + (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc + loc + 8); + stbtt_int32 off = ttUSHORT(fc + loc + 10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix( + name, nlen, fc + stringOffset + off, slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & + // language + if (i + 1 < count && ttUSHORT(fc + loc + 12 + 6) == next_id && + ttUSHORT(fc + loc + 12) == platform && + ttUSHORT(fc + loc + 12 + 2) == encoding && + ttUSHORT(fc + loc + 12 + 4) == language) { + slen = ttUSHORT(fc + loc + 12 + 8); + off = ttUSHORT(fc + loc + 12 + 10); + if (slen == 0) { + if (matchlen == nlen) return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal( + (char*)(name + matchlen), nlen - matchlen, + (char*)(fc + stringOffset + off), slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8* fc, stbtt_uint32 offset, + stbtt_uint8* name, stbtt_int32 flags) { + stbtt_int32 nlen = (stbtt_int32)STBTT_strlen((char*)name); + stbtt_uint32 nm, hd; + if (!stbtt__isfont(fc + offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc + hd + 44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore + // the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char* font_collection, + char* name_utf8, stbtt_int32 flags) { + stbtt_int32 i; + for (i = 0;; ++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8*)font_collection, off, + (stbtt_uint8*)name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char* data, int offset, + float pixel_height, unsigned char* pixels, + int pw, int ph, int first_char, + int num_chars, stbtt_bakedchar* chardata) { + return stbtt_BakeFontBitmap_internal((unsigned char*)data, offset, + pixel_height, pixels, pw, ph, first_char, + num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char* data, + int index) { + return stbtt_GetFontOffsetForIndex_internal((unsigned char*)data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char* data) { + return stbtt_GetNumberOfFonts_internal((unsigned char*)data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo* info, const unsigned char* data, + int offset) { + return stbtt_InitFont_internal(info, (unsigned char*)data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char* fontdata, + const char* name, int flags) { + return stbtt_FindMatchingFont_internal((unsigned char*)fontdata, (char*)name, + flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char* s1, int len1, + const char* s2, int len2) { + return stbtt_CompareUTF8toUTF16_bigendian_internal((char*)s1, len1, (char*)s2, + len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + +// FULL VERSION HISTORY +// +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but +// only kern not GPOS) 1.22 (2019-08-11) minimize missing-glyph duplication; +// fix kerning if both 'GPOS' and 'kern' are defined 1.21 (2019-02-25) fix +// warning 1.20 (2019-02-07) PackFontRange skips missing codepoints; +// GetScaleFontVMetrics() 1.19 (2018-02-11) OpenType GPOS kerning (horizontal +// only), STBTT_fmod 1.18 (2018-01-29) add missing function 1.17 (2017-07-23) +// make more arguments const; doc fix 1.16 (2017-07-12) SDF support 1.15 +// (2017-03-03) make more arguments const 1.14 (2017-01-16) num-fonts-in-TTC +// function 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts 1.12 +// (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata +// for PackFontRanges 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for +// vertical & horizontal edges 1.07 (2015-08-01) allow PackFontRanges to +// accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate +// phases; fix stbtt_GetFontOFfsetForIndex (never worked for +// non-0 input?); fixed an assert() bug in the new +// rasterizer replace assert() with STBTT_assert() in new +// rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on +// test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various +// fixes 1.02 (2014-12-10) fix various warnings & compile issues w/ +// stb_rect_pack, C++ 1.01 (2014-12-08) fix subpixel position when +// oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/customchar-ui/main.cpp b/customchar-ui/main.cpp new file mode 100644 index 0000000..06cb97d --- /dev/null +++ b/customchar-ui/main.cpp @@ -0,0 +1,264 @@ +#include + +#include // TODO: Remove later when not needed + +#include +#include "imgui.h" +#include "imgui_impl_glfw.h" +#include "imgui_impl_opengl3.h" +#if defined(IMGUI_IMPL_OPENGL_ES2) +#include +#endif +#if defined(__APPLE__) +#define GL_SILENCE_DEPRECATION // Silence deprecation warnings on macos +#endif +#include // Will drag system OpenGL headers + +#include "chat_history.h" +#include "chat_message.h" + +// [Win32] Our example includes a copy of glfw3.lib pre-compiled with VS2010 to +// maximize ease of testing and compatibility with old VS compilers. To link +// with VS2010-era libraries, VS2015+ requires linking with +// legacy_stdio_definitions.lib, which we do using this pragma. Your own project +// should not be affected, as you are likely to link with a newer binary of GLFW +// that is adequate for your version of Visual Studio. +#if defined(_MSC_VER) && (_MSC_VER >= 1900) && \ + !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) +#pragma comment(lib, "legacy_stdio_definitions") +#endif + +// Global variables +// synchronize +// everytime user sends message, IMGUI sets global variable to message +// signal client server ... lock/unlock mutex +constexpr int TEXT_MESSAGE_SIZE = 1024 * 8; +constexpr int INIT_WINDOW_WIDTH = 720; +constexpr int INIT_WINDOW_HEIGHT = 400; + +static void glfw_error_callback(int error, const char* description) { + fprintf(stderr, "Glfw Error %d: %s\n", error, description); +} + +/** + * @brief Function to handle sending the chat message + * + * @param text Char array of text to be sent + * @param history The chatlog as a shared_ptr + * @return true if successfully sent + */ +bool my_turn = true; +bool handleSend(char* text, std::shared_ptr history) { + // TODO: Probably want to only add to chat history once the message has been + // sent. Also don't hardcode "Me" as the sender + if (text[0] != '\0') + history->add_message(text, my_turn ? "Me" : "CustomChar"); + my_turn = !my_turn; + + // Clear text input area + strncpy(text, "", TEXT_MESSAGE_SIZE); + + // If successfully sent return true + return true; +} + +/** + * @brief Main ImGUI loop + */ +void runImgui(std::shared_ptr history) { + // Setup window + glfwSetErrorCallback(glfw_error_callback); + if (!glfwInit()) return; + +// Decide GL+GLSL versions +#if defined(IMGUI_IMPL_OPENGL_ES2) + // GL ES 2.0 + GLSL 100 + const char* glsl_version = "#version 100"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); +#elif defined(__APPLE__) + // GL 3.2 + GLSL 150 + const char* glsl_version = "#version 150"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac +#else + // GL 3.0 + GLSL 130 + const char* glsl_version = "#version 130"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + // glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ + // only glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only +#endif + + // Create window with graphics context + GLFWwindow* window = glfwCreateWindow(INIT_WINDOW_WIDTH, INIT_WINDOW_HEIGHT, + "CustomChar", NULL, NULL); + if (window == NULL) return; + glfwMakeContextCurrent(window); + glfwSwapInterval(1); // Enable vsync + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + (void)io; + // io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable + // Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // + // Enable Gamepad Controls + + // Setup Dear ImGui style + // ImGui::StyleColorsDark(); + ImGui::StyleColorsDark(); + + // Setup Platform/Renderer backends + ImGui_ImplGlfw_InitForOpenGL(window, true); + ImGui_ImplOpenGL3_Init(glsl_version); + + // Load Fonts + // - If no fonts are loaded, dear imgui will use the default font. You can + // also load multiple fonts and use ImGui::PushFont()/PopFont() to select + // them. + // - AddFontFromFileTTF() will return the ImFont* so you can store it if you + // need to select the font among multiple. + // - If the file cannot be loaded, the function will return NULL. Please + // handle those errors in your application (e.g. use an assertion, or + // display an error and quit). + // - The fonts will be rasterized at a given size (w/ oversampling) and + // stored into a texture when calling + // ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame + // below will call. + // - Read 'docs/FONTS.md' for more instructions and details. + // - Remember that in C/C++ if you want to include a backslash \ in a string + // literal you need to write a double backslash \\ ! + // io.Fonts->AddFontDefault(); + // io.Fonts->AddFontFromFileTTF("BaiJamjuree-Regular.ttf", 16.0f); + // io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f); + // io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); + // io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f); + // ImFont* font = + // io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, + // NULL, io.Fonts->GetGlyphRangesJapanese()); IM_ASSERT(font != NULL); + + // Our state + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + bool justSent = true; + + // Initial text + char text[TEXT_MESSAGE_SIZE] = ""; + + // Main loop + while (!glfwWindowShouldClose(window)) { + // Poll and handle events (inputs, window resize, etc.) + // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard + // flags to tell if dear imgui wants to use your inputs. + // - When io.WantCaptureMouse is true, do not dispatch mouse input + // data to your main application, or clear/overwrite your copy of + // the mouse data. + // - When io.WantCaptureKeyboard is true, do not dispatch keyboard + // input data to your main application, or clear/overwrite your copy + // of the keyboard data. Generally you may always pass all inputs to + // dear imgui, and hide them from your application based on those + // two flags + glfwPollEvents(); + + // Start the Dear ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + /** + * @brief Shows connection window if not connected, otherwise show + * basic chat window + */ + // Is connected + int TEXTBOX_HEIGHT = ImGui::GetTextLineHeight() * 4; + + // Make window take up full system window + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(io.DisplaySize); + + // Create window + ImGui::Begin("CustomChar", NULL, + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); + + // Child window scrollable area + ImGuiWindowFlags window_flags = ImGuiWindowFlags_None; + + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); + ImGui::BeginChild( + "ChildR", + ImVec2(0, ImGui::GetContentRegionAvail().y - TEXTBOX_HEIGHT - 20), true, + window_flags); + + // TODO: Format chat history + ImGui::Dummy(ImVec2(0, ImGui::GetContentRegionAvail().y)); + + for (ChatMessage message : history->get_char_history()) { + ImGui::Spacing(); + // ImGui::TextWrapped("%s", message.get_time().c_str()); + ImGui::TextWrapped("> %s: %s", message.get_sender().c_str(), + message.get_message().c_str()); + } + if (history->has_new_message() || justSent) { + ImGui::SetScrollHereY(1.0f); + } + + ImGui::EndChild(); + ImGui::PopStyleVar(); + + // Text input area flags + ImGuiInputTextFlags input_flags = ImGuiInputTextFlags_EnterReturnsTrue | + ImGuiInputTextFlags_CtrlEnterForNewLine | + ImGuiInputTextFlags_AllowTabInput | + ImGuiInputTextFlags_CtrlEnterForNewLine; + + // Refocus text area if text was just sent + if (justSent) { + ImGui::SetKeyboardFocusHere(); + justSent = false; + } + + // Create text area and send button + if (ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), + ImVec2(-FLT_MIN, TEXTBOX_HEIGHT), + input_flags)) { + justSent = handleSend(text, history); + }; + + ImGui::End(); + break; + + // Rendering + ImGui::Render(); + int display_w, display_h; + glfwGetFramebufferSize(window, &display_w, &display_h); + glViewport(0, 0, display_w, display_h); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, + clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + glfwSwapBuffers(window); + } + + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + glfwDestroyWindow(window); + glfwTerminate(); +} + +int main() { + // Initialize chat history + std::shared_ptr history = std::make_shared(); + + // Main GUI loop + runImgui(history); + + return 0; +} diff --git a/docs/customchar.png b/docs/customchar.png new file mode 100644 index 0000000..552d8d8 Binary files /dev/null and b/docs/customchar.png differ