diff --git a/README.md b/README.md index aedb21d..ef2dbdd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# zgui v0.2.0 - dear imgui bindings +# zgui v0.5.0 - dear imgui bindings Easy to use, hand-crafted API with default arguments, named parameters and Zig style text formatting. [Here](https://github.com/michal-z/zig-gamedev/tree/main/samples/minimal_zgpu_zgui) is a simple sample application, and [here](https://github.com/michal-z/zig-gamedev/tree/main/samples/gui_test_wgpu) is a full one. @@ -7,14 +7,18 @@ Easy to use, hand-crafted API with default arguments, named parameters and Zig s * Most public dear imgui API exposed * All memory allocations go through user provided Zig allocator * [DrawList API](#drawlist-api) for vector graphics, text rendering and custom widgets -* [Plot API](#plot-api) for advanced data visualizations * [Test engine API](#test-engine-api) for automatic testing +* [Plot API](#plot-api) for advanced data visualizations +* [Gizmo API](#gizmo-api) for gizmo +* [Node editor API](#node-editor-api) for node based stuff ## Versions -* [ImGui](https://github.com/ocornut/imgui/tree/v1.90.4-docking) `1.90.4-docking` -* [ImGui test engine](https://github.com/ocornut/imgui_test_engine/tree/v1.90.4) `1.90.4` +* [ImGui](https://github.com/ocornut/imgui/tree/v1.91.0-docking) `1.91.0-docking` +* [ImGui test engine](https://github.com/ocornut/imgui_test_engine/tree/v1.91.0) `1.91.0` * [ImPlot](https://github.com/epezent/implot) `O.17` +* [ImGuizmo](https://github.com/CedricGuillemet/ImGuizmo) `1.89 WIP` +* [ImGuiNodeEditor](https://github.com/thedmd/imgui-node-editor/tree/v0.9.3) `O.9.3` ## Getting started @@ -139,21 +143,6 @@ draw_list.addPolyline( .{ .col = 0xff_00_aa_11, .thickness = 7 }, ); ``` -### Plot API -```zig -if (zgui.plot.beginPlot("Line Plot", .{ .h = -1.0 })) { - zgui.plot.setupAxis(.x1, .{ .label = "xaxis" }); - zgui.plot.setupAxisLimits(.x1, .{ .min = 0, .max = 5 }); - zgui.plot.setupLegend(.{ .south = true, .west = true }, .{}); - zgui.plot.setupFinish(); - zgui.plot.plotLineValues("y data", i32, .{ .v = &.{ 0, 1, 0, 1, 0, 1 } }); - zgui.plot.plotLine("xy data", f32, .{ - .xv = &.{ 0.1, 0.2, 0.5, 2.5 }, - .yv = &.{ 0.1, 0.3, 0.5, 0.9 }, - }); - zgui.plot.endPlot(); -} -``` ### Test Engine API Zig wraper for [ImGUI test engine](https://github.com/ocornut/imgui_test_engine). @@ -199,3 +188,60 @@ fn registerTests() void { ); } ``` + +### Plot API +```zig +if (zgui.plot.beginPlot("Line Plot", .{ .h = -1.0 })) { + zgui.plot.setupAxis(.x1, .{ .label = "xaxis" }); + zgui.plot.setupAxisLimits(.x1, .{ .min = 0, .max = 5 }); + zgui.plot.setupLegend(.{ .south = true, .west = true }, .{}); + zgui.plot.setupFinish(); + zgui.plot.plotLineValues("y data", i32, .{ .v = &.{ 0, 1, 0, 1, 0, 1 } }); + zgui.plot.plotLine("xy data", f32, .{ + .xv = &.{ 0.1, 0.2, 0.5, 2.5 }, + .yv = &.{ 0.1, 0.3, 0.5, 0.9 }, + }); + zgui.plot.endPlot(); +} +``` + +### Gizmo API + +Zig wraper for [ImGuizmo](https://github.com/CedricGuillemet/ImGuizmo). + + +### Node editor API + +Zig wraper for [ImGuiNodeEditor](https://github.com/thedmd/imgui-node-editor). + +```zig +var node_editor = zgui.node_editor.EditorContext.create(.{ .enable_smooth_zoom = true }), + +zgui.node_editor.setCurrentEditor(node_editor); +defer zgui.node_editor.setCurrentEditor(null); +{ + zgui.node_editor.begin("NodeEditor", .{ 0, 0 }); + defer zgui.node_editor.end(); + + zgui.node_editor.beginNode(1); + { + defer zgui.node_editor.endNode(); + + zgui.textUnformatted("Node A"); + + zgui.node_editor.beginPin(1, .input); + { + defer zgui.node_editor.endPin(); + zgui.textUnformatted("-> In"); + } + + zgui.sameLine(.{}); + + zgui.node_editor.beginPin(2, .output); + { + defer zgui.node_editor.endPin(); + zgui.textUnformatted("Out ->"); + } + } +} +``` diff --git a/build.zig b/build.zig index b401699..4db50da 100644 --- a/build.zig +++ b/build.zig @@ -25,6 +25,16 @@ pub fn build(b: *std.Build) void { "with_implot", "Build with bundled implot source", ) orelse true, + .with_gizmo = b.option( + bool, + "with_gizmo", + "Build with bundled ImGuizmo tool", + ) orelse true, + .with_node_editor = b.option( + bool, + "with_node_editor", + "Build with bundled ImGui node editor", + ) orelse true, .with_te = b.option( bool, "with_te", @@ -51,7 +61,7 @@ pub fn build(b: *std.Build) void { }, }); - const cflags = &.{"-fno-sanitize=undefined"}; + const cflags = &.{ "-fno-sanitize=undefined", "-Wno-elaborated-enum-base" }; const imgui = if (options.shared) blk: { const lib = b.addSharedLibrary(.{ @@ -102,26 +112,61 @@ pub fn build(b: *std.Build) void { .flags = cflags, }); - if (options.with_implot) { - imgui.defineCMacro("ZGUI_IMPLOT", "1"); - imgui.addCSourceFiles(.{ - .files = &.{ - "libs/imgui/implot_demo.cpp", - "libs/imgui/implot.cpp", - "libs/imgui/implot_items.cpp", - }, - .flags = cflags, - }); - } else { - imgui.defineCMacro("ZGUI_IMPLOT", "0"); - } - if (options.use_wchar32) { imgui.defineCMacro("IMGUI_USE_WCHAR32", "1"); } + if (options.with_implot) { + imgui.addIncludePath(b.path("libs/implot")); + + imgui.addCSourceFile(.{ + .file = b.path("src/zplot.cpp"), + .flags = cflags, + }); + + imgui.addCSourceFiles(.{ + .files = &.{ + "libs/implot/implot_demo.cpp", + "libs/implot/implot.cpp", + "libs/implot/implot_items.cpp", + }, + .flags = cflags, + }); + } + + if (options.with_gizmo) { + imgui.addIncludePath(b.path("libs/imguizmo/")); + + imgui.addCSourceFile(.{ + .file = b.path("src/zgizmo.cpp"), + .flags = cflags, + }); + + imgui.addCSourceFiles(.{ + .files = &.{ + "libs/imguizmo/ImGuizmo.cpp", + }, + .flags = cflags, + }); + } + + if (options.with_node_editor) { + imgui.addCSourceFile(.{ + .file = b.path("src/znode_editor.cpp"), + .flags = cflags, + }); + + imgui.addCSourceFile(.{ .file = b.path("libs/node_editor/crude_json.cpp"), .flags = cflags }); + imgui.addCSourceFile(.{ .file = b.path("libs/node_editor/imgui_canvas.cpp"), .flags = cflags }); + imgui.addCSourceFile(.{ .file = b.path("libs/node_editor/imgui_node_editor_api.cpp"), .flags = cflags }); + imgui.addCSourceFile(.{ .file = b.path("libs/node_editor/imgui_node_editor.cpp"), .flags = cflags }); + } + if (options.with_te) { - imgui.defineCMacro("ZGUI_TE", "1"); + imgui.addCSourceFile(.{ + .file = b.path("src/zte.cpp"), + .flags = cflags, + }); imgui.defineCMacro("IMGUI_ENABLE_TEST_ENGINE", null); imgui.defineCMacro("IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL", "1"); @@ -179,8 +224,6 @@ pub fn build(b: *std.Build) void { imgui.linkLibrary(winpthreads); imgui.addSystemIncludePath(b.path("libs/winpthreads/include")); } - } else { - imgui.defineCMacro("ZGUI_TE", "0"); } switch (options.backend) { @@ -249,6 +292,12 @@ pub fn build(b: *std.Build) void { .no_backend => {}, } + if (target.result.os.tag == .macos) { + const system_sdk = b.dependency("system_sdk", .{}); + imgui.addSystemIncludePath(system_sdk.path("macos12/usr/include")); + imgui.addFrameworkPath(system_sdk.path("macos12/System/Library/Frameworks")); + } + const test_step = b.step("test", "Run zgui tests"); const tests = b.addTest(.{ diff --git a/build.zig.zon b/build.zig.zon index 2a766c5..84b5318 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = "zgui", - .version = "0.2.0", + .version = "0.5.0", .paths = .{ "build.zig", "build.zig.zon", @@ -11,5 +11,6 @@ .dependencies = .{ .zglfw = .{ .path = "../zglfw" }, .zgpu = .{ .path = "../zgpu" }, + .system_sdk = .{ .path = "../system-sdk" }, }, } diff --git a/libs/imgui/backends/imgui_impl_dx12.cpp b/libs/imgui/backends/imgui_impl_dx12.cpp index 86cb043..37a7f3b 100644 --- a/libs/imgui/backends/imgui_impl_dx12.cpp +++ b/libs/imgui/backends/imgui_impl_dx12.cpp @@ -791,6 +791,7 @@ bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FO D3D12_CPU_DESCRIPTOR_HANDLE font_srv_cpu_desc_handle, D3D12_GPU_DESCRIPTOR_HANDLE font_srv_gpu_desc_handle) { ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); // Setup backend capabilities flags @@ -847,7 +848,7 @@ void ImGui_ImplDX12_Shutdown() void ImGui_ImplDX12_NewFrame() { ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); - IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX12_Init()?"); + IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX12_Init()?"); if (!bd->pPipelineState) ImGui_ImplDX12_CreateDeviceObjects(); diff --git a/libs/imgui/backends/imgui_impl_glfw.cpp b/libs/imgui/backends/imgui_impl_glfw.cpp index c859946..b6b5c61 100644 --- a/libs/imgui/backends/imgui_impl_glfw.cpp +++ b/libs/imgui/backends/imgui_impl_glfw.cpp @@ -10,9 +10,8 @@ // [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+). // [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. - // Issues: -// [ ] Platform: Multi-viewport support: ParentViewportID not honored, and so io.ConfigViewportsNoDefaultParent has no effect (minor). +// [ ] Platform: Multi-viewport: ParentViewportID not honored, and so io.ConfigViewportsNoDefaultParent has no effect (minor). // 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. @@ -25,6 +24,9 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2024-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2024-07-08: *BREAKING* Renamed ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback to ImGui_ImplGlfw_InstallEmscriptenCallbacks(), added GLFWWindow* parameter. +// 2024-07-08: Emscripten: Added support for GLFW3 contrib port (GLFW 3.4.0 features + bug fixes): to enable, replace -sUSE_GLFW=3 with --use-port=contrib.glfw3 (requires emscripten 3.1.59+) (https://github.com/pongasoft/emscripten-glfw) +// 2024-07-02: Emscripten: Added io.PlatformOpenInShellFn() handler for Emscripten versions. // 2023-12-19: Emscripten: Added ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback() to register canvas selector and auto-resize GLFW window. // 2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys. // 2023-07-18: Inputs: Revert ignoring mouse data on GLFW_CURSOR_DISABLED as it can be used differently. User may set ImGuiConfigFLags_NoMouse if desired. (#5625, #6609) @@ -93,17 +95,26 @@ #ifdef _WIN32 #undef APIENTRY +#ifndef GLFW_EXPOSE_NATIVE_WIN32 #define GLFW_EXPOSE_NATIVE_WIN32 +#endif #include // for glfwGetWin32Window() #endif #ifdef __APPLE__ +#ifndef GLFW_EXPOSE_NATIVE_COCOA #define GLFW_EXPOSE_NATIVE_COCOA +#endif #include // for glfwGetCocoaWindow() #endif #ifdef __EMSCRIPTEN__ #include #include +#ifdef EMSCRIPTEN_USE_PORT_CONTRIB_GLFW3 +#include +#else +#define EMSCRIPTEN_USE_EMBEDDED_GLFW3 +#endif #endif // We gather version tests as define in order to easily see which features are version-dependent. @@ -395,7 +406,7 @@ void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yo if (bd->PrevUserCallbackScroll != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window)) bd->PrevUserCallbackScroll(window, xoffset, yoffset); -#ifdef __EMSCRIPTEN__ +#ifdef EMSCRIPTEN_USE_EMBEDDED_GLFW3 // Ignore GLFW events: will be processed in ImGui_ImplEmscripten_WheelCallback(). return; #endif @@ -406,7 +417,7 @@ void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yo static int ImGui_ImplGlfw_TranslateUntranslatedKey(int key, int scancode) { -#if GLFW_HAS_GETKEYNAME && !defined(__EMSCRIPTEN__) +#if GLFW_HAS_GETKEYNAME && !defined(EMSCRIPTEN_USE_EMBEDDED_GLFW3) // 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. @@ -417,7 +428,7 @@ static int ImGui_ImplGlfw_TranslateUntranslatedKey(int key, int scancode) GLFWerrorfun prev_error_callback = glfwSetErrorCallback(nullptr); const char* key_name = glfwGetKeyName(key, scancode); glfwSetErrorCallback(prev_error_callback); -#if GLFW_HAS_GETERROR && !defined(__EMSCRIPTEN__) // Eat errors (see #5908) +#if GLFW_HAS_GETERROR && !defined(EMSCRIPTEN_USE_EMBEDDED_GLFW3) // Eat errors (see #5908) (void)glfwGetError(nullptr); #endif if (key_name && key_name[0] != 0 && key_name[1] == 0) @@ -525,7 +536,7 @@ void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor*, int) bd->WantUpdateMonitors = true; } -#ifdef __EMSCRIPTEN__ +#ifdef EMSCRIPTEN_USE_EMBEDDED_GLFW3 static EM_BOOL ImGui_ImplEmscripten_WheelCallback(int, const EmscriptenWheelEvent* ev, void*) { // Mimic Emscripten_HandleWheel() in SDL. @@ -599,9 +610,14 @@ void ImGui_ImplGlfw_SetCallbacksChainForAllWindows(bool chain_for_all_windows) bd->CallbacksChainForAllWindows = chain_for_all_windows; } +#ifdef __EMSCRIPTEN__ +EM_JS(void, ImGui_ImplGlfw_EmscriptenOpenURL, (char const *url), { url = url ? UTF8ToString(url) : null; if (url) window.open(url, '_blank'); }); +#endif + static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, GlfwClientApi client_api) { ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); //printf("GLFW_VERSION: %d.%d.%d (%d)", GLFW_VERSION_MAJOR, GLFW_VERSION_MINOR, GLFW_VERSION_REVISION, GLFW_VERSION_COMBINED); @@ -627,6 +643,10 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw io.SetClipboardTextFn = ImGui_ImplGlfw_SetClipboardText; io.GetClipboardTextFn = ImGui_ImplGlfw_GetClipboardText; io.ClipboardUserData = bd->Window; +#ifdef __EMSCRIPTEN__ + io.PlatformOpenInShellFn = [](ImGuiContext *, const char *url) + { ImGui_ImplGlfw_EmscriptenOpenURL(url); return true; }; +#endif // Create mouse cursors // (By design, on X11 cursors are user configurable and some cursors may be missing. When a cursor doesn't exist, @@ -657,12 +677,6 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw // Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any. if (install_callbacks) ImGui_ImplGlfw_InstallCallbacks(window); - // Register Emscripten Wheel callback to workaround issue in Emscripten GLFW Emulation (#6096) - // We intentionally do not check 'if (install_callbacks)' here, as some users may set it to false and call GLFW callback themselves. - // FIXME: May break chaining in case user registered their own Emscripten callback? -#ifdef __EMSCRIPTEN__ - emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, false, ImGui_ImplEmscripten_WheelCallback); -#endif // Update monitors the first time (note: monitor callback are broken in GLFW 3.2 and earlier, see github.com/glfw/glfw/issues/784) ImGui_ImplGlfw_UpdateMonitors(); @@ -717,8 +731,9 @@ void ImGui_ImplGlfw_Shutdown() if (bd->InstalledCallbacks) ImGui_ImplGlfw_RestoreCallbacks(bd->Window); -#ifdef __EMSCRIPTEN__ - emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, false, nullptr); +#ifdef EMSCRIPTEN_USE_EMBEDDED_GLFW3 + if (bd->CanvasSelector) + emscripten_set_wheel_callback(bd->CanvasSelector, nullptr, false, nullptr); #endif for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) @@ -750,7 +765,7 @@ static void ImGui_ImplGlfw_UpdateMouseData() ImGuiViewport* viewport = platform_io.Viewports[n]; GLFWwindow* window = (GLFWwindow*)viewport->PlatformHandle; -#ifdef __EMSCRIPTEN__ +#ifdef EMSCRIPTEN_USE_EMBEDDED_GLFW3 const bool is_window_focused = true; #else const bool is_window_focused = glfwGetWindowAttrib(window, GLFW_FOCUSED) != 0; @@ -845,7 +860,7 @@ static void ImGui_ImplGlfw_UpdateGamepads() return; io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; -#if GLFW_HAS_GAMEPAD_API && !defined(__EMSCRIPTEN__) +#if GLFW_HAS_GAMEPAD_API && !defined(EMSCRIPTEN_USE_EMBEDDED_GLFW3) GLFWgamepadstate gamepad; if (!glfwGetGamepadState(GLFW_JOYSTICK_1, &gamepad)) return; @@ -935,7 +950,7 @@ void ImGui_ImplGlfw_NewFrame() { ImGuiIO& io = ImGui::GetIO(); ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); - IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplGlfw_InitForXXX()?"); + IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplGlfw_InitForXXX()?"); // Setup display size (every frame to accommodate for window resizing) int w, h; @@ -968,7 +983,7 @@ void ImGui_ImplGlfw_NewFrame() ImGui_ImplGlfw_UpdateGamepads(); } -#ifdef __EMSCRIPTEN__ +#ifdef EMSCRIPTEN_USE_EMBEDDED_GLFW3 static EM_BOOL ImGui_ImplGlfw_OnCanvasSizeChange(int event_type, const EmscriptenUiEvent* event, void* user_data) { ImGui_ImplGlfw_Data* bd = (ImGui_ImplGlfw_Data*)user_data; @@ -989,11 +1004,11 @@ static EM_BOOL ImGui_ImplEmscripten_FullscreenChangeCallback(int event_type, con // 'canvas_selector' is a CSS selector. The event listener is applied to the first element that matches the query. // STRING MUST PERSIST FOR THE APPLICATION DURATION. PLEASE USE A STRING LITERAL OR ENSURE POINTER WILL STAY VALID. -void ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback(const char* canvas_selector) +void ImGui_ImplGlfw_InstallEmscriptenCallbacks(GLFWwindow *, const char *canvas_selector) { IM_ASSERT(canvas_selector != nullptr); ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); - IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplGlfw_InitForXXX()?"); + IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplGlfw_InitForXXX()?"); bd->CanvasSelector = canvas_selector; emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, bd, false, ImGui_ImplGlfw_OnCanvasSizeChange); @@ -1001,9 +1016,24 @@ void ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback(const char* canvas_sel // Change the size of the GLFW window according to the size of the canvas ImGui_ImplGlfw_OnCanvasSizeChange(EMSCRIPTEN_EVENT_RESIZE, {}, bd); -} -#endif + // Register Emscripten Wheel callback to workaround issue in Emscripten GLFW Emulation (#6096) + // We intentionally do not check 'if (install_callbacks)' here, as some users may set it to false and call GLFW callback themselves. + // FIXME: May break chaining in case user registered their own Emscripten callback? + emscripten_set_wheel_callback(bd->CanvasSelector, nullptr, false, ImGui_ImplEmscripten_WheelCallback); +} +#elif defined(EMSCRIPTEN_USE_PORT_CONTRIB_GLFW3) +// When using --use-port=contrib.glfw3 for the GLFW implementation, you can override the behavior of this call +// by invoking emscripten_glfw_make_canvas_resizable afterward. +// See https://github.com/pongasoft/emscripten-glfw/blob/master/docs/Usage.md#how-to-make-the-canvas-resizable-by-the-user for an explanation +void ImGui_ImplGlfw_InstallEmscriptenCallbacks(GLFWwindow *window, const char *canvas_selector) +{ + GLFWwindow *w = (GLFWwindow *)(EM_ASM_INT({ return Module.glfwGetWindow(UTF8ToString($0)); }, canvas_selector)); + IM_ASSERT(window == w); // Sanity check + IM_UNUSED(w); + emscripten_glfw_make_canvas_resizable(window, "window", nullptr); +} +#endif // #ifdef EMSCRIPTEN_USE_PORT_CONTRIB_GLFW3 //-------------------------------------------------------------------------------------------------------- // MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT diff --git a/libs/imgui/backends/imgui_impl_glfw.h b/libs/imgui/backends/imgui_impl_glfw.h index 14e6ee6..910362b 100644 --- a/libs/imgui/backends/imgui_impl_glfw.h +++ b/libs/imgui/backends/imgui_impl_glfw.h @@ -8,11 +8,10 @@ // [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen (Windows only). // [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+). +// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange' (note: the resizing cursors requires GLFW 3.4+). // [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. - // Issues: -// [ ] Platform: Multi-viewport support: ParentViewportID not honored, and so io.ConfigViewportsNoDefaultParent has no effect (minor). +// [ ] Platform: Multi-viewport: ParentViewportID not honored, and so io.ConfigViewportsNoDefaultParent has no effect (minor). // 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. @@ -29,15 +28,17 @@ struct GLFWwindow; struct GLFWmonitor; +// Follow "Getting Started" link and check examples/ folder to learn about using backends! 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(); -// Emscripten related initialization phase methods +// Emscripten related initialization phase methods (call after ImGui_ImplGlfw_InitForOpenGL) #ifdef __EMSCRIPTEN__ -IMGUI_IMPL_API void ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback(const char* canvas_selector); +IMGUI_IMPL_API void ImGui_ImplGlfw_InstallEmscriptenCallbacks(GLFWwindow* window, const char* canvas_selector); +//static inline void ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback(const char* canvas_selector) { ImGui_ImplGlfw_InstallEmscriptenCallbacks(nullptr, canvas_selector); } } // Renamed in 1.91.0 #endif // GLFW callbacks install diff --git a/libs/imgui/backends/imgui_impl_opengl3.cpp b/libs/imgui/backends/imgui_impl_opengl3.cpp index 586219d..5d1dadb 100644 --- a/libs/imgui/backends/imgui_impl_opengl3.cpp +++ b/libs/imgui/backends/imgui_impl_opengl3.cpp @@ -5,7 +5,8 @@ // Implemented features: // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! -// [x] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only). +// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only). +// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // About WebGL/ES: // - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES. @@ -22,6 +23,10 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2024-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2024-06-28: OpenGL: ImGui_ImplOpenGL3_NewFrame() recreates font texture if it has been destroyed by ImGui_ImplOpenGL3_DestroyFontsTexture(). (#7748) +// 2024-05-07: OpenGL: Update loader for Linux to support EGL/GLVND. (#7562) +// 2024-04-16: OpenGL: Detect ES3 contexts on desktop based on version string, to e.g. avoid calling glPolygonMode() on them. (#7447) // 2024-01-09: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" and variants, fixing regression on distros missing a symlink. // 2023-11-08: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" instead of "libGL.so.1", accommodating for NetBSD systems having only "libGL.so.3" available. (#6983) // 2023-10-05: OpenGL: Rename symbols in our internal loader so that LTO compilation with another copy of gl3w is possible. (#6875, #6668, #4445) @@ -121,6 +126,7 @@ // Clang/GCC warnings with -Weverything #if defined(__clang__) #pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: ignore unknown flags #pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness #pragma clang diagnostic ignored "-Wunused-macros" // warning: macro is not used @@ -181,9 +187,10 @@ extern "C" { #endif // Desktop GL 2.0+ has extension and glPolygonMode() which GL ES and WebGL don't have.. +// A desktop ES context can technically compile fine with our loader, so we also perform a runtime checks #if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) #define IMGUI_IMPL_OPENGL_HAS_EXTENSIONS // has glGetIntegerv(GL_NUM_EXTENSIONS) -#define IMGUI_IMPL_OPENGL_HAS_POLYGON_MODE // has glPolygonMode() +#define IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE // may have glPolygonMode() #endif // Desktop GL 2.1+ and GL ES 3.0+ have glBindBuffer() with GL_PIXEL_UNPACK_BUFFER target. @@ -234,6 +241,7 @@ struct ImGui_ImplOpenGL3_Data unsigned int VboHandle, ElementsHandle; GLsizeiptr VertexBufferSize; GLsizeiptr IndexBufferSize; + bool HasPolygonMode; bool HasClipOrigin; bool UseBufferSubData; @@ -247,6 +255,10 @@ static ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr; } +// Forward Declarations +static void ImGui_ImplOpenGL3_InitPlatformInterface(); +static void ImGui_ImplOpenGL3_ShutdownPlatformInterface(); + // OpenGL vertex attribute state (for ES 1.0 and ES 2.0 only) #ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY struct ImGui_ImplOpenGL3_VtxAttribState @@ -275,6 +287,7 @@ struct ImGui_ImplOpenGL3_VtxAttribState bool ImGui_ImplOpenGL3_Init(const char* glsl_version) { ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); // Initialize our loader @@ -298,16 +311,13 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) bd->GlProfileIsES2 = true; #else // Desktop or GLES 3 + const char *gl_version_str = (const char *)glGetString(GL_VERSION); 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); - } + sscanf(gl_version_str, "%d.%d", &major, &minor); // Query GL_VERSION in desktop GL 2.x, the string will start with "." bd->GlVersion = (GLuint)(major * 100 + minor * 10); #if defined(GL_CONTEXT_PROFILE_MASK) if (bd->GlVersion >= 320) @@ -317,6 +327,9 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) #if defined(IMGUI_IMPL_OPENGL_ES3) bd->GlProfileIsES3 = true; +#else + if (strncmp(gl_version_str, "OpenGL ES 3", 11) == 0) + bd->GlProfileIsES3 = true; #endif bd->UseBufferSubData = false; @@ -331,13 +344,14 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) #endif #ifdef IMGUI_IMPL_OPENGL_DEBUG - printf("GlVersion = %d\nGlProfileIsCompat = %d\nGlProfileMask = 0x%X\nGlProfileIsES2 = %d, GlProfileIsES3 = %d\nGL_VENDOR = '%s'\nGL_RENDERER = '%s'\n", bd->GlVersion, bd->GlProfileIsCompat, bd->GlProfileMask, bd->GlProfileIsES2, bd->GlProfileIsES3, (const char*)glGetString(GL_VENDOR), (const char*)glGetString(GL_RENDERER)); // [DEBUG] + printf("GlVersion = %d, \"%s\"\nGlProfileIsCompat = %d\nGlProfileMask = 0x%X\nGlProfileIsES2 = %d, GlProfileIsES3 = %d\nGL_VENDOR = '%s'\nGL_RENDERER = '%s'\n", bd->GlVersion, gl_version_str, bd->GlProfileIsCompat, bd->GlProfileMask, bd->GlProfileIsES2, bd->GlProfileIsES3, (const char *)glGetString(GL_VENDOR), (const char *)glGetString(GL_RENDERER)); // [DEBUG] #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 + io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) // 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 nullptr if unsure. @@ -363,6 +377,9 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture); // Detect extensions we support +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE + bd->HasPolygonMode = (!bd->GlProfileIsES2 && !bd->GlProfileIsES3); +#endif bd->HasClipOrigin = (bd->GlVersion >= 450); #ifdef IMGUI_IMPL_OPENGL_HAS_EXTENSIONS GLint num_extensions = 0; @@ -375,6 +392,9 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) } #endif + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + ImGui_ImplOpenGL3_InitPlatformInterface(); + return true; } @@ -384,20 +404,23 @@ void ImGui_ImplOpenGL3_Shutdown() IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplOpenGL3_ShutdownPlatformInterface(); ImGui_ImplOpenGL3_DestroyDeviceObjects(); io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasViewports); IM_DELETE(bd); } void ImGui_ImplOpenGL3_NewFrame() { ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); - IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplOpenGL3_Init()?"); + IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplOpenGL3_Init()?"); if (!bd->ShaderHandle) ImGui_ImplOpenGL3_CreateDeviceObjects(); + if (!bd->FontTexture) + ImGui_ImplOpenGL3_CreateFontsTexture(); } static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) @@ -416,8 +439,9 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid if (bd->GlVersion >= 310) glDisable(GL_PRIMITIVE_RESTART); #endif -#ifdef IMGUI_IMPL_OPENGL_HAS_POLYGON_MODE - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE + if (bd->HasPolygonMode) + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); #endif // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT) @@ -505,8 +529,12 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) #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_OPENGL_HAS_POLYGON_MODE - GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE + GLint last_polygon_mode[2]; + if (bd->HasPolygonMode) + { + 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); @@ -644,18 +672,21 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) if (bd->GlVersion >= 310) { if (last_enable_primitive_restart) glEnable(GL_PRIMITIVE_RESTART); else glDisable(GL_PRIMITIVE_RESTART); } #endif -#ifdef IMGUI_IMPL_OPENGL_HAS_POLYGON_MODE +#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE // Desktop OpenGL 3.0 and OpenGL 3.1 had separate polygon draw modes for front-facing and back-facing faces of polygons - if (bd->GlVersion <= 310 || bd->GlProfileIsCompat) + if (bd->HasPolygonMode) { - glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); - glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]); + if (bd->GlVersion <= 310 || bd->GlProfileIsCompat) + { + glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); + glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]); + } + else + { + glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); + } } - else - { - glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); - } -#endif // IMGUI_IMPL_OPENGL_HAS_POLYGON_MODE +#endif // IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE 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]); @@ -753,7 +784,7 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects() glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_BUFFER_PIXEL_UNPACK - GLint last_pixel_unpack_buffer; + GLint last_pixel_unpack_buffer = 0; if (bd->GlVersion >= 210) { glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &last_pixel_unpack_buffer); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } #endif #ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY @@ -948,6 +979,34 @@ void ImGui_ImplOpenGL3_DestroyDeviceObjects() ImGui_ImplOpenGL3_DestroyFontsTexture(); } +//-------------------------------------------------------------------------------------------------------- +// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT +// This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously. +// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first.. +//-------------------------------------------------------------------------------------------------------- + +static void ImGui_ImplOpenGL3_RenderWindow(ImGuiViewport *viewport, void *) +{ + if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear)) + { + ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); + glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + } + ImGui_ImplOpenGL3_RenderDrawData(viewport->DrawData); +} + +static void ImGui_ImplOpenGL3_InitPlatformInterface() +{ + ImGuiPlatformIO &platform_io = ImGui::GetPlatformIO(); + platform_io.Renderer_RenderWindow = ImGui_ImplOpenGL3_RenderWindow; +} + +static void ImGui_ImplOpenGL3_ShutdownPlatformInterface() +{ + ImGui::DestroyPlatformWindows(); +} + //----------------------------------------------------------------------------- #if defined(__GNUC__) diff --git a/libs/imgui/backends/imgui_impl_opengl3.h b/libs/imgui/backends/imgui_impl_opengl3.h index a99d307..ab83591 100644 --- a/libs/imgui/backends/imgui_impl_opengl3.h +++ b/libs/imgui/backends/imgui_impl_opengl3.h @@ -5,7 +5,8 @@ // Implemented features: // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! -// [x] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only). +// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only). +// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // About WebGL/ES: // - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES. @@ -44,9 +45,9 @@ extern "C" { 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 +// Configuration flags to add in your imconfig file: +// #define IMGUI_IMPL_OPENGL_ES2 // Enable ES 2 (Auto-detected on Emscripten) +// #define IMGUI_IMPL_OPENGL_ES3 // Enable ES 3 (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) \ diff --git a/libs/imgui/backends/imgui_impl_wgpu.cpp b/libs/imgui/backends/imgui_impl_wgpu.cpp index e87c5be..b1d71c2 100644 --- a/libs/imgui/backends/imgui_impl_wgpu.cpp +++ b/libs/imgui/backends/imgui_impl_wgpu.cpp @@ -760,6 +760,7 @@ void ImGui_ImplWGPU_InvalidateDeviceObjects() bool ImGui_ImplWGPU_Init(ImGui_ImplWGPU_InitInfo* init_info) { ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); // Setup backend capabilities flags @@ -787,7 +788,7 @@ bool ImGui_ImplWGPU_Init(ImGui_ImplWGPU_InitInfo* init_info) // Create buffers with a default size (they will later be grown as needed) bd->pFrameResources = new FrameResources[bd->numFramesInFlight]; - for (int i = 0; i < bd->numFramesInFlight; i++) + for (unsigned int i = 0; i < bd->numFramesInFlight; i++) { FrameResources* fr = &bd->pFrameResources[i]; fr->IndexBuffer = nullptr; diff --git a/libs/imgui/backends/imgui_impl_wgpu.h b/libs/imgui/backends/imgui_impl_wgpu.h index c0c3b87..31d31c9 100644 --- a/libs/imgui/backends/imgui_impl_wgpu.h +++ b/libs/imgui/backends/imgui_impl_wgpu.h @@ -34,11 +34,12 @@ struct ImGui_ImplWGPU_InitInfo ImGui_ImplWGPU_InitInfo() { PipelineMultisampleState.count = 1; - PipelineMultisampleState.mask = -1u; + PipelineMultisampleState.mask = UINT32_MAX; PipelineMultisampleState.alphaToCoverageEnabled = false; } }; +// Follow "Getting Started" link and check examples/ folder to learn about using backends! IMGUI_IMPL_API bool ImGui_ImplWGPU_Init(ImGui_ImplWGPU_InitInfo* init_info); IMGUI_IMPL_API void ImGui_ImplWGPU_Shutdown(); IMGUI_IMPL_API void ImGui_ImplWGPU_NewFrame(); diff --git a/libs/imgui/backends/imgui_impl_win32.cpp b/libs/imgui/backends/imgui_impl_win32.cpp index 3b436c9..6c043a9 100644 --- a/libs/imgui/backends/imgui_impl_win32.cpp +++ b/libs/imgui/backends/imgui_impl_win32.cpp @@ -17,63 +17,10 @@ // - Documentation https://dearimgui.com/docs (same as your local docs/ folder). // - Introduction, links and more at the top of imgui.cpp -#include "imgui.h" -#ifndef IMGUI_DISABLE -// FIX(zig-gamedev): -// #include "imgui_impl_win32.h" -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include -#include // GET_X_LPARAM(), GET_Y_LPARAM() -#include -#include - -// Configuration flags to add in your imconfig.h file: -//#define IMGUI_IMPL_WIN32_DISABLE_GAMEPAD // Disable gamepad support. This was meaningful before <1.81 but we now load XInput dynamically so the option is now less relevant. - -// Using XInput for gamepad (will load DLL dynamically) -#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD -#include -typedef DWORD (WINAPI *PFN_XInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES*); -typedef DWORD (WINAPI *PFN_XInputGetState)(DWORD, XINPUT_STATE*); -#endif - -// FIX(zig-gamedev): -extern "C" { - IMGUI_IMPL_API bool ImGui_ImplWin32_Init(void* hwnd); - IMGUI_IMPL_API bool ImGui_ImplWin32_InitForOpenGL(void* hwnd); - IMGUI_IMPL_API void ImGui_ImplWin32_Shutdown(); - IMGUI_IMPL_API void ImGui_ImplWin32_NewFrame(); - - // Win32 message handler your application need to call. - // - Intentionally commented out in a '#if 0' block to avoid dragging dependencies on from this helper. - // - You should COPY the line below into your .cpp code to forward declare the function and then you can call it. - // - Call from your application's message handler. Keep calling your message handler unless this function returns TRUE. - - #if 0 - extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); - #endif - - // DPI-related helpers (optional) - // - Use to enable DPI awareness without having to create an application manifest. - // - Your own app may already do this via a manifest or explicit calls. This is mostly useful for our examples/ apps. - // - In theory we could call simple functions from Windows SDK such as SetProcessDPIAware(), SetProcessDpiAwareness(), etc. - // but most of the functions provided by Microsoft require Windows 8.1/10+ SDK at compile time and Windows 8/10+ at runtime, - // neither we want to require the user to have. So we dynamically select and load those functions to avoid dependencies. - IMGUI_IMPL_API void ImGui_ImplWin32_EnableDpiAwareness(); - IMGUI_IMPL_API float ImGui_ImplWin32_GetDpiScaleForHwnd(void* hwnd); // HWND hwnd - IMGUI_IMPL_API float ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor); // HMONITOR monitor - - // Transparency related helpers (optional) [experimental] - // - Use to enable alpha compositing transparency with the desktop. - // - Use together with e.g. clearing your framebuffer with zero-alpha. - IMGUI_IMPL_API void ImGui_ImplWin32_EnableAlphaCompositing(void* hwnd); // HWND hwnd -}; // extern "C" - // CHANGELOG // (minor and older changes stripped away, please see git history for details) // 2024-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. +// 2024-07-08: Inputs: Fixed ImGuiMod_Super being mapped to VK_APPS instead of VK_LWIN||VK_RWIN. (#7768) // 2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys, app back/forward keys. // 2023-09-25: Inputs: Synthesize key-down event on key-up for VK_SNAPSHOT / ImGuiKey_PrintScreen as Windows doesn't emit it (same behavior as GLFW/SDL). // 2023-09-07: Inputs: Added support for keyboard codepage conversion for when application is compiled in MBCS mode and using a non-Unicode window. @@ -126,8 +73,71 @@ extern "C" { // 2017-10-23: Inputs: Using Win32 ::SetCapture/::GetCapture() to retrieve mouse positions outside the client area when dragging. // 2016-11-12: Inputs: Only call Win32 ::SetCursor(nullptr) when io.MouseDrawCursor is set. +#include "imgui.h" +#ifndef IMGUI_DISABLE +// FIX(zig-gamedev): +// #include "imgui_impl_win32.h" +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include // GET_X_LPARAM(), GET_Y_LPARAM() +#include +#include + +// Using XInput for gamepad (will load DLL dynamically) +#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD +#include +typedef DWORD(WINAPI *PFN_XInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES *); +typedef DWORD(WINAPI *PFN_XInputGetState)(DWORD, XINPUT_STATE *); +#endif + +// Clang/GCC warnings with -Weverything +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-function-type" // warning: cast between incompatible function types (for loader) +#endif +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wcast-function-type" // warning: cast between incompatible function types (for loader) +#endif + +// FIX(zig-gamedev): +extern "C" +{ + IMGUI_IMPL_API bool ImGui_ImplWin32_Init(void *hwnd); + IMGUI_IMPL_API bool ImGui_ImplWin32_InitForOpenGL(void *hwnd); + IMGUI_IMPL_API void ImGui_ImplWin32_Shutdown(); + IMGUI_IMPL_API void ImGui_ImplWin32_NewFrame(); + + // Win32 message handler your application need to call. + // - Intentionally commented out in a '#if 0' block to avoid dragging dependencies on from this helper. + // - You should COPY the line below into your .cpp code to forward declare the function and then you can call it. + // - Call from your application's message handler. Keep calling your message handler unless this function returns TRUE. + +#if 0 + extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); +#endif + + // DPI-related helpers (optional) + // - Use to enable DPI awareness without having to create an application manifest. + // - Your own app may already do this via a manifest or explicit calls. This is mostly useful for our examples/ apps. + // - In theory we could call simple functions from Windows SDK such as SetProcessDPIAware(), SetProcessDpiAwareness(), etc. + // but most of the functions provided by Microsoft require Windows 8.1/10+ SDK at compile time and Windows 8/10+ at runtime, + // neither we want to require the user to have. So we dynamically select and load those functions to avoid dependencies. + IMGUI_IMPL_API void ImGui_ImplWin32_EnableDpiAwareness(); + IMGUI_IMPL_API float ImGui_ImplWin32_GetDpiScaleForHwnd(void *hwnd); // HWND hwnd + IMGUI_IMPL_API float ImGui_ImplWin32_GetDpiScaleForMonitor(void *monitor); // HMONITOR monitor + + // Transparency related helpers (optional) [experimental] + // - Use to enable alpha compositing transparency with the desktop. + // - Use together with e.g. clearing your framebuffer with zero-alpha. + IMGUI_IMPL_API void ImGui_ImplWin32_EnableAlphaCompositing(void *hwnd); // HWND hwnd +}; // extern "C" + // Forward Declarations -static void ImGui_ImplWin32_InitPlatformInterface(bool platformHasOwnDC); +static void ImGui_ImplWin32_InitPlatformInterface(bool platform_has_own_dc); static void ImGui_ImplWin32_ShutdownPlatformInterface(); static void ImGui_ImplWin32_UpdateMonitors(); @@ -177,6 +187,7 @@ static void ImGui_ImplWin32_UpdateKeyboardCodePage() static bool ImGui_ImplWin32_InitEx(void* hwnd, bool platform_has_own_dc) { ImGuiIO& io = ImGui::GetIO(); + IMGUI_CHECKVERSION(); IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); INT64 perf_frequency, perf_counter; @@ -329,7 +340,7 @@ static void ImGui_ImplWin32_UpdateKeyModifiers() io.AddKeyEvent(ImGuiMod_Ctrl, IsVkDown(VK_CONTROL)); io.AddKeyEvent(ImGuiMod_Shift, IsVkDown(VK_SHIFT)); io.AddKeyEvent(ImGuiMod_Alt, IsVkDown(VK_MENU)); - io.AddKeyEvent(ImGuiMod_Super, IsVkDown(VK_APPS)); + io.AddKeyEvent(ImGuiMod_Super, IsVkDown(VK_LWIN) || IsVkDown(VK_RWIN)); } // This code supports multi-viewports (multiple OS Windows mapped into different Dear ImGui viewports) @@ -475,9 +486,9 @@ static void ImGui_ImplWin32_UpdateMonitors() void ImGui_ImplWin32_NewFrame() { - ImGuiIO& io = ImGui::GetIO(); ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); - IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplWin32_Init()?"); + IM_ASSERT(bd != nullptr && "Context or backend not initialized? Did you call ImGui_ImplWin32_Init()?"); + ImGuiIO &io = ImGui::GetIO(); // Setup display size (every frame to accommodate for window resizing) RECT rect = { 0, 0, 0, 0 }; @@ -675,11 +686,12 @@ static ImGuiMouseSource GetMouseSourceFromMessageExtraInfo() IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { - if (ImGui::GetCurrentContext() == nullptr) + // Most backends don't have silent checks like this one, but we need it because WndProc are called early in CreateWindow(). + // We silently allow both context or just only backend data to be nullptr. + ImGui_ImplWin32_Data *bd = ImGui_ImplWin32_GetBackendData(); + if (bd == nullptr) return 0; - - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); + ImGuiIO &io = ImGui::GetIO(); switch (msg) { @@ -707,7 +719,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA ::ScreenToClient(hwnd, &mouse_pos); io.AddMouseSourceEvent(mouse_source); io.AddMousePosEvent((float)mouse_pos.x, (float)mouse_pos.y); - break; + return 0; } case WM_MOUSELEAVE: case WM_NCMOUSELEAVE: @@ -720,7 +732,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA bd->MouseTrackedArea = 0; io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); } - break; + return 0; } case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK: case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: @@ -1075,6 +1087,9 @@ static void ImGui_ImplWin32_CreateWindow(ImGuiViewport* viewport) vd->HwndOwned = true; viewport->PlatformRequestResize = false; viewport->PlatformHandle = viewport->PlatformHandleRaw = vd->Hwnd; + + // Secondary viewports store their imgui context + ::SetPropA(vd->Hwnd, "IMGUI_CONTEXT", ImGui::GetCurrentContext()); } static void ImGui_ImplWin32_DestroyWindow(ImGuiViewport* viewport) @@ -1100,10 +1115,20 @@ static void ImGui_ImplWin32_ShowWindow(ImGuiViewport* viewport) { ImGui_ImplWin32_ViewportData* vd = (ImGui_ImplWin32_ViewportData*)viewport->PlatformUserData; IM_ASSERT(vd->Hwnd != 0); + + // ShowParent() also brings parent to front, which is not always desirable, + // so we temporarily disable parenting. (#7354) + if (vd->HwndParent != NULL) + ::SetWindowLongPtr(vd->Hwnd, GWLP_HWNDPARENT, (LONG_PTR) nullptr); + if (viewport->Flags & ImGuiViewportFlags_NoFocusOnAppearing) ::ShowWindow(vd->Hwnd, SW_SHOWNA); else ::ShowWindow(vd->Hwnd, SW_SHOW); + + // Restore + if (vd->HwndParent != NULL) + ::SetWindowLongPtr(vd->Hwnd, GWLP_HWNDPARENT, (LONG_PTR)vd->HwndParent); } static void ImGui_ImplWin32_UpdateWindow(ImGuiViewport* viewport) @@ -1267,16 +1292,22 @@ static void ImGui_ImplWin32_OnChangedViewport(ImGuiViewport* viewport) static LRESULT CALLBACK ImGui_ImplWin32_WndProcHandler_PlatformWindow(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { - if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) - return true; + // Allow secondary viewport WndProc to be called regardless of current context + ImGuiContext *hwnd_ctx = (ImGuiContext *)::GetPropA(hWnd, "IMGUI_CONTEXT"); + ImGuiContext *prev_ctx = ImGui::GetCurrentContext(); + if (hwnd_ctx != prev_ctx && hwnd_ctx != NULL) + ImGui::SetCurrentContext(hwnd_ctx); - if (ImGuiViewport* viewport = ImGui::FindViewportByPlatformHandle((void*)hWnd)) + LRESULT result = 0; + if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) + result = true; + else if (ImGuiViewport *viewport = ImGui::FindViewportByPlatformHandle((void *)hWnd)) { switch (msg) { case WM_CLOSE: viewport->PlatformRequestClose = true; - return 0; + break; case WM_MOVE: viewport->PlatformRequestMove = true; break; @@ -1285,7 +1316,7 @@ static LRESULT CALLBACK ImGui_ImplWin32_WndProcHandler_PlatformWindow(HWND hWnd, break; case WM_MOUSEACTIVATE: if (viewport->Flags & ImGuiViewportFlags_NoFocusOnClick) - return MA_NOACTIVATE; + result = MA_NOACTIVATE; break; case WM_NCHITTEST: // Let mouse pass-through the window. This will allow the backend to call io.AddMouseViewportEvent() correctly. (which is optional). @@ -1293,12 +1324,15 @@ static LRESULT CALLBACK ImGui_ImplWin32_WndProcHandler_PlatformWindow(HWND hWnd, // If you cannot easily access those viewport flags from your windowing/event code: you may manually synchronize its state e.g. in // your main loop after calling UpdatePlatformWindows(). Iterate all viewports/platform windows and pass the flag to your windowing system. if (viewport->Flags & ImGuiViewportFlags_NoInputs) - return HTTRANSPARENT; + result = HTTRANSPARENT; break; } } - - return DefWindowProc(hWnd, msg, wParam, lParam); + if (result == 0) + result = DefWindowProc(hWnd, msg, wParam, lParam); + if (hwnd_ctx != prev_ctx && hwnd_ctx != NULL) + ImGui::SetCurrentContext(prev_ctx); + return result; } static void ImGui_ImplWin32_InitPlatformInterface(bool platform_has_own_dc) @@ -1357,4 +1391,11 @@ static void ImGui_ImplWin32_ShutdownPlatformInterface() //--------------------------------------------------------------------------------------------------------- +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + #endif // #ifndef IMGUI_DISABLE diff --git a/libs/imgui/backends/imgui_impl_win32.h b/libs/imgui/backends/imgui_impl_win32.h index cebe661..4cf54d8 100644 --- a/libs/imgui/backends/imgui_impl_win32.h +++ b/libs/imgui/backends/imgui_impl_win32.h @@ -21,6 +21,7 @@ #include "imgui.h" // IMGUI_IMPL_API #ifndef IMGUI_DISABLE +// Follow "Getting Started" link and check examples/ folder to learn about using backends! IMGUI_IMPL_API bool ImGui_ImplWin32_Init(void* hwnd); IMGUI_IMPL_API bool ImGui_ImplWin32_InitForOpenGL(void* hwnd); IMGUI_IMPL_API void ImGui_ImplWin32_Shutdown(); diff --git a/libs/imgui/imconfig.h b/libs/imgui/imconfig.h index d556cba..6e4c743 100644 --- a/libs/imgui/imconfig.h +++ b/libs/imgui/imconfig.h @@ -21,10 +21,11 @@ //---- 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 ) +// - Windows 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) // MSVC Windows: DLL export +//#define IMGUI_API __declspec(dllimport) // MSVC Windows: DLL import +//#define IMGUI_API __attribute__((visibility("default"))) // GCC/Clang: override visibility when set is hidden //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names. //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -42,6 +43,7 @@ //#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_SHELL_FUNCTIONS // Don't implement default io.PlatformOpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")). //#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) @@ -49,6 +51,9 @@ //#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 +//---- Enable Test Engine / Automation features. +//#define IMGUI_ENABLE_TEST_ENGINE // Enable imgui_test_engine hooks. Generally set automatically by include "imgui_te_config.h", see Test Engine for details. + //---- Include imgui_user.h at the end of imgui.h as a convenience // May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included. //#define IMGUI_INCLUDE_IMGUI_USER_H diff --git a/libs/imgui/imgui.cpp b/libs/imgui/imgui.cpp index 602fde6..c5c8320 100644 --- a/libs/imgui/imgui.cpp +++ b/libs/imgui/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.4 +// dear imgui, v1.91.0 // (main code and documentation) // Help: @@ -7,15 +7,19 @@ // - Read top of imgui.cpp for more details, links and comments. // Resources: -// - FAQ https://dearimgui.com/faq -// - Getting Started https://dearimgui.com/getting-started -// - Homepage https://github.com/ocornut/imgui -// - Releases & changelog https://github.com/ocornut/imgui/releases -// - Gallery https://github.com/ocornut/imgui/issues/6897 (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 -// - Tests & Automation https://github.com/ocornut/imgui_test_engine +// - FAQ ........................ https://dearimgui.com/faq (in repository as docs/FAQ.md) +// - Homepage ................... https://github.com/ocornut/imgui +// - Releases & changelog ....... https://github.com/ocornut/imgui/releases +// - Gallery .................... https://github.com/ocornut/imgui/issues/7503 (please post your screenshots/video there!) +// - Wiki ....................... https://github.com/ocornut/imgui/wiki (lots of good stuff there) +// - Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started (how to integrate in an existing app by adding ~25 lines of code) +// - Third-party Extensions https://github.com/ocornut/imgui/wiki/Useful-Extensions (ImPlot & many more) +// - Bindings/Backends https://github.com/ocornut/imgui/wiki/Bindings (language bindings, backends for various tech/engines) +// - Glossary https://github.com/ocornut/imgui/wiki/Glossary +// - Debug Tools https://github.com/ocornut/imgui/wiki/Debug-Tools +// - Software using Dear ImGui https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui +// - Issues & support ........... https://github.com/ocornut/imgui/issues +// - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your apps) // For first-time users having issues compiling/linking/running/loading fonts: // please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above. @@ -26,7 +30,7 @@ // See LICENSE.txt for copyright and licensing details (standard MIT License). // This library is free but needs your support to sustain development and maintenance. // Businesses: you can support continued development via B2B invoiced technical support, maintenance and sponsoring contracts. -// PLEASE reach out at omar AT dearimgui DOT com. See https://github.com/ocornut/imgui/wiki/Sponsors +// PLEASE reach out at omar AT dearimgui DOT com. See https://github.com/ocornut/imgui/wiki/Funding // Businesses: you can also purchase licenses for the Dear ImGui Automation/Test Engine. // It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library. @@ -73,6 +77,7 @@ CODE // [SECTION] RENDER HELPERS // [SECTION] INITIALIZATION, SHUTDOWN // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) +// [SECTION] ID STACK // [SECTION] INPUTS // [SECTION] ERROR CHECKING // [SECTION] ITEM SUBMISSION @@ -139,7 +144,7 @@ CODE - CTRL+X, CTRL+C, CTRL+V: Use OS clipboard. - CTRL+Z, CTRL+Y: Undo, Redo. - ESCAPE: Revert text to its original value. - - On OSX, controls are automatically adjusted to match standard OSX text editing shortcuts and behaviors. + - On OSX, controls are automatically adjusted to match standard OSX text editing 2ts and behaviors. - KEYBOARD CONTROLS - Basic: @@ -433,6 +438,67 @@ CODE - likewise io.MousePos and GetMousePos() will use OS coordinates. If you query mouse positions to interact with non-imgui coordinates you will need to offset them, e.g. subtract GetWindowViewport()->Pos. + - 2024/07/25 (1.91.0) - obsoleted GetContentRegionMax(), GetWindowContentRegionMin() and GetWindowContentRegionMax(). (see #7838 on GitHub for more info) + you should never need those functions. you can do everything with GetCursorScreenPos() and GetContentRegionAvail() in a more simple way. + - instead of: GetWindowContentRegionMax().x - GetCursorPos().x + - you can use: GetContentRegionAvail().x + - instead of: GetWindowContentRegionMax().x + GetWindowPos().x + - you can use: GetCursorScreenPos().x + GetContentRegionAvail().x // when called from left edge of window + - instead of: GetContentRegionMax() + - you can use: GetContentRegionAvail() + GetCursorScreenPos() - GetWindowPos() // right edge in local coordinates + - instead of: GetWindowContentRegionMax().x - GetWindowContentRegionMin().x + - you can use: GetContentRegionAvail() // when called from left edge of window + - 2024/07/15 (1.91.0) - renamed ImGuiSelectableFlags_DontClosePopups to ImGuiSelectableFlags_NoAutoClosePopups. (#1379, #1468, #2200, #4936, #5216, #7302, #7573) + (internals: also renamed ImGuiItemFlags_SelectableDontClosePopup into ImGuiItemFlags_AutoClosePopups with inverted behaviors) + - 2024/07/15 (1.91.0) - obsoleted PushButtonRepeat()/PopButtonRepeat() in favor of using new PushItemFlag(ImGuiItemFlags_ButtonRepeat, ...)/PopItemFlag(). + - 2024/07/02 (1.91.0) - commented out obsolete ImGuiModFlags (renamed to ImGuiKeyChord in 1.89). (#4921, #456) + - commented out obsolete ImGuiModFlags_XXX values (renamed to ImGuiMod_XXX in 1.89). (#4921, #456) + - ImGuiModFlags_Ctrl -> ImGuiMod_Ctrl, ImGuiModFlags_Shift -> ImGuiMod_Shift etc. + - 2024/07/02 (1.91.0) - IO, IME: renamed platform IME hook and added explicit context for consistency and future-proofness. + - old: io.SetPlatformImeDataFn(ImGuiViewport* viewport, ImGuiPlatformImeData* data); + - new: io.PlatformSetImeDataFn(ImGuiContext* ctx, ImGuiViewport* viewport, ImGuiPlatformImeData* data); + - 2024/06/21 (1.90.9) - BeginChild: added ImGuiChildFlags_NavFlattened as a replacement for the window flag ImGuiWindowFlags_NavFlattened: the feature only ever made sense for BeginChild() anyhow. + - old: BeginChild("Name", size, 0, ImGuiWindowFlags_NavFlattened); + - new: BeginChild("Name", size, ImGuiChildFlags_NavFlattened, 0); + - 2024/06/21 (1.90.9) - io: ClearInputKeys() (first exposed in 1.89.8) doesn't clear mouse data, newly added ClearInputMouse() does. + - 2024/06/20 (1.90.9) - renamed ImGuiDragDropFlags_SourceAutoExpirePayload to ImGuiDragDropFlags_PayloadAutoExpire. + - 2024/06/18 (1.90.9) - style: renamed ImGuiCol_TabActive -> ImGuiCol_TabSelected, ImGuiCol_TabUnfocused -> ImGuiCol_TabDimmed, ImGuiCol_TabUnfocusedActive -> ImGuiCol_TabDimmedSelected. + - 2024/06/10 (1.90.9) - removed old nested structure: renaming ImGuiStorage::ImGuiStoragePair type to ImGuiStoragePair (simpler for many languages). + - 2024/06/06 (1.90.8) - reordered ImGuiInputTextFlags values. This should not be breaking unless you are using generated headers that have values not matching the main library. + - 2024/06/06 (1.90.8) - removed 'ImGuiButtonFlags_MouseButtonDefault_ = ImGuiButtonFlags_MouseButtonLeft', was mostly unused and misleading. + - 2024/05/27 (1.90.7) - commented out obsolete symbols marked obsolete in 1.88 (May 2022): + - old: CaptureKeyboardFromApp(bool) + - new: SetNextFrameWantCaptureKeyboard(bool) + - old: CaptureMouseFromApp(bool) + - new: SetNextFrameWantCaptureMouse(bool) + - 2024/05/22 (1.90.7) - inputs (internals): renamed ImGuiKeyOwner_None to ImGuiKeyOwner_NoOwner, to make use more explicit and reduce confusion with the default it is a non-zero value and cannot be the default value (never made public, but disclosing as I expect a few users caught on owner-aware inputs). + - inputs (internals): renamed ImGuiInputFlags_RouteGlobalLow -> ImGuiInputFlags_RouteGlobal, ImGuiInputFlags_RouteGlobal -> ImGuiInputFlags_RouteGlobalOverFocused, ImGuiInputFlags_RouteGlobalHigh -> ImGuiInputFlags_RouteGlobalHighest. + - inputs (internals): Shortcut(), SetShortcutRouting(): swapped last two parameters order in function signatures: + - old: Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0); + - new: Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0, ImGuiID owner_id = 0); + - inputs (internals): owner-aware versions of IsKeyPressed(), IsKeyChordPressed(), IsMouseClicked(): swapped last two parameters order in function signatures. + - old: IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0); + - new: IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id = 0); + - old: IsMouseClicked(ImGuiMouseButton button, ImGuiID owner_id, ImGuiInputFlags flags = 0); + - new: IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGuiID owner_id = 0); + for various reasons those changes makes sense. They are being made because making some of those API public. + only past users of imgui_internal.h with the extra parameters will be affected. Added asserts for valid flags in various functions to detect _some_ misuses, BUT NOT ALL. + - 2024/05/21 (1.90.7) - docking: changed signature of DockSpaceOverViewport() to add explicit dockspace id if desired. pass 0 to use old behavior. (#7611) + - old: DockSpaceOverViewport(const ImGuiViewport* viewport = NULL, ImGuiDockNodeFlags flags = 0, ...); + - new: DockSpaceOverViewport(ImGuiID dockspace_id = 0, const ImGuiViewport* viewport = NULL, ImGuiDockNodeFlags flags = 0, ...); + - 2024/05/16 (1.90.7) - inputs: on macOS X, Cmd and Ctrl keys are now automatically swapped by io.AddKeyEvent() as this naturally align with how macOS X uses those keys. + - it shouldn't really affect you unless you had custom shortcut swapping in place for macOS X apps. + - removed ImGuiMod_Shortcut which was previously dynamically remapping to Ctrl or Cmd/Super. It is now unnecessary to specific cross-platform idiomatic shortcuts. (#2343, #4084, #5923, #456) + - 2024/05/14 (1.90.7) - backends: SDL_Renderer2 and SDL_Renderer3 backend now take a SDL_Renderer* in their RenderDrawData() functions. + - 2024/04/18 (1.90.6) - TreeNode: Fixed a layout inconsistency when using an empty/hidden label followed by a SameLine() call. (#7505, #282) + - old: TreeNode("##Hidden"); SameLine(); Text("Hello"); // <-- This was actually incorrect! BUT appeared to look ok with the default style where ItemSpacing.x == FramePadding.x * 2 (it didn't look aligned otherwise). + - new: TreeNode("##Hidden"); SameLine(0, 0); Text("Hello"); // <-- This is correct for all styles values. + with the fix, IF you were successfully using TreeNode("")+SameLine(); you will now have extra spacing between your TreeNode and the following item. + You'll need to change the SameLine() call to SameLine(0,0) to remove this extraneous spacing. This seemed like the more sensible fix that's not making things less consistent. + (Note: when using this idiom you are likely to also use ImGuiTreeNodeFlags_SpanAvailWidth). + - 2024/03/18 (1.90.5) - merged the radius_x/radius_y parameters in ImDrawList::AddEllipse(), AddEllipseFilled() and PathEllipticalArcTo() into a single ImVec2 parameter. Exceptionally, because those functions were added in 1.90, we are not adding inline redirection functions. The transition is easy and should affect few users. (#2743, #7417) + - 2024/03/08 (1.90.5) - inputs: more formally obsoleted GetKeyIndex() when IMGUI_DISABLE_OBSOLETE_FUNCTIONS is set. It has been unnecessary and a no-op since 1.87 (it returns the same value as passed when used with a 1.87+ backend using io.AddKeyEvent() function). (#4921) + - IsKeyPressed(GetKeyIndex(ImGuiKey_XXX)) -> use IsKeyPressed(ImGuiKey_XXX) - 2024/01/15 (1.90.2) - commented out obsolete ImGuiIO::ImeWindowHandle marked obsolete in 1.87, favor of writing to 'void* ImGuiViewport::PlatformHandleRaw'. - 2023/12/19 (1.90.1) - commented out obsolete ImGuiKey_KeyPadEnter redirection to ImGuiKey_KeypadEnter. - 2023/11/06 (1.90.1) - removed CalcListClipping() marked obsolete in 1.86. Prefer using ImGuiListClipper which can return non-contiguous ranges. @@ -545,7 +611,7 @@ CODE - Backend writing to io.MouseHoveredViewport -> backend should call io.AddMouseViewportEvent() [Docking branch w/ multi-viewports only] note: for all calls to IO new functions, the Dear ImGui context should be bound/current. read https://github.com/ocornut/imgui/issues/4921 for details. - - 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(). Removed GetKeyIndex(), now unecessary. All IsKeyXXX() functions now take ImGuiKey values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release Notes for details. + - 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(). Removed GetKeyIndex(), now unnecessary. All IsKeyXXX() functions now take ImGuiKey values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release Notes for details. - IsKeyPressed(MY_NATIVE_KEY_XXX) -> use IsKeyPressed(ImGuiKey_XXX) - IsKeyPressed(GetKeyIndex(ImGuiKey_XXX)) -> use IsKeyPressed(ImGuiKey_XXX) - Backend writing to io.KeyMap[],io.KeysDown[] -> backend should call io.AddKeyEvent() (+ call io.SetKeyEventNativeData() if you want legacy user code to stil function with legacy key codes). @@ -943,7 +1009,7 @@ CODE A: - Businesses: please reach out to "omar AT dearimgui DOT com" if you work in a place using Dear ImGui! We can discuss ways for your company to fund development via invoiced technical support, maintenance or sponsoring contacts. This is among the most useful thing you can do for Dear ImGui. With increased funding, we sustain and grow work on this project. - Also see https://github.com/ocornut/imgui/wiki/Sponsors + >>> See https://github.com/ocornut/imgui/wiki/Funding - Businesses: you can also purchase licenses for the Dear ImGui Automation/Test Engine. - If you are experienced with Dear ImGui and C++, look at the GitHub issues, look at the Wiki, and see how you want to help and can help! - Disclose your usage of Dear ImGui via a dev blog post, a tweet, a screenshot, a mention somewhere etc. @@ -979,7 +1045,7 @@ CODE #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) +#if defined(_WIN32) && defined(IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) && defined(IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) #define IMGUI_DISABLE_WIN32_FUNCTIONS #endif #if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) @@ -994,9 +1060,11 @@ CODE #else #include #endif -#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) // UWP doesn't have all Win32 functions +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP || WINAPI_FAMILY == WINAPI_FAMILY_GAMES) +// The UWP and GDK Win32 API subsets don't support clipboard nor IME functions #define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS #define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS +#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS #endif #endif @@ -1034,6 +1102,7 @@ CODE #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 +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #elif defined(__GNUC__) // We disable -Wpragmas because GCC doesn't provide a has_warning equivalent and some forks/patches may not follow the warning/version association. #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind @@ -1048,7 +1117,7 @@ CODE #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_SCORING 0 // Display navigation scoring preview when hovering items. Hold CTRL to display for all candidates. CTRL+Arrow to change last direction. #define IMGUI_DEBUG_NAV_RECTS 0 // Display the reference navigation rectangle for each window // 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. @@ -1073,7 +1142,6 @@ static const float DOCKING_TRANSPARENT_PAYLOAD_ALPHA = 0.50f; // For u //------------------------------------------------------------------------- static void SetCurrentWindow(ImGuiWindow* window); -static void FindHoveredWindow(); static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags); static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window); @@ -1089,10 +1157,14 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext*, ImGuiSetti // Platform Dependents default implementation for IO functions static const char* GetClipboardTextFn_DefaultImpl(void* user_data_ctx); static void SetClipboardTextFn_DefaultImpl(void* user_data_ctx, const char* text); -static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport* viewport, ImGuiPlatformImeData* data); +static void PlatformSetImeDataFn_DefaultImpl(ImGuiContext* ctx, ImGuiViewport* viewport, ImGuiPlatformImeData* data); +static bool PlatformOpenInShellFn_DefaultImpl(ImGuiContext* ctx, const char* path); namespace ImGui { +// Item +static void ItemHandleShortcut(ImGuiID id); + // Navigation static void NavUpdate(); static void NavUpdateWindowing(); @@ -1135,6 +1207,7 @@ static void RenderWindowDecorations(ImGuiWindow* window, const ImRec 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 void SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect); // Viewports const ImGuiID IMGUI_VIEWPORT_DEFAULT_ID = 0x11111111; // Using an arbitrary constant instead of e.g. ImHashStr("ViewportDefault", 0); so it's easier to spot in the debugger. The exact value doesn't matter. @@ -1201,59 +1274,61 @@ static void* GImAllocatorUserData = NULL; ImGuiStyle::ImGuiStyle() { - Alpha = 1.0f; // Global alpha applies to everything in Dear ImGui. - DisabledAlpha = 0.60f; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. - WindowPadding = ImVec2(8,8); // Padding within a window - WindowRounding = 0.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and are not recommended. - WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. - WindowMinSize = ImVec2(32,32); // Minimum window size - WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text - WindowMenuButtonPosition= ImGuiDir_Left; // Position of the collapsing/docking button in the title bar (left/right). Defaults to ImGuiDir_Left. - ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows - ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested. - PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows - PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested. - FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets) - FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets). - FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested. - ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines - ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) - CellPadding = ImVec2(4,2); // Padding within a table cell. CellPadding.y may be altered between different rows. - TouchExtraPadding = ImVec2(0,0); // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! - IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). - ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). - ScrollbarSize = 14.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar - ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar - GrabMinSize = 12.0f; // Minimum width/height of a grab box for slider/scrollbar - GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. - LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. - TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. - TabBorderSize = 0.0f; // Thickness of border around tabs. - TabMinWidthForCloseButton = 0.0f; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. - TabBarBorderSize = 1.0f; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. - TableAngledHeadersAngle = 35.0f * (IM_PI / 180.0f); // Angle of angled headers (supported values range from -50 degrees to +50 degrees). - ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. - ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. - SelectableTextAlign = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. - SeparatorTextBorderSize = 3.0f; // Thickkness of border in SeparatorText() - SeparatorTextAlign = ImVec2(0.0f,0.5f);// Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). - SeparatorTextPadding = ImVec2(20.0f,3.f);// Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y. - DisplayWindowPadding = ImVec2(19,19); // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. - DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. - DockingSeparatorSize = 2.0f; // Thickness of resizing border between docked 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. + Alpha = 1.0f; // Global alpha applies to everything in Dear ImGui. + DisabledAlpha = 0.60f; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. + WindowPadding = ImVec2(8,8); // Padding within a window + WindowRounding = 0.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and are not recommended. + WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. + WindowMinSize = ImVec2(32,32); // Minimum window size + WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text + WindowMenuButtonPosition = ImGuiDir_Left; // Position of the collapsing/docking button in the title bar (left/right). Defaults to ImGuiDir_Left. + ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows + ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested. + PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows + PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested. + FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets) + FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets). + FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested. + ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines + ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) + CellPadding = ImVec2(4,2); // Padding within a table cell. Cellpadding.x is locked for entire table. CellPadding.y may be altered between different rows. + TouchExtraPadding = ImVec2(0,0); // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). + ScrollbarSize = 14.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar + ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar + GrabMinSize = 12.0f; // Minimum width/height of a grab box for slider/scrollbar + GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. + TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. + TabBorderSize = 0.0f; // Thickness of border around tabs. + TabMinWidthForCloseButton = 0.0f; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. + TabBarBorderSize = 1.0f; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. + TabBarOverlineSize = 2.0f; // Thickness of tab-bar overline, which highlights the selected tab-bar. + TableAngledHeadersAngle = 35.0f * (IM_PI / 180.0f); // Angle of angled headers (supported values range from -50 degrees to +50 degrees). + TableAngledHeadersTextAlign = ImVec2(0.5f,0.0f);// Alignment of angled headers within the cell + ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. + ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. + SelectableTextAlign = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. + SeparatorTextBorderSize = 3.0f; // Thickness of border in SeparatorText() + SeparatorTextAlign = ImVec2(0.0f,0.5f);// Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). + SeparatorTextPadding = ImVec2(20.0f,3.f);// Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y. + DisplayWindowPadding = ImVec2(19,19); // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. + DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. + DockingSeparatorSize = 2.0f; // Thickness of resizing border between docked windows + MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. + AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. + AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). + AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). + CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + CircleTessellationMaxError = 0.30f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. // Behaviors - HoverStationaryDelay = 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_Stationary). Time required to consider mouse stationary. - HoverDelayShort = 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayShort). Usually used along with HoverStationaryDelay. - HoverDelayNormal = 0.40f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). " - HoverFlagsForTooltipMouse = ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse. - HoverFlagsForTooltipNav = ImGuiHoveredFlags_NoSharedDelay | ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. + HoverStationaryDelay = 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_Stationary). Time required to consider mouse stationary. + HoverDelayShort = 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayShort). Usually used along with HoverStationaryDelay. + HoverDelayNormal = 0.40f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). " + HoverFlagsForTooltipMouse = ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse. + HoverFlagsForTooltipNav = ImGuiHoveredFlags_NoSharedDelay | ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. // Default theme ImGui::StyleColorsDark(this); @@ -1283,6 +1358,7 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor) LogSliderDeadzone = ImTrunc(LogSliderDeadzone * scale_factor); TabRounding = ImTrunc(TabRounding * scale_factor); TabMinWidthForCloseButton = (TabMinWidthForCloseButton != FLT_MAX) ? ImTrunc(TabMinWidthForCloseButton * scale_factor) : FLT_MAX; + TabBarOverlineSize = ImTrunc(TabBarOverlineSize * scale_factor); SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor); DockingSeparatorSize = ImTrunc(DockingSeparatorSize * scale_factor); DisplayWindowPadding = ImTrunc(DisplayWindowPadding * scale_factor); @@ -1335,6 +1411,7 @@ ImGuiIO::ImGuiIO() #else ConfigMacOSXBehaviors = false; #endif + ConfigNavSwapGamepadButtons = false; ConfigInputTrickleEventQueue = true; ConfigInputTextCursorBlink = true; ConfigInputTextEnterKeepActive = false; @@ -1356,6 +1433,7 @@ ImGuiIO::ImGuiIO() // Note: Initialize() will setup default clipboard/ime handlers. BackendPlatformName = BackendRendererName = NULL; BackendPlatformUserData = BackendRendererUserData = BackendLanguageUserData = NULL; + PlatformOpenInShellUserData = NULL; PlatformLocaleDecimalPoint = '.'; // Input (NB: we already have memset zero the entire structure!) @@ -1444,7 +1522,7 @@ void ImGuiIO::ClearEventsQueue() g.InputEventsQueue.clear(); } -// Clear current keyboard/mouse/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons. +// Clear current keyboard/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons. void ImGuiIO::ClearInputKeys() { #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO @@ -1452,12 +1530,26 @@ void ImGuiIO::ClearInputKeys() #endif for (int n = 0; n < IM_ARRAYSIZE(KeysData); n++) { + if (ImGui::IsMouseKey((ImGuiKey)(n + ImGuiKey_KeysData_OFFSET))) + continue; KeysData[n].Down = false; KeysData[n].DownDuration = -1.0f; KeysData[n].DownDurationPrev = -1.0f; } KeyCtrl = KeyShift = KeyAlt = KeySuper = false; KeyMods = ImGuiMod_None; + InputQueueCharacters.resize(0); // Behavior of old ClearInputCharacters(). +} + +void ImGuiIO::ClearInputMouse() +{ + for (ImGuiKey key = ImGuiKey_Mouse_BEGIN; key < ImGuiKey_Mouse_END; key = (ImGuiKey)(key + 1)) + { + ImGuiKeyData* key_data = &KeysData[key - ImGuiKey_KeysData_OFFSET]; + key_data->Down = false; + key_data->DownDuration = -1.0f; + key_data->DownDurationPrev = -1.0f; + } MousePos = ImVec2(-FLT_MAX, -FLT_MAX); for (int n = 0; n < IM_ARRAYSIZE(MouseDown); n++) { @@ -1465,7 +1557,6 @@ void ImGuiIO::ClearInputKeys() MouseDownDuration[n] = MouseDownDurationPrev[n] = -1.0f; } MouseWheel = MouseWheelH = 0.0f; - InputQueueCharacters.resize(0); // Behavior of old ClearInputCharacters(). } // Removed this as it is ambiguous/misleading and generally incorrect to use with the existence of a higher-level input queue. @@ -1499,7 +1590,7 @@ static ImGuiInputEvent* FindLatestInputEvent(ImGuiContext* ctx, ImGuiInputEventT // - bool down: Is the key down? use false to signify a key release. // - float analog_value: 0.0f..1.0f // IMPORTANT: THIS FUNCTION AND OTHER "ADD" GRABS THE CONTEXT FROM OUR INSTANCE. -// WE NEED TO ENSURE THAT ALL FUNCTION CALLS ARE FULLFILLING THIS, WHICH IS WHY GetKeyData() HAS AN EXPLICIT CONTEXT. +// WE NEED TO ENSURE THAT ALL FUNCTION CALLS ARE FULFILLING THIS, WHICH IS WHY GetKeyData() HAS AN EXPLICIT CONTEXT. void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) { //if (e->Down) { IMGUI_DEBUG_LOG_IO("AddKeyEvent() Key='%s' %d, NativeKeycode = %d, NativeScancode = %d\n", ImGui::GetKeyName(e->Key), e->Down, e->NativeKeycode, e->NativeScancode); } @@ -1507,9 +1598,19 @@ void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) if (key == ImGuiKey_None || !AppAcceptingEvents) return; ImGuiContext& g = *Ctx; - IM_ASSERT(ImGui::IsNamedKeyOrModKey(key)); // Backend needs to pass a valid ImGuiKey_ constant. 0..511 values are legacy native key codes which are not accepted by this API. + IM_ASSERT(ImGui::IsNamedKeyOrMod(key)); // Backend needs to pass a valid ImGuiKey_ constant. 0..511 values are legacy native key codes which are not accepted by this API. IM_ASSERT(ImGui::IsAliasKey(key) == false); // Backend cannot submit ImGuiKey_MouseXXX values they are automatically inferred from AddMouseXXX() events. - IM_ASSERT(key != ImGuiMod_Shortcut); // We could easily support the translation here but it seems saner to not accept it (TestEngine perform a translation itself) + + // MacOS: swap Cmd(Super) and Ctrl + if (g.IO.ConfigMacOSXBehaviors) + { + if (key == ImGuiMod_Super) { key = ImGuiMod_Ctrl; } + else if (key == ImGuiMod_Ctrl) { key = ImGuiMod_Super; } + else if (key == ImGuiKey_LeftSuper) { key = ImGuiKey_LeftCtrl; } + else if (key == ImGuiKey_RightSuper){ key = ImGuiKey_RightCtrl; } + else if (key == ImGuiKey_LeftCtrl) { key = ImGuiKey_LeftSuper; } + else if (key == ImGuiKey_RightCtrl) { key = ImGuiKey_RightSuper; } + } // Verify that backend isn't mixing up using new io.AddKeyEvent() api and old io.KeysDown[] + io.KeyMap[] data. #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO @@ -1614,12 +1715,36 @@ void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down) if (!AppAcceptingEvents) return; + // On MacOS X: Convert Ctrl(Super)+Left click into Right-click: handle held button. + if (ConfigMacOSXBehaviors && mouse_button == 0 && MouseCtrlLeftAsRightClick) + { + // Order of both statements matterns: this event will still release mouse button 1 + mouse_button = 1; + if (!down) + MouseCtrlLeftAsRightClick = false; + } + // Filter duplicate const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_MouseButton, (int)mouse_button); const bool latest_button_down = latest_event ? latest_event->MouseButton.Down : g.IO.MouseDown[mouse_button]; if (latest_button_down == down) return; + // On MacOS X: Convert Ctrl(Super)+Left click into Right-click. + // - Note that this is actual physical Ctrl which is ImGuiMod_Super for us. + // - At this point we want from !down to down, so this is handling the initial press. + if (ConfigMacOSXBehaviors && mouse_button == 0 && down) + { + const ImGuiInputEvent* latest_super_event = FindLatestInputEvent(&g, ImGuiInputEventType_Key, (int)ImGuiMod_Super); + if (latest_super_event ? latest_super_event->Key.Down : g.IO.KeySuper) + { + IMGUI_DEBUG_LOG_IO("[io] Super+Left Click aliased into Right Click\n"); + MouseCtrlLeftAsRightClick = true; + AddMouseButtonEvent(1, true); // This is just quicker to write that passing through, as we need to filter duplicate again. + return; + } + } + ImGuiInputEvent e; e.Type = ImGuiInputEventType_MouseButton; e.Source = ImGuiInputSource_Mouse; @@ -2006,6 +2131,10 @@ void ImFormatStringToTempBuffer(const char** out_buf, const char** out_buf_end, va_end(args); } +// FIXME: Should rework API toward allowing multiple in-flight temp buffers (easier and safer for caller) +// by making the caller acquire a temp buffer token, with either explicit or destructor release, e.g. +// ImGuiTempBufferToken token; +// ImFormatStringToTempBuffer(token, ...); void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end, const char* fmt, va_list args) { ImGuiContext& g = *GImGui; @@ -2376,6 +2505,20 @@ const char* ImTextFindPreviousUtf8Codepoint(const char* in_text_start, const cha return in_text_start; } +int ImTextCountLines(const char* in_text, const char* in_text_end) +{ + if (in_text_end == NULL) + in_text_end = in_text + strlen(in_text); // FIXME-OPT: Not optimal approach, discourage use for now. + int count = 0; + while (in_text < in_text_end) + { + const char* line_end = (const char*)memchr(in_text, '\n', in_text_end - in_text); + in_text = line_end ? line_end + 1 : in_text_end; + count++; + } + return count; +} + IM_MSVC_RUNTIME_CHECKS_RESTORE //----------------------------------------------------------------------------- @@ -2469,18 +2612,16 @@ void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& //----------------------------------------------------------------------------- // std::lower_bound but without the bullshit -static ImGuiStorage::ImGuiStoragePair* LowerBound(ImVector& data, ImGuiID key) +ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStoragePair* in_end, ImGuiID key) { - ImGuiStorage::ImGuiStoragePair* first = data.Data; - ImGuiStorage::ImGuiStoragePair* last = data.Data + data.Size; - size_t count = (size_t)(last - first); - while (count > 0) + ImGuiStoragePair* in_p = in_begin; + for (size_t count = (size_t)(in_end - in_p); count > 0; ) { size_t count2 = count >> 1; - ImGuiStorage::ImGuiStoragePair* mid = first + count2; + ImGuiStoragePair* mid = in_p + count2; if (mid->key < key) { - first = ++mid; + in_p = ++mid; count -= count2 + 1; } else @@ -2488,29 +2629,28 @@ static ImGuiStorage::ImGuiStoragePair* LowerBound(ImVectorkey; + ImGuiID rhs_v = ((const ImGuiStoragePair*)rhs)->key; + return (lhs_v > rhs_v ? +1 : lhs_v < rhs_v ? -1 : 0); } // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. void ImGuiStorage::BuildSortByKey() { - 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); + ImQsort(Data.Data, (size_t)Data.Size, sizeof(ImGuiStoragePair), PairComparerByID); } int ImGuiStorage::GetInt(ImGuiID key, int default_val) const { - ImGuiStoragePair* it = LowerBound(const_cast&>(Data), key); - if (it == Data.end() || it->key != key) + ImGuiStoragePair* it = ImLowerBound(const_cast(Data.Data), const_cast(Data.Data + Data.Size), key); + if (it == Data.Data + Data.Size || it->key != key) return default_val; return it->val_i; } @@ -2522,16 +2662,16 @@ bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const { - ImGuiStoragePair* it = LowerBound(const_cast&>(Data), key); - if (it == Data.end() || it->key != key) + ImGuiStoragePair* it = ImLowerBound(const_cast(Data.Data), const_cast(Data.Data + Data.Size), key); + if (it == Data.Data + Data.Size || 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) + ImGuiStoragePair* it = ImLowerBound(const_cast(Data.Data), const_cast(Data.Data + Data.Size), key); + if (it == Data.Data + Data.Size || it->key != key) return NULL; return it->val_p; } @@ -2539,8 +2679,8 @@ void* ImGuiStorage::GetVoidPtr(ImGuiID key) const // 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) + ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); + if (it == Data.Data + Data.Size || it->key != key) it = Data.insert(it, ImGuiStoragePair(key, default_val)); return &it->val_i; } @@ -2552,16 +2692,16 @@ bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val) float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val) { - ImGuiStoragePair* it = LowerBound(Data, key); - if (it == Data.end() || it->key != key) + ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); + if (it == Data.Data + Data.Size || 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) + ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); + if (it == Data.Data + Data.Size || it->key != key) it = Data.insert(it, ImGuiStoragePair(key, default_val)); return &it->val_p; } @@ -2569,8 +2709,8 @@ void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val) // 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) + ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); + if (it == Data.Data + Data.Size || it->key != key) Data.insert(it, ImGuiStoragePair(key, val)); else it->val_i = val; @@ -2583,8 +2723,8 @@ void ImGuiStorage::SetBool(ImGuiID key, bool val) void ImGuiStorage::SetFloat(ImGuiID key, float val) { - ImGuiStoragePair* it = LowerBound(Data, key); - if (it == Data.end() || it->key != key) + ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); + if (it == Data.Data + Data.Size || it->key != key) Data.insert(it, ImGuiStoragePair(key, val)); else it->val_f = val; @@ -2592,8 +2732,8 @@ void ImGuiStorage::SetFloat(ImGuiID key, float val) void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val) { - ImGuiStoragePair* it = LowerBound(Data, key); - if (it == Data.end() || it->key != key) + ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key); + if (it == Data.Data + Data.Size || it->key != key) Data.insert(it, ImGuiStoragePair(key, val)); else it->val_p = val; @@ -2604,6 +2744,7 @@ void ImGuiStorage::SetAllInt(int v) for (int i = 0; i < Data.Size; i++) Data[i].val_i = v; } +IM_MSVC_RUNTIME_CHECKS_RESTORE //----------------------------------------------------------------------------- // [SECTION] ImGuiTextFilter @@ -2671,15 +2812,15 @@ void ImGuiTextFilter::Build() bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const { - if (Filters.empty()) + if (Filters.Size == 0) return true; if (text == NULL) - text = ""; + text = text_end = ""; for (const ImGuiTextRange& f : Filters) { - if (f.empty()) + if (f.b == f.e) continue; if (f.b[0] == '-') { @@ -2846,15 +2987,6 @@ static void ImGuiListClipper_SeekCursorAndSetupPrevLine(float pos_y, float line_ } } -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)); @@ -2891,6 +3023,7 @@ void ImGuiListClipper::Begin(int items_count, float items_height) data->Reset(this); data->LossynessOffset = window->DC.CursorStartPosLossyness.y; TempData = data; + StartSeekOffsetY = data->LossynessOffset; } void ImGuiListClipper::End() @@ -2901,7 +3034,7 @@ void ImGuiListClipper::End() ImGuiContext& g = *Ctx; IMGUI_DEBUG_LOG_CLIPPER("Clipper: End() in '%s'\n", g.CurrentWindow->Name); if (ItemsCount >= 0 && ItemsCount < INT_MAX && DisplayStart >= 0) - ImGuiListClipper_SeekCursorForItem(this, ItemsCount); + SeekCursorForItem(ItemsCount); // Restore temporary buffer and fix back pointers which may be invalidated when nesting IM_ASSERT(data->ListClipper == this); @@ -2925,6 +3058,17 @@ void ImGuiListClipper::IncludeItemsByIndex(int item_begin, int item_end) data->Ranges.push_back(ImGuiListClipperRange::FromIndices(item_begin, item_end)); } +// This is already called while stepping. +// The ONLY reason you may want to call this is if you passed INT_MAX to ImGuiListClipper::Begin() because you couldn't step item count beforehand. +void ImGuiListClipper::SeekCursorForItem(int item_n) +{ + // - Perform the add and multiply with double to allow seeking through larger ranges. + // - StartPosY starts from ItemsFrozen, by adding SeekOffsetY we generally cancel that out (SeekOffsetY == LossynessOffset - ItemsFrozen * ItemsHeight). + // - The reason we store SeekOffsetY instead of inferring it, is because we want to allow user to perform Seek after the last step, where ImGuiListClipperData is already done. + float pos_y = (float)((double)StartPosY + StartSeekOffsetY + (double)item_n * ItemsHeight); + ImGuiListClipper_SeekCursorAndSetupPrevLine(pos_y, ItemsHeight); +} + static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) { ImGuiContext& g = *clipper->Ctx; @@ -2979,7 +3123,8 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision(clipper->StartPosY) || ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y); if (affected_by_floating_point_precision) clipper->ItemsHeight = window->DC.PrevLineSize.y + g.Style.ItemSpacing.y; // FIXME: Technically wouldn't allow multi-line entries. - + if (clipper->ItemsHeight == 0.0f && clipper->ItemsCount == INT_MAX) // Accept that no item have been submitted if in indeterminate mode. + return false; IM_ASSERT(clipper->ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!"); calc_clipping = true; // If item height had to be calculated, calculate clipping afterwards. } @@ -2988,6 +3133,9 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) const int already_submitted = clipper->DisplayEnd; if (calc_clipping) { + // Record seek offset, this is so ImGuiListClipper::Seek() can be called after ImGuiListClipperData is done + clipper->StartSeekOffsetY = (double)data->LossynessOffset - data->ItemsFrozen * (double)clipper->ItemsHeight; + if (g.LogEnabled) { // If logging is active, do not perform any clipping @@ -3008,9 +3156,27 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) data->Ranges.push_back(ImGuiListClipperRange::FromPositions(nav_rect_abs.Min.y, nav_rect_abs.Max.y, 0, 0)); // Add visible range + float min_y = window->ClipRect.Min.y; + float max_y = window->ClipRect.Max.y; + + // Add box selection range + ImGuiBoxSelectState* bs = &g.BoxSelectState; + if (bs->IsActive && bs->Window == window) + { + // FIXME: Selectable() use of half-ItemSpacing isn't consistent in matter of layout, as ItemAdd(bb) stray above ItemSize()'s CursorPos. + // RangeSelect's BoxSelect relies on comparing overlap of previous and current rectangle and is sensitive to that. + // As a workaround we currently half ItemSpacing worth on each side. + min_y -= g.Style.ItemSpacing.y; + max_y += g.Style.ItemSpacing.y; + + // Box-select on 2D area requires different clipping. + if (bs->UnclipMode) + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(bs->UnclipRect.Min.y, bs->UnclipRect.Max.y, 0, 0)); + } + const int off_min = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Up) ? -1 : 0; const int off_max = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Down) ? 1 : 0; - data->Ranges.push_back(ImGuiListClipperRange::FromPositions(window->ClipRect.Min.y, window->ClipRect.Max.y, off_min, off_max)); + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(min_y, max_y, off_min, off_max)); } // Convert position ranges to item index ranges @@ -3035,7 +3201,7 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) clipper->DisplayStart = ImMax(data->Ranges[data->StepNo].Min, already_submitted); clipper->DisplayEnd = ImMin(data->Ranges[data->StepNo].Max, clipper->ItemsCount); if (clipper->DisplayStart > already_submitted) //-V1051 - ImGuiListClipper_SeekCursorForItem(clipper, clipper->DisplayStart); + clipper->SeekCursorForItem(clipper->DisplayStart); data->StepNo++; if (clipper->DisplayStart == clipper->DisplayEnd && data->StepNo < data->Ranges.Size) continue; @@ -3045,7 +3211,7 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) // After the last step: Let the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), // Advance the cursor to the end of the list and then returns 'false' to end the loop. if (clipper->ItemsCount < INT_MAX) - ImGuiListClipper_SeekCursorForItem(clipper, clipper->ItemsCount); + clipper->SeekCursorForItem(clipper->ItemsCount); return false; } @@ -3158,41 +3324,45 @@ void ImGui::PopStyleColor(int count) static const ImGuiCol GWindowDockStyleColors[ImGuiWindowDockStyleCol_COUNT] = { - ImGuiCol_Text, ImGuiCol_Tab, ImGuiCol_TabHovered, ImGuiCol_TabActive, ImGuiCol_TabUnfocused, ImGuiCol_TabUnfocusedActive + ImGuiCol_Text, ImGuiCol_TabHovered, ImGuiCol_Tab, ImGuiCol_TabSelected, ImGuiCol_TabSelectedOverline, ImGuiCol_TabDimmed, ImGuiCol_TabDimmedSelected, ImGuiCol_TabDimmedSelectedOverline, }; static const ImGuiDataVarInfo GStyleVarInfo[] = { - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, DisabledAlpha) }, // ImGuiStyleVar_DisabledAlpha - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, CellPadding) }, // ImGuiStyleVar_CellPadding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBarBorderSize) }, // ImGuiStyleVar_TabBarBorderSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize) },// ImGuiStyleVar_SeparatorTextBorderSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) }, // ImGuiStyleVar_SeparatorTextAlign - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding) }, // ImGuiStyleVar_SeparatorTextPadding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, DockingSeparatorSize) }, // ImGuiStyleVar_DockingSeparatorSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, DisabledAlpha) }, // ImGuiStyleVar_DisabledAlpha + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, CellPadding) }, // ImGuiStyleVar_CellPadding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBorderSize) }, // ImGuiStyleVar_TabBorderSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBarBorderSize) }, // ImGuiStyleVar_TabBarBorderSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBarOverlineSize) }, // ImGuiStyleVar_TabBarOverlineSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersAngle)}, // ImGuiStyleVar_TableAngledHeadersAngle + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)}, // ImGuiStyleVar_SeparatorTextBorderSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) }, // ImGuiStyleVar_SeparatorTextAlign + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding) }, // ImGuiStyleVar_SeparatorTextPadding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, DockingSeparatorSize) }, // ImGuiStyleVar_DockingSeparatorSize }; const ImGuiDataVarInfo* ImGui::GetStyleVarInfo(ImGuiStyleVar idx) @@ -3289,11 +3459,13 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) 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_Tab: return "Tab"; + case ImGuiCol_TabSelected: return "TabSelected"; + case ImGuiCol_TabSelectedOverline: return "TabSelectedOverline"; + case ImGuiCol_TabDimmed: return "TabDimmed"; + case ImGuiCol_TabDimmedSelected: return "TabDimmedSelected"; + case ImGuiCol_TabDimmedSelectedOverline: return "TabDimmedSelectedOverline"; case ImGuiCol_DockingPreview: return "DockingPreview"; case ImGuiCol_DockingEmptyBg: return "DockingEmptyBg"; case ImGuiCol_PlotLines: return "PlotLines"; @@ -3305,6 +3477,7 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) case ImGuiCol_TableBorderLight: return "TableBorderLight"; case ImGuiCol_TableRowBg: return "TableRowBg"; case ImGuiCol_TableRowBgAlt: return "TableRowBgAlt"; + case ImGuiCol_TextLink: return "TextLink"; case ImGuiCol_TextSelectedBg: return "TextSelectedBg"; case ImGuiCol_DragDropTarget: return "DragDropTarget"; case ImGuiCol_NavHighlight: return "NavHighlight"; @@ -3450,7 +3623,7 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con const ImFont* font = draw_list->_Data->Font; const float font_size = draw_list->_Data->FontSize; - const float font_scale = font_size / font->FontSize; + const float font_scale = draw_list->_Data->FontScale; const char* text_end_ellipsis = NULL; const float ellipsis_width = font->EllipsisWidth * font_scale; @@ -3627,7 +3800,7 @@ void ImGui::DestroyContext(ImGuiContext* ctx) IM_DELETE(ctx); } -// IMPORTANT: ###xxx suffixes must be same in ALL languages +// IMPORTANT: ###xxx suffixes must be same in ALL languages to allow for automation. static const ImGuiLocEntry GLocalizationEntriesEnUS[] = { { ImGuiLocKey_VersionStr, "Dear ImGui " IMGUI_VERSION " (" IM_STRINGIFY(IMGUI_VERSION_NUM) ")" }, @@ -3638,6 +3811,7 @@ static const ImGuiLocEntry GLocalizationEntriesEnUS[] = { ImGuiLocKey_WindowingMainMenuBar, "(Main menu bar)" }, { ImGuiLocKey_WindowingPopup, "(Popup)" }, { ImGuiLocKey_WindowingUntitled, "(Untitled)" }, + { ImGuiLocKey_CopyLink, "Copy Link###CopyLink" }, { ImGuiLocKey_DockingHideTabBar, "Hide tab bar###HideTabBar" }, { ImGuiLocKey_DockingHoldShiftToDock, "Hold SHIFT to enable Docking window." }, { ImGuiLocKey_DockingDragToUndockOrMoveNode,"Click and drag to move or undock whole node." }, @@ -3669,7 +3843,8 @@ void ImGui::Initialize() g.IO.GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations g.IO.SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; g.IO.ClipboardUserData = (void*)&g; // Default implementation use the ImGuiContext as user data (ideally those would be arguments to the function) - g.IO.SetPlatformImeDataFn = SetPlatformImeDataFn_DefaultImpl; + g.IO.PlatformOpenInShellFn = PlatformOpenInShellFn_DefaultImpl; + g.IO.PlatformSetImeDataFn = PlatformSetImeDataFn_DefaultImpl; // Create default viewport ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)(); @@ -3749,7 +3924,7 @@ void ImGui::Shutdown() g.FontStack.clear(); g.OpenPopupStack.clear(); g.BeginPopupStack.clear(); - g.NavTreeNodeStack.clear(); + g.TreeNodeStack.clear(); g.CurrentViewport = g.MouseViewport = g.MouseLastHoveredViewport = NULL; g.Viewports.clear_delete(); @@ -3764,6 +3939,9 @@ void ImGui::Shutdown() g.TablesTempData.clear_destruct(); g.DrawChannelsTempMergeBuffer.clear(); + g.MultiSelectStorage.Clear(); + g.MultiSelectTempData.clear_destruct(); + g.ClipboardHandlerData.clear(); g.MenusIdSubmittedThisFrame.clear(); g.InputTextState.ClearFreeMemory(); @@ -3861,45 +4039,6 @@ ImGuiWindow::~ImGuiWindow() ColumnsStorage.clear_destruct(); } -ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) -{ - ImGuiID seed = IDStack.back(); - ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed); - ImGuiContext& g = *Ctx; - if (g.DebugHookIdInfo == id) - ImGui::DebugHookIdInfo(id, ImGuiDataType_String, str, str_end); - return id; -} - -ImGuiID ImGuiWindow::GetID(const void* ptr) -{ - ImGuiID seed = IDStack.back(); - ImGuiID id = ImHashData(&ptr, sizeof(void*), seed); - ImGuiContext& g = *Ctx; - if (g.DebugHookIdInfo == id) - ImGui::DebugHookIdInfo(id, ImGuiDataType_Pointer, ptr, NULL); - return id; -} - -ImGuiID ImGuiWindow::GetID(int n) -{ - ImGuiID seed = IDStack.back(); - ImGuiID id = ImHashData(&n, sizeof(n), seed); - ImGuiContext& g = *Ctx; - if (g.DebugHookIdInfo == id) - ImGui::DebugHookIdInfo(id, ImGuiDataType_S32, (void*)(intptr_t)n, NULL); - return id; -} - -// This is only used in rare/specific situations to manufacture an ID out of nowhere. -ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs) -{ - ImGuiID seed = IDStack.back(); - ImRect r_rel = ImGui::WindowRectAbsToRel(this, r_abs); - ImGuiID id = ImHashData(&r_rel, sizeof(r_rel), seed); - return id; -} - static void SetCurrentWindow(ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -3908,6 +4047,7 @@ static void SetCurrentWindow(ImGuiWindow* window) if (window) { g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); + g.FontScale = g.FontSize / g.Font->FontSize; ImGui::NavUpdateCurrentWindowIsScrollPushableX(); } } @@ -3917,6 +4057,8 @@ void ImGui::GcCompactTransientMiscBuffers() ImGuiContext& g = *GImGui; g.ItemFlagsStack.clear(); g.GroupStack.clear(); + g.MultiSelectTempDataStacked = 0; + g.MultiSelectTempData.clear_destruct(); TableGcCompactSettings(); } @@ -4001,9 +4143,6 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) // (Please note that this is WIP and not all keys/inputs are thoroughly declared by all widgets yet) g.ActiveIdUsingNavDirMask = 0x00; g.ActiveIdUsingAllKeyboardKeys = false; -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - g.ActiveIdUsingNavInputMask = 0x00; -#endif } void ImGui::ClearActiveID() @@ -4041,7 +4180,7 @@ void ImGui::MarkItemEdited(ImGuiID id) // We accept a MarkItemEdited() on drag and drop targets (see https://github.com/ocornut/imgui/issues/1875#issuecomment-978243343) // We accept 'ActiveIdPreviousFrame == id' for InputText() returning an edit after it has been taken ActiveId away (#4714) - IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id); + IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id || (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive)); //IM_ASSERT(g.CurrentWindow->DC.LastItemId == id); g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; @@ -4213,7 +4352,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag // Done with rectangle culling so we can perform heavier checks now. if (!(item_flags & ImGuiItemFlags_NoWindowHoverableCheck) && !IsWindowContentHoverable(window, ImGuiHoveredFlags_None)) { - g.HoveredIdDisabled = true; + g.HoveredIdIsDisabled = true; return false; } @@ -4235,6 +4374,12 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag if (g.HoveredIdPreviousFrame != id) return false; } + + // Display shortcut (only works with mouse) + // (ImGuiItemStatusFlags_HasShortcut in LastItemData denotes we want a tooltip) + if (id == g.LastItemData.ID && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasShortcut)) + if (IsItemHovered(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_DelayNormal)) + SetTooltip("%s", GetKeyChordName(g.LastItemData.Shortcut)); } // When disabled we'll return false but still set HoveredId @@ -4243,7 +4388,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag // Release active id if turning disabled if (g.ActiveId == id && id != 0) ClearActiveID(); - g.HoveredIdDisabled = true; + g.HoveredIdIsDisabled = true; return false; } @@ -4275,7 +4420,7 @@ bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id) ImGuiWindow* window = g.CurrentWindow; if (!bb.Overlaps(window->ClipRect)) if (id == 0 || (id != g.ActiveId && id != g.ActiveIdPreviousFrame && id != g.NavId && id != g.NavActivateId)) - if (!g.LogEnabled) + if (!g.ItemUnclipByLog) return true; return false; } @@ -4389,7 +4534,7 @@ ImGuiIO& ImGui::GetIO() ImGuiPlatformIO& ImGui::GetPlatformIO() { - IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); + IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext()?"); return GImGui->PlatformIO; } @@ -4437,26 +4582,18 @@ static ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t draw ImDrawList* ImGui::GetBackgroundDrawList(ImGuiViewport* viewport) { + if (viewport == NULL) + viewport = GImGui->CurrentWindow->Viewport; return GetViewportBgFgDrawList((ImGuiViewportP*)viewport, 0, "##Background"); } -ImDrawList* ImGui::GetBackgroundDrawList() -{ - ImGuiContext& g = *GImGui; - return GetBackgroundDrawList(g.CurrentWindow->Viewport); -} - ImDrawList* ImGui::GetForegroundDrawList(ImGuiViewport* viewport) { + if (viewport == NULL) + viewport = GImGui->CurrentWindow->Viewport; return GetViewportBgFgDrawList((ImGuiViewportP*)viewport, 1, "##Foreground"); } -ImDrawList* ImGui::GetForegroundDrawList() -{ - ImGuiContext& g = *GImGui; - return GetForegroundDrawList(g.CurrentWindow->Viewport); -} - ImDrawListSharedData* ImGui::GetDrawListSharedData() { return &GImGui->DrawListSharedData; @@ -4606,7 +4743,7 @@ void ImGui::UpdateMouseMovingWindowEndFrame() 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) + if (g.HoveredIdIsDisabled) g.MovingWindow = NULL; } else if (root_window == NULL && g.NavWindow != NULL) @@ -4662,6 +4799,9 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() { ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; + + // FIXME-DPI: This storage was added on 2021/03/31 for test engine, but if we want to multiply WINDOWS_HOVER_PADDING + // by DpiScale, we need to make this window-agnostic anyhow, maybe need storing inside ImGuiWindow. g.WindowsHoverPadding = ImMax(g.Style.TouchExtraPadding, ImVec2(WINDOWS_HOVER_PADDING, WINDOWS_HOVER_PADDING)); // Find the window hovered by mouse: @@ -4669,15 +4809,16 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() // - 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(); + FindHoveredWindowEx(g.IO.MousePos, false, &g.HoveredWindow, &g.HoveredWindowUnderMovingWindow); IM_ASSERT(g.HoveredWindow == NULL || g.HoveredWindow == g.MovingWindow || g.HoveredWindow->Viewport == g.MouseViewport); + g.HoveredWindowBeforeClear = g.HoveredWindow; // Modal windows prevents mouse from hovering behind them. ImGuiWindow* modal_window = GetTopMostPopupModal(); if (modal_window && g.HoveredWindow && !IsWindowWithinBeginStackOf(g.HoveredWindow->RootWindow, modal_window)) // FIXME-MERGE: RootWindowDockTree ? clear_hovered_windows = true; - // Disabled mouse? + // Disabled mouse hovering (we don't currently clear MousePos, we could) if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) clear_hovered_windows = true; @@ -4695,7 +4836,7 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() io.MouseDownOwnedUnlessPopupClose[i] = (g.HoveredWindow != NULL) || has_open_modal; } mouse_any_down |= io.MouseDown[i]; - if (io.MouseDown[i]) + if (io.MouseDown[i] || io.MouseReleased[i]) // Increase release frame for our evaluation of earliest button (#1392) if (mouse_earliest_down == -1 || io.MouseClickedTime[i] < io.MouseClickedTime[mouse_earliest_down]) mouse_earliest_down = i; } @@ -4734,6 +4875,27 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() io.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false; } +// Calling SetupDrawListSharedData() is followed by SetCurrentFont() which sets up the remaining data. +static void SetupDrawListSharedData() +{ + ImGuiContext& g = *GImGui; + ImRect virtual_space(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); + for (ImGuiViewportP* viewport : g.Viewports) + virtual_space.Add(viewport->GetMainRect()); + g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4(); + g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; + g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError); + g.DrawListSharedData.InitialFlags = ImDrawListFlags_None; + if (g.Style.AntiAliasedLines) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines; + if (g.Style.AntiAliasedLinesUseTex && !(g.IO.Fonts->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; +} + void ImGui::NewFrame() { IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?"); @@ -4779,23 +4941,9 @@ void ImGui::NewFrame() // Setup current font and draw list shared data // FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal! g.IO.Fonts->Locked = true; + SetupDrawListSharedData(); SetCurrentFont(GetDefaultFont()); IM_ASSERT(g.Font->IsLoaded()); - ImRect virtual_space(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); - for (ImGuiViewportP* viewport : g.Viewports) - virtual_space.Add(viewport->GetMainRect()); - g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4(); - g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; - g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError); - g.DrawListSharedData.InitialFlags = ImDrawListFlags_None; - if (g.Style.AntiAliasedLines) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines; - if (g.Style.AntiAliasedLinesUseTex && !(g.Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedLines)) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex; - if (g.Style.AntiAliasedFill) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill; - if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset; // Mark rendering data as invalid to prevent user who may have a handle on it to use it. for (ImGuiViewportP* viewport : g.Viewports) @@ -4820,7 +4968,7 @@ void ImGui::NewFrame() g.HoveredIdPreviousFrame = g.HoveredId; g.HoveredId = 0; g.HoveredIdAllowOverlap = false; - g.HoveredIdDisabled = false; + g.HoveredIdIsDisabled = false; // Clear ActiveID if the item is not alive anymore. // In 1.87, the common most call to KeepAliveID() was moved from GetID() to ItemAdd(). @@ -4848,25 +4996,8 @@ void ImGui::NewFrame() { g.ActiveIdUsingNavDirMask = 0x00; g.ActiveIdUsingAllKeyboardKeys = false; -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - g.ActiveIdUsingNavInputMask = 0x00; -#endif } -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - if (g.ActiveId == 0) - g.ActiveIdUsingNavInputMask = 0; - else if (g.ActiveIdUsingNavInputMask != 0) - { - // If your custom widget code used: { g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel); } - // Since IMGUI_VERSION_NUM >= 18804 it should be: { SetKeyOwner(ImGuiKey_Escape, g.ActiveId); SetKeyOwner(ImGuiKey_NavGamepadCancel, g.ActiveId); } - if (g.ActiveIdUsingNavInputMask & (1 << ImGuiNavInput_Cancel)) - SetKeyOwner(ImGuiKey_Escape, g.ActiveId); - if (g.ActiveIdUsingNavInputMask & ~(1 << ImGuiNavInput_Cancel)) - IM_ASSERT(0); // Other values unsupported - } -#endif - // Record when we have been stationary as this state is preserved while over same item. // FIXME: The way this is expressed means user cannot alter HoverStationaryDelay during the frame to use varying values. // To allow this we should store HoverItemMaxStationaryTime+ID and perform the >= check in IsItemHovered() function. @@ -4985,7 +5116,8 @@ void ImGui::NewFrame() g.CurrentWindowStack.resize(0); g.BeginPopupStack.resize(0); g.ItemFlagsStack.resize(0); - g.ItemFlagsStack.push_back(ImGuiItemFlags_None); + g.ItemFlagsStack.push_back(ImGuiItemFlags_AutoClosePopups); // Default flags + g.CurrentItemFlags = g.ItemFlagsStack.back(); g.GroupStack.resize(0); // Docking @@ -5287,13 +5419,13 @@ void ImGui::EndFrame() // Notify Platform/OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME) ImGuiPlatformImeData* ime_data = &g.PlatformImeData; - if (g.IO.SetPlatformImeDataFn && memcmp(ime_data, &g.PlatformImeDataPrev, sizeof(ImGuiPlatformImeData)) != 0) + if (g.IO.PlatformSetImeDataFn != NULL && memcmp(ime_data, &g.PlatformImeDataPrev, sizeof(ImGuiPlatformImeData)) != 0) { ImGuiViewport* viewport = FindViewportByID(g.PlatformImeViewport); - IMGUI_DEBUG_LOG_IO("[io] Calling io.SetPlatformImeDataFn(): WantVisible: %d, InputPos (%.2f,%.2f)\n", ime_data->WantVisible, ime_data->InputPos.x, ime_data->InputPos.y); + IMGUI_DEBUG_LOG_IO("[io] Calling io.PlatformSetImeDataFn(): WantVisible: %d, InputPos (%.2f,%.2f)\n", ime_data->WantVisible, ime_data->InputPos.x, ime_data->InputPos.y); if (viewport == NULL) viewport = GetMainViewport(); - g.IO.SetPlatformImeDataFn(viewport, ime_data); + g.IO.PlatformSetImeDataFn(&g, viewport, ime_data); } // Hide implicit/fallback "Debug" window if it hasn't been used @@ -5314,12 +5446,16 @@ void ImGui::EndFrame() 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)); + bool is_elapsed = (g.DragDropSourceFrameCount + 1 < g.FrameCount) && ((g.DragDropSourceFlags & ImGuiDragDropFlags_PayloadAutoExpire) || g.DragDropMouseButton == -1 || !IsMouseDown(g.DragDropMouseButton)); if (is_delivered || is_elapsed) ClearDragDrop(); } - // Drag and Drop: Fallback for source tooltip. This is not ideal but better than nothing. + // Drag and Drop: Fallback for missing source tooltip. This is not ideal but better than nothing. + // If you want to handle source item disappearing: instead of submitting your description tooltip + // in the BeginDragDropSource() block of the dragged item, you can submit them from a safe single spot + // (e.g. end of your item loop, or before EndFrame) by reading payload data. + // In the typical case, the contents of drag tooltip should be possible to infer solely from payload data. if (g.DragDropActive && g.DragDropSourceFrameCount < g.FrameCount && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) { g.DragDropWithinSource = true; @@ -5463,22 +5599,26 @@ ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_tex } // 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() +// - Typically write output back to g.HoveredWindow and g.HoveredWindowUnderMovingWindow. +// - FIXME: Note that we have an inconsequential lag here: OuterRectClipped is updated in Begin(), so windows moved programmatically +// 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. +// - The 'find_first_and_in_any_viewport = true' mode is only used by TestEngine. It is simpler to maintain here. +void ImGui::FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_viewport, ImGuiWindow** out_hovered_window, ImGuiWindow** out_hovered_window_under_moving_window) { ImGuiContext& g = *GImGui; + ImGuiWindow* hovered_window = NULL; + ImGuiWindow* hovered_window_under_moving_window = NULL; // Special handling for the window being moved: Ignore the mouse viewport check (because it may reset/lose its viewport during the undocking frame) - ImGuiViewportP* moving_window_viewport = g.MovingWindow ? g.MovingWindow->Viewport : NULL; - if (g.MovingWindow) + ImGuiViewportP* backup_moving_window_viewport = NULL; + if (find_first_and_in_any_viewport == false && g.MovingWindow) + { + backup_moving_window_viewport = g.MovingWindow->Viewport; g.MovingWindow->Viewport = g.MouseViewport; - - ImGuiWindow* hovered_window = NULL; - ImGuiWindow* hovered_window_ignoring_moving_window = NULL; - if (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoMouseInputs)) - hovered_window = g.MovingWindow; + if (!(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; @@ -5496,7 +5636,7 @@ static void FindHoveredWindow() // Using the clipped AABB, a child window will typically be clipped by its parent (not always) ImVec2 hit_padding = (window->Flags & (ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) ? padding_regular : padding_for_resize; - if (!window->OuterRectClipped.ContainsWithPad(g.IO.MousePos, hit_padding)) + if (!window->OuterRectClipped.ContainsWithPad(pos, hit_padding)) continue; // Support for one rectangular hole in any given window @@ -5505,24 +5645,32 @@ static void FindHoveredWindow() { 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)) + if (ImRect(hole_pos, hole_pos + hole_size).Contains(pos)) continue; } - if (hovered_window == NULL) + if (find_first_and_in_any_viewport) + { hovered_window = window; - IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer. - if (hovered_window_ignoring_moving_window == NULL && (!g.MovingWindow || window->RootWindowDockTree != g.MovingWindow->RootWindowDockTree)) - hovered_window_ignoring_moving_window = window; - if (hovered_window && hovered_window_ignoring_moving_window) break; + } + else + { + if (hovered_window == NULL) + hovered_window = window; + IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer. + if (hovered_window_under_moving_window == NULL && (!g.MovingWindow || window->RootWindowDockTree != g.MovingWindow->RootWindowDockTree)) + hovered_window_under_moving_window = window; + if (hovered_window && hovered_window_under_moving_window) + break; + } } - g.HoveredWindow = hovered_window; - g.HoveredWindowUnderMovingWindow = hovered_window_ignoring_moving_window; - - if (g.MovingWindow) - g.MovingWindow->Viewport = moving_window_viewport; + *out_hovered_window = hovered_window; + if (out_hovered_window_under_moving_window != NULL) + *out_hovered_window_under_moving_window = hovered_window_under_moving_window; + if (find_first_and_in_any_viewport == false && g.MovingWindow) + g.MovingWindow->Viewport = backup_moving_window_viewport; } bool ImGui::IsItemActive() @@ -5585,9 +5733,15 @@ bool ImGui::IsItemToggledOpen() return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledOpen) ? true : false; } +// Call after a Selectable() or TreeNode() involved in multi-selection. +// Useful if you need the per-item information before reaching EndMultiSelect(), e.g. for rendering purpose. +// This is only meant to be called inside a BeginMultiSelect()/EndMultiSelect() block. +// (Outside of multi-select, it would be misleading/ambiguous to report this signal, as widgets +// return e.g. a pressed event and user code is in charge of altering selection in ways we cannot predict.) bool ImGui::IsItemToggledSelection() { ImGuiContext& g = *GImGui; + IM_ASSERT(g.CurrentMultiSelect != NULL); // Can only be used inside a BeginMultiSelect()/EndMultiSelect() return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledSelection) ? true : false; } @@ -5647,7 +5801,8 @@ void ImGui::SetItemAllowOverlap() } #endif -// FIXME: It might be undesirable that this will likely disable KeyOwner-aware shortcuts systems. Consider a more fine-tuned version for the two users of this function. +// This is a shortcut for not taking ownership of 100+ keys, frequently used by drag operations. +// FIXME: It might be undesirable that this will likely disable KeyOwner-aware shortcuts systems. Consider a more fine-tuned version if needed? void ImGui::SetActiveIdUsingAllKeyboardKeys() { ImGuiContext& g = *GImGui; @@ -5701,7 +5856,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I IM_ASSERT(id != 0); // Sanity check as it is likely that some user will accidentally pass ImGuiWindowFlags into the ImGuiChildFlags argument. - const ImGuiChildFlags ImGuiChildFlags_SupportedMask_ = ImGuiChildFlags_Border | ImGuiChildFlags_AlwaysUseWindowPadding | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle; + const ImGuiChildFlags ImGuiChildFlags_SupportedMask_ = ImGuiChildFlags_Border | ImGuiChildFlags_AlwaysUseWindowPadding | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle | ImGuiChildFlags_NavFlattened; IM_UNUSED(ImGuiChildFlags_SupportedMask_); IM_ASSERT((child_flags & ~ImGuiChildFlags_SupportedMask_) == 0 && "Illegal ImGuiChildFlags value. Did you pass ImGuiWindowFlags values instead of ImGuiChildFlags?"); IM_ASSERT((window_flags & ImGuiWindowFlags_AlwaysAutoResize) == 0 && "Cannot specify ImGuiWindowFlags_AlwaysAutoResize for BeginChild(). Use ImGuiChildFlags_AlwaysAutoResize!"); @@ -5713,6 +5868,8 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS if (window_flags & ImGuiWindowFlags_AlwaysUseWindowPadding) child_flags |= ImGuiChildFlags_AlwaysUseWindowPadding; + if (window_flags & ImGuiWindowFlags_NavFlattened) + child_flags |= ImGuiChildFlags_NavFlattened; #endif if (child_flags & ImGuiChildFlags_AutoResizeX) child_flags &= ~ImGuiChildFlags_ResizeX; @@ -5791,7 +5948,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I const ImGuiID temp_id_for_activation = ImHashStr("##Child", 0, id); if (g.ActiveId == temp_id_for_activation) ClearActiveID(); - if (g.NavActivateId == id && !(window_flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavWindowHasScrollY)) + if (g.NavActivateId == id && !(child_flags & ImGuiChildFlags_NavFlattened) && (child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavWindowHasScrollY)) { FocusWindow(child_window); NavInitWindow(child_window, false); @@ -5817,7 +5974,8 @@ void ImGui::EndChild() ImGuiWindow* parent_window = g.CurrentWindow; ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + child_size); ItemSize(child_size); - if ((child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavWindowHasScrollY) && !(child_window->Flags & ImGuiWindowFlags_NavFlattened)) + const bool nav_flattened = (child_window->ChildFlags & ImGuiChildFlags_NavFlattened) != 0; + if ((child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavWindowHasScrollY) && !nav_flattened) { ItemAdd(bb, child_window->ChildId); RenderNavHighlight(bb, child_window->ChildId); @@ -5829,10 +5987,12 @@ void ImGui::EndChild() else { // Not navigable into - ItemAdd(bb, 0); + // - This is a bit of a fringe use case, mostly useful for undecorated, non-scrolling contents childs, or empty childs. + // - We could later decide to not apply this path if ImGuiChildFlags_FrameStyle or ImGuiChildFlags_Borders is set. + ItemAdd(bb, child_window->ChildId, NULL, ImGuiItemFlags_NoNav); // But when flattened we directly reach items, adjust active layer mask accordingly - if (child_window->Flags & ImGuiWindowFlags_NavFlattened) + if (nav_flattened) parent_window->DC.NavLayersActiveMaskNext |= child_window->DC.NavLayersActiveMaskNext; } if (g.HoveredWindow == child_window) @@ -5988,7 +6148,7 @@ static inline ImVec2 CalcWindowMinSize(ImGuiWindow* window) // Reduce artifacts with very small windows ImGuiWindow* window_for_height = GetWindowForTitleAndMenuHeight(window); - size_min.y = ImMax(size_min.y, window_for_height->TitleBarHeight() + window_for_height->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); + size_min.y = ImMax(size_min.y, window_for_height->TitleBarHeight + window_for_height->MenuBarHeight + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); return size_min; } @@ -6058,16 +6218,32 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont { // Maximum window size is determined by the viewport size or monitor size ImVec2 size_min = CalcWindowMinSize(window); - ImVec2 size_max = (window->ViewportOwned || ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_Popup))) ? ImVec2(FLT_MAX, FLT_MAX) : ImGui::GetMainViewport()->WorkSize - style.DisplaySafeAreaPadding * 2.0f; - const int monitor_idx = window->ViewportAllowPlatformMonitorExtend; - if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0) - size_max = g.PlatformIO.Monitors[monitor_idx].WorkSize - style.DisplaySafeAreaPadding * 2.0f; + ImVec2 size_max = ImVec2(FLT_MAX, FLT_MAX); + + // Child windows are layed within their parent (unless they are also popups/menus) and thus have no restriction + if ((window->Flags & ImGuiWindowFlags_ChildWindow) == 0 || (window->Flags & ImGuiWindowFlags_Popup) != 0) + { + if (!window->ViewportOwned) + size_max = ImGui::GetMainViewport()->WorkSize - style.DisplaySafeAreaPadding * 2.0f; + const int monitor_idx = window->ViewportAllowPlatformMonitorExtend; + if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size) + size_max = g.PlatformIO.Monitors[monitor_idx].WorkSize - style.DisplaySafeAreaPadding * 2.0f; + } + ImVec2 size_auto_fit = ImClamp(size_desired, size_min, ImMax(size_min, size_max)); + // FIXME: CalcWindowAutoFitSize() doesn't take into account that only one axis may be auto-fit when calculating scrollbars, + // we may need to compute/store three variants of size_auto_fit, for x/y/xy. + // Here we implement a workaround for child windows only, but a full solution would apply to normal windows as well: + if ((window->ChildFlags & ImGuiChildFlags_ResizeX) && !(window->ChildFlags & ImGuiChildFlags_ResizeY)) + size_auto_fit.y = window->SizeFull.y; + else if (!(window->ChildFlags & ImGuiChildFlags_ResizeX) && (window->ChildFlags & ImGuiChildFlags_ResizeY)) + size_auto_fit.x = window->SizeFull.x; + // When the window cannot fit all contents (either because of constraints, either because screen is too small), // we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than ViewportSize-WindowPadding. ImVec2 size_auto_fit_after_constraint = CalcWindowSizeAfterConstraint(window, size_auto_fit); - bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x - size_pad.x - decoration_w_without_scrollbars < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar); + bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x - size_pad.x - decoration_w_without_scrollbars < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar); bool will_have_scrollbar_y = (size_auto_fit_after_constraint.y - size_pad.y - decoration_h_without_scrollbars < size_contents.y && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar); if (will_have_scrollbar_x) size_auto_fit.y += style.ScrollbarSize; @@ -6188,13 +6364,13 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si int ret_auto_fit_mask = 0x00; const float grip_draw_size = IM_TRUNC(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f)); - const float grip_hover_inner_size = IM_TRUNC(grip_draw_size * 0.75f); + const float grip_hover_inner_size = (resize_grip_count > 0) ? IM_TRUNC(grip_draw_size * 0.75f) : 0.0f; const float grip_hover_outer_size = g.IO.ConfigWindowsResizeFromEdges ? WINDOWS_HOVER_PADDING : 0.0f; ImRect clamp_rect = visibility_rect; const bool window_move_from_title_bar = g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar); if (window_move_from_title_bar) - clamp_rect.Min.y -= window->TitleBarHeight(); + clamp_rect.Min.y -= window->TitleBarHeight; ImVec2 pos_target(FLT_MAX, FLT_MAX); ImVec2 size_target(FLT_MAX, FLT_MAX); @@ -6279,6 +6455,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si if (held && g.IO.MouseDoubleClicked[0]) { // Double-clicking bottom or right border auto-fit on this axis + // FIXME: CalcWindowAutoFitSize() doesn't take into account that only one side may be auto-fit when calculating scrollbars. // FIXME: Support top and right borders: rework CalcResizePosSizeFromAnyCorner() to be reusable in both cases. if (border_n == 1 || border_n == 3) // Right and bottom border { @@ -6326,10 +6503,14 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si border_target = ImClamp(border_target, clamp_min, clamp_max); if (flags & ImGuiWindowFlags_ChildWindow) // Clamp resizing of childs within parent { - if ((flags & (ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar)) == 0 || (flags & ImGuiWindowFlags_NoScrollbar)) - border_target.x = ImClamp(border_target.x, window->ParentWindow->InnerClipRect.Min.x, window->ParentWindow->InnerClipRect.Max.x); - if (flags & ImGuiWindowFlags_NoScrollbar) - border_target.y = ImClamp(border_target.y, window->ParentWindow->InnerClipRect.Min.y, window->ParentWindow->InnerClipRect.Max.y); + ImGuiWindow* parent_window = window->ParentWindow; + ImGuiWindowFlags parent_flags = parent_window->Flags; + ImRect border_limit_rect = parent_window->InnerRect; + border_limit_rect.Expand(ImVec2(-ImMax(parent_window->WindowPadding.x, parent_window->WindowBorderSize), -ImMax(parent_window->WindowPadding.y, parent_window->WindowBorderSize))); + if ((axis == ImGuiAxis_X) && ((parent_flags & (ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar)) == 0 || (parent_flags & ImGuiWindowFlags_NoScrollbar))) + border_target.x = ImClamp(border_target.x, border_limit_rect.Min.x, border_limit_rect.Max.x); + if ((axis == ImGuiAxis_Y) && (parent_flags & ImGuiWindowFlags_NoScrollbar)) + border_target.y = ImClamp(border_target.y, border_limit_rect.Min.y, border_limit_rect.Max.y); } if (!ignore_resize) CalcResizePosSizeFromAnyCorner(window, border_target, ImMin(def.SegmentN1, def.SegmentN2), &pos_target, &size_target); @@ -6398,8 +6579,10 @@ static inline void ClampWindowPos(ImGuiWindow* window, const ImRect& visibility_ { ImGuiContext& g = *GImGui; ImVec2 size_for_clamping = window->Size; - if (g.IO.ConfigWindowsMoveFromTitleBarOnly && (!(window->Flags & ImGuiWindowFlags_NoTitleBar) || window->DockNodeAsHost)) + if (g.IO.ConfigWindowsMoveFromTitleBarOnly && window->DockNodeAsHost) size_for_clamping.y = ImGui::GetFrameHeight(); // Not using window->TitleBarHeight() as DockNodeAsHost will report 0.0f here. + else 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); } @@ -6435,7 +6618,7 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) } if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive) { - float y = window->Pos.y + window->TitleBarHeight() - 1; + 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), border_col, g.Style.FrameBorderSize); } } @@ -6509,7 +6692,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar ImDrawList* bg_draw_list = window->DockIsActive ? window->DockNode->HostWindow->DrawList : window->DrawList; if (window->DockIsActive || (flags & ImGuiWindowFlags_DockNodeHost)) bg_draw_list->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG); - bg_draw_list->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom); + bg_draw_list->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom); if (window->DockIsActive || (flags & ImGuiWindowFlags_DockNodeHost)) bg_draw_list->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG); } @@ -6691,13 +6874,37 @@ void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags window->RootWindowPopupTree = parent_window->RootWindowPopupTree; if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) // FIXME: simply use _NoTitleBar ? window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight; - while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened) + while (window->RootWindowForNav->ChildFlags & ImGuiChildFlags_NavFlattened) { IM_ASSERT(window->RootWindowForNav->ParentWindow != NULL); window->RootWindowForNav = window->RootWindowForNav->ParentWindow; } } +// [EXPERIMENTAL] Called by Begin(). NextWindowData is valid at this point. +// This is designed as a toy/test-bed for +void ImGui::UpdateWindowSkipRefresh(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + window->SkipRefresh = false; + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasRefreshPolicy) == 0) + return; + if (g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_TryToAvoidRefresh) + { + // FIXME-IDLE: Tests for e.g. mouse clicks or keyboard while focused. + if (window->Appearing) // If currently appearing + return; + if (window->Hidden) // If was hidden (previous frame) + return; + if ((g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_RefreshOnHover) && g.HoveredWindow && window->RootWindow == g.HoveredWindow->RootWindow) + return; + if ((g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_RefreshOnFocus) && g.NavWindow && window->RootWindow == g.NavWindow->RootWindow) + return; + window->DrawList = NULL; + window->SkipRefresh = true; + } +} + // 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. @@ -6710,7 +6917,7 @@ void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags // - WindowE // .. returns NULL // Notes: // - FindBlockingModal(NULL) == NULL is generally equivalent to GetTopMostPopupModal() == NULL. -// Only difference is here we check for ->Active/WasActive but it may be unecessary. +// Only difference is here we check for ->Active/WasActive but it may be unnecessary. ImGuiWindow* ImGui::FindBlockingModal(ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -6763,9 +6970,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) 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); @@ -6849,6 +7053,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window_stack_data.Window = window; window_stack_data.ParentLastItemDataBackup = g.LastItemData; window_stack_data.StackSizesOnBegin.SetToContextState(&g); + window_stack_data.DisabledOverrideReenable = (flags & ImGuiWindowFlags_Tooltip) && (g.CurrentItemFlags & ImGuiItemFlags_Disabled); g.CurrentWindowStack.push_back(window_stack_data); if (flags & ImGuiWindowFlags_ChildMenu) g.BeginMenuDepth++; @@ -6876,10 +7081,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } // Add to focus scope stack - PushFocusScope((flags & ImGuiWindowFlags_NavFlattened) ? g.CurrentFocusScopeId : window->ID); + PushFocusScope((window->ChildFlags & ImGuiChildFlags_NavFlattened) ? g.CurrentFocusScopeId : window->ID); window->NavRootFocusScopeId = g.CurrentFocusScopeId; - // Add to popup stack + // Add to popup stacks: update OpenPopupStack[] data, push to BeginPopupStack[] if (flags & ImGuiWindowFlags_Popup) { ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size]; @@ -6945,11 +7150,18 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->Appearing) SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false); + // [EXPERIMENTAL] Skip Refresh mode + UpdateWindowSkipRefresh(window); + + // Nested root windows (typically tooltips) override disabled state + if (window_stack_data.DisabledOverrideReenable && window->RootWindow == window) + BeginDisabledOverrideReenable(); + // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow() g.CurrentWindow = NULL; // When reusing window again multiple times a frame, just append content (don't need to setup again) - if (first_begin_of_the_frame) + if (first_begin_of_the_frame && !window->SkipRefresh) { // Initialize const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345) @@ -7041,9 +7253,17 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Lock menu offset so size calculation can use it as menu-bar windows need a minimum size. window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x); window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y; + window->TitleBarHeight = (flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : g.FontSize + g.Style.FramePadding.y * 2.0f; + window->MenuBarHeight = (flags & ImGuiWindowFlags_MenuBar) ? window->DC.MenuBarOffset.y + g.FontSize + g.Style.FramePadding.y * 2.0f : 0.0f; + // Depending on condition we use previous or current window size to compare against contents size to decide if a scrollbar should be visible. + // Those flags will be altered further down in the function depending on more conditions. bool use_current_size_for_scrollbar_x = window_just_created; bool use_current_size_for_scrollbar_y = window_just_created; + if (window_size_x_set_by_api && window->ContentSizeExplicit.x != 0.0f) + use_current_size_for_scrollbar_x = true; + if (window_size_y_set_by_api && window->ContentSizeExplicit.y != 0.0f) // #7252 + use_current_size_for_scrollbar_y = true; // Collapse window by double-clicking on title bar // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing @@ -7051,8 +7271,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { // 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 (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max)) + if (g.IO.MouseClickedCount[0] == 2 && GetKeyOwner(ImGuiKey_MouseLeft) == ImGuiKeyOwner_NoOwner) + window->WantCollapseToggle = true; if (window->WantCollapseToggle) { window->Collapsed = !window->Collapsed; @@ -7070,11 +7291,11 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // SIZE // Outer Decoration Sizes - // (we need to clear ScrollbarSize immediatly as CalcWindowAutoFitSize() needs it and can be called from other locations). + // (we need to clear ScrollbarSize immediately as CalcWindowAutoFitSize() needs it and can be called from other locations). const ImVec2 scrollbar_sizes_from_last_frame = window->ScrollbarSizes; window->DecoOuterSizeX1 = 0.0f; window->DecoOuterSizeX2 = 0.0f; - window->DecoOuterSizeY1 = window->TitleBarHeight() + window->MenuBarHeight(); + window->DecoOuterSizeY1 = window->TitleBarHeight + window->MenuBarHeight; window->DecoOuterSizeY2 = 0.0f; window->ScrollbarSizes = ImVec2(0.0f, 0.0f); @@ -7283,7 +7504,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) 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->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); // Amend the partially filled window->DecorationXXX values. @@ -7305,7 +7526,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) const ImRect title_bar_rect = window->TitleBarRect(); window->OuterRectClipped = outer_rect; if (window->DockIsActive) - window->OuterRectClipped.Min.y += window->TitleBarHeight(); + window->OuterRectClipped.Min.y += window->TitleBarHeight; window->OuterRectClipped.ClipWith(host_rect); // Inner rectangle @@ -7320,16 +7541,18 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->DecoOuterSizeY2; // Inner clipping rectangle. - // Will extend a little bit outside the normal work region. - // This is to allow e.g. Selectable or CollapsingHeader or some separators to cover that space. - // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. + // - Extend a outside of normal work region up to borders. + // - This is to allow e.g. Selectable or CollapsingHeader or some separators to cover that space. + // - It also makes clipped items be more noticeable. + // - And is consistent on both axis (prior to 2024/05/03 ClipRect used WindowPadding.x * 0.5f on left and right edge), see #3312 + // - 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(ImTrunc(window->WindowPadding.x * 0.5f), window->WindowBorderSize)); + window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + 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(ImTrunc(window->WindowPadding.x * 0.5f), window->WindowBorderSize)); + window->InnerClipRect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - window->WindowBorderSize); window->InnerClipRect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y - window->WindowBorderSize); window->InnerClipRect.ClipWithFull(host_rect); @@ -7445,7 +7668,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) 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.TreeHasStackDataDepthMask = 0x00; window->DC.ChildWindows.resize(0); window->DC.StateStorage = &window->StateStorage; window->DC.CurrentColumns = NULL; @@ -7464,6 +7687,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->AutoFitFramesY > 0) window->AutoFitFramesY--; + // Clear SetNextWindowXXX data (can aim to move this higher in the function) + g.NextWindowData.ClearFlags(); + // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) // We ImGuiFocusRequestFlags_UnlessBelowModal to: // - Avoid focusing a window that is created outside of a modal. This will prevent active modal from being closed. @@ -7514,10 +7740,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // 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. - if (window->DockIsActive) - SetLastItemData(window->MoveId, g.CurrentItemFlags, window->DockTabItemStatusFlags, window->DockTabItemRect); - else - SetLastItemData(window->MoveId, g.CurrentItemFlags, IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, title_bar_rect); + SetLastItemDataForWindow(window, title_bar_rect); // [DEBUG] #ifndef IMGUI_DISABLE_DEBUG_TOOLS @@ -7533,21 +7756,26 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } else { + // Skip refresh always mark active + if (window->SkipRefresh) + window->Active = true; + // Append SetCurrentViewport(window, window->Viewport); SetCurrentWindow(window); + g.NextWindowData.ClearFlags(); + SetLastItemDataForWindow(window, window->TitleBarRect()); } - if (!(flags & ImGuiWindowFlags_DockNodeHost)) + if (!(flags & ImGuiWindowFlags_DockNodeHost) && !window->SkipRefresh) PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true); // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) window->WriteAccessed = false; window->BeginCount++; - g.NextWindowData.ClearFlags(); // Update visibility - if (first_begin_of_the_frame) + if (first_begin_of_the_frame && !window->SkipRefresh) { // When we are about to select this tab (which will only be visible on the _next frame_), flag it with a non-zero HiddenFramesCannotSkipItems. // This will have the important effect of actually returning true in Begin() and not setting SkipItems, allowing an earlier submission of the window contents. @@ -7565,8 +7793,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { // 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 || window->DockIsActive); - const bool nav_request = (flags & ImGuiWindowFlags_NavFlattened) && (g.NavAnyRequest && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav); + IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0 || window->DockIsActive); + const bool nav_request = (window->ChildFlags & ImGuiChildFlags_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) { @@ -7615,6 +7843,11 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->SkipItems && !window->Appearing) IM_ASSERT(window->Appearing == false); // Please report on GitHub if this triggers: https://github.com/ocornut/imgui/issues/4177 } + else if (first_begin_of_the_frame) + { + // Skip refresh mode + window->SkipItems = true; + } // [DEBUG] io.ConfigDebugBeginReturnValue override return value to test Begin/End and BeginChild/EndChild behaviors. // (The implicit fallback window is NOT automatically ended allowing it to always be able to receive commands without crashing) @@ -7631,6 +7864,15 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) return !window->SkipItems; } +static void ImGui::SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect) +{ + ImGuiContext& g = *GImGui; + if (window->DockIsActive) + SetLastItemData(window->MoveId, g.CurrentItemFlags, window->DockTabItemStatusFlags, window->DockTabItemRect); + else + SetLastItemData(window->MoveId, g.CurrentItemFlags, IsMouseHoveringRect(rect.Min, rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, rect); +} + void ImGui::End() { ImGuiContext& g = *GImGui; @@ -7642,7 +7884,7 @@ void ImGui::End() IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size > 1, "Calling End() too many times!"); return; } - IM_ASSERT(g.CurrentWindowStack.Size > 0); + ImGuiWindowStackData& window_stack_data = g.CurrentWindowStack.back(); // Error checking: verify that user doesn't directly call End() on a child window. if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_DockNodeHost) && !window->DockIsActive) @@ -7651,9 +7893,17 @@ void ImGui::End() // Close anything that is open if (window->DC.CurrentColumns) EndColumns(); - if (!(window->Flags & ImGuiWindowFlags_DockNodeHost)) // Pop inner window clip rectangle + if (!(window->Flags & ImGuiWindowFlags_DockNodeHost) && !window->SkipRefresh) // Pop inner window clip rectangle PopClipRect(); PopFocusScope(); + if (window_stack_data.DisabledOverrideReenable && window->RootWindow == window) + EndDisabledOverrideReenable(); + + if (window->SkipRefresh) + { + IM_ASSERT(window->DrawList == NULL); + window->DrawList = &window->DrawListInst; + } // Stop logging if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging @@ -7668,12 +7918,12 @@ void ImGui::End() host_window->DC.CursorMaxPos = window->DC.CursorMaxPos + window->WindowPadding - host_window->WindowPadding; // Pop from window stack - g.LastItemData = g.CurrentWindowStack.back().ParentLastItemDataBackup; + g.LastItemData = window_stack_data.ParentLastItemDataBackup; if (window->Flags & ImGuiWindowFlags_ChildMenu) g.BeginMenuDepth--; if (window->Flags & ImGuiWindowFlags_Popup) g.BeginPopupStack.pop_back(); - g.CurrentWindowStack.back().StackSizesOnBegin.CompareWithContextState(&g); + window_stack_data.StackSizesOnBegin.CompareWithContextState(&g); g.CurrentWindowStack.pop_back(); SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window); if (g.CurrentWindow) @@ -7767,9 +8017,13 @@ void ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags) if ((flags & ImGuiFocusRequestFlags_UnlessBelowModal) && (g.NavWindow != window)) // Early out in common case. if (ImGuiWindow* blocking_modal = FindBlockingModal(window)) { + // This block would typically be reached in two situations: + // - API call to FocusWindow() with a window under a modal and ImGuiFocusRequestFlags_UnlessBelowModal flag. + // - User clicking on void or anything behind a modal while a modal is open (window == NULL) IMGUI_DEBUG_LOG_FOCUS("[focus] FocusWindow(\"%s\", UnlessBelowModal): prevented by \"%s\".\n", window ? window->Name : "", blocking_modal->Name); if (window && window == window->RootWindow && (window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0) - BringWindowToDisplayBehind(window, blocking_modal); // Still bring to right below modal. + BringWindowToDisplayBehind(window, blocking_modal); // Still bring right under modal. (FIXME: Could move in focus list too?) + ClosePopupsOverWindow(GetTopMostPopupModal(), false); // Note how we need to use GetTopMostPopupModal() aad NOT blocking_modal, to handle nested modals return; } @@ -7870,12 +8124,14 @@ void ImGui::SetCurrentFont(ImFont* font) g.Font = font; g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale); g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; + g.FontScale = g.FontSize / g.Font->FontSize; ImFontAtlas* atlas = g.Font->ContainerAtlas; g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; g.DrawListSharedData.TexUvLines = atlas->TexUvLines; g.DrawListSharedData.Font = g.Font; g.DrawListSharedData.FontSize = g.FontSize; + g.DrawListSharedData.FontScale = g.FontScale; } void ImGui::PushFont(ImFont* font) @@ -7934,7 +8190,7 @@ void ImGui::BeginDisabled(bool disabled) } if (was_disabled || disabled) g.CurrentItemFlags |= ImGuiItemFlags_Disabled; - g.ItemFlagsStack.push_back(g.CurrentItemFlags); + g.ItemFlagsStack.push_back(g.CurrentItemFlags); // FIXME-OPT: can we simply skip this and use DisabledStackSize? g.DisabledStackSize++; } @@ -7951,24 +8207,27 @@ void ImGui::EndDisabled() g.Style.Alpha = g.DisabledAlphaBackup; //PopStyleVar(); } -void ImGui::PushTabStop(bool tab_stop) +// Could have been called BeginDisabledDisable() but it didn't want to be award nominated for most awkward function name. +// Ideally we would use a shared e.g. BeginDisabled()->BeginDisabledEx() but earlier needs to be optimal. +// The whole code for this is awkward, will reevaluate if we find a way to implement SetNextItemDisabled(). +void ImGui::BeginDisabledOverrideReenable() { - PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); + ImGuiContext& g = *GImGui; + IM_ASSERT(g.CurrentItemFlags & ImGuiItemFlags_Disabled); + g.Style.Alpha = g.DisabledAlphaBackup; + g.CurrentItemFlags &= ~ImGuiItemFlags_Disabled; + g.ItemFlagsStack.push_back(g.CurrentItemFlags); + g.DisabledStackSize++; } -void ImGui::PopTabStop() +void ImGui::EndDisabledOverrideReenable() { - PopItemFlag(); -} - -void ImGui::PushButtonRepeat(bool repeat) -{ - PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); -} - -void ImGui::PopButtonRepeat() -{ - PopItemFlag(); + ImGuiContext& g = *GImGui; + g.DisabledStackSize--; + IM_ASSERT(g.DisabledStackSize > 0); + g.ItemFlagsStack.pop_back(); + g.CurrentItemFlags = g.ItemFlagsStack.back(); + g.Style.Alpha = g.DisabledAlphaBackup * g.Style.DisabledAlpha; } void ImGui::PushTextWrapPos(float wrap_pos_x) @@ -8088,7 +8347,7 @@ bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) // When changing hovered window we requires a bit of stationary delay before activating hover timer. // FIXME: We don't support delay other than stationary one for now, other delay would need a way - // to fullfill the possibility that multiple IsWindowHovered() with varying flag could return true + // to fulfill the possibility that multiple IsWindowHovered() with varying flag could return true // for different windows of the hierarchy. Possibly need a Hash(Current+Flags) ==> (Timer) cache. // We can implement this for _Stationary because the data is linked to HoveredWindow rather than CurrentWindow. if (flags & ImGuiHoveredFlags_ForTooltip) @@ -8402,6 +8661,14 @@ void ImGui::SetNextWindowClass(const ImGuiWindowClass* window_class) g.NextWindowData.WindowClass = *window_class; } +// This is experimental and meant to be a toy for exploring a future/wider range of features. +void ImGui::SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasRefreshPolicy; + g.NextWindowData.RefreshFlagsVal = flags; +} + ImDrawList* ImGui::GetWindowDrawList() { ImGuiWindow* window = GetCurrentWindow(); @@ -8443,6 +8710,7 @@ void ImGui::SetWindowFontScale(float scale) ImGuiWindow* window = GetCurrentWindow(); window->FontWindowScale = scale; g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); + g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize; } void ImGui::PushFocusScope(ImGuiID id) @@ -8585,6 +8853,70 @@ ImGuiStorage* ImGui::GetStateStorage() return window->DC.StateStorage; } +bool ImGui::IsRectVisible(const ImVec2& size) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size)); +} + +bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->ClipRect.Overlaps(ImRect(rect_min, rect_max)); +} + +//----------------------------------------------------------------------------- +// [SECTION] ID STACK +//----------------------------------------------------------------------------- + +// This is one of the very rare legacy case where we use ImGuiWindow methods, +// it should ideally be flattened at some point but it's been used a lots by widgets. +IM_MSVC_RUNTIME_CHECKS_OFF +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); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + ImGuiContext& g = *Ctx; + if (g.DebugHookIdInfo == id) + ImGui::DebugHookIdInfo(id, ImGuiDataType_String, str, str_end); +#endif + return id; +} + +ImGuiID ImGuiWindow::GetID(const void* ptr) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHashData(&ptr, sizeof(void*), seed); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + ImGuiContext& g = *Ctx; + if (g.DebugHookIdInfo == id) + ImGui::DebugHookIdInfo(id, ImGuiDataType_Pointer, ptr, NULL); +#endif + return id; +} + +ImGuiID ImGuiWindow::GetID(int n) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHashData(&n, sizeof(n), seed); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + ImGuiContext& g = *Ctx; + if (g.DebugHookIdInfo == id) + ImGui::DebugHookIdInfo(id, ImGuiDataType_S32, (void*)(intptr_t)n, NULL); +#endif + 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; +} + void ImGui::PushID(const char* str_id) { ImGuiContext& g = *GImGui; @@ -8622,8 +8954,10 @@ void ImGui::PushOverrideID(ImGuiID id) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; +#ifndef IMGUI_DISABLE_DEBUG_TOOLS if (g.DebugHookIdInfo == id) DebugHookIdInfo(id, ImGuiDataType_ID, NULL, NULL); +#endif window->IDStack.push_back(id); } @@ -8633,18 +8967,22 @@ void ImGui::PushOverrideID(ImGuiID id) ImGuiID ImGui::GetIDWithSeed(const char* str, const char* str_end, ImGuiID seed) { ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiContext& g = *GImGui; if (g.DebugHookIdInfo == id) DebugHookIdInfo(id, ImGuiDataType_String, str, str_end); +#endif return id; } ImGuiID ImGui::GetIDWithSeed(int n, ImGuiID seed) { ImGuiID id = ImHashData(&n, sizeof(n), seed); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiContext& g = *GImGui; if (g.DebugHookIdInfo == id) DebugHookIdInfo(id, ImGuiDataType_S32, (void*)(intptr_t)n, NULL); +#endif return id; } @@ -8673,22 +9011,18 @@ ImGuiID ImGui::GetID(const void* ptr_id) return window->GetID(ptr_id); } -bool ImGui::IsRectVisible(const ImVec2& size) +ImGuiID ImGui::GetID(int int_id) { ImGuiWindow* window = GImGui->CurrentWindow; - return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size)); + return window->GetID(int_id); } - -bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) -{ - ImGuiWindow* window = GImGui->CurrentWindow; - return window->ClipRect.Overlaps(ImRect(rect_min, rect_max)); -} - +IM_MSVC_RUNTIME_CHECKS_RESTORE //----------------------------------------------------------------------------- // [SECTION] INPUTS //----------------------------------------------------------------------------- +// - GetModForModKey() [Internal] +// - FixupKeyChord() [Internal] // - GetKeyData() [Internal] // - GetKeyIndex() [Internal] // - GetKeyName() @@ -8750,23 +9084,25 @@ bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) // - Shortcut() [Internal] //----------------------------------------------------------------------------- -ImGuiKeyChord ImGui::FixupKeyChord(ImGuiContext* ctx, ImGuiKeyChord key_chord) +static ImGuiKeyChord GetModForModKey(ImGuiKey key) { - // Convert ImGuiMod_Shortcut and add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified. + if (key == ImGuiKey_LeftCtrl || key == ImGuiKey_RightCtrl) + return ImGuiMod_Ctrl; + if (key == ImGuiKey_LeftShift || key == ImGuiKey_RightShift) + return ImGuiMod_Shift; + if (key == ImGuiKey_LeftAlt || key == ImGuiKey_RightAlt) + return ImGuiMod_Alt; + if (key == ImGuiKey_LeftSuper || key == ImGuiKey_RightSuper) + return ImGuiMod_Super; + return ImGuiMod_None; +} + +ImGuiKeyChord ImGui::FixupKeyChord(ImGuiKeyChord key_chord) +{ + // Add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified. ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); if (IsModKey(key)) - { - if (key == ImGuiKey_LeftCtrl || key == ImGuiKey_RightCtrl) - key_chord |= ImGuiMod_Ctrl; - if (key == ImGuiKey_LeftShift || key == ImGuiKey_RightShift) - key_chord |= ImGuiMod_Shift; - if (key == ImGuiKey_LeftAlt || key == ImGuiKey_RightAlt) - key_chord |= ImGuiMod_Alt; - if (key == ImGuiKey_LeftSuper || key == ImGuiKey_RightSuper) - key_chord |= ImGuiMod_Super; - } - if (key_chord & ImGuiMod_Shortcut) - return (key_chord & ~ImGuiMod_Shortcut) | (ctx->IO.ConfigMacOSXBehaviors ? ImGuiMod_Super : ImGuiMod_Ctrl); + key_chord |= GetModForModKey(key); return key_chord; } @@ -8776,7 +9112,7 @@ ImGuiKeyData* ImGui::GetKeyData(ImGuiContext* ctx, ImGuiKey key) // Special storage location for mods if (key & ImGuiMod_Mask_) - key = ConvertSingleModFlagToKey(ctx, key); + key = ConvertSingleModFlagToKey(key); #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO IM_ASSERT(key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_NamedKey_END); @@ -8789,6 +9125,7 @@ ImGuiKeyData* ImGui::GetKeyData(ImGuiContext* ctx, ImGuiKey key) } #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO +// Formally moved to obsolete section in 1.90.5 in spite of documented as obsolete since 1.87 ImGuiKey ImGui::GetKeyIndex(ImGuiKey key) { ImGuiContext& g = *GImGui; @@ -8827,10 +9164,12 @@ IM_STATIC_ASSERT(ImGuiKey_NamedKey_COUNT == IM_ARRAYSIZE(GKeyNames)); const char* ImGui::GetKeyName(ImGuiKey key) { - ImGuiContext& g = *GImGui; + if (key == ImGuiKey_None) + return "None"; #ifdef IMGUI_DISABLE_OBSOLETE_KEYIO - IM_ASSERT((IsNamedKeyOrModKey(key) || key == ImGuiKey_None) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend and user code."); + IM_ASSERT(IsNamedKeyOrMod(key) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend and user code."); #else + ImGuiContext& g = *GImGui; if (IsLegacyKey(key)) { if (g.IO.KeyMap[key] == -1) @@ -8839,27 +9178,33 @@ const char* ImGui::GetKeyName(ImGuiKey key) key = (ImGuiKey)g.IO.KeyMap[key]; } #endif - if (key == ImGuiKey_None) - return "None"; if (key & ImGuiMod_Mask_) - key = ConvertSingleModFlagToKey(&g, key); + key = ConvertSingleModFlagToKey(key); if (!IsNamedKey(key)) return "Unknown"; return GKeyNames[key - ImGuiKey_NamedKey_BEGIN]; } -// ImGuiMod_Shortcut is translated to either Ctrl or Super. +// Return untranslated names: on macOS, Cmd key will show as Ctrl, Ctrl key will show as super. +// Lifetime of return value: valid until next call to same function. const char* ImGui::GetKeyChordName(ImGuiKeyChord key_chord) { ImGuiContext& g = *GImGui; - key_chord = FixupKeyChord(&g, key_chord); + + const ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); + if (IsModKey(key)) + key_chord &= ~GetModForModKey(key); // Return "Ctrl+LeftShift" instead of "Ctrl+Shift+LeftShift" ImFormatString(g.TempKeychordName, IM_ARRAYSIZE(g.TempKeychordName), "%s%s%s%s%s", (key_chord & ImGuiMod_Ctrl) ? "Ctrl+" : "", (key_chord & ImGuiMod_Shift) ? "Shift+" : "", (key_chord & ImGuiMod_Alt) ? "Alt+" : "", - (key_chord & ImGuiMod_Super) ? (g.IO.ConfigMacOSXBehaviors ? "Cmd+" : "Super+") : "", - GetKeyName((ImGuiKey)(key_chord & ~ImGuiMod_Mask_))); + (key_chord & ImGuiMod_Super) ? "Super+" : "", + (key != ImGuiKey_None || key_chord == ImGuiKey_None) ? GetKeyName(key) : ""); + size_t len; + if (key == ImGuiKey_None && key_chord != 0) + if ((len = strlen(g.TempKeychordName)) != 0) // Remove trailing '+' + g.TempKeychordName[len - 1] = 0; return g.TempKeychordName; } @@ -8929,9 +9274,9 @@ static void ImGui::UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt) routing_entry = &rt->Entries[old_routing_idx]; routing_entry->RoutingCurrScore = routing_entry->RoutingNextScore; routing_entry->RoutingCurr = routing_entry->RoutingNext; // Update entry - routing_entry->RoutingNext = ImGuiKeyOwner_None; + routing_entry->RoutingNext = ImGuiKeyOwner_NoOwner; routing_entry->RoutingNextScore = 255; - if (routing_entry->RoutingCurr == ImGuiKeyOwner_None) + if (routing_entry->RoutingCurr == ImGuiKeyOwner_NoOwner) continue; rt->EntriesNext.push_back(*routing_entry); // Write alive ones into new buffer @@ -8940,7 +9285,7 @@ static void ImGui::UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt) if (routing_entry->Mods == g.IO.KeyMods) { ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key); - if (owner_data->OwnerCurr == ImGuiKeyOwner_None) + if (owner_data->OwnerCurr == ImGuiKeyOwner_NoOwner) { owner_data->OwnerCurr = routing_entry->RoutingCurr; //IMGUI_DEBUG_LOG("SetKeyOwner(%s, owner_id=0x%08X) via Routing\n", GetKeyName(key), routing_entry->RoutingCurr); @@ -8960,7 +9305,7 @@ static void ImGui::UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt) static inline ImGuiID GetRoutingIdFromOwnerId(ImGuiID owner_id) { ImGuiContext& g = *GImGui; - return (owner_id != ImGuiKeyOwner_None && owner_id != ImGuiKeyOwner_Any) ? owner_id : g.CurrentFocusScopeId; + return (owner_id != ImGuiKeyOwner_NoOwner && owner_id != ImGuiKeyOwner_Any) ? owner_id : g.CurrentFocusScopeId; } ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord) @@ -8977,8 +9322,8 @@ ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord) ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); if (key == ImGuiKey_None) - key = ConvertSingleModFlagToKey(&g, mods); - IM_ASSERT(IsNamedKey(key) && (key_chord & ImGuiMod_Shortcut) == 0); // Please call ConvertShortcutMod() in calling function. + key = ConvertSingleModFlagToKey(mods); + IM_ASSERT(IsNamedKey(key)); // Get (in the majority of case, the linked list will have one element so this should be 2 reads. // Subsequent elements will be contiguous in memory as list is sorted/rebuilt in NewFrame). @@ -9000,19 +9345,18 @@ ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord) } // Current score encoding (lower is highest priority): -// - 0: ImGuiInputFlags_RouteGlobalHigh -// - 1: ImGuiInputFlags_RouteFocused (if item active) -// - 2: ImGuiInputFlags_RouteGlobal +// - 0: ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverActive +// - 1: ImGuiInputFlags_ActiveItem or ImGuiInputFlags_RouteFocused (if item active) +// - 2: ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused // - 3+: ImGuiInputFlags_RouteFocused (if window in focus-stack) -// - 254: ImGuiInputFlags_RouteGlobalLow +// - 254: ImGuiInputFlags_RouteGlobal // - 255: never route // 'flags' should include an explicit routing policy static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInputFlags flags) { + ImGuiContext& g = *GImGui; if (flags & ImGuiInputFlags_RouteFocused) { - ImGuiContext& g = *GImGui; - // ActiveID gets top priority // (we don't check g.ActiveIdUsingAllKeys here. Routing is applied but if input ownership is tested later it may discard it) if (owner_id != 0 && g.ActiveId == owner_id) @@ -9030,15 +9374,23 @@ static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInput for (int index_in_focus_path = 0; index_in_focus_path < g.NavFocusRoute.Size; index_in_focus_path++) if (g.NavFocusRoute.Data[index_in_focus_path].ID == focus_scope_id) return 3 + index_in_focus_path; - return 255; } - - // ImGuiInputFlags_RouteGlobalHigh is default, so calls without flags are not conditional - if (flags & ImGuiInputFlags_RouteGlobal) - return 2; - if (flags & ImGuiInputFlags_RouteGlobalLow) + else if (flags & ImGuiInputFlags_RouteActive) + { + if (owner_id != 0 && g.ActiveId == owner_id) + return 1; + return 255; + } + else if (flags & ImGuiInputFlags_RouteGlobal) + { + if (flags & ImGuiInputFlags_RouteOverActive) + return 0; + if (flags & ImGuiInputFlags_RouteOverFocused) + return 2; return 254; + } + IM_ASSERT(0); return 0; } @@ -9051,7 +9403,7 @@ static bool IsKeyChordPotentiallyCharInput(ImGuiKeyChord key_chord) // When the right mods are pressed it cannot be a char input so we won't filter the shortcut out. ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); - const bool ignore_char_inputs = ((mods & ImGuiMod_Ctrl) && !(mods & ImGuiMod_Alt)) || (g.IO.ConfigMacOSXBehaviors && (mods & ImGuiMod_Super)); + const bool ignore_char_inputs = ((mods & ImGuiMod_Ctrl) && !(mods & ImGuiMod_Alt)) || (g.IO.ConfigMacOSXBehaviors && (mods & ImGuiMod_Ctrl)); if (ignore_char_inputs) return false; @@ -9065,17 +9417,19 @@ static bool IsKeyChordPotentiallyCharInput(ImGuiKeyChord key_chord) // - Routes and key ownership are attributed at the beginning of next frame based on best score and mod state. // (Conceptually this does a "Submit for next frame" + "Test for current frame". // As such, it could be called TrySetXXX or SubmitXXX, or the Submit and Test operations should be separate.) -bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags) +bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id) { ImGuiContext& g = *GImGui; - if ((flags & ImGuiInputFlags_RouteMask_) == 0) - flags |= ImGuiInputFlags_RouteGlobalHigh; // IMPORTANT: This is the default for SetShortcutRouting() but NOT Shortcut() + if ((flags & ImGuiInputFlags_RouteTypeMask_) == 0) + flags |= ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused | ImGuiInputFlags_RouteOverActive; // IMPORTANT: This is the default for SetShortcutRouting() but NOT Shortcut() else - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteMask_)); // Check that only 1 routing flag is used - IM_ASSERT(owner_id != ImGuiKeyOwner_Any && owner_id != ImGuiKeyOwner_None); + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteTypeMask_)); // Check that only 1 routing flag is used + IM_ASSERT(owner_id != ImGuiKeyOwner_Any && owner_id != ImGuiKeyOwner_NoOwner); + if (flags & (ImGuiInputFlags_RouteOverFocused | ImGuiInputFlags_RouteOverActive | ImGuiInputFlags_RouteUnlessBgFocused)) + IM_ASSERT(flags & ImGuiInputFlags_RouteGlobal); - // Convert ImGuiMod_Shortcut and add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified. - key_chord = FixupKeyChord(&g, key_chord); + // Add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified. + key_chord = FixupKeyChord(key_chord); // [DEBUG] Debug break requested by user if (g.DebugBreakInShortcutRouting == key_chord) @@ -9088,38 +9442,45 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI // Note how ImGuiInputFlags_RouteAlways won't set routing and thus won't set owner. May want to rework this? if (flags & ImGuiInputFlags_RouteAlways) { - IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, owner_id=0x%08X, flags=%04X) -> always\n", GetKeyChordName(key_chord), owner_id, flags); + IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> always, no register\n", GetKeyChordName(key_chord), flags, owner_id); return true; } - // Specific culling when there's an active. + // Specific culling when there's an active item. if (g.ActiveId != 0 && g.ActiveId != owner_id) { + if (flags & ImGuiInputFlags_RouteActive) + return false; + // Cull shortcuts with no modifiers when it could generate a character. // e.g. Shortcut(ImGuiKey_G) also generates 'g' character, should not trigger when InputText() is active. // but Shortcut(Ctrl+G) should generally trigger when InputText() is active. // TL;DR: lettered shortcut with no mods or with only Alt mod will not trigger while an item reading text input is active. // (We cannot filter based on io.InputQueueCharacters[] contents because of trickling and key<>chars submission order are undefined) - if ((flags & ImGuiInputFlags_RouteFocused) && g.IO.WantTextInput && IsKeyChordPotentiallyCharInput(key_chord)) + if (g.IO.WantTextInput && IsKeyChordPotentiallyCharInput(key_chord)) { - IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, owner_id=0x%08X, flags=%04X) -> filtered as potential char input\n", GetKeyChordName(key_chord), owner_id, flags); + IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> filtered as potential char input\n", GetKeyChordName(key_chord), flags, owner_id); return false; } // ActiveIdUsingAllKeyboardKeys trumps all for ActiveId - if ((flags & ImGuiInputFlags_RouteGlobalHigh) == 0 && g.ActiveIdUsingAllKeyboardKeys) + if ((flags & ImGuiInputFlags_RouteOverActive) == 0 && g.ActiveIdUsingAllKeyboardKeys) { ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); if (key == ImGuiKey_None) - key = ConvertSingleModFlagToKey(&g, (ImGuiKey)(key_chord & ImGuiMod_Mask_)); + key = ConvertSingleModFlagToKey((ImGuiKey)(key_chord & ImGuiMod_Mask_)); if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END) return false; } } - // FIXME-SHORTCUT: A way to configure the location/focus-scope to test would render this more flexible. - const int score = CalcRoutingScore(g.CurrentFocusScopeId, owner_id, flags); - IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, owner_id=0x%08X, flags=%04X) -> score %d\n", GetKeyChordName(key_chord), owner_id, flags, score); + // Where do we evaluate route for? + ImGuiID focus_scope_id = g.CurrentFocusScopeId; + if (flags & ImGuiInputFlags_RouteFromRootWindow) + focus_scope_id = g.CurrentWindow->RootWindow->ID; // See PushFocusScope() call in Begin() + + const int score = CalcRoutingScore(focus_scope_id, owner_id, flags); + IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> score %d\n", GetKeyChordName(key_chord), flags, owner_id, score); if (score == 255) return false; @@ -9143,9 +9504,8 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI // Note: this cannot be turned into GetShortcutRouting() because we do the owner_id->routing_id translation, name would be more misleading. bool ImGui::TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id) { - ImGuiContext& g = *GImGui; const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id); - key_chord = FixupKeyChord(&g, key_chord); + key_chord = FixupKeyChord(key_chord); ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); // FIXME: Could avoid creating entry. return routing_data->RoutingCurr == routing_id; } @@ -9169,11 +9529,11 @@ bool ImGui::IsKeyDown(ImGuiKey key, ImGuiID owner_id) bool ImGui::IsKeyPressed(ImGuiKey key, bool repeat) { - return IsKeyPressed(key, ImGuiKeyOwner_Any, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None); + return IsKeyPressed(key, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None, ImGuiKeyOwner_Any); } // Important: unless legacy IsKeyPressed(ImGuiKey, bool repeat=true) which DEFAULT to repeat, this requires EXPLICIT repeat. -bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags) +bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id) { const ImGuiKeyData* key_data = GetKeyData(key); if (!key_data->Down) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on key ownership) @@ -9243,10 +9603,10 @@ bool ImGui::IsMouseDown(ImGuiMouseButton button, ImGuiID owner_id) bool ImGui::IsMouseClicked(ImGuiMouseButton button, bool repeat) { - return IsMouseClicked(button, ImGuiKeyOwner_Any, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None); + return IsMouseClicked(button, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None, ImGuiKeyOwner_Any); } -bool ImGui::IsMouseClicked(ImGuiMouseButton button, ImGuiID owner_id, ImGuiInputFlags flags) +bool ImGui::IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGuiID owner_id) { ImGuiContext& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); @@ -9454,6 +9814,9 @@ static void ImGui::UpdateKeyboardInputs() ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; + if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) + io.ClearInputKeys(); + // Import legacy keys or verify they are not used #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO if (io.BackendUsingLegacyKeyArrays == 0) @@ -9574,7 +9937,7 @@ static void ImGui::UpdateKeyboardInputs() ImGuiKeyOwnerData* owner_data = &g.KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; owner_data->OwnerCurr = owner_data->OwnerNext; if (!key_data->Down) // Important: ownership is released on the frame after a release. Ensure a 'MouseDown -> CloseWindow -> MouseUp' chain doesn't lead to someone else seeing the MouseUp. - owner_data->OwnerNext = ImGuiKeyOwner_None; + owner_data->OwnerNext = ImGuiKeyOwner_NoOwner; owner_data->LockThisFrame = owner_data->LockUntilRelease = owner_data->LockUntilRelease && key_data->Down; // Clear LockUntilRelease when key is not Down anymore } @@ -9733,8 +10096,8 @@ void ImGui::UpdateMouseWheel() } ImVec2 wheel; - wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, ImGuiKeyOwner_None) ? g.IO.MouseWheelH : 0.0f; - wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, ImGuiKeyOwner_None) ? g.IO.MouseWheel : 0.0f; + wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheelH : 0.0f; + wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheel : 0.0f; //IMGUI_DEBUG_LOG("MouseWheel X:%.3f Y:%.3f\n", wheel_x, wheel_y); ImGuiWindow* mouse_window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow; @@ -9821,7 +10184,7 @@ void ImGui::SetNextFrameWantCaptureMouse(bool want_capture_mouse) #ifndef IMGUI_DISABLE_DEBUG_TOOLS static const char* GetInputSourceName(ImGuiInputSource source) { - const char* input_source_names[] = { "None", "Mouse", "Keyboard", "Gamepad", "Clipboard" }; + const char* input_source_names[] = { "None", "Mouse", "Keyboard", "Gamepad" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT && source >= 0 && source < ImGuiInputSource_COUNT); return input_source_names[source]; } @@ -9908,6 +10271,8 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) else if (e->Type == ImGuiInputEventType_Key) { // Trickling Rule: Stop processing queued events if we got multiple action on the same button + if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) + continue; ImGuiKey key = e->Key.Key; IM_ASSERT(key != ImGuiKey_None); ImGuiKeyData* key_data = GetKeyData(key); @@ -9928,6 +10293,8 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) } else if (e->Type == ImGuiInputEventType_Text) { + if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) + continue; // 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; @@ -9972,13 +10339,16 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) // - we clear in EndFrame() and not now in order allow application/user code polling this flag // (e.g. custom backend may want to clear additional data, custom widgets may want to react with a "canceling" event). if (g.IO.AppFocusLost) + { g.IO.ClearInputKeys(); + g.IO.ClearInputMouse(); + } } ImGuiID ImGui::GetKeyOwner(ImGuiKey key) { - if (!IsNamedKeyOrModKey(key)) - return ImGuiKeyOwner_None; + if (!IsNamedKeyOrMod(key)) + return ImGuiKeyOwner_NoOwner; ImGuiContext& g = *GImGui; ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key); @@ -9986,7 +10356,7 @@ ImGuiID ImGui::GetKeyOwner(ImGuiKey key) if (g.ActiveIdUsingAllKeyboardKeys && owner_id != g.ActiveId && owner_id != ImGuiKeyOwner_Any) if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END) - return ImGuiKeyOwner_None; + return ImGuiKeyOwner_NoOwner; return owner_id; } @@ -9997,7 +10367,7 @@ ImGuiID ImGui::GetKeyOwner(ImGuiKey key) // All paths are also testing for key not being locked, for the rare cases that key have been locked with using ImGuiInputFlags_LockXXX flags. bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id) { - if (!IsNamedKeyOrModKey(key)) + if (!IsNamedKeyOrMod(key)) return true; ImGuiContext& g = *GImGui; @@ -10016,7 +10386,7 @@ bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id) { if (owner_data->LockThisFrame) return false; - if (owner_data->OwnerCurr != ImGuiKeyOwner_None) + if (owner_data->OwnerCurr != ImGuiKeyOwner_NoOwner) return false; } @@ -10031,7 +10401,7 @@ bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id) void ImGui::SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags) { ImGuiContext& g = *GImGui; - IM_ASSERT(IsNamedKeyOrModKey(key) && (owner_id != ImGuiKeyOwner_Any || (flags & (ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease)))); // Can only use _Any with _LockXXX flags (to eat a key away without an ID to retrieve it) + IM_ASSERT(IsNamedKeyOrMod(key) && (owner_id != ImGuiKeyOwner_Any || (flags & (ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease)))); // Can only use _Any with _LockXXX flags (to eat a key away without an ID to retrieve it) IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetKeyOwner) == 0); // Passing flags not supported by this function! //IMGUI_DEBUG_LOG("SetKeyOwner(%s, owner_id=0x%08X, flags=%08X)\n", GetKeyName(key), owner_id, flags); @@ -10051,7 +10421,6 @@ void ImGui::SetKeyOwnersForKeyChord(ImGuiKeyChord key_chord, ImGuiID owner_id, I if (key_chord & ImGuiMod_Shift) { SetKeyOwner(ImGuiMod_Shift, owner_id, flags); } if (key_chord & ImGuiMod_Alt) { SetKeyOwner(ImGuiMod_Alt, owner_id, flags); } if (key_chord & ImGuiMod_Super) { SetKeyOwner(ImGuiMod_Super, owner_id, flags); } - if (key_chord & ImGuiMod_Shortcut) { SetKeyOwner(ImGuiMod_Shortcut, owner_id, flags); } if (key_chord & ~ImGuiMod_Mask_) { SetKeyOwner((ImGuiKey)(key_chord & ~ImGuiMod_Mask_), owner_id, flags); } } @@ -10076,17 +10445,22 @@ void ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags) } } +void ImGui::SetItemKeyOwner(ImGuiKey key) +{ + SetItemKeyOwner(key, ImGuiInputFlags_None); +} + // This is the only public API until we expose owner_id versions of the API as replacements. bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord) { - return IsKeyChordPressed(key_chord, 0, ImGuiInputFlags_None); + return IsKeyChordPressed(key_chord, ImGuiInputFlags_None, ImGuiKeyOwner_Any); } // This is equivalent to comparing KeyMods + doing a IsKeyPressed() -bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags) +bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id) { ImGuiContext& g = *GImGui; - key_chord = FixupKeyChord(&g, key_chord); + key_chord = FixupKeyChord(key_chord); ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); if (g.IO.KeyMods != mods) return false; @@ -10094,35 +10468,69 @@ bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiIn // Special storage location for mods ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); if (key == ImGuiKey_None) - key = ConvertSingleModFlagToKey(&g, mods); - if (!IsKeyPressed(key, owner_id, (flags & ImGuiInputFlags_RepeatMask_))) + key = ConvertSingleModFlagToKey(mods); + if (!IsKeyPressed(key, (flags & ImGuiInputFlags_RepeatMask_), owner_id)) return false; return true; } -void ImGui::SetNextItemShortcut(ImGuiKeyChord key_chord) +void ImGui::SetNextItemShortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags) { ImGuiContext& g = *GImGui; g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasShortcut; g.NextItemData.Shortcut = key_chord; + g.NextItemData.ShortcutFlags = flags; } -bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags) +// Called from within ItemAdd: at this point we can read from NextItemData and write to LastItemData +void ImGui::ItemHandleShortcut(ImGuiID id) { - //ImGuiContext& g = *GImGui; - //IMGUI_DEBUG_LOG("Shortcut(%s, owner_id=0x%08X, flags=%X)\n", GetKeyChordName(key_chord, g.TempBuffer.Data, g.TempBuffer.Size), owner_id, flags); + ImGuiContext& g = *GImGui; + ImGuiInputFlags flags = g.NextItemData.ShortcutFlags; + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetNextItemShortcut) == 0); // Passing flags not supported by SetNextItemShortcut()! + + if (g.LastItemData.InFlags & ImGuiItemFlags_Disabled) + return; + if (flags & ImGuiInputFlags_Tooltip) + { + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasShortcut; + g.LastItemData.Shortcut = g.NextItemData.Shortcut; + } + if (!Shortcut(g.NextItemData.Shortcut, flags & ImGuiInputFlags_SupportedByShortcut, id) || g.NavActivateId != 0) + return; + + // FIXME: Generalize Activation queue? + g.NavActivateId = id; // Will effectively disable clipping. + g.NavActivateFlags = ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_FromShortcut; + //if (g.ActiveId == 0 || g.ActiveId == id) + g.NavActivateDownId = g.NavActivatePressedId = id; + NavHighlightActivated(id); +} + +bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags) +{ + return Shortcut(key_chord, flags, ImGuiKeyOwner_Any); +} + +bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id) +{ + ImGuiContext& g = *GImGui; + //IMGUI_DEBUG_LOG("Shortcut(%s, flags=%X, owner_id=0x%08X)\n", GetKeyChordName(key_chord, g.TempBuffer.Data, g.TempBuffer.Size), flags, owner_id); // When using (owner_id == 0/Any): SetShortcutRouting() will use CurrentFocusScopeId and filter with this, so IsKeyPressed() is fine with he 0/Any. - if ((flags & ImGuiInputFlags_RouteMask_) == 0) + if ((flags & ImGuiInputFlags_RouteTypeMask_) == 0) flags |= ImGuiInputFlags_RouteFocused; // Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its focus scope by default) // Effectively makes Shortcut() always input-owner aware. - if (owner_id == ImGuiKeyOwner_Any || owner_id == ImGuiKeyOwner_None) + if (owner_id == ImGuiKeyOwner_Any || owner_id == ImGuiKeyOwner_NoOwner) owner_id = GetRoutingIdFromOwnerId(owner_id); + if (g.CurrentItemFlags & ImGuiItemFlags_Disabled) + return false; + // Submit route - if (!SetShortcutRouting(key_chord, owner_id, flags)) + if (!SetShortcutRouting(key_chord, flags, owner_id)) return false; // Default repeat behavior for Shortcut() @@ -10130,8 +10538,12 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags if ((flags & ImGuiInputFlags_Repeat) != 0 && (flags & ImGuiInputFlags_RepeatUntilMask_) == 0) flags |= ImGuiInputFlags_RepeatUntilKeyModsChange; - if (!IsKeyChordPressed(key_chord, owner_id, flags)) + if (!IsKeyChordPressed(key_chord, flags, owner_id)) return false; + + // Claim mods during the press + SetKeyOwnersForKeyChord(key_chord & ImGuiMod_Mask_, owner_id); + IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByShortcut) == 0); // Passing flags not supported by this function! return true; } @@ -10141,24 +10553,26 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags // [SECTION] ERROR CHECKING //----------------------------------------------------------------------------- -// Helper function to verify ABI compatibility between caller code and compiled version of Dear ImGui. +// Verify ABI compatibility between caller code and compiled version of Dear ImGui. This helps detects some build issues. +// Called by IMGUI_CHECKVERSION(). // Verify that the type sizes are matching between the calling file's compilation unit and imgui.cpp's compilation unit -// 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 +// If this triggers you have mismatched headers and compiled code versions. +// - It could be because of a build issue (using new headers with old compiled code) +// - It could be because of 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 why it is required you put them in your imconfig file (and NOT only before including imgui.h). +// Otherwise it is possible that different compilation units would see different structure layout. +// If you don't want to modify imconfig.h you can use the IMGUI_USER_CONFIG define to change filename. 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_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!"); } + 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; } @@ -10235,10 +10649,6 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() 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; - // Perform simple check: error if Docking or Viewport are enabled _exactly_ on frame 1 (instead of frame 0 or later), which is a common error leading to loss of .ini data. if (g.FrameCount == 1 && (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable) && (g.ConfigFlagsLastFrame & ImGuiConfigFlags_DockingEnable) == 0) IM_ASSERT(0 && "Please set DockingEnable before the first call to NewFrame()! Otherwise you will lose your .ini settings!"); @@ -10366,6 +10776,11 @@ void ImGui::ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, vo if (log_callback) log_callback(user_data, "Recovered from missing EndTabBar() in '%s'", window->Name); EndTabBar(); } + while (g.CurrentMultiSelect != NULL && g.CurrentMultiSelect->Storage->Window == window) + { + if (log_callback) log_callback(user_data, "Recovered from missing EndMultiSelect() in '%s'", window->Name); + EndMultiSelect(); + } while (window->DC.TreeDepth > 0) { if (log_callback) log_callback(user_data, "Recovered from missing TreePop() in '%s'", window->Name); @@ -10384,7 +10799,13 @@ void ImGui::ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, vo while (g.DisabledStackSize > stack_sizes->SizeOfDisabledStack) //-V1044 { if (log_callback) log_callback(user_data, "Recovered from missing EndDisabled() in '%s'", window->Name); - EndDisabled(); + if (g.CurrentItemFlags & ImGuiItemFlags_Disabled) + EndDisabled(); + else + { + EndDisabledOverrideReenable(); + g.CurrentWindowStack.back().DisabledOverrideReenable = false; + } } while (g.ColorStack.Size > stack_sizes->SizeOfColorStack) { @@ -10456,7 +10877,6 @@ void ImGuiStackSizes::CompareWithContextState(ImGuiContext* ctx) // [SECTION] ITEM SUBMISSION //----------------------------------------------------------------------------- // - KeepAliveID() -// - ItemHandleShortcut() [Internal] // - ItemAdd() //----------------------------------------------------------------------------- @@ -10470,24 +10890,11 @@ void ImGui::KeepAliveID(ImGuiID id) g.ActiveIdPreviousFrameIsAlive = true; } -static void ItemHandleShortcut(ImGuiID id) -{ - // FIXME: Generalize Activation queue? - ImGuiContext& g = *GImGui; - if (ImGui::Shortcut(g.NextItemData.Shortcut, id, ImGuiInputFlags_None) && g.NavActivateId == 0) - { - g.NavActivateId = id; // Will effectively disable clipping. - g.NavActivateFlags = ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_FromShortcut; - if (g.ActiveId == 0 || g.ActiveId == id) - g.NavActivateDownId = g.NavActivatePressedId = id; - ImGui::NavHighlightActivated(id); - } -} - // 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. // THIS IS IN THE PERFORMANCE CRITICAL PATH (UNTIL THE CLIPPING TEST AND EARLY-RETURN) +IM_MSVC_RUNTIME_CHECKS_OFF bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGuiItemFlags extra_flags) { ImGuiContext& g = *GImGui; @@ -10522,7 +10929,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu 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)) + if (window == g.NavWindow || ((window->ChildFlags | g.NavWindow->ChildFlags) & ImGuiChildFlags_NavFlattened)) NavProcessItem(); } @@ -10540,15 +10947,12 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu #endif // Clipping test - // (this is a modified copy of IsClippedEx() so we can reuse the is_rect_visible value) - //const bool is_clipped = IsClippedEx(bb, id); - //if (is_clipped) - // return false; + // (this is an inline copy of IsClippedEx() so we can reuse the is_rect_visible value, otherwise we'd do 'if (IsClippedEx(bb, id)) return false') // g.NavActivateId is not necessarily == g.NavId, in the case of remote activation (e.g. shortcuts) const bool is_rect_visible = bb.Overlaps(window->ClipRect); if (!is_rect_visible) if (id == 0 || (id != g.ActiveId && id != g.ActiveIdPreviousFrame && id != g.NavId && id != g.NavActivateId)) - if (!g.LogEnabled) + if (!g.ItemUnclipByLog) return false; // [DEBUG] @@ -10575,7 +10979,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; return true; } - +IM_MSVC_RUNTIME_CHECKS_RESTORE //----------------------------------------------------------------------------- // [SECTION] LAYOUT @@ -10600,9 +11004,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu // - GetFrameHeight() // - GetFrameHeightWithSpacing() // - GetContentRegionMax() -// - GetContentRegionMaxAbs() [Internal] // - GetContentRegionAvail(), -// - GetWindowContentRegionMin(), GetWindowContentRegionMax() // - BeginGroup() // - EndGroup() // Also see in imgui_widgets: tab bars, and in imgui_tables: tables, columns. @@ -10612,6 +11014,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu // 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. // THIS IS IN THE PERFORMANCE CRITICAL PATH. +IM_MSVC_RUNTIME_CHECKS_OFF void ImGui::ItemSize(const ImVec2& size, float text_baseline_y) { ImGuiContext& g = *GImGui; @@ -10647,6 +11050,7 @@ void ImGui::ItemSize(const ImVec2& size, float text_baseline_y) if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) SameLine(); } +IM_MSVC_RUNTIME_CHECKS_RESTORE // Gets back to previous line and continue with horizontal layout // offset_from_start_x == 0 : follow right after previous item @@ -10816,8 +11220,8 @@ float ImGui::CalcItemWidth() 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); + float region_avail_x = GetContentRegionAvail().x; + w = ImMax(1.0f, region_avail_x + w); } w = IM_TRUNC(w); return w; @@ -10829,22 +11233,19 @@ float ImGui::CalcItemWidth() // 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; + ImVec2 avail; if (size.x < 0.0f || size.y < 0.0f) - region_max = GetContentRegionMaxAbs(); + avail = GetContentRegionAvail(); 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); + size.x = ImMax(4.0f, avail.x + size.x); // <-- size.x is negative here so we are subtracting if (size.y == 0.0f) size.y = default_h; else if (size.y < 0.0f) - size.y = ImMax(4.0f, region_max.y - window->DC.CursorPos.y + size.y); + size.y = ImMax(4.0f, avail.y + size.y); // <-- size.y is negative here so we are subtracting return size; } @@ -10873,33 +11274,23 @@ float ImGui::GetFrameHeightWithSpacing() return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y; } -// FIXME: All the Contents Region function are messy or misleading. WE WILL AIM TO OBSOLETE ALL OF THEM WITH A NEW "WORK RECT" API. Thanks for your patience! - -// FIXME: This is in window space (not screen space!). -ImVec2 ImGui::GetContentRegionMax() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - ImVec2 mx = (window->DC.CurrentColumns || g.CurrentTable) ? window->WorkRect.Max : window->ContentRegionRect.Max; - return mx - window->Pos; -} - -// [Internal] Absolute coordinate. Saner. This is not exposed until we finishing refactoring work rect features. -ImVec2 ImGui::GetContentRegionMaxAbs() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - ImVec2 mx = (window->DC.CurrentColumns || g.CurrentTable) ? window->WorkRect.Max : window->ContentRegionRect.Max; - return mx; -} - ImVec2 ImGui::GetContentRegionAvail() { - ImGuiWindow* window = GImGui->CurrentWindow; - return GetContentRegionMaxAbs() - window->DC.CursorPos; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImVec2 mx = (window->DC.CurrentColumns || g.CurrentTable) ? window->WorkRect.Max : window->ContentRegionRect.Max; + return mx - window->DC.CursorPos; +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +// You should never need those functions. Always use GetCursorScreenPos() and GetContentRegionAvail()! +// They are bizarre local-coordinates which don't play well with scrolling. +ImVec2 ImGui::GetContentRegionMax() +{ + return GetContentRegionAvail() + GetCursorScreenPos() - GetWindowPos(); } -// In window space (not screen space!) ImVec2 ImGui::GetWindowContentRegionMin() { ImGuiWindow* window = GImGui->CurrentWindow; @@ -10911,6 +11302,7 @@ ImVec2 ImGui::GetWindowContentRegionMax() ImGuiWindow* window = GImGui->CurrentWindow; return window->ContentRegionRect.Max - window->Pos; } +#endif // Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) // Groups are currently a mishmash of functionalities which should perhaps be clarified and separated. @@ -10956,11 +11348,11 @@ void ImGui::EndGroup() if (window->DC.IsSetPos) ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); - ImRect group_bb(group_data.BackupCursorPos, ImMax(window->DC.CursorMaxPos, group_data.BackupCursorPos)); - + // Include LastItemData.Rect.Max as a workaround for e.g. EndTable() undershooting with CursorMaxPos report. (#7543) + ImRect group_bb(group_data.BackupCursorPos, ImMax(ImMax(window->DC.CursorMaxPos, g.LastItemData.Rect.Max), group_data.BackupCursorPos)); window->DC.CursorPos = group_data.BackupCursorPos; window->DC.CursorPosPrevLine = group_data.BackupCursorPosPrevLine; - window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos); + window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, group_bb.Max); window->DC.Indent = group_data.BackupIndent; window->DC.GroupOffset = group_data.BackupGroupOffset; window->DC.CurrLineSize = group_data.BackupCurrLineSize; @@ -10975,7 +11367,7 @@ void ImGui::EndGroup() 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. + 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); @@ -11444,7 +11836,7 @@ void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) ImGuiPopupData popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack. popup_ref.PopupId = id; popup_ref.Window = NULL; - popup_ref.BackupNavWindow = g.NavWindow; // When popup closes focus may be restored to NavWindow (depend on window type). + popup_ref.RestoreNavWindow = g.NavWindow; // When popup closes focus may be restored to NavWindow (depend on window type). popup_ref.OpenFrameCount = g.FrameCount; popup_ref.OpenParentId = parent_window->IDStack.back(); popup_ref.OpenPopupPos = NavCalcPreferredRefPos(); @@ -11493,6 +11885,7 @@ void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to return; // Don't close our own child popup windows. + //IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupsOverWindow(\"%s\") restore_under=%d\n", ref_window ? ref_window->Name : "", restore_focus_to_window_under_popup); int popup_count_to_keep = 0; if (ref_window) { @@ -11550,18 +11943,19 @@ void ImGui::ClosePopupsExceptModals() 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); + IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupToLevel(%d), restore_under=%d\n", remaining, restore_focus_to_window_under_popup); IM_ASSERT(remaining >= 0 && remaining < g.OpenPopupStack.Size); // Trim open popup stack - ImGuiWindow* popup_window = g.OpenPopupStack[remaining].Window; - ImGuiWindow* popup_backup_nav_window = g.OpenPopupStack[remaining].BackupNavWindow; + ImGuiPopupData prev_popup = g.OpenPopupStack[remaining]; g.OpenPopupStack.resize(remaining); - if (restore_focus_to_window_under_popup) + // Restore focus (unless popup window was not yet submitted, and didn't have a chance to take focus anyhow. See #7325 for an edge case) + if (restore_focus_to_window_under_popup && prev_popup.Window) { - ImGuiWindow* focus_window = (popup_window && popup_window->Flags & ImGuiWindowFlags_ChildMenu) ? popup_window->ParentWindow : popup_backup_nav_window; - if (focus_window && !focus_window->WasActive && popup_window) + ImGuiWindow* popup_window = prev_popup.Window; + ImGuiWindow* focus_window = (popup_window->Flags & ImGuiWindowFlags_ChildMenu) ? popup_window->ParentWindow : prev_popup.RestoreNavWindow; + if (focus_window && !focus_window->WasActive) FocusTopMostWindowUnderOne(popup_window, NULL, NULL, ImGuiFocusRequestFlags_RestoreFocusedChild); // Fallback else FocusWindow(focus_window, (g.NavLayer == ImGuiNavLayer_Main) ? ImGuiFocusRequestFlags_RestoreFocusedChild : ImGuiFocusRequestFlags_None); @@ -11599,8 +11993,8 @@ void ImGui::CloseCurrentPopup() window->DC.NavHideHighlightOneFrame = true; } -// Attention! BeginPopup() adds default flags which BeginPopupEx()! -bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags flags) +// Attention! BeginPopup() adds default flags when calling BeginPopupEx()! +bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags) { ImGuiContext& g = *GImGui; if (!IsPopupOpen(id, ImGuiPopupFlags_None)) @@ -11610,13 +12004,12 @@ bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags flags) } char name[20]; - if (flags & ImGuiWindowFlags_ChildMenu) + if (extra_window_flags & ImGuiWindowFlags_ChildMenu) ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.BeginMenuDepth); // 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 | ImGuiWindowFlags_NoDocking; - bool is_open = Begin(name, NULL, flags); + bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoDocking); if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) EndPopup(); @@ -12022,7 +12415,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) // 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); + IM_ASSERT((window->ChildFlags | g.NavWindow->ChildFlags) & ImGuiChildFlags_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 @@ -12047,6 +12440,9 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) if (dbx != 0.0f || dby != 0.0f) { // For non-overlapping boxes, use distance between boxes + // FIXME-NAV: Quadrant may be incorrect because of (1) dbx bias and (2) curr.Max.y bias applied by NavBiasScoringRect() where typically curr.Max.y==curr.Min.y + // One typical case where this happens, with style.WindowMenuButtonPosition == ImGuiDir_Right, pressing Left to navigate from Close to Collapse tends to fail. + // Also see #6344. Calling ImGetDirQuadrantFromDelta() with unbiased values may be good but side-effects are plenty. dax = dbx; day = dby; dist_axial = dist_box; @@ -12325,6 +12721,7 @@ void ImGui::NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavM { ImGuiContext& g = *GImGui; IM_ASSERT(g.NavWindow != NULL); + //IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequestSubmit: dir %c, window \"%s\"\n", "-WENS"[move_dir + 1], g.NavWindow->Name); if (move_flags & ImGuiNavMoveFlags_IsTabbing) move_flags |= ImGuiNavMoveFlags_AllowCurrentNavId; @@ -12354,7 +12751,7 @@ void ImGui::NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result) } // Called by TreePop() to implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere -void ImGui::NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiNavTreeNodeData* tree_node_data) +void ImGui::NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiTreeNodeStackData* tree_node_data) { ImGuiContext& g = *GImGui; g.NavMoveScoringItems = false; @@ -12497,7 +12894,10 @@ static ImVec2 ImGui::NavCalcPreferredRefPos() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.NavWindow; - if (g.NavDisableHighlight || !g.NavDisableMouseHover || !window) + const bool activated_shortcut = g.ActiveId != 0 && g.ActiveIdFromShortcut && g.ActiveId == g.LastItemData.ID; + + // Testing for !activated_shortcut here could in theory be removed if we decided that activating a remote shortcut altered one of the g.NavDisableXXX flag. + if ((g.NavDisableHighlight || !g.NavDisableMouseHover || !window) && !activated_shortcut) { // 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. @@ -12508,14 +12908,19 @@ static ImVec2 ImGui::NavCalcPreferredRefPos() else { // When navigation is active and mouse is disabled, pick a position around the bottom left of the currently navigated item + ImRect ref_rect; + if (activated_shortcut) + ref_rect = g.LastItemData.NavRect; + else + ref_rect = WindowRectRelToAbs(window, window->NavRectRel[g.NavLayer]); + // Take account of upcoming scrolling (maybe set mouse pos should be done in EndFrame?) - 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); + ref_rect.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())); + ImVec2 pos = ImVec2(ref_rect.Min.x + ImMin(g.Style.FramePadding.x * 4, ref_rect.GetWidth()), ref_rect.Max.y - ImMin(g.Style.FramePadding.y, ref_rect.GetHeight())); ImGuiViewport* viewport = window->Viewport; return ImTrunc(ImClamp(pos, viewport->Pos, viewport->Pos + viewport->Size)); // ImTrunc() is important because non-integer mouse position application in backend might be lossy and result in undesirable non-zero delta. } @@ -12569,6 +12974,7 @@ static void ImGui::NavUpdate() // Process navigation init request (select first/default focus) g.NavJustMovedToId = 0; + g.NavJustMovedToFocusScopeId = g.NavJustMovedFromFocusScopeId = 0; if (g.NavInitResult.ID != 0) NavInitRequestApplyResult(); g.NavInitRequest = false; @@ -12610,10 +13016,10 @@ static void ImGui::NavUpdate() g.NavActivateFlags = ImGuiActivateFlags_None; if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) { - const bool activate_down = (nav_keyboard_active && IsKeyDown(ImGuiKey_Space, ImGuiKeyOwner_None)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_None)); - const bool activate_pressed = activate_down && ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Space, ImGuiKeyOwner_None)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_None))); - const bool input_down = (nav_keyboard_active && (IsKeyDown(ImGuiKey_Enter, ImGuiKeyOwner_None) || IsKeyDown(ImGuiKey_KeypadEnter, ImGuiKeyOwner_None))) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadInput, ImGuiKeyOwner_None)); - const bool input_pressed = input_down && ((nav_keyboard_active && (IsKeyPressed(ImGuiKey_Enter, ImGuiKeyOwner_None) || IsKeyPressed(ImGuiKey_KeypadEnter, ImGuiKeyOwner_None))) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadInput, ImGuiKeyOwner_None))); + const bool activate_down = (nav_keyboard_active && IsKeyDown(ImGuiKey_Space, ImGuiKeyOwner_NoOwner)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_NoOwner)); + const bool activate_pressed = activate_down && ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Space, 0, ImGuiKeyOwner_NoOwner)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, 0, ImGuiKeyOwner_NoOwner))); + const bool input_down = (nav_keyboard_active && (IsKeyDown(ImGuiKey_Enter, ImGuiKeyOwner_NoOwner) || IsKeyDown(ImGuiKey_KeypadEnter, ImGuiKeyOwner_NoOwner))) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadInput, ImGuiKeyOwner_NoOwner)); + const bool input_pressed = input_down && ((nav_keyboard_active && (IsKeyPressed(ImGuiKey_Enter, 0, ImGuiKeyOwner_NoOwner) || IsKeyPressed(ImGuiKey_KeypadEnter, 0, ImGuiKeyOwner_NoOwner))) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadInput, 0, ImGuiKeyOwner_NoOwner))); if (g.ActiveId == 0 && activate_pressed) { g.NavActivateId = g.NavId; @@ -12721,9 +13127,12 @@ void ImGui::NavInitRequestApplyResult() ImGuiNavItemData* result = &g.NavInitResult; if (g.NavId != result->ID) { + g.NavJustMovedFromFocusScopeId = g.NavFocusScopeId; g.NavJustMovedToId = result->ID; g.NavJustMovedToFocusScopeId = result->FocusScopeId; g.NavJustMovedToKeyMods = 0; + g.NavJustMovedToIsTabbing = false; + g.NavJustMovedToHasSelectionData = (result->InFlags & ImGuiItemFlags_HasSelectionUserData) != 0; } // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called) @@ -12786,11 +13195,11 @@ void ImGui::NavUpdateCreateMoveRequest() g.NavMoveScrollFlags = ImGuiScrollFlags_None; if (window && !g.NavWindowingTarget && !(window->Flags & ImGuiWindowFlags_NoNavInputs)) { - const ImGuiInputFlags repeat_mode = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateNavMove; - if (!IsActiveIdUsingNavDir(ImGuiDir_Left) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadLeft, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_LeftArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Left; } - if (!IsActiveIdUsingNavDir(ImGuiDir_Right) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadRight, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_RightArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Right; } - if (!IsActiveIdUsingNavDir(ImGuiDir_Up) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadUp, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_UpArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Up; } - if (!IsActiveIdUsingNavDir(ImGuiDir_Down) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadDown, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_DownArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Down; } + const ImGuiInputFlags repeat_mode = ImGuiInputFlags_Repeat | (ImGuiInputFlags)ImGuiInputFlags_RepeatRateNavMove; + if (!IsActiveIdUsingNavDir(ImGuiDir_Left) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadLeft, repeat_mode, ImGuiKeyOwner_NoOwner)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_LeftArrow, repeat_mode, ImGuiKeyOwner_NoOwner)))) { g.NavMoveDir = ImGuiDir_Left; } + if (!IsActiveIdUsingNavDir(ImGuiDir_Right) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadRight, repeat_mode, ImGuiKeyOwner_NoOwner)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_RightArrow, repeat_mode, ImGuiKeyOwner_NoOwner)))) { g.NavMoveDir = ImGuiDir_Right; } + if (!IsActiveIdUsingNavDir(ImGuiDir_Up) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadUp, repeat_mode, ImGuiKeyOwner_NoOwner)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_UpArrow, repeat_mode, ImGuiKeyOwner_NoOwner)))) { g.NavMoveDir = ImGuiDir_Up; } + if (!IsActiveIdUsingNavDir(ImGuiDir_Down) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadDown, repeat_mode, ImGuiKeyOwner_NoOwner)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_DownArrow, repeat_mode, ImGuiKeyOwner_NoOwner)))) { g.NavMoveDir = ImGuiDir_Down; } } g.NavMoveClipDir = g.NavMoveDir; g.NavScoringNoClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); @@ -12886,7 +13295,7 @@ void ImGui::NavUpdateCreateTabbingRequest() if (window == NULL || g.NavWindowingTarget != NULL || (window->Flags & ImGuiWindowFlags_NoNavInputs)) return; - const bool tab_pressed = IsKeyPressed(ImGuiKey_Tab, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat) && !g.IO.KeyCtrl && !g.IO.KeyAlt; + const bool tab_pressed = IsKeyPressed(ImGuiKey_Tab, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner) && !g.IO.KeyCtrl && !g.IO.KeyAlt; if (!tab_pressed) return; @@ -12967,17 +13376,23 @@ void ImGui::NavMoveRequestApplyResult() g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; } - // FIXME: Could become optional e.g. ImGuiNavMoveFlags_NoClearActiveId if we later want to apply navigation requests without altering active input. - if (g.ActiveId != result->ID) + // Clear active id unless requested not to + // FIXME: ImGuiNavMoveFlags_NoClearActiveId is currently unused as we don't have a clear strategy to preserve active id after interaction, + // so this is mostly provided as a gateway for further experiments (see #1418, #2890) + if (g.ActiveId != result->ID && (g.NavMoveFlags & ImGuiNavMoveFlags_NoClearActiveId) == 0) ClearActiveID(); // Don't set NavJustMovedToId if just landed on the same spot (which may happen with ImGuiNavMoveFlags_AllowCurrentNavId) // PageUp/PageDown however sets always set NavJustMovedTo (vs Home/End which doesn't) mimicking Windows behavior. if ((g.NavId != result->ID || (g.NavMoveFlags & ImGuiNavMoveFlags_IsPageMove)) && (g.NavMoveFlags & ImGuiNavMoveFlags_NoSelect) == 0) { + g.NavJustMovedFromFocusScopeId = g.NavFocusScopeId; g.NavJustMovedToId = result->ID; g.NavJustMovedToFocusScopeId = result->FocusScopeId; g.NavJustMovedToKeyMods = g.NavMoveKeyMods; + g.NavJustMovedToIsTabbing = (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) != 0; + g.NavJustMovedToHasSelectionData = (result->InFlags & ImGuiItemFlags_HasSelectionUserData) != 0; + //IMGUI_DEBUG_LOG_NAV("[nav] NavJustMovedFromFocusScopeId = 0x%08X, NavJustMovedToFocusScopeId = 0x%08X\n", g.NavJustMovedFromFocusScopeId, g.NavJustMovedToFocusScopeId); } // Apply new NavID/Focus @@ -13022,7 +13437,7 @@ static void ImGui::NavUpdateCancelRequest() ImGuiContext& g = *GImGui; const bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; - if (!(nav_keyboard_active && IsKeyPressed(ImGuiKey_Escape, ImGuiKeyOwner_None)) && !(nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, ImGuiKeyOwner_None))) + if (!(nav_keyboard_active && IsKeyPressed(ImGuiKey_Escape, 0, ImGuiKeyOwner_NoOwner)) && !(nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, 0, ImGuiKeyOwner_NoOwner))) return; IMGUI_DEBUG_LOG_NAV("[nav] NavUpdateCancelRequest()\n"); @@ -13071,10 +13486,10 @@ static float ImGui::NavUpdatePageUpPageDown() if ((window->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL) return 0.0f; - const bool page_up_held = IsKeyDown(ImGuiKey_PageUp, ImGuiKeyOwner_None); - const bool page_down_held = IsKeyDown(ImGuiKey_PageDown, ImGuiKeyOwner_None); - const bool home_pressed = IsKeyPressed(ImGuiKey_Home, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat); - const bool end_pressed = IsKeyPressed(ImGuiKey_End, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat); + const bool page_up_held = IsKeyDown(ImGuiKey_PageUp, ImGuiKeyOwner_NoOwner); + const bool page_down_held = IsKeyDown(ImGuiKey_PageDown, ImGuiKeyOwner_NoOwner); + const bool home_pressed = IsKeyPressed(ImGuiKey_Home, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner); + const bool end_pressed = IsKeyPressed(ImGuiKey_End, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner); if (page_up_held == page_down_held && home_pressed == end_pressed) // Proceed if either (not both) are pressed, otherwise early out return 0.0f; @@ -13084,9 +13499,9 @@ static float ImGui::NavUpdatePageUpPageDown() if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavWindowHasScrollY) { // Fallback manual-scroll when window has no navigable item - if (IsKeyPressed(ImGuiKey_PageUp, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat)) + if (IsKeyPressed(ImGuiKey_PageUp, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner)) SetScrollY(window, window->Scroll.y - window->InnerRect.GetHeight()); - else if (IsKeyPressed(ImGuiKey_PageDown, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat)) + else if (IsKeyPressed(ImGuiKey_PageDown, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner)) SetScrollY(window, window->Scroll.y + window->InnerRect.GetHeight()); else if (home_pressed) SetScrollY(window, 0.0f); @@ -13279,9 +13694,9 @@ static void ImGui::NavUpdateWindowing() const ImGuiID owner_id = ImHashStr("###NavUpdateWindowing"); const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; - const bool keyboard_next_window = allow_windowing && g.ConfigNavWindowingKeyNext && Shortcut(g.ConfigNavWindowingKeyNext, owner_id, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways); - const bool keyboard_prev_window = allow_windowing && g.ConfigNavWindowingKeyPrev && Shortcut(g.ConfigNavWindowingKeyPrev, owner_id, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways); - const bool start_windowing_with_gamepad = allow_windowing && nav_gamepad_active && !g.NavWindowingTarget && IsKeyPressed(ImGuiKey_NavGamepadMenu, 0, ImGuiInputFlags_None); + const bool keyboard_next_window = allow_windowing && g.ConfigNavWindowingKeyNext && Shortcut(g.ConfigNavWindowingKeyNext, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways, owner_id); + const bool keyboard_prev_window = allow_windowing && g.ConfigNavWindowingKeyPrev && Shortcut(g.ConfigNavWindowingKeyPrev, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways, owner_id); + const bool start_windowing_with_gamepad = allow_windowing && nav_gamepad_active && !g.NavWindowingTarget && IsKeyPressed(ImGuiKey_NavGamepadMenu, ImGuiInputFlags_None); const bool start_windowing_with_keyboard = allow_windowing && !g.NavWindowingTarget && (keyboard_next_window || keyboard_prev_window); // Note: enabled even without NavEnableKeyboard! if (start_windowing_with_gamepad || start_windowing_with_keyboard) if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.WindowsFocusOrder.Size - 1, -INT_MAX, -1)) @@ -13292,7 +13707,7 @@ static void ImGui::NavUpdateWindowing() g.NavWindowingToggleLayer = start_windowing_with_gamepad ? true : false; // Gamepad starts toggling layer g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_Keyboard : ImGuiInputSource_Gamepad; - // Register ownership of our mods. Using ImGuiInputFlags_RouteGlobalHigh in the Shortcut() calls instead would probably be correct but may have more side-effects. + // Manually register ownership of our mods. Using a global route in the Shortcut() calls instead would probably be correct but may have more side-effects. if (keyboard_next_window || keyboard_prev_window) SetKeyOwnersForKeyChord((g.ConfigNavWindowingKeyNext | g.ConfigNavWindowingKeyPrev) & ImGuiMod_Mask_, owner_id); } @@ -13340,7 +13755,7 @@ static void ImGui::NavUpdateWindowing() // Keyboard: Press and Release ALT to toggle menu layer const ImGuiKey windowing_toggle_keys[] = { ImGuiKey_LeftAlt, ImGuiKey_RightAlt }; for (ImGuiKey windowing_toggle_key : windowing_toggle_keys) - if (nav_keyboard_active && IsKeyPressed(windowing_toggle_key, ImGuiKeyOwner_None)) + if (nav_keyboard_active && IsKeyPressed(windowing_toggle_key, 0, ImGuiKeyOwner_NoOwner)) { g.NavWindowingToggleLayer = true; g.NavWindowingToggleKey = windowing_toggle_key; @@ -13355,7 +13770,7 @@ static void ImGui::NavUpdateWindowing() // We cancel toggling nav layer if an owner has claimed the key. if (io.InputQueueCharacters.Size > 0 || io.KeyCtrl || io.KeyShift || io.KeySuper) g.NavWindowingToggleLayer = false; - if (TestKeyOwner(g.NavWindowingToggleKey, ImGuiKeyOwner_None) == false || TestKeyOwner(ImGuiMod_Alt, ImGuiKeyOwner_None) == false) + if (TestKeyOwner(g.NavWindowingToggleKey, ImGuiKeyOwner_NoOwner) == false || TestKeyOwner(ImGuiMod_Alt, ImGuiKeyOwner_NoOwner) == false) g.NavWindowingToggleLayer = false; // Apply layer toggle on Alt release @@ -13484,6 +13899,8 @@ void ImGui::NavUpdateWindowingOverlay() 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); + if (g.ContextName[0] != 0) + SeparatorText(g.ContextName); for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--) { ImGuiWindow* window = g.WindowsFocusOrder[n]; @@ -13513,6 +13930,8 @@ bool ImGui::IsDragDropActive() void ImGui::ClearDragDrop() { ImGuiContext& g = *GImGui; + if (g.DragDropActive) + IMGUI_DEBUG_LOG_ACTIVEID("[dragdrop] ClearDragDrop()\n"); g.DragDropActive = false; g.DragDropPayload.Clear(); g.DragDropAcceptFlags = ImGuiDragDropFlags_None; @@ -13551,7 +13970,7 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) bool source_drag_active = false; ImGuiID source_id = 0; ImGuiID source_parent_id = 0; - if (!(flags & ImGuiDragDropFlags_SourceExtern)) + if ((flags & ImGuiDragDropFlags_SourceExtern) == 0) { source_id = g.LastItemData.ID; if (source_id != 0) @@ -13607,48 +14026,55 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) } else { + // When ImGuiDragDropFlags_SourceExtern is set: window = NULL; source_id = ImHashStr("#SourceExtern"); source_drag_active = true; + mouse_button = g.IO.MouseDown[0] ? 0 : -1; + KeepAliveID(source_id); + SetActiveID(source_id, NULL); } - if (source_drag_active) + IM_ASSERT(g.DragDropWithinTarget == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget() + if (!source_drag_active) + return false; + + // Activate drag and drop + if (!g.DragDropActive) { - if (!g.DragDropActive) - { - IM_ASSERT(source_id != 0); - ClearDragDrop(); - ImGuiPayload& payload = g.DragDropPayload; - payload.SourceId = source_id; - payload.SourceParentId = source_parent_id; - g.DragDropActive = true; - g.DragDropSourceFlags = flags; - g.DragDropMouseButton = mouse_button; - if (payload.SourceId == g.ActiveId) - g.ActiveIdNoClearOnFocusLoss = true; - } - g.DragDropSourceFrameCount = g.FrameCount; - g.DragDropWithinSource = true; - - if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) - { - // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request explicit) - // We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller may be emitting contents. - bool ret; - if (g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip)) - ret = BeginTooltipHidden(); - else - ret = BeginTooltip(); - IM_ASSERT(ret); // FIXME-NEWBEGIN: If this ever becomes false, we need to Begin("##Hidden", NULL, ImGuiWindowFlags_NoSavedSettings) + SetWindowHiddendAndSkipItemsForCurrentFrame(). - IM_UNUSED(ret); - } - - if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern)) - g.LastItemData.StatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; - - return true; + IM_ASSERT(source_id != 0); + ClearDragDrop(); + IMGUI_DEBUG_LOG_ACTIVEID("[dragdrop] BeginDragDropSource() DragDropActive = true, source_id = 0x%08X%s\n", + source_id, (flags & ImGuiDragDropFlags_SourceExtern) ? " (EXTERN)" : ""); + 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; } - return false; + g.DragDropSourceFrameCount = g.FrameCount; + g.DragDropWithinSource = true; + + if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + { + // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request explicit) + // We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller may be emitting contents. + bool ret; + if (g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip)) + ret = BeginTooltipHidden(); + else + ret = BeginTooltip(); + IM_ASSERT(ret); // FIXME-NEWBEGIN: If this ever becomes false, we need to Begin("##Hidden", NULL, ImGuiWindowFlags_NoSavedSettings) + SetWindowHiddendAndSkipItemsForCurrentFrame(). + IM_UNUSED(ret); + } + + if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern)) + g.LastItemData.StatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; + + return true; } void ImGui::EndDragDropSource() @@ -13678,7 +14104,7 @@ bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_s 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() + IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() if (cond == ImGuiCond_Always || payload.DataFrameCount == -1) { @@ -13727,9 +14153,9 @@ bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) if (window->SkipItems) return false; - IM_ASSERT(g.DragDropWithinTarget == false); + IM_ASSERT(g.DragDropWithinTarget == false && g.DragDropWithinSource == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget() g.DragDropTargetRect = bb; - g.DragDropTargetClipRect = window->ClipRect; // May want to be overriden by user depending on use case? + g.DragDropTargetClipRect = window->ClipRect; // May want to be overridden by user depending on use case? g.DragDropTargetId = id; g.DragDropWithinTarget = true; return true; @@ -13762,7 +14188,7 @@ bool ImGui::BeginDragDropTarget() if (g.DragDropPayload.SourceId == id) return false; - IM_ASSERT(g.DragDropWithinTarget == false); + IM_ASSERT(g.DragDropWithinTarget == false && g.DragDropWithinSource == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget() g.DragDropTargetRect = display_rect; g.DragDropTargetClipRect = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasClipRect) ? g.LastItemData.ClipRect : window->ClipRect; g.DragDropTargetId = id; @@ -13805,11 +14231,15 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop RenderDragDropTargetRect(r, g.DragDropTargetClipRect); 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 ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) && g.DragDropMouseButton == -1) + payload.Delivery = was_accepted_previously && (g.DragDropSourceFrameCount < g.FrameCount); + else + payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting OS window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery)) return NULL; - //IMGUI_DEBUG_LOG("AcceptDragDropPayload(): %08X: return payload\n", g.DragDropTargetId); + if (payload.Delivery) + IMGUI_DEBUG_LOG_ACTIVEID("[dragdrop] AcceptDragDropPayload(): 0x%08X: payload delivery\n", g.DragDropTargetId); return &payload; } @@ -13959,7 +14389,7 @@ void ImGui::LogBegin(ImGuiLogType type, int auto_open_depth) IM_ASSERT(g.LogEnabled == false); IM_ASSERT(g.LogFile == NULL); IM_ASSERT(g.LogBuffer.empty()); - g.LogEnabled = true; + g.LogEnabled = g.ItemUnclipByLog = true; g.LogType = type; g.LogNextPrefix = g.LogNextSuffix = NULL; g.LogDepthRef = window->DC.TreeDepth; @@ -14058,7 +14488,7 @@ void ImGui::LogFinish() break; } - g.LogEnabled = false; + g.LogEnabled = g.ItemUnclipByLog = false; g.LogType = ImGuiLogType_None; g.LogFile = NULL; g.LogBuffer.clear(); @@ -14078,10 +14508,10 @@ void ImGui::LogButtons() #endif const bool log_to_file = Button("Log To File"); SameLine(); const bool log_to_clipboard = Button("Log To Clipboard"); SameLine(); - PushTabStop(false); + PushItemFlag(ImGuiItemFlags_NoTabStop, true); SetNextItemWidth(80.0f); SliderInt("Default Depth", &g.LogDepthToExpandDefault, 0, 9, NULL); - PopTabStop(); + PopItemFlag(); PopID(); // Start logging at the end of the function so that the buttons don't appear in the log @@ -14861,6 +15291,10 @@ static void ImGui::UpdateViewportsNewFrame() g.PlatformMonitorsFullWorkRect.Add(monitor->WorkPos); g.PlatformMonitorsFullWorkRect.Add(monitor->WorkPos + monitor->WorkSize); } + else + { + g.FallbackMonitor = g.PlatformIO.Monitors[0]; + } for (ImGuiPlatformMonitor& monitor : g.PlatformIO.Monitors) { g.PlatformMonitorsFullWorkRect.Add(monitor.WorkPos); @@ -17380,6 +17814,7 @@ static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_w // Begin tab bar ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_AutoSelectNewTabs; // | ImGuiTabBarFlags_NoTabListScrollingButtons); tab_bar_flags |= ImGuiTabBarFlags_SaveSettings | ImGuiTabBarFlags_DockNode;// | ImGuiTabBarFlags_FittingPolicyScroll; + tab_bar_flags |= ImGuiTabBarFlags_DrawSelectedOverline; if (!host_window->Collapsed && is_focused) tab_bar_flags |= ImGuiTabBarFlags_IsFocused; tab_bar->ID = GetID("#TabBar"); @@ -17863,7 +18298,7 @@ static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDock ImRect tab_bb(tab_pos.x, tab_pos.y, tab_pos.x + tab_size.x, tab_pos.y + tab_size.y); tab_pos.x += tab_size.x + g.Style.ItemInnerSpacing.x; const ImU32 overlay_col_text = GetColorU32(payload_window->DockStyle.Colors[ImGuiWindowDockStyleCol_Text]); - const ImU32 overlay_col_tabs = GetColorU32(payload_window->DockStyle.Colors[ImGuiWindowDockStyleCol_TabActive]); + const ImU32 overlay_col_tabs = GetColorU32(payload_window->DockStyle.Colors[ImGuiWindowDockStyleCol_TabSelected]); PushStyleColor(ImGuiCol_Text, overlay_col_text); for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++) { @@ -18327,7 +18762,7 @@ void ImGui::SetWindowDock(ImGuiWindow* window, ImGuiID dock_id, ImGuiCond cond) // The Central Node is always displayed even when empty and shrink/extend according to the requested size of its neighbors. // DockSpace() needs to be submitted _before_ any window they can host. If you use a dockspace, submit it early in your app. // When ImGuiDockNodeFlags_KeepAliveOnly is set, nothing is submitted in the current window (function may be called from any location). -ImGuiID ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags flags, const ImGuiWindowClass* window_class) +ImGuiID ImGui::DockSpace(ImGuiID dockspace_id, const ImVec2& size_arg, ImGuiDockNodeFlags flags, const ImGuiWindowClass* window_class) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindowRead(); @@ -18343,16 +18778,16 @@ ImGuiID ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags window = GetCurrentWindow(); // call to set window->WriteAccessed = true; IM_ASSERT((flags & ImGuiDockNodeFlags_DockSpace) == 0); - IM_ASSERT(id != 0); - ImGuiDockNode* node = DockContextFindNodeByID(&g, id); - if (!node) + IM_ASSERT(dockspace_id != 0); + ImGuiDockNode* node = DockContextFindNodeByID(&g, dockspace_id); + if (node == NULL) { - IMGUI_DEBUG_LOG_DOCKING("[docking] DockSpace: dockspace node 0x%08X created\n", id); - node = DockContextAddNode(&g, id); + IMGUI_DEBUG_LOG_DOCKING("[docking] DockSpace: dockspace node 0x%08X created\n", dockspace_id); + node = DockContextAddNode(&g, dockspace_id); node->SetLocalFlags(ImGuiDockNodeFlags_CentralNode); } if (window_class && window_class->ClassId != node->WindowClass.ClassId) - IMGUI_DEBUG_LOG_DOCKING("[docking] DockSpace: dockspace node 0x%08X: setup WindowClass 0x%08X -> 0x%08X\n", id, node->WindowClass.ClassId, window_class->ClassId); + IMGUI_DEBUG_LOG_DOCKING("[docking] DockSpace: dockspace node 0x%08X: setup WindowClass 0x%08X -> 0x%08X\n", dockspace_id, node->WindowClass.ClassId, window_class->ClassId); node->SharedFlags = flags; node->WindowClass = window_class ? *window_class : ImGuiWindowClass(); @@ -18362,7 +18797,7 @@ ImGuiID ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags { IM_ASSERT(node->IsDockSpace() == false && "Cannot call DockSpace() twice a frame with the same ID"); node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace); - return id; + return dockspace_id; } node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace); @@ -18370,7 +18805,7 @@ ImGuiID ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags if (flags & ImGuiDockNodeFlags_KeepAliveOnly) { node->LastFrameAlive = g.FrameCount; - return id; + return dockspace_id; } const ImVec2 content_avail = GetContentRegionAvail(); @@ -18395,7 +18830,7 @@ ImGuiID ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags window_flags |= ImGuiWindowFlags_NoBackground; char title[256]; - ImFormatString(title, IM_ARRAYSIZE(title), "%s/DockSpace_%08X", window->Name, id); + ImFormatString(title, IM_ARRAYSIZE(title), "%s/DockSpace_%08X", window->Name, dockspace_id); PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.0f); Begin(title, NULL, window_flags); @@ -18424,22 +18859,23 @@ ImGuiID ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags ImRect bb(node->Pos, node->Pos + size); ItemSize(size); - ItemAdd(bb, id, NULL, ImGuiItemFlags_NoNav); // Not a nav point (could be, would need to draw the nav rect and replicate/refactor activation from BeginChild(), but seems like CTRL+Tab works better here?) + ItemAdd(bb, dockspace_id, NULL, ImGuiItemFlags_NoNav); // Not a nav point (could be, would need to draw the nav rect and replicate/refactor activation from BeginChild(), but seems like CTRL+Tab works better here?) if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && IsWindowChildOf(g.HoveredWindow, host_window, false, true)) // To fullfill IsItemHovered(), similar to EndChild() g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; - return id; + return dockspace_id; } // Tips: Use with ImGuiDockNodeFlags_PassthruCentralNode! -// The limitation with this call is that your window won't have a menu bar. +// The limitation with this call is that your window won't have a local menu bar, but you can also use BeginMainMenuBar(). // Even though we could pass window flags, it would also require the user to be able to call BeginMenuBar() somehow meaning we can't Begin/End in a single function. -// But you can also use BeginMainMenuBar(). If you really want a menu bar inside the same window as the one hosting the dockspace, you will need to copy this code somewhere and tweak it. -ImGuiID ImGui::DockSpaceOverViewport(const ImGuiViewport* viewport, ImGuiDockNodeFlags dockspace_flags, const ImGuiWindowClass* window_class) +// If you really want a menu bar inside the same window as the one hosting the dockspace, you will need to copy this code somewhere and tweak it. +ImGuiID ImGui::DockSpaceOverViewport(ImGuiID dockspace_id, const ImGuiViewport* viewport, ImGuiDockNodeFlags dockspace_flags, const ImGuiWindowClass* window_class) { if (viewport == NULL) viewport = GetMainViewport(); + // Submit a window filling the entire viewport SetNextWindowPos(viewport->WorkPos); SetNextWindowSize(viewport->WorkSize); SetNextWindowViewport(viewport->ID); @@ -18451,7 +18887,7 @@ ImGuiID ImGui::DockSpaceOverViewport(const ImGuiViewport* viewport, ImGuiDockNod host_window_flags |= ImGuiWindowFlags_NoBackground; char label[32]; - ImFormatString(label, IM_ARRAYSIZE(label), "DockSpaceViewport_%08X", viewport->ID); + ImFormatString(label, IM_ARRAYSIZE(label), "WindowOverViewport_%08X", viewport->ID); PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); @@ -18459,8 +18895,11 @@ ImGuiID ImGui::DockSpaceOverViewport(const ImGuiViewport* viewport, ImGuiDockNod Begin(label, NULL, host_window_flags); PopStyleVar(3); - ImGuiID dockspace_id = GetID("DockSpace"); + // Submit the dockspace + if (dockspace_id == 0) + dockspace_id = GetID("DockSpace"); DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags, window_class); + End(); return dockspace_id; @@ -18962,6 +19401,13 @@ static ImGuiDockNode* ImGui::DockContextBindNodeToWindow(ImGuiContext* ctx, ImGu return node; } +static void StoreDockStyleForWindow(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++) + window->DockStyle.Colors[color_n] = ImGui::ColorConvertFloat4ToU32(g.Style.Colors[GWindowDockStyleColors[color_n]]); +} + void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) { ImGuiContext& g = *GImGui; @@ -19025,8 +19471,7 @@ void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) } // Store style overrides - for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++) - window->DockStyle.Colors[color_n] = ColorConvertFloat4ToU32(g.Style.Colors[GWindowDockStyleColors[color_n]]); + StoreDockStyleForWindow(window); // Fast path return. It is common for windows to hold on a persistent DockId but be the only visible window, // and never create neither a host window neither a tab bar. @@ -19102,6 +19547,7 @@ void ImGui::BeginDockableDragDropSource(ImGuiWindow* window) // When ConfigDockingWithShift is set, display a tooltip to increase UI affordance. // We cannot set for HoveredWindowUnderMovingWindow != NULL here, as it is only valid/useful when drag and drop is already active // (because of the 'is_mouse_dragging_with_an_expected_destination' logic in UpdateViewportsNewFrame() function) + IM_ASSERT(g.NextWindowData.Flags == 0); if (g.IO.ConfigDockingWithShift && g.MouseStationaryTimer >= 1.0f && g.ActiveId >= 1.0f) SetTooltip("%s", LocalizeGetMsg(ImGuiLocKey_DockingHoldShiftToDock)); return; @@ -19111,14 +19557,12 @@ void ImGui::BeginDockableDragDropSource(ImGuiWindow* window) window = window->RootWindowDockTree; IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0); bool is_drag_docking = (g.IO.ConfigDockingWithShift) || ImRect(0, 0, window->SizeFull.x, GetFrameHeight()).Contains(g.ActiveIdClickOffset); // FIXME-DOCKING: Need to make this stateful and explicit - if (is_drag_docking && BeginDragDropSource(ImGuiDragDropFlags_SourceNoPreviewTooltip | ImGuiDragDropFlags_SourceNoHoldToOpenOthers | ImGuiDragDropFlags_SourceAutoExpirePayload)) + ImGuiDragDropFlags drag_drop_flags = ImGuiDragDropFlags_SourceNoPreviewTooltip | ImGuiDragDropFlags_SourceNoHoldToOpenOthers | ImGuiDragDropFlags_PayloadAutoExpire | ImGuiDragDropFlags_PayloadNoCrossContext | ImGuiDragDropFlags_PayloadNoCrossProcess; + if (is_drag_docking && BeginDragDropSource(drag_drop_flags)) { SetDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, &window, sizeof(window)); EndDragDropSource(); - - // Store style overrides - for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++) - window->DockStyle.Colors[color_n] = ColorConvertFloat4ToU32(g.Style.Colors[GWindowDockStyleColors[color_n]]); + StoreDockStyleForWindow(window); // Store style overrides while dragging (even when not docked) because docking preview may need it. } } @@ -19432,6 +19876,10 @@ static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettings //----------------------------------------------------------------------------- // [SECTION] PLATFORM DEPENDENT HELPERS //----------------------------------------------------------------------------- +// - Default clipboard handlers +// - Default shell function handlers +// - Default IME handlers +//----------------------------------------------------------------------------- #if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) @@ -19557,8 +20005,62 @@ static void SetClipboardTextFn_DefaultImpl(void* user_data_ctx, const char* text g.ClipboardHandlerData[(int)(text_end - text)] = 0; } +#endif // Default clipboard handlers + +//----------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS +#if defined(__APPLE__) && TARGET_OS_IPHONE +#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS #endif +#if defined(_WIN32) && defined(IMGUI_DISABLE_WIN32_FUNCTIONS) +#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS +#endif +#endif + +#ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS +#ifdef _WIN32 +#include // ShellExecuteA() +#ifdef _MSC_VER +#pragma comment(lib, "shell32") +#endif +static bool PlatformOpenInShellFn_DefaultImpl(ImGuiContext*, const char* path) +{ + return (INT_PTR)::ShellExecuteA(NULL, "open", path, NULL, NULL, SW_SHOWDEFAULT) > 32; +} +#else +#include +#include +static bool PlatformOpenInShellFn_DefaultImpl(ImGuiContext*, const char* path) +{ +#if defined(__APPLE__) + const char* args[] { "open", "--", path, NULL }; +#else + const char* args[] { "xdg-open", path, NULL }; +#endif + pid_t pid = fork(); + if (pid < 0) + return false; + if (!pid) + { + execvp(args[0], const_cast(args)); + exit(-1); + } + else + { + int status; + waitpid(pid, &status, 0); + return WEXITSTATUS(status) == 0; + } +} +#endif +#else +static bool PlatformOpenInShellFn_DefaultImpl(ImGuiContext*, const char*) { return false; } +#endif // Default shell handlers + +//----------------------------------------------------------------------------- + // Win32 API IME support (for Asian languages, etc.) #if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) @@ -19567,7 +20069,7 @@ static void SetClipboardTextFn_DefaultImpl(void* user_data_ctx, const char* text #pragma comment(lib, "imm32") #endif -static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport* viewport, ImGuiPlatformImeData* data) +static void PlatformSetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport* viewport, ImGuiPlatformImeData* data) { // Notify OS Input Method Editor of text input position HWND hwnd = (HWND)viewport->PlatformHandleRaw; @@ -19593,9 +20095,9 @@ static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport* viewport, ImGuiPlatf #else -static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport*, ImGuiPlatformImeData*) {} +static void PlatformSetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport*, ImGuiPlatformImeData*) {} -#endif +#endif // Default IME handlers //----------------------------------------------------------------------------- // [SECTION] METRICS/DEBUGGER WINDOW @@ -19861,6 +20363,11 @@ void ImGui::ShowMetricsWindow(bool* p_open) // Basic info Text("Dear ImGui %s", GetVersion()); + if (g.ContextName[0] != 0) + { + SameLine(); + Text("(Context Name: \"%s\")", g.ContextName); + } 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 current allocations", io.MetricsRenderWindows, g.DebugAllocInfo.TotalAllocCount - g.DebugAllocInfo.TotalFreeCount); @@ -20077,14 +20584,11 @@ void ImGui::ShowMetricsWindow(bool* p_open) { for (int i = 0; i < g.PlatformIO.Monitors.Size; i++) { - const ImGuiPlatformMonitor& mon = g.PlatformIO.Monitors[i]; - BulletText("Monitor #%d: DPI %.0f%%\n MainMin (%.0f,%.0f), MainMax (%.0f,%.0f), MainSize (%.0f,%.0f)\n WorkMin (%.0f,%.0f), WorkMax (%.0f,%.0f), WorkSize (%.0f,%.0f)", - i, mon.DpiScale * 100.0f, - mon.MainPos.x, mon.MainPos.y, mon.MainPos.x + mon.MainSize.x, mon.MainPos.y + mon.MainSize.y, mon.MainSize.x, mon.MainSize.y, - mon.WorkPos.x, mon.WorkPos.y, mon.WorkPos.x + mon.WorkSize.x, mon.WorkPos.y + mon.WorkSize.y, mon.WorkSize.x, mon.WorkSize.y); + DebugNodePlatformMonitor(&g.PlatformIO.Monitors[i], "Monitor", i); if (IsItemHovered()) cfg->HighlightMonitorIdx = i; } + DebugNodePlatformMonitor(&g.FallbackMonitor, "Fallback", 0); TreePop(); } @@ -20128,9 +20632,9 @@ void ImGui::ShowMetricsWindow(bool* p_open) { // As it's difficult to interact with tree nodes while popups are open, we display everything inline. ImGuiWindow* window = popup_data.Window; - BulletText("PopupID: %08x, Window: '%s' (%s%s), BackupNavWindow '%s', ParentWindow '%s'", + BulletText("PopupID: %08x, Window: '%s' (%s%s), RestoreNavWindow '%s', ParentWindow '%s'", popup_data.PopupId, window ? window->Name : "NULL", window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? "Child;" : "", window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? "Menu;" : "", - popup_data.BackupNavWindow ? popup_data.BackupNavWindow->Name : "NULL", window && window->ParentWindow ? window->ParentWindow->Name : "NULL"); + popup_data.RestoreNavWindow ? popup_data.RestoreNavWindow->Name : "NULL", window && window->ParentWindow ? window->ParentWindow->Name : "NULL"); } TreePop(); } @@ -20179,6 +20683,17 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } + // Details for MultiSelect + if (TreeNode("MultiSelect", "MultiSelect (%d)", g.MultiSelectStorage.GetAliveCount())) + { + ImGuiBoxSelectState* bs = &g.BoxSelectState; + BulletText("BoxSelect ID=0x%08X, Starting = %d, Active %d", bs->ID, bs->IsStarting, bs->IsActive); + for (int n = 0; n < g.MultiSelectStorage.GetMapSize(); n++) + if (ImGuiMultiSelectState* state = g.MultiSelectStorage.TryGetMapData(n)) + DebugNodeMultiSelectState(state); + TreePop(); + } + // Details for Docking #ifdef IMGUI_HAS_DOCK if (TreeNode("Docking")) @@ -20281,7 +20796,12 @@ void ImGui::ShowMetricsWindow(bool* p_open) for (int n = buf_size - 1; n >= 0; n--) { ImGuiDebugAllocEntry* entry = &info->LastEntriesBuf[(info->LastEntriesIdx - n + buf_size) % buf_size]; - BulletText("Frame %06d: %+3d ( %2d malloc, %2d free )%s", entry->FrameCount, entry->AllocCount - entry->FreeCount, entry->AllocCount, entry->FreeCount, (n == 0) ? " (most recent)" : ""); + BulletText("Frame %06d: %+3d ( %2d alloc, %2d free )", entry->FrameCount, entry->AllocCount - entry->FreeCount, entry->AllocCount, entry->FreeCount); + if (n == 0) + { + SameLine(); + Text("<- %d frames ago", g.FrameCount - entry->FrameCount); + } } TreePop(); } @@ -20343,7 +20863,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key); - if (owner_data->OwnerCurr == ImGuiKeyOwner_None) + if (owner_data->OwnerCurr == ImGuiKeyOwner_NoOwner) continue; Text("%s: 0x%08X%s", GetKeyName(key), owner_data->OwnerCurr, owner_data->LockUntilRelease ? " LockUntilRelease" : owner_data->LockThisFrame ? " LockThisFrame" : ""); @@ -20905,8 +21425,11 @@ void ImGui::DebugNodeStorage(ImGuiStorage* storage, const char* label) { if (!TreeNode(label, "%s: %d entries, %d bytes", label, storage->Data.Size, storage->Data.size_in_bytes())) return; - for (const ImGuiStorage::ImGuiStoragePair& p : storage->Data) + for (const ImGuiStoragePair& p : storage->Data) + { BulletText("Key 0x%08X Value { i: %d }", p.key, p.val_i); // Important: we currently don't store a type, real value may not be integer. + DebugLocateItemOnHover(p.key); + } TreePop(); } @@ -20987,6 +21510,14 @@ void ImGui::DebugNodeViewport(ImGuiViewportP* viewport) } } +void ImGui::DebugNodePlatformMonitor(ImGuiPlatformMonitor* monitor, const char* label, int idx) +{ + BulletText("%s %d: DPI %.0f%%\n MainMin (%.0f,%.0f), MainMax (%.0f,%.0f), MainSize (%.0f,%.0f)\n WorkMin (%.0f,%.0f), WorkMax (%.0f,%.0f), WorkSize (%.0f,%.0f)", + label, idx, monitor->DpiScale * 100.0f, + monitor->MainPos.x, monitor->MainPos.y, monitor->MainPos.x + monitor->MainSize.x, monitor->MainPos.y + monitor->MainSize.y, monitor->MainSize.x, monitor->MainSize.y, + monitor->WorkPos.x, monitor->WorkPos.y, monitor->WorkPos.x + monitor->WorkSize.x, monitor->WorkPos.y + monitor->WorkSize.y, monitor->WorkSize.x, monitor->WorkSize.y); +} + void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) { if (window == NULL) @@ -21019,6 +21550,12 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) (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" : ""); + if (flags & ImGuiWindowFlags_ChildWindow) + BulletText("ChildFlags: 0x%08X (%s%s%s%s..)", window->ChildFlags, + (window->ChildFlags & ImGuiChildFlags_Border) ? "Border " : "", + (window->ChildFlags & ImGuiChildFlags_ResizeX) ? "ResizeX " : "", + (window->ChildFlags & ImGuiChildFlags_ResizeY) ? "ResizeY " : "", + (window->ChildFlags & ImGuiChildFlags_NavFlattened) ? "NavFlattened " : ""); BulletText("WindowClassId: 0x%08X", window->WindowClass.ClassId); 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); @@ -21115,14 +21652,20 @@ void ImGui::DebugLogV(const char* fmt, va_list args) { ImGuiContext& g = *GImGui; const int old_size = g.DebugLogBuf.size(); - g.DebugLogBuf.appendf("[%05d] ", g.FrameCount); + if (g.ContextName[0] != 0) + g.DebugLogBuf.appendf("[%s] [%05d] ", g.ContextName, g.FrameCount); + else + g.DebugLogBuf.appendf("[%05d] ", g.FrameCount); g.DebugLogBuf.appendfv(fmt, args); g.DebugLogIndex.append(g.DebugLogBuf.c_str(), old_size, g.DebugLogBuf.size()); if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToTTY) IMGUI_DEBUG_PRINTF("%s", g.DebugLogBuf.begin() + old_size); #ifdef IMGUI_ENABLE_TEST_ENGINE + // IMGUI_TEST_ENGINE_LOG() adds a trailing \n automatically + const int new_size = g.DebugLogBuf.size(); + const bool trailing_carriage_return = (g.DebugLogBuf[new_size - 1] == '\n'); if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToTestEngine) - IMGUI_TEST_ENGINE_LOG("%s", g.DebugLogBuf.begin() + old_size); + IMGUI_TEST_ENGINE_LOG("%.*s", new_size - old_size - (trailing_carriage_return ? 1 : 0), g.DebugLogBuf.begin() + old_size); #endif } @@ -21132,7 +21675,7 @@ static void SameLineOrWrap(const ImVec2& size) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImVec2 pos(window->DC.CursorPosPrevLine.x + g.Style.ItemSpacing.x, window->DC.CursorPosPrevLine.y); - if (window->ClipRect.Contains(ImRect(pos, pos + size))) + if (window->WorkRect.Contains(ImRect(pos, pos + size))) ImGui::SameLine(); } @@ -21171,7 +21714,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open) ShowDebugLogFlag("IO", ImGuiDebugLogFlags_EventIO); ShowDebugLogFlag("Nav", ImGuiDebugLogFlags_EventNav); ShowDebugLogFlag("Popup", ImGuiDebugLogFlags_EventPopup); - //ShowDebugLogFlag("Selection", ImGuiDebugLogFlags_EventSelection); + ShowDebugLogFlag("Selection", ImGuiDebugLogFlags_EventSelection); ShowDebugLogFlag("Viewport", ImGuiDebugLogFlags_EventViewport); ShowDebugLogFlag("InputRouting", ImGuiDebugLogFlags_EventInputRouting); @@ -21183,6 +21726,22 @@ void ImGui::ShowDebugLogWindow(bool* p_open) SameLine(); if (SmallButton("Copy")) SetClipboardText(g.DebugLogBuf.c_str()); + SameLine(); + if (SmallButton("Configure Outputs..")) + OpenPopup("Outputs"); + if (BeginPopup("Outputs")) + { + CheckboxFlags("OutputToTTY", &g.DebugLogFlags, ImGuiDebugLogFlags_OutputToTTY); +#ifndef IMGUI_ENABLE_TEST_ENGINE + BeginDisabled(); +#endif + CheckboxFlags("OutputToTestEngine", &g.DebugLogFlags, ImGuiDebugLogFlags_OutputToTestEngine); +#ifndef IMGUI_ENABLE_TEST_ENGINE + EndDisabled(); +#endif + EndPopup(); + } + BeginChild("##log", ImVec2(0.0f, 0.0f), ImGuiChildFlags_Border, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); const ImGuiDebugLogFlags backup_log_flags = g.DebugLogFlags; @@ -21192,25 +21751,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open) clipper.Begin(g.DebugLogIndex.size()); while (clipper.Step()) for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) - { - const char* line_begin = g.DebugLogIndex.get_line_begin(g.DebugLogBuf.c_str(), line_no); - const char* line_end = g.DebugLogIndex.get_line_end(g.DebugLogBuf.c_str(), line_no); - TextUnformatted(line_begin, line_end); // Display line - ImRect text_rect = g.LastItemData.Rect; - if (IsItemHovered()) - for (const char* p = line_begin; p <= line_end - 10; p++) // Search for 0x???????? identifiers - { - ImGuiID id = 0; - if (p[0] != '0' || (p[1] != 'x' && p[1] != 'X') || sscanf(p + 2, "%X", &id) != 1) - continue; - ImVec2 p0 = CalcTextSize(line_begin, p); - ImVec2 p1 = CalcTextSize(p, p + 10); - g.LastItemData.Rect = ImRect(text_rect.Min + ImVec2(p0.x, 0.0f), text_rect.Min + ImVec2(p0.x + p1.x, p1.y)); - if (IsMouseHoveringRect(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, true)) - DebugLocateItemOnHover(id); - p += 10; - } - } + DebugTextUnformattedWithLocateItem(g.DebugLogIndex.get_line_begin(g.DebugLogBuf.c_str(), line_no), g.DebugLogIndex.get_line_end(g.DebugLogBuf.c_str(), line_no)); g.DebugLogFlags = backup_log_flags; if (GetScrollY() >= GetScrollMaxY()) SetScrollHereY(1.0f); @@ -21219,6 +21760,28 @@ void ImGui::ShowDebugLogWindow(bool* p_open) End(); } +// Display line, search for 0xXXXXXXXX identifiers and call DebugLocateItemOnHover() when hovered. +void ImGui::DebugTextUnformattedWithLocateItem(const char* line_begin, const char* line_end) +{ + TextUnformatted(line_begin, line_end); + if (!IsItemHovered()) + return; + ImGuiContext& g = *GImGui; + ImRect text_rect = g.LastItemData.Rect; + for (const char* p = line_begin; p <= line_end - 10; p++) + { + ImGuiID id = 0; + if (p[0] != '0' || (p[1] != 'x' && p[1] != 'X') || sscanf(p + 2, "%X", &id) != 1 || ImCharIsXdigitA(p[10])) + continue; + ImVec2 p0 = CalcTextSize(line_begin, p); + ImVec2 p1 = CalcTextSize(p, p + 10); + g.LastItemData.Rect = ImRect(text_rect.Min + ImVec2(p0.x, 0.0f), text_rect.Min + ImVec2(p0.x + p1.x, p1.y)); + if (IsMouseHoveringRect(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, true)) + DebugLocateItemOnHover(id); + p += 10; + } +} + //----------------------------------------------------------------------------- // [SECTION] OTHER DEBUG TOOLS (ITEM PICKER, ID STACK TOOL) //----------------------------------------------------------------------------- @@ -21476,7 +22039,7 @@ void ImGui::ShowIDStackToolWindow(bool* p_open) 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 && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, 0, ImGuiInputFlags_RouteGlobal)) + if (tool->CopyToClipboardOnCtrlC && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused)) { tool->CopyToClipboardLastTime = (float)g.Time; char* p = g.TempBuffer.Data; @@ -21539,8 +22102,6 @@ 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::ShowIDStackToolWindow(bool*) {} void ImGui::DebugStartItemPicker() {} diff --git a/libs/imgui/imgui.h b/libs/imgui/imgui.h index c2df670..a0e1ee0 100644 --- a/libs/imgui/imgui.h +++ b/libs/imgui/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.90.4 +// dear imgui, v1.91.0 // (headers) // Help: @@ -7,15 +7,19 @@ // - Read top of imgui.cpp for more details, links and comments. // Resources: -// - FAQ https://dearimgui.com/faq -// - Getting Started https://dearimgui.com/getting-started -// - Homepage https://github.com/ocornut/imgui -// - Releases & changelog https://github.com/ocornut/imgui/releases -// - Gallery https://github.com/ocornut/imgui/issues/6897 (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 -// - Tests & Automation https://github.com/ocornut/imgui_test_engine +// - FAQ ........................ https://dearimgui.com/faq (in repository as docs/FAQ.md) +// - Homepage ................... https://github.com/ocornut/imgui +// - Releases & changelog ....... https://github.com/ocornut/imgui/releases +// - Gallery .................... https://github.com/ocornut/imgui/issues/7503 (please post your screenshots/video there!) +// - Wiki ....................... https://github.com/ocornut/imgui/wiki (lots of good stuff there) +// - Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started (how to integrate in an existing app by adding ~25 lines of code) +// - Third-party Extensions https://github.com/ocornut/imgui/wiki/Useful-Extensions (ImPlot & many more) +// - Bindings/Backends https://github.com/ocornut/imgui/wiki/Bindings (language bindings, backends for various tech/engines) +// - Glossary https://github.com/ocornut/imgui/wiki/Glossary +// - Debug Tools https://github.com/ocornut/imgui/wiki/Debug-Tools +// - Software using Dear ImGui https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui +// - Issues & support ........... https://github.com/ocornut/imgui/issues +// - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your apps) // For first-time users having issues compiling/linking/running/loading fonts: // please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above. @@ -23,8 +27,8 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.90.4" -#define IMGUI_VERSION_NUM 19040 +#define IMGUI_VERSION "1.91.0" +#define IMGUI_VERSION_NUM 19100 #define IMGUI_HAS_TABLE #define IMGUI_HAS_VIEWPORT // Viewport WIP branch #define IMGUI_HAS_DOCK // Docking WIP branch @@ -37,11 +41,12 @@ Index of this file: // [SECTION] Dear ImGui end-user API functions // [SECTION] Flags & Enumerations // [SECTION] Tables API flags and structures (ImGuiTableFlags, ImGuiTableColumnFlags, ImGuiTableRowFlags, ImGuiTableBgTarget, ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs) -// [SECTION] Helpers: Memory allocations macros, ImVector<> +// [SECTION] Helpers: Debug log, Memory allocations macros, ImVector<> // [SECTION] ImGuiStyle // [SECTION] ImGuiIO // [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiWindowClass, ImGuiPayload) // [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) +// [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectIO, ImGuiSelectionRequest, ImGuiSelectionBasicStorage, ImGuiSelectionExternalStorage) // [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData) // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont) // [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport) @@ -88,6 +93,8 @@ Index of this file: #endif #define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR) / sizeof(*(_ARR)))) // Size of a static C-style array. Don't use on pointers! #define IM_UNUSED(_VAR) ((void)(_VAR)) // Used to silence "unused variable warnings". Often useful as asserts may be stripped out from final builds. + +// Check that version and structures layouts are matching between compiled imgui code and caller. Read comments above DebugCheckVersionAndDataLayout() for details. #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) // Helper Macros - IM_FMTARGS, IM_FMTLIST: Apply printf-style warnings to our formatting functions. @@ -128,6 +135,7 @@ Index of this file: #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" #pragma clang diagnostic ignored "-Wreserved-identifier" // warning: identifier '_Xxx' is reserved because it starts with '_' followed by a capital letter +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind @@ -138,6 +146,17 @@ Index of this file: // [SECTION] Forward declarations and basic types //----------------------------------------------------------------------------- +// Scalar data types +typedef unsigned int ImGuiID;// A unique ID used by widgets (typically the result of hashing a stack of string) +typedef signed char ImS8; // 8-bit signed integer +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 + // 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) @@ -158,13 +177,18 @@ struct ImGuiIO; // Main configuration and I/O between your a 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 ImGuiMultiSelectIO; // Structure to interact with a BeginMultiSelect()/EndMultiSelect() block 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 ImGuiPlatformIO; // Multi-viewport support: interface for Platform/Renderer backends + viewports to render struct ImGuiPlatformMonitor; // Multi-viewport support: user-provided bounds for each connected monitor/display. Used when positioning popups and tooltips to avoid them straddling monitors -struct ImGuiPlatformImeData; // Platform IME data for io.SetPlatformImeDataFn() function. +struct ImGuiPlatformImeData; // Platform IME data for io.PlatformSetImeDataFn() function. +struct ImGuiSelectionBasicStorage; // Optional helper to store multi-selection state + apply multi-selection requests. +struct ImGuiSelectionExternalStorage;//Optional helper to apply multi-selection requests to existing randomly accessible storage. +struct ImGuiSelectionRequest; // A selection request (stored in ImGuiMultiSelectIO) struct ImGuiSizeCallbackData; // Callback data when using SetNextWindowSizeConstraints() (rare/advanced use) -struct ImGuiStorage; // Helper for key->value storage +struct ImGuiStorage; // Helper for key->value storage (container sorted by key) +struct ImGuiStoragePair; // Helper for key->value storage (pair) 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 @@ -176,24 +200,26 @@ struct ImGuiWindowClass; // Window class (rare/advanced uses: provide // Enumerations // - We don't use strongly typed enums much because they add constraints (can't extend in private code, can't store typed in bit fields, extra casting on iteration) // - Tip: Use your programming IDE navigation facilities on the names in the _central column_ below to find the actual flags/enum lists! -// In Visual Studio IDE: CTRL+comma ("Edit.GoToAll") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. -// With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. +// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. +// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. +enum ImGuiDir : int; // -> enum ImGuiDir // Enum: A cardinal direction (Left, Right, Up, Down) enum ImGuiKey : int; // -> enum ImGuiKey // Enum: A key identifier (ImGuiKey_XXX or ImGuiMod_XXX value) enum ImGuiMouseSource : int; // -> enum ImGuiMouseSource // Enum; A mouse input source identifier (Mouse, TouchScreen, Pen) +enum ImGuiSortDirection : ImU8; // -> enum ImGuiSortDirection // Enum: A sorting direction (ascending or descending) typedef int ImGuiCol; // -> enum ImGuiCol_ // Enum: A color identifier for styling typedef int ImGuiCond; // -> enum ImGuiCond_ // Enum: A condition for many Set*() functions typedef int ImGuiDataType; // -> enum ImGuiDataType_ // Enum: A primary data type -typedef int ImGuiDir; // -> enum ImGuiDir_ // Enum: A cardinal direction typedef int ImGuiMouseButton; // -> enum ImGuiMouseButton_ // Enum: A mouse button identifier (0=left, 1=right, 2=middle) typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A mouse cursor shape -typedef int ImGuiSortDirection; // -> enum ImGuiSortDirection_ // Enum: A sorting direction (ascending or descending) typedef int ImGuiStyleVar; // -> enum ImGuiStyleVar_ // Enum: A variable identifier for styling typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A color target for TableSetBgColor() // Flags (declared as int 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. +// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. +// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. 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 @@ -207,9 +233,12 @@ typedef int ImGuiDockNodeFlags; // -> enum ImGuiDockNodeFlags_ // Flags: f 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 ImGuiInputFlags; // -> enum ImGuiInputFlags_ // Flags: for Shortcut(), SetNextItemShortcut() typedef int ImGuiInputTextFlags; // -> enum ImGuiInputTextFlags_ // Flags: for InputText(), InputTextMultiline() +typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag(), shared by all items typedef int ImGuiKeyChord; // -> ImGuiKey | ImGuiMod_XXX // Flags: for IsKeyChordPressed(), Shortcut() etc. an ImGuiKey optionally OR-ed with one or more ImGuiMod_XXX values. typedef int ImGuiPopupFlags; // -> enum ImGuiPopupFlags_ // Flags: for OpenPopup*(), BeginPopupContext*(), IsPopupOpen() +typedef int ImGuiMultiSelectFlags; // -> enum ImGuiMultiSelectFlags_// Flags: for BeginMultiSelect() 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() @@ -235,17 +264,6 @@ typedef void* ImTextureID; // Default: store a pointer or an integer fi 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 int ImWchar32; // A single decoded U32 character/code point. We encode them as multi bytes UTF-8 when used in strings. @@ -256,6 +274,11 @@ typedef ImWchar32 ImWchar; typedef ImWchar16 ImWchar; #endif +// Multi-Selection item index or identifier when using BeginMultiSelect() +// - Used by SetNextItemSelectionUserData() + and inside ImGuiMultiSelectIO structure. +// - Most users are likely to use this store an item INDEX but this may be used to store a POINTER/ID as well. Read comments near ImGuiMultiSelectIO for details. +typedef ImS64 ImGuiSelectionUserData; + // Callback and functions types typedef int (*ImGuiInputTextCallback)(ImGuiInputTextCallbackData* data); // Callback function for ImGui::InputText() typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data); // Callback function for ImGui::SetNextWindowSizeConstraints() @@ -376,10 +399,10 @@ namespace ImGui IMGUI_API bool IsWindowHovered(ImGuiHoveredFlags flags=0); // is current window hovered and hoverable (e.g. not blocked by a popup/modal)? See ImGuiHoveredFlags_ for options. IMPORTANT: If you are trying to check whether your mouse should be dispatched to Dear ImGui or to your underlying app, you should not use this function! Use the 'io.WantCaptureMouse' boolean for that! Refer to FAQ entry "How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" for details. IMGUI_API ImDrawList* GetWindowDrawList(); // get draw list associated to the current window, to append your own drawing primitives IMGUI_API float GetWindowDpiScale(); // get DPI scale currently associated to the current window's viewport. - IMGUI_API ImVec2 GetWindowPos(); // get current window position in screen space (note: it is unlikely you need to use this. Consider using current layout pos instead, GetCursorScreenPos()) - IMGUI_API ImVec2 GetWindowSize(); // get current window size (note: it is unlikely you need to use this. Consider using GetCursorScreenPos() and e.g. GetContentRegionAvail() instead) - IMGUI_API float GetWindowWidth(); // get current window width (shortcut for GetWindowSize().x) - IMGUI_API float GetWindowHeight(); // get current window height (shortcut for GetWindowSize().y) + IMGUI_API ImVec2 GetWindowPos(); // get current window position in screen space (IT IS UNLIKELY YOU EVER NEED TO USE THIS. Consider always using GetCursorScreenPos() and GetContentRegionAvail() instead) + IMGUI_API ImVec2 GetWindowSize(); // get current window size (IT IS UNLIKELY YOU EVER NEED TO USE THIS. Consider always using GetCursorScreenPos() and GetContentRegionAvail() instead) + IMGUI_API float GetWindowWidth(); // get current window width (IT IS UNLIKELY YOU EVER NEED TO USE THIS). Shortcut for GetWindowSize().x. + IMGUI_API float GetWindowHeight(); // get current window height (IT IS UNLIKELY YOU EVER NEED TO USE THIS). Shortcut for GetWindowSize().y. IMGUI_API ImGuiViewport*GetWindowViewport(); // get viewport currently associated to the current window. // Window manipulation @@ -403,14 +426,6 @@ namespace ImGui IMGUI_API void SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond = 0); // set named window collapsed state IMGUI_API void SetWindowFocus(const char* name); // set named window to be focused / top-most. use NULL to remove focus. - // Content region - // - Retrieve available space from a given point. GetContentRegionAvail() is frequently useful. - // - Those functions are bound to be redesigned (they are confusing, incomplete and the Min/Max return values are in local window coordinates which increases confusion) - IMGUI_API ImVec2 GetContentRegionAvail(); // == GetContentRegionMax() - GetCursorPos() - IMGUI_API ImVec2 GetContentRegionMax(); // current content boundaries (typically window boundaries including scrolling, or current column boundaries), in windows coordinates - IMGUI_API ImVec2 GetWindowContentRegionMin(); // content boundaries min for the full window (roughly (0,0)-Scroll), in window coordinates - IMGUI_API ImVec2 GetWindowContentRegionMax(); // content boundaries max for the full window (roughly (0,0)+Size-Scroll) where Size can be overridden with SetNextWindowContentSize(), in window coordinates - // Windows Scrolling // - Any change of Scroll will be applied at the beginning of next frame in the first call to Begin(). // - You may instead use SetNextWindowScroll() prior to calling Begin() to avoid this delay, as an alternative to using SetScrollX()/SetScrollY(). @@ -434,10 +449,8 @@ namespace ImGui IMGUI_API void PushStyleVar(ImGuiStyleVar idx, float val); // modify a style float variable. always use this if you modify the style after NewFrame(). IMGUI_API void PushStyleVar(ImGuiStyleVar idx, const ImVec2& val); // modify a style ImVec2 variable. always use this if you modify the style after NewFrame(). IMGUI_API void PopStyleVar(int count = 1); - IMGUI_API void PushTabStop(bool tab_stop); // == tab stop enable. Allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets - IMGUI_API void PopTabStop(); - IMGUI_API void PushButtonRepeat(bool repeat); // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (using io.KeyRepeatDelay/io.KeyRepeatRate setting). Note that you can call IsItemActive() after any Button() to tell if the button is held in the current frame. - IMGUI_API void PopButtonRepeat(); + IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); // modify specified shared item flag, e.g. PushItemFlag(ImGuiItemFlags_NoTabStop, true) + IMGUI_API void PopItemFlag(); // Parameters stacks (current window) IMGUI_API void PushItemWidth(float item_width); // push width of items for common large "item+label" widgets. >0.0f: width in pixels, <0.0f align xx pixels to the right of window (so -FLT_MIN always align width to the right side). @@ -461,19 +474,22 @@ namespace ImGui // - 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. + // - YOU CAN DO 99% OF WHAT YOU NEED WITH ONLY GetCursorScreenPos() and GetContentRegionAvail(). // - Attention! We currently have inconsistencies between window-local and absolute positions we will aim to fix with future API: // - Absolute coordinate: GetCursorScreenPos(), SetCursorScreenPos(), all ImDrawList:: functions. -> this is the preferred way forward. - // - Window-local coordinates: SameLine(), GetCursorPos(), SetCursorPos(), GetCursorStartPos(), GetContentRegionMax(), GetWindowContentRegion*(), PushTextWrapPos() - // - GetCursorScreenPos() = GetCursorPos() + GetWindowPos(). GetWindowPos() is almost only ever useful to convert from window-local to absolute coordinates. - IMGUI_API ImVec2 GetCursorScreenPos(); // cursor position in absolute coordinates (prefer using this, also more useful to work with ImDrawList API). - IMGUI_API void SetCursorScreenPos(const ImVec2& pos); // cursor position in absolute coordinates - IMGUI_API ImVec2 GetCursorPos(); // [window-local] cursor position in window coordinates (relative to window position) + // - Window-local coordinates: SameLine(offset), GetCursorPos(), SetCursorPos(), GetCursorStartPos(), PushTextWrapPos() + // - Window-local coordinates: GetContentRegionMax(), GetWindowContentRegionMin(), GetWindowContentRegionMax() --> all obsoleted. YOU DON'T NEED THEM. + // - GetCursorScreenPos() = GetCursorPos() + GetWindowPos(). GetWindowPos() is almost only ever useful to convert from window-local to absolute coordinates. Try not to use it. + IMGUI_API ImVec2 GetCursorScreenPos(); // cursor position, absolute coordinates. THIS IS YOUR BEST FRIEND (prefer using this rather than GetCursorPos(), also more useful to work with ImDrawList API). + IMGUI_API void SetCursorScreenPos(const ImVec2& pos); // cursor position, absolute coordinates. THIS IS YOUR BEST FRIEND. + IMGUI_API ImVec2 GetContentRegionAvail(); // available space from current position. THIS IS YOUR BEST FRIEND. + IMGUI_API ImVec2 GetCursorPos(); // [window-local] cursor position in window-local coordinates. This is not your best friend. IMGUI_API float GetCursorPosX(); // [window-local] " IMGUI_API float GetCursorPosY(); // [window-local] " IMGUI_API void SetCursorPos(const ImVec2& local_pos); // [window-local] " IMGUI_API void SetCursorPosX(float local_x); // [window-local] " IMGUI_API void SetCursorPosY(float local_y); // [window-local] " - IMGUI_API ImVec2 GetCursorStartPos(); // [window-local] initial cursor position, in window coordinates + IMGUI_API ImVec2 GetCursorStartPos(); // [window-local] initial cursor position, in window-local coordinates. Call GetCursorScreenPos() after Begin() to get the absolute coordinates version. // Other layout functions IMGUI_API void Separator(); // separator, generally horizontal. inside a menu bar or in horizontal layout mode, this becomes a vertical separator. @@ -510,6 +526,7 @@ namespace ImGui 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); + IMGUI_API ImGuiID GetID(int int_id); // Widgets: Text IMGUI_API void TextUnformatted(const char* text, const char* text_end = NULL); // raw text without formatting. Roughly equivalent to Text("%s", text) but: A) doesn't require null terminated string if 'text_end' is specified, B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text. @@ -541,6 +558,8 @@ namespace ImGui 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 + IMGUI_API bool TextLink(const char* label); // hyperlink text button, return true when clicked + IMGUI_API void TextLinkOpenURL(const char* label, const char* url = NULL); // hyperlink text button, automatically open file/url when clicked // Widgets: Images // - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples @@ -651,6 +670,7 @@ namespace ImGui 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. + IMGUI_API void SetNextItemStorageID(ImGuiID storage_id); // set id to use for open/close storage (default to same as item id). // Widgets: Selectables // - A selectable highlights when hovered, and can display another color when selected. @@ -658,6 +678,18 @@ namespace ImGui IMGUI_API bool Selectable(const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // "bool selected" carry the selection state (read-only). Selectable() is clicked is returns true so you can modify your selection state. size.x==0.0: use remaining width, size.x>0.0: specify width. size.y==0.0: use label height, size.y>0.0: specify height IMGUI_API bool Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // "bool* p_selected" point to the selection state (read-write), as a convenient helper. + // Multi-selection system for Selectable(), Checkbox(), TreeNode() functions [BETA] + // - This enables standard multi-selection/range-selection idioms (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc.) in a way that also allow a clipper to be used. + // - ImGuiSelectionUserData is often used to store your item index within the current view (but may store something else). + // - Read comments near ImGuiMultiSelectIO for instructions/details and see 'Demo->Widgets->Selection State & Multi-Select' for demo. + // - TreeNode() is technically supported but... using this correctly is more complicated. You need some sort of linear/random access to your tree, + // which is suited to advanced trees setups already implementing filters and clipper. We will work simplifying the current demo. + // - 'selection_size' and 'items_count' parameters are optional and used by a few features. If they are costly for you to compute, you may avoid them. + IMGUI_API ImGuiMultiSelectIO* BeginMultiSelect(ImGuiMultiSelectFlags flags, int selection_size = -1, int items_count = -1); + IMGUI_API ImGuiMultiSelectIO* EndMultiSelect(); + IMGUI_API void SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data); + IMGUI_API bool IsItemToggledSelection(); // Was the last item selection state toggled? Useful if you need the per-item information _before_ reaching EndMultiSelect(). We only returns toggle _event_ in order to handle clipping correctly. + // Widgets: List Boxes // - This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label. // - You can submit contents and manage your selection state however you want it, by creating e.g. Selectable() or any other items. @@ -699,7 +731,8 @@ namespace ImGui // Tooltips // - Tooltips are windows following the mouse. They do not take focus away. - // - A tooltip window can contain items of any types. SetTooltip() is a shortcut for the 'if (BeginTooltip()) { Text(...); EndTooltip(); }' idiom. + // - A tooltip window can contain items of any types. + // - SetTooltip() is more or less a shortcut for the 'if (BeginTooltip()) { Text(...); EndTooltip(); }' idiom (with a subtlety that it discard any previously submitted tooltip) IMGUI_API bool BeginTooltip(); // begin/append a tooltip window. IMGUI_API void EndTooltip(); // only call EndTooltip() if BeginTooltip()/BeginItemTooltip() returns true! IMGUI_API void SetTooltip(const char* fmt, ...) IM_FMTARGS(1); // set a text-only tooltip. Often used after a ImGui::IsItemHovered() check. Override any previous call to SetTooltip(). @@ -710,7 +743,7 @@ namespace ImGui // - SetItemTooltip() is a shortcut for the 'if (IsItemHovered(ImGuiHoveredFlags_ForTooltip)) { SetTooltip(...); }' idiom. // - Where 'ImGuiHoveredFlags_ForTooltip' itself is a shortcut to use 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav' depending on active input type. For mouse it defaults to 'ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort'. IMGUI_API bool BeginItemTooltip(); // begin/append a tooltip window if preceding item was hovered. - IMGUI_API void SetItemTooltip(const char* fmt, ...) IM_FMTARGS(1); // set a text-only tooltip if preceeding item was hovered. override any previous call to SetTooltip(). + IMGUI_API void SetItemTooltip(const char* fmt, ...) IM_FMTARGS(1); // set a text-only tooltip if preceding item was hovered. override any previous call to SetTooltip(). IMGUI_API void SetItemTooltipV(const char* fmt, va_list args) IM_FMTLIST(1); // Popups, Modals @@ -776,7 +809,7 @@ namespace ImGui // - 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 bool BeginTable(const char* str_id, int columns, 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. @@ -809,6 +842,7 @@ namespace ImGui IMGUI_API const char* TableGetColumnName(int column_n = -1); // return "" if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column. IMGUI_API ImGuiTableColumnFlags TableGetColumnFlags(int column_n = -1); // return column flags so you can query their Enabled/Visible/Sorted/Hovered status flags. Pass -1 to use current column. IMGUI_API void TableSetColumnEnabled(int column_n, bool v);// change user accessible enabled/disabled state of a column. Set to false to hide the column. User can use the context menu to change this themselves (right-click in headers, or right-click in columns body with ImGuiTableFlags_ContextMenuInBody) + IMGUI_API int TableGetHoveredColumn(); // return hovered column. return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. Can also use (TableGetColumnFlags() & ImGuiTableColumnFlags_IsHovered) instead. IMGUI_API void TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n = -1); // change the color of a cell, row, or column. See ImGuiTableBgTarget_ flags for details. // Legacy Columns API (prefer using Tables!) @@ -838,14 +872,14 @@ namespace ImGui // - Drag from window menu button (upper-left button) to undock an entire node (all windows). // - When io.ConfigDockingWithShift == true, you instead need to hold SHIFT to enable docking. // About dockspaces: - // - Use DockSpaceOverViewport() to create an explicit dock node covering the screen or a specific viewport. + // - Use DockSpaceOverViewport() to create a window covering the screen or a specific viewport + a dockspace inside it. // This is often used with ImGuiDockNodeFlags_PassthruCentralNode to make it transparent. // - Use DockSpace() to create an explicit dock node _within_ an existing window. See Docking demo for details. // - Important: Dockspaces need to be submitted _before_ any window they can host. Submit it early in your frame! // - Important: Dockspaces need to be kept alive if hidden, otherwise windows docked into it will be undocked. // e.g. if you have multiple tabs with a dockspace inside each tab: submit the non-visible dockspaces with ImGuiDockNodeFlags_KeepAliveOnly. - IMGUI_API ImGuiID DockSpace(ImGuiID id, const ImVec2& size = ImVec2(0, 0), ImGuiDockNodeFlags flags = 0, const ImGuiWindowClass* window_class = NULL); - IMGUI_API ImGuiID DockSpaceOverViewport(const ImGuiViewport* viewport = NULL, ImGuiDockNodeFlags flags = 0, const ImGuiWindowClass* window_class = NULL); + IMGUI_API ImGuiID DockSpace(ImGuiID dockspace_id, const ImVec2& size = ImVec2(0, 0), ImGuiDockNodeFlags flags = 0, const ImGuiWindowClass* window_class = NULL); + IMGUI_API ImGuiID DockSpaceOverViewport(ImGuiID dockspace_id = 0, const ImGuiViewport* viewport = NULL, ImGuiDockNodeFlags flags = 0, const ImGuiWindowClass* window_class = NULL); IMGUI_API void SetNextWindowDockID(ImGuiID dock_id, ImGuiCond cond = 0); // set next window dock id IMGUI_API void SetNextWindowClass(const ImGuiWindowClass* window_class); // set next window class (control docking compatibility + provide hints to platform backend via custom viewport flags and platform parent/child relationship) IMGUI_API ImGuiID GetWindowDockID(); @@ -877,6 +911,7 @@ namespace ImGui // 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) + // - Tooltips windows by exception are opted out of disabling. // - 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(); @@ -922,10 +957,8 @@ namespace ImGui IMGUI_API ImGuiViewport* GetMainViewport(); // return primary/default viewport. This can never be NULL. // Background/Foreground Draw Lists - IMGUI_API ImDrawList* GetBackgroundDrawList(); // get background draw list for the viewport associated to the current window. this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents. - IMGUI_API ImDrawList* GetForegroundDrawList(); // get foreground draw list for the viewport associated to the current window. this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents. - IMGUI_API ImDrawList* GetBackgroundDrawList(ImGuiViewport* viewport); // get background draw list for the given viewport. this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents. - IMGUI_API ImDrawList* GetForegroundDrawList(ImGuiViewport* viewport); // get foreground draw list for the given viewport. this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents. + IMGUI_API ImDrawList* GetBackgroundDrawList(ImGuiViewport* viewport = NULL); // get background draw list for the given viewport or viewport associated to the current window. this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents. + IMGUI_API ImDrawList* GetForegroundDrawList(ImGuiViewport* viewport = NULL); // get foreground draw list for the given viewport or viewport associated to the current window. this draw list will be the top-most rendered one. Useful to quickly draw shapes/text over dear imgui contents. // Miscellaneous Utilities IMGUI_API bool IsRectVisible(const ImVec2& size); // test if rectangle (of given size, starting from cursor position) is visible / not clipped. @@ -959,6 +992,32 @@ namespace ImGui 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: Shortcut Testing & Routing [BETA] + // - ImGuiKeyChord = a ImGuiKey + optional ImGuiMod_Alt/ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Super. + // ImGuiKey_C // Accepted by functions taking ImGuiKey or ImGuiKeyChord arguments) + // ImGuiMod_Ctrl | ImGuiKey_C // Accepted by functions taking ImGuiKeyChord arguments) + // only ImGuiMod_XXX values are legal to combine with an ImGuiKey. You CANNOT combine two ImGuiKey values. + // - The general idea is that several callers may register interest in a shortcut, and only one owner gets it. + // Parent -> call Shortcut(Ctrl+S) // When Parent is focused, Parent gets the shortcut. + // Child1 -> call Shortcut(Ctrl+S) // When Child1 is focused, Child1 gets the shortcut (Child1 overrides Parent shortcuts) + // Child2 -> no call // When Child2 is focused, Parent gets the shortcut. + // The whole system is order independent, so if Child1 makes its calls before Parent, results will be identical. + // This is an important property as it facilitate working with foreign code or larger codebase. + // - To understand the difference: + // - IsKeyChordPressed() compares mods and call IsKeyPressed() -> function has no side-effect. + // - Shortcut() submits a route, routes are resolved, if it currently can be routed it calls IsKeyChordPressed() -> function has (desirable) side-effects as it can prevents another call from getting the route. + // - Visualize registered routes in 'Metrics/Debugger->Inputs'. + IMGUI_API bool Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0); + IMGUI_API void SetNextItemShortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0); + + // Inputs Utilities: Key/Input Ownership [BETA] + // - One common use case would be to allow your items to disable standard inputs behaviors such + // as Tab or Alt key handling, Mouse Wheel scrolling, etc. + // e.g. Button(...); SetItemKeyOwner(ImGuiKey_MouseWheelY); to make hovering/activating a button disable wheel for scrolling. + // - Reminder ImGuiKey enum include access to mouse buttons and gamepad, so key ownership can apply to them. + // - Many related features are still in imgui_internal.h. For instance, most IsKeyXXX()/IsMouseXXX() functions have an owner-id-aware version. + IMGUI_API void SetItemKeyOwner(ImGuiKey key); // Set key owner to last item ID if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'. + // Inputs Utilities: Mouse specific // - To refer to a mouse button, you may use named enums in your code e.g. ImGuiMouseButton_Left, ImGuiMouseButton_Right. // - You can also use regular integer: it is forever guaranteed that 0=Left, 1=Right, 2=Middle. @@ -973,8 +1032,8 @@ namespace ImGui 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 bool IsMouseDragging(ImGuiMouseButton button, float lock_threshold = -1.0f); // is mouse dragging? (uses io.MouseDraggingThreshold if lock_threshold < 0.0f) + 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 (uses io.MouseDraggingThreshold if lock_threshold < 0.0f) IMGUI_API void ResetMouseDragDelta(ImGuiMouseButton button = 0); // IMGUI_API ImGuiMouseCursor GetMouseCursor(); // get desired mouse cursor shape. Important: reset in ImGui::NewFrame(), this is updated during the frame. valid before Render(). If you use software rendering by setting io.MouseDrawCursor ImGui will render those for you IMGUI_API void SetMouseCursor(ImGuiMouseCursor cursor_type); // set desired mouse cursor shape @@ -1000,6 +1059,10 @@ namespace ImGui IMGUI_API void DebugFlashStyleColor(ImGuiCol idx); IMGUI_API void DebugStartItemPicker(); 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. +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + IMGUI_API void DebugLog(const char* fmt, ...) IM_FMTARGS(1); // Call via IMGUI_DEBUG_LOG() for maximum stripping in caller code! + IMGUI_API void DebugLogV(const char* fmt, va_list args) IM_FMTLIST(1); +#endif // Memory Allocators // - Those functions are not reliant on the current context. @@ -1056,7 +1119,6 @@ enum ImGuiWindowFlags_ ImGuiWindowFlags_NoInputs = ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, // [Internal] - ImGuiWindowFlags_NavFlattened = 1 << 23, // [BETA] On child window: share focus scope, 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() @@ -1066,7 +1128,8 @@ enum ImGuiWindowFlags_ // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 30, // Obsoleted in 1.90: Use ImGuiChildFlags_AlwaysUseWindowPadding in BeginChild() call. + ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 30, // Obsoleted in 1.90.0: Use ImGuiChildFlags_AlwaysUseWindowPadding in BeginChild() call. + ImGuiWindowFlags_NavFlattened = 1 << 31, // Obsoleted in 1.90.9: Use ImGuiChildFlags_NavFlattened in BeginChild() call. #endif }; @@ -1090,34 +1153,56 @@ enum ImGuiChildFlags_ ImGuiChildFlags_AutoResizeY = 1 << 5, // Enable auto-resizing height. Read "IMPORTANT: Size measurement" details above. ImGuiChildFlags_AlwaysAutoResize = 1 << 6, // Combined with AutoResizeX/AutoResizeY. Always measure size even when child is hidden, always return true, always disable clipping optimization! NOT RECOMMENDED. ImGuiChildFlags_FrameStyle = 1 << 7, // Style the child window like a framed item: use FrameBg, FrameRounding, FrameBorderSize, FramePadding instead of ChildBg, ChildRounding, ChildBorderSize, WindowPadding. + ImGuiChildFlags_NavFlattened = 1 << 8, // [BETA] Share focus scope, allow gamepad/keyboard navigation to cross over parent border to this child or between sibling child windows. +}; + +// Flags for ImGui::PushItemFlag() +// (Those are shared by all items) +enum ImGuiItemFlags_ +{ + ImGuiItemFlags_None = 0, // (Default) + ImGuiItemFlags_NoTabStop = 1 << 0, // false // Disable keyboard tabbing. This is a "lighter" version of ImGuiItemFlags_NoNav. + ImGuiItemFlags_NoNav = 1 << 1, // false // Disable any form of focusing (keyboard/gamepad directional navigation and SetKeyboardFocusHere() calls). + ImGuiItemFlags_NoNavDefaultFocus = 1 << 2, // false // Disable item being a candidate for default focus (e.g. used by title bar items). + ImGuiItemFlags_ButtonRepeat = 1 << 3, // false // Any button-like behavior will have repeat mode enabled (based on io.KeyRepeatDelay and io.KeyRepeatRate values). Note that you can also call IsItemActive() after any button to tell if it is being held. + ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window. }; // Flags for ImGui::InputText() // (Those are per-item flags. There are shared flags in ImGuiIO: io.ConfigInputTextCursorBlink and io.ConfigInputTextEnterKeepActive) enum ImGuiInputTextFlags_ { + // Basic filters (also see ImGuiInputTextFlags_CallbackCharFilter) 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_CharsScientific = 1 << 2, // Allow 0123456789.+-*/eE (Scientific notation input) + ImGuiInputTextFlags_CharsUppercase = 1 << 3, // Turn a..z into A..Z + ImGuiInputTextFlags_CharsNoBlank = 1 << 4, // Filter out spaces, tabs + + // Inputs + ImGuiInputTextFlags_AllowTabInput = 1 << 5, // Pressing TAB input a '\t' character into the text field + ImGuiInputTextFlags_EnterReturnsTrue = 1 << 6, // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider looking at the IsItemDeactivatedAfterEdit() function. + ImGuiInputTextFlags_EscapeClearsAll = 1 << 7, // Escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) + ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 8, // In multi-line mode, validate with Enter, add new line with Ctrl+Enter (default is opposite: validate with Ctrl+Enter, add line with Enter). + + // Other options + ImGuiInputTextFlags_ReadOnly = 1 << 9, // Read-only mode + ImGuiInputTextFlags_Password = 1 << 10, // Password mode, display all characters as '*', disable copy + ImGuiInputTextFlags_AlwaysOverwrite = 1 << 11, // Overwrite mode + ImGuiInputTextFlags_AutoSelectAll = 1 << 12, // Select entire text when first taking mouse focus + ImGuiInputTextFlags_ParseEmptyRefVal = 1 << 13, // InputFloat(), InputInt(), InputScalar() etc. only: parse empty string as zero value. + ImGuiInputTextFlags_DisplayEmptyRefVal = 1 << 14, // InputFloat(), InputInt(), InputScalar() etc. only: when value is zero, do not display it. Generally used with ImGuiInputTextFlags_ParseEmptyRefVal. + ImGuiInputTextFlags_NoHorizontalScroll = 1 << 15, // Disable following the cursor horizontally ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). - ImGuiInputTextFlags_CharsScientific = 1 << 17, // Allow 0123456789.+-*/eE (Scientific notation input) - ImGuiInputTextFlags_CallbackResize = 1 << 18, // Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) - ImGuiInputTextFlags_CallbackEdit = 1 << 19, // Callback on any edit (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the underlying buffer while focus is active) - ImGuiInputTextFlags_EscapeClearsAll = 1 << 20, // Escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) + + // Callback features + ImGuiInputTextFlags_CallbackCompletion = 1 << 17, // Callback on pressing TAB (for completion handling) + ImGuiInputTextFlags_CallbackHistory = 1 << 18, // Callback on pressing Up/Down arrows (for history handling) + ImGuiInputTextFlags_CallbackAlways = 1 << 19, // Callback on each iteration. User code may query cursor position, modify text buffer. + ImGuiInputTextFlags_CallbackCharFilter = 1 << 20, // Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. + ImGuiInputTextFlags_CallbackResize = 1 << 21, // 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 << 22, // 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 //ImGuiInputTextFlags_AlwaysInsertMode = ImGuiInputTextFlags_AlwaysOverwrite // [renamed in 1.82] name was not matching behavior @@ -1137,12 +1222,13 @@ enum ImGuiTreeNodeFlags_ ImGuiTreeNodeFlags_OpenOnArrow = 1 << 7, // Only open when clicking on the arrow part. If ImGuiTreeNodeFlags_OpenOnDoubleClick is also set, single-click arrow or double-click all box to open. ImGuiTreeNodeFlags_Leaf = 1 << 8, // No collapsing, no arrow (use as a convenience for leaf nodes). ImGuiTreeNodeFlags_Bullet = 1 << 9, // Display a bullet instead of arrow. IMPORTANT: node can still be marked open/close if you don't set the _Leaf flag! - ImGuiTreeNodeFlags_FramePadding = 1 << 10, // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding(). - ImGuiTreeNodeFlags_SpanAvailWidth = 1 << 11, // Extend hit box to the right-most edge, even if not framed. This is not the default in order to allow adding other items on the same line. In the future we may refactor the hit system to be front-to-back, allowing natural overlaps and then this can become the default. - ImGuiTreeNodeFlags_SpanFullWidth = 1 << 12, // Extend hit box to the left-most and right-most edges (bypass the indented area). - ImGuiTreeNodeFlags_SpanAllColumns = 1 << 13, // Frame will span all columns of its container table (text will still fit in current column) - ImGuiTreeNodeFlags_NavLeftJumpsBackHere = 1 << 14, // (WIP) Nav: left direction may move to this TreeNode() from any of its child (items submitted between TreeNode and TreePop) - //ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 15, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible + 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() before the node. + 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 without using AllowOverlap mode. + ImGuiTreeNodeFlags_SpanFullWidth = 1 << 12, // Extend hit box to the left-most and right-most edges (cover the indent area). + ImGuiTreeNodeFlags_SpanTextWidth = 1 << 13, // Narrow hit box + narrow hovering highlight, will only cover the label text. + ImGuiTreeNodeFlags_SpanAllColumns = 1 << 14, // Frame will span all columns of its container table (text will still fit in current column) + ImGuiTreeNodeFlags_NavLeftJumpsBackHere = 1 << 15, // (WIP) Nav: left direction may move to this TreeNode() from any of its child (items submitted between TreeNode and TreePop) + //ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 16, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible ImGuiTreeNodeFlags_CollapsingHeader = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_NoAutoOpenOnLog, #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -1179,14 +1265,16 @@ enum ImGuiPopupFlags_ enum ImGuiSelectableFlags_ { ImGuiSelectableFlags_None = 0, - ImGuiSelectableFlags_DontClosePopups = 1 << 0, // Clicking this doesn't close parent popup window + ImGuiSelectableFlags_NoAutoClosePopups = 1 << 0, // Clicking this doesn't close parent popup window (overrides ImGuiItemFlags_AutoClosePopups) ImGuiSelectableFlags_SpanAllColumns = 1 << 1, // Frame will span all columns of its container table (text will still fit in current column) ImGuiSelectableFlags_AllowDoubleClick = 1 << 2, // Generate press events on double clicks too ImGuiSelectableFlags_Disabled = 1 << 3, // Cannot be selected, display grayed out text ImGuiSelectableFlags_AllowOverlap = 1 << 4, // (WIP) Hit testing to allow subsequent widgets to overlap this one + ImGuiSelectableFlags_Highlight = 1 << 5, // Make the item be displayed as if it is hovered #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - ImGuiSelectableFlags_AllowItemOverlap = ImGuiSelectableFlags_AllowOverlap, // Renamed in 1.89.7 + ImGuiSelectableFlags_DontClosePopups = ImGuiSelectableFlags_NoAutoClosePopups, // Renamed in 1.91.0 + ImGuiSelectableFlags_AllowItemOverlap = ImGuiSelectableFlags_AllowOverlap, // Renamed in 1.89.7 #endif }; @@ -1215,8 +1303,9 @@ enum ImGuiTabBarFlags_ ImGuiTabBarFlags_NoCloseWithMiddleMouseButton = 1 << 3, // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You may handle this behavior manually 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_DrawSelectedOverline = 1 << 6, // Draw selected overline markers over selected tab + ImGuiTabBarFlags_FittingPolicyResizeDown = 1 << 7, // Resize tabs when they don't fit + ImGuiTabBarFlags_FittingPolicyScroll = 1 << 8, // Add scroll buttons when tabs don't fit ImGuiTabBarFlags_FittingPolicyMask_ = ImGuiTabBarFlags_FittingPolicyResizeDown | ImGuiTabBarFlags_FittingPolicyScroll, ImGuiTabBarFlags_FittingPolicyDefault_ = ImGuiTabBarFlags_FittingPolicyResizeDown, }; @@ -1319,12 +1408,18 @@ enum ImGuiDragDropFlags_ 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) + ImGuiDragDropFlags_PayloadAutoExpire = 1 << 5, // Automatically expire the payload if the source cease to be submitted (otherwise payloads are persisting while being dragged) + ImGuiDragDropFlags_PayloadNoCrossContext = 1 << 6, // Hint to specify that the payload may not be copied outside current dear imgui context. + ImGuiDragDropFlags_PayloadNoCrossProcess = 1 << 7, // Hint to specify that the payload may not be copied outside current process. // 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. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiDragDropFlags_SourceAutoExpirePayload = ImGuiDragDropFlags_PayloadAutoExpire, // Renamed in 1.90.9 +#endif }; // Standard Drag and Drop payload types. You can define you own payload types using short strings. Types starting with '_' are defined by Dear ImGui. @@ -1344,11 +1439,12 @@ enum ImGuiDataType_ ImGuiDataType_U64, // unsigned long long / unsigned __int64 ImGuiDataType_Float, // float ImGuiDataType_Double, // double + ImGuiDataType_Bool, // bool (provided for user convenience, not supported by scalar widgets) ImGuiDataType_COUNT }; // A cardinal direction -enum ImGuiDir_ +enum ImGuiDir : int { ImGuiDir_None = -1, ImGuiDir_Left = 0, @@ -1359,7 +1455,7 @@ enum ImGuiDir_ }; // A sorting direction -enum ImGuiSortDirection_ +enum ImGuiSortDirection : ImU8 { ImGuiSortDirection_None = 0, ImGuiSortDirection_Ascending = 1, // Ascending = 0->9, A->Z etc. @@ -1478,13 +1574,13 @@ enum ImGuiKey : int // - 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... + // - On macOS, we swap Cmd(Super) and Ctrl keys at the time of the io.AddKeyEvent() call. ImGuiMod_None = 0, - ImGuiMod_Ctrl = 1 << 12, // Ctrl + ImGuiMod_Ctrl = 1 << 12, // Ctrl (non-macOS), Cmd (macOS) ImGuiMod_Shift = 1 << 13, // Shift ImGuiMod_Alt = 1 << 14, // Option/Menu - ImGuiMod_Super = 1 << 15, // Cmd/Super/Windows - ImGuiMod_Shortcut = 1 << 11, // Alias for Ctrl (non-macOS) _or_ Super (macOS). - ImGuiMod_Mask_ = 0xF800, // 5-bits + ImGuiMod_Super = 1 << 15, // Windows/Super (non-macOS), Ctrl (macOS) + ImGuiMod_Mask_ = 0xF000, // 4-bits // [Internal] Prior to 1.87 we required user to fill io.KeysDown[512] using their own native index + the io.KeyMap[] array. // We are ditching this method but keeping a legacy path for user code doing e.g. IsKeyPressed(MY_NATIVE_KEY_CODE) @@ -1501,11 +1597,37 @@ enum ImGuiKey : int #endif #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiMod_Shortcut = ImGuiMod_Ctrl, // Removed in 1.90.7, you can now simply use ImGuiMod_Ctrl ImGuiKey_ModCtrl = ImGuiMod_Ctrl, ImGuiKey_ModShift = ImGuiMod_Shift, ImGuiKey_ModAlt = ImGuiMod_Alt, ImGuiKey_ModSuper = ImGuiMod_Super, // Renamed in 1.89 //ImGuiKey_KeyPadEnter = ImGuiKey_KeypadEnter, // Renamed in 1.87 #endif }; +// Flags for Shortcut(), SetNextItemShortcut(), +// (and for upcoming extended versions of IsKeyPressed(), IsMouseClicked(), Shortcut(), SetKeyOwner(), SetItemKeyOwner() that are still in imgui_internal.h) +// Don't mistake with ImGuiInputTextFlags! (which is for ImGui::InputText() function) +enum ImGuiInputFlags_ +{ + ImGuiInputFlags_None = 0, + ImGuiInputFlags_Repeat = 1 << 0, // Enable repeat. Return true on successive repeats. Default for legacy IsKeyPressed(). NOT Default for legacy IsMouseClicked(). MUST BE == 1. + + // Flags for Shortcut(), SetNextItemShortcut() + // - Routing policies: RouteGlobal+OverActive >> RouteActive or RouteFocused (if owner is active item) >> RouteGlobal+OverFocused >> RouteFocused (if in focused window stack) >> RouteGlobal. + // - Default policy is RouteFocused. Can select only 1 policy among all available. + ImGuiInputFlags_RouteActive = 1 << 10, // Route to active item only. + ImGuiInputFlags_RouteFocused = 1 << 11, // Route to windows in the focus stack (DEFAULT). Deep-most focused window takes inputs. Active item takes inputs over deep-most focused window. + ImGuiInputFlags_RouteGlobal = 1 << 12, // Global route (unless a focused window or active item registered the route). + ImGuiInputFlags_RouteAlways = 1 << 13, // Do not register route, poll keys directly. + // - Routing options + ImGuiInputFlags_RouteOverFocused = 1 << 14, // Option: global route: higher priority than focused route (unless active item in focused route). + ImGuiInputFlags_RouteOverActive = 1 << 15, // Option: global route: higher priority than active item. Unlikely you need to use that: will interfere with every active items, e.g. CTRL+A registered by InputText will be overridden by this. May not be fully honored as user/internal code is likely to always assume they can access keys when active. + ImGuiInputFlags_RouteUnlessBgFocused = 1 << 16, // Option: global route: will not be applied if underlying background/void is focused (== no Dear ImGui windows are focused). Useful for overlay applications. + ImGuiInputFlags_RouteFromRootWindow = 1 << 17, // Option: route evaluated from the point of view of root window rather than current window. + + // Flags for SetNextItemShortcut() + ImGuiInputFlags_Tooltip = 1 << 18, // Automatically display a tooltip when hovering item [BETA] Unsure of right api (opt-in/opt-out) +}; + #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO // OBSOLETED in 1.88 (from July 2022): ImGuiNavInput and io.NavInputs[]. // Official backends between 1.60 and 1.86: will keep working and feed gamepad inputs as long as IMGUI_DISABLE_OBSOLETE_KEYIO is not set. @@ -1526,11 +1648,12 @@ enum ImGuiConfigFlags_ ImGuiConfigFlags_NavEnableGamepad = 1 << 1, // Master gamepad navigation enable flag. Backend also needs to set ImGuiBackendFlags_HasGamepad. ImGuiConfigFlags_NavEnableSetMousePos = 1 << 2, // Instruct navigation to move the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is awkward. Will update io.MousePos and set io.WantSetMousePos=true. If enabled you MUST honor io.WantSetMousePos requests in your backend, otherwise ImGui will react as if the mouse is jumping around back and forth. ImGuiConfigFlags_NavNoCaptureKeyboard = 1 << 3, // Instruct navigation to not set the io.WantCaptureKeyboard flag when io.NavActive is set. - ImGuiConfigFlags_NoMouse = 1 << 4, // Instruct imgui to clear mouse position/buttons in NewFrame(). This allows ignoring the mouse information set by the backend. + ImGuiConfigFlags_NoMouse = 1 << 4, // Instruct dear imgui to disable mouse inputs and interactions. ImGuiConfigFlags_NoMouseCursorChange = 1 << 5, // Instruct backend to not alter mouse cursor shape and visibility. Use if the backend cursor changes are interfering with yours and you don't want to use SetMouseCursor() to change mouse cursor. You may want to honor requests from imgui by reading GetMouseCursor() yourself instead. + ImGuiConfigFlags_NoKeyboard = 1 << 6, // Instruct dear imgui to disable keyboard inputs and interactions. This is done by ignoring keyboard events and clearing existing states. // [BETA] Docking - ImGuiConfigFlags_DockingEnable = 1 << 6, // Docking enable flags. + ImGuiConfigFlags_DockingEnable = 1 << 7, // Docking enable flags. // [BETA] Viewports // When using viewports it is recommended that your default value for ImGuiCol_WindowBg is opaque (Alpha=1.0) so transition to a viewport won't be noticeable. @@ -1594,11 +1717,13 @@ enum ImGuiCol_ 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_TabHovered, // Tab background, when hovered + ImGuiCol_Tab, // Tab background, when tab-bar is focused & tab is unselected + ImGuiCol_TabSelected, // Tab background, when tab-bar is focused & tab is selected + ImGuiCol_TabSelectedOverline, // Tab horizontal overline, when tab-bar is focused & tab is selected + ImGuiCol_TabDimmed, // Tab background, when tab-bar is unfocused & tab is unselected + ImGuiCol_TabDimmedSelected, // Tab background, when tab-bar is unfocused & tab is selected + ImGuiCol_TabDimmedSelectedOverline,//..horizontal overline, when tab-bar is unfocused & tab is selected ImGuiCol_DockingPreview, // Preview overlay color when about to docking something ImGuiCol_DockingEmptyBg, // Background color for empty node (e.g. CentralNode with no window docked into it) ImGuiCol_PlotLines, @@ -1610,55 +1735,67 @@ enum ImGuiCol_ 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_TextLink, // Hyperlink color 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 + ImGuiCol_COUNT, + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiCol_TabActive = ImGuiCol_TabSelected, // [renamed in 1.90.9] + ImGuiCol_TabUnfocused = ImGuiCol_TabDimmed, // [renamed in 1.90.9] + ImGuiCol_TabUnfocusedActive = ImGuiCol_TabDimmedSelected, // [renamed in 1.90.9] +#endif }; // Enumeration for PushStyleVar() / PopStyleVar() to temporarily modify the ImGuiStyle structure. // - The enum only refers to fields of ImGuiStyle which makes sense to be pushed/popped inside UI code. // During initialization or between frames, feel free to just poke into ImGuiStyle directly. // - 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. +// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. +// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. // - 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_TabBarBorderSize, // float TabBarBorderSize - ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign - ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign - ImGuiStyleVar_SeparatorTextBorderSize,// float SeparatorTextBorderSize - ImGuiStyleVar_SeparatorTextAlign, // ImVec2 SeparatorTextAlign - ImGuiStyleVar_SeparatorTextPadding,// ImVec2 SeparatorTextPadding - ImGuiStyleVar_DockingSeparatorSize,// float DockingSeparatorSize + // 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_TabBorderSize, // float TabBorderSize + ImGuiStyleVar_TabBarBorderSize, // float TabBarBorderSize + ImGuiStyleVar_TabBarOverlineSize, // float TabBarOverlineSize + ImGuiStyleVar_TableAngledHeadersAngle, // float TableAngledHeadersAngle + ImGuiStyleVar_TableAngledHeadersTextAlign,// ImVec2 TableAngledHeadersTextAlign + ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign + ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign + ImGuiStyleVar_SeparatorTextBorderSize, // float SeparatorTextBorderSize + ImGuiStyleVar_SeparatorTextAlign, // ImVec2 SeparatorTextAlign + ImGuiStyleVar_SeparatorTextPadding, // ImVec2 SeparatorTextPadding + ImGuiStyleVar_DockingSeparatorSize, // float DockingSeparatorSize ImGuiStyleVar_COUNT }; @@ -1669,10 +1806,8 @@ enum ImGuiButtonFlags_ 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, + ImGuiButtonFlags_MouseButtonMask_ = ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight | ImGuiButtonFlags_MouseButtonMiddle, // [Internal] + //ImGuiButtonFlags_MouseButtonDefault_ = ImGuiButtonFlags_MouseButtonLeft, }; // Flags for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton() @@ -1727,8 +1862,9 @@ 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_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_WrapAround = 1 << 8, // Enable wrapping around from max to min and from min to max (only supported by DragXXX() functions for now. 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 @@ -1945,15 +2081,25 @@ 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 + ImGuiSortDirection SortDirection; // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending ImGuiTableColumnSortSpecs() { memset(this, 0, sizeof(*this)); } }; //----------------------------------------------------------------------------- -// [SECTION] Helpers: Memory allocations macros, ImVector<> +// [SECTION] Helpers: Debug log, memory allocations macros, ImVector<> //----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// Debug Logging into ShowDebugLogWindow(), tty and more. +//----------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_DEBUG_TOOLS +#define IMGUI_DEBUG_LOG(...) ImGui::DebugLog(__VA_ARGS__) +#else +#define IMGUI_DEBUG_LOG(...) ((void)0) +#endif + //----------------------------------------------------------------------------- // IM_MALLOC(), IM_FREE(), IM_NEW(), IM_PLACEMENT_NEW(), IM_DELETE() // We call C++ constructor on own allocated memory via the placement "new(ptr) Type()" syntax. @@ -2073,7 +2219,7 @@ struct ImGuiStyle float FrameBorderSize; // Thickness of border around frames. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). ImVec2 ItemSpacing; // Horizontal and vertical spacing between widgets/lines. ImVec2 ItemInnerSpacing; // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label). - ImVec2 CellPadding; // Padding within a table cell. CellPadding.y may be altered between different rows. + ImVec2 CellPadding; // Padding within a table cell. Cellpadding.x is locked for entire table. CellPadding.y may be altered between different rows. ImVec2 TouchExtraPadding; // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! float IndentSpacing; // Horizontal indentation when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). float ColumnsMinSpacing; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). @@ -2086,15 +2232,17 @@ struct ImGuiStyle float TabBorderSize; // Thickness of border around tabs. float TabMinWidthForCloseButton; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. float TabBarBorderSize; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. + float TabBarOverlineSize; // Thickness of tab-bar overline, which highlights the selected tab-bar. float TableAngledHeadersAngle; // Angle of angled headers (supported values range from -50.0f degrees to +50.0f degrees). + ImVec2 TableAngledHeadersTextAlign;// Alignment of angled headers within the cell ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered). ImVec2 SelectableTextAlign; // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. - float SeparatorTextBorderSize; // Thickkness of border in SeparatorText() + float SeparatorTextBorderSize; // Thickness of border in SeparatorText() ImVec2 SeparatorTextAlign; // Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). ImVec2 SeparatorTextPadding; // Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y. - ImVec2 DisplayWindowPadding; // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. - ImVec2 DisplaySafeAreaPadding; // If you cannot see the edges of your screen (e.g. on a TV) increase the safe area padding. Apply to popups/tooltips as well regular windows. NB: Prefer configuring your TV sets correctly! + ImVec2 DisplayWindowPadding; // Apply to regular windows: amount which we enforce to keep visible when moving near edges of your screen. + ImVec2 DisplaySafeAreaPadding; // Apply to every windows, menus, popups, tooltips: amount where we avoid displaying contents. Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured). float DockingSeparatorSize; // Thickness of resizing border between docked windows float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). We apply per-monitor DPI scaling over this scale. 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). @@ -2121,6 +2269,9 @@ struct ImGuiStyle //----------------------------------------------------------------------------- // 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. +// It is generally expected that: +// - initialization: backends and user code writes to ImGuiIO. +// - main loop: backends writes to ImGuiIO, user code and imgui code reads from ImGuiIO. //----------------------------------------------------------------------------- // [Internal] Storage used by IsKeyDown(), IsKeyPressed() etc functions. @@ -2168,7 +2319,8 @@ struct ImGuiIO // 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 ConfigMacOSXBehaviors; // = defined(__APPLE__) // Swap Cmd<>Ctrl keys + 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 ConfigNavSwapGamepadButtons; // = false // Swap Activate<>Cancel (A<>B) buttons, matching typical "Nintendo/Japanese style" gamepad layout. bool ConfigInputTrickleEventQueue; // = true // Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates. bool ConfigInputTextCursorBlink; // = true // Enable blinking cursor (optional as some users consider it to be distracting). bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will keep item active and select contents (single-line only). @@ -2205,7 +2357,7 @@ struct ImGuiIO // Option to deactivate io.AddFocusEvent(false) handling. // - May facilitate interactions with a debugger when focus loss leads to clearing inputs data. // - Backends may have other side-effects on focus loss, so this will reduce side-effects but not necessary remove all of them. - bool ConfigDebugIgnoreFocusLoss; // = false // Ignore io.AddFocusEvent(false), consequently not calling io.ClearInputKeys() in input processing. + bool ConfigDebugIgnoreFocusLoss; // = false // Ignore io.AddFocusEvent(false), consequently not calling io.ClearInputKeys()/io.ClearInputMouse() in input processing. // Option to audit .ini data bool ConfigDebugIniSettings; // = false // Save .ini data with extra comments (particularly helpful for Docking, but makes saving slower) @@ -2228,9 +2380,15 @@ struct ImGuiIO void (*SetClipboardTextFn)(void* user_data, const char* text); void* ClipboardUserData; + // Optional: Open link/folder/file in OS Shell + // (default to use ShellExecuteA() on Windows, system() on Linux/Mac) + bool (*PlatformOpenInShellFn)(ImGuiContext* ctx, const char* path); + void* PlatformOpenInShellUserData; + // 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); + void (*PlatformSetImeDataFn)(ImGuiContext* ctx, ImGuiViewport* viewport, ImGuiPlatformImeData* data); + //void (*SetPlatformImeDataFn)(ImGuiViewport* viewport, ImGuiPlatformImeData* data); // [Renamed to io.PlatformSetImeDataFn in 1.91.0] // Optional: Platform locale ImWchar PlatformLocaleDecimalPoint; // '.' // [Experimental] Configure decimal point e.g. '.' or ',' useful for some languages (e.g. German), generally pulled from *localeconv()->decimal_point @@ -2255,7 +2413,8 @@ struct ImGuiIO IMGUI_API void SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native_scancode, int native_legacy_index = -1); // [Optional] Specify index for legacy <1.87 IsKeyXXX() functions with native indices + specify native keycode, scancode. IMGUI_API void SetAppAcceptingEvents(bool accepting_events); // Set master flag for accepting key/mouse/text events (default to true). Useful if you have native dialog boxes that are interrupting your application loop/refresh, and you want to disable events being queued while your app is frozen. IMGUI_API void ClearEventsQueue(); // Clear all incoming events. - IMGUI_API void ClearInputKeys(); // Clear current keyboard/mouse/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons. + IMGUI_API void ClearInputKeys(); // Clear current keyboard/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons. + IMGUI_API void ClearInputMouse(); // Clear current mouse state. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS IMGUI_API void ClearInputCharacters(); // [Obsoleted in 1.89.8] Clear the current frame text input buffer. Now included within ClearInputKeys(). #endif @@ -2280,16 +2439,6 @@ struct ImGuiIO int MetricsActiveWindows; // Number of active windows ImVec2 MouseDelta; // Mouse delta. Note that this is zero if either current or previous position are invalid (-FLT_MAX,-FLT_MAX), so a disappearing/reappearing mouse won't have a huge delta. - // Legacy: before 1.87, we required backend to fill io.KeyMap[] (imgui->native map) during initialization and io.KeysDown[] (native indices) every frame. - // This is still temporarily supported as a legacy feature. However the new preferred scheme is for backend to call io.AddKeyEvent(). - // Old (<1.87): ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Space]) --> New (1.87+) ImGui::IsKeyPressed(ImGuiKey_Space) -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - int KeyMap[ImGuiKey_COUNT]; // [LEGACY] Input: map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. The first 512 are now unused and should be kept zero. Legacy backend will write into KeyMap[] using ImGuiKey_ indices which are always >512. - bool KeysDown[ImGuiKey_COUNT]; // [LEGACY] Input: Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). This used to be [512] sized. It is now ImGuiKey_COUNT to allow legacy io.KeysDown[GetKeyIndex(...)] to work without an overflow. - float NavInputs[ImGuiNavInput_COUNT]; // [LEGACY] Since 1.88, NavInputs[] was removed. Backends from 1.60 to 1.86 won't build. Feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. - //void* ImeWindowHandle; // [Obsoleted in 1.87] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor positioning. -#endif - //------------------------------------------------------------------ // [Internal] Dear ImGui will maintain those fields. Forward compatibility not guaranteed! //------------------------------------------------------------------ @@ -2311,7 +2460,7 @@ struct ImGuiIO bool KeySuper; // Keyboard modifier down: Cmd/Super/Windows // Other state maintained from data above + IO function calls - ImGuiKeyChord KeyMods; // Key mods flags (any of ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Alt/ImGuiMod_Super flags, same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper but merged into flags. DOES NOT CONTAINS ImGuiMod_Shortcut which is pretranslated). Read-only, updated by NewFrame() + ImGuiKeyChord KeyMods; // Key mods flags (any of ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Alt/ImGuiMod_Super flags, same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper but merged into flags. Read-only, updated by NewFrame() ImGuiKeyData KeysData[ImGuiKey_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) @@ -2325,6 +2474,7 @@ struct ImGuiIO bool MouseDownOwned[5]; // Track if button was clicked inside a dear imgui window or over void blocked by a popup. We don't request mouse capture from the application if click started outside ImGui bounds. bool MouseDownOwnedUnlessPopupClose[5]; // Track if button was clicked inside a dear imgui window. bool MouseWheelRequestAxisSwap; // On a non-Mac system, holding SHIFT requests WheelY to perform the equivalent of a WheelX event. On a Mac system this is already enforced by the system. + bool MouseCtrlLeftAsRightClick; // (OSX) Set to true when the current click was a ctrl-click that spawned a simulated right click 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 ImVec2 MouseDragMaxDistanceAbs[5]; // Maximum distance, absolute, on each axis, of how much mouse has traveled from the clicking point @@ -2337,6 +2487,16 @@ struct ImGuiIO ImWchar16 InputQueueSurrogate; // For AddInputCharacterUTF16() ImVector InputQueueCharacters; // Queue of _characters_ input (obtained by platform backend). Fill using AddInputCharacter() helper. + // Legacy: before 1.87, we required backend to fill io.KeyMap[] (imgui->native map) during initialization and io.KeysDown[] (native indices) every frame. + // This is still temporarily supported as a legacy feature. However the new preferred scheme is for backend to call io.AddKeyEvent(). + // Old (<1.87): ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Space]) --> New (1.87+) ImGui::IsKeyPressed(ImGuiKey_Space) +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + int KeyMap[ImGuiKey_COUNT]; // [LEGACY] Input: map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. The first 512 are now unused and should be kept zero. Legacy backend will write into KeyMap[] using ImGuiKey_ indices which are always >512. + bool KeysDown[ImGuiKey_COUNT]; // [LEGACY] Input: Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). This used to be [512] sized. It is now ImGuiKey_COUNT to allow legacy io.KeysDown[GetKeyIndex(...)] to work without an overflow. + float NavInputs[ImGuiNavInput_COUNT]; // [LEGACY] Since 1.88, NavInputs[] was removed. Backends from 1.60 to 1.86 won't build. Feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. + //void* ImeWindowHandle; // [Obsoleted in 1.87] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor positioning. +#endif + IMGUI_API ImGuiIO(); }; @@ -2361,6 +2521,8 @@ struct ImGuiInputTextCallbackData void* UserData; // What user passed to InputText() // Read-only // Arguments for the different callback events + // - During Resize callback, Buf will be same as your input buffer. + // - However, during Completion/History/Always callback, Buf always points to our own internal data (it is not the same as your buffer)! Changes to it will be reflected into your own buffer shortly after the callback. // - 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; @@ -2505,6 +2667,16 @@ struct ImGuiTextBuffer IMGUI_API void appendfv(const char* fmt, va_list args) IM_FMTLIST(2); }; +// [Internal] Key+Value for ImGuiStorage +struct ImGuiStoragePair +{ + ImGuiID key; + union { int val_i; float val_f; void* val_p; }; + ImGuiStoragePair(ImGuiID _key, int _val) { key = _key; val_i = _val; } + ImGuiStoragePair(ImGuiID _key, float _val) { key = _key; val_f = _val; } + ImGuiStoragePair(ImGuiID _key, void* _val) { key = _key; val_p = _val; } +}; + // Helper: Key->Value storage // Typically you don't have to worry about this since a storage is held within each Window. // We use it to e.g. store collapse state for a tree (Int 0/1) @@ -2516,15 +2688,6 @@ struct ImGuiTextBuffer struct ImGuiStorage { // [Internal] - struct ImGuiStoragePair - { - ImGuiID key; - union { int val_i; float val_f; void* val_p; }; - ImGuiStoragePair(ImGuiID _key, int _val) { key = _key; val_i = _val; } - ImGuiStoragePair(ImGuiID _key, float _val) { key = _key; val_f = _val; } - ImGuiStoragePair(ImGuiID _key, void* _val) { key = _key; val_p = _val; } - }; - ImVector Data; // - Get***() functions find pair, never add/allocate. Pairs are sorted so a query is O(log N) @@ -2553,6 +2716,10 @@ struct ImGuiStorage IMGUI_API void BuildSortByKey(); // Obsolete: use on your own storage if you know only integer are being stored (open/close all tree nodes) IMGUI_API void SetAllInt(int val); + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + //typedef ::ImGuiStoragePair ImGuiStoragePair; // 1.90.8: moved type outside struct +#endif }; // Helper: Manually clip large list of items. @@ -2583,9 +2750,10 @@ struct ImGuiListClipper int ItemsCount; // [Internal] Number of items float ItemsHeight; // [Internal] Height of item after a first step and item submission can calculate it float StartPosY; // [Internal] Cursor position at the time of Begin() or after table frozen rows are all processed + double StartSeekOffsetY; // [Internal] Account for frozen rows in a table and initial loss of precision in very large windows. void* TempData; // [Internal] Internal data - // items_count: Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step) + // items_count: Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step, and you can call SeekCursorForItem() manually if you need) // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing(). IMGUI_API ImGuiListClipper(); IMGUI_API ~ImGuiListClipper(); @@ -2598,6 +2766,11 @@ struct ImGuiListClipper inline void IncludeItemByIndex(int item_index) { IncludeItemsByIndex(item_index, item_index + 1); } IMGUI_API void IncludeItemsByIndex(int item_begin, int item_end); // item_end is exclusive e.g. use (42, 42+1) to make item 42 never clipped. + // Seek cursor toward given item. This is automatically called while stepping. + // - The only reason to call this is: you can use ImGuiListClipper::Begin(INT_MAX) if you don't know item count ahead of time. + // - In this case, after all steps are done, you'll want to call SeekCursorForItem(item_count). + IMGUI_API void SeekCursorForItem(int item_index); + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS inline void IncludeRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.9] inline void ForceDisplayRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.6] @@ -2678,6 +2851,154 @@ struct ImColor static ImColor HSV(float h, float s, float v, float a = 1.0f) { float r, g, b; ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); return ImColor(r, g, b, a); } }; +//----------------------------------------------------------------------------- +// [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiSelectionRequestType, ImGuiSelectionRequest, ImGuiMultiSelectIO, ImGuiSelectionBasicStorage) +//----------------------------------------------------------------------------- + +// Multi-selection system +// Documentation at: https://github.com/ocornut/imgui/wiki/Multi-Select +// - Refer to 'Demo->Widgets->Selection State & Multi-Select' for demos using this. +// - This system implements standard multi-selection idioms (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc) +// with support for clipper (skipping non-visible items), box-select and many other details. +// - Selectable(), Checkbox() are supported but custom widgets may use it as well. +// - TreeNode() is technically supported but... using this correctly is more complicated: you need some sort of linear/random access to your tree, +// which is suited to advanced trees setups also implementing filters and clipper. We will work toward simplifying and demoing it. +// - In the spirit of Dear ImGui design, your code owns actual selection data. +// This is designed to allow all kinds of selection storage you may use in your application e.g. set/map/hash. +// About ImGuiSelectionBasicStorage: +// - This is an optional helper to store a selection state and apply selection requests. +// - It is used by our demos and provided as a convenience to quickly implement multi-selection. +// Usage: +// - Identify submitted items with SetNextItemSelectionUserData(), most likely using an index into your current data-set. +// - Store and maintain actual selection data using persistent object identifiers. +// - Usage flow: +// BEGIN - (1) Call BeginMultiSelect() and retrieve the ImGuiMultiSelectIO* result. +// - (2) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 6. +// - (3) [If using clipper] You need to make sure RangeSrcItem is always submitted. Calculate its index and pass to clipper.IncludeItemByIndex(). If storing indices in ImGuiSelectionUserData, a simple clipper.IncludeItemByIndex(ms_io->RangeSrcItem) call will work. +// LOOP - (4) Submit your items with SetNextItemSelectionUserData() + Selectable()/TreeNode() calls. +// END - (5) Call EndMultiSelect() and retrieve the ImGuiMultiSelectIO* result. +// - (6) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 2. +// If you submit all items (no clipper), Step 2 and 3 are optional and will be handled by each item themselves. It is fine to always honor those steps. +// About ImGuiSelectionUserData: +// - This can store an application-defined identifier (e.g. index or pointer) submitted via SetNextItemSelectionUserData(). +// - In return we store them into RangeSrcItem/RangeFirstItem/RangeLastItem and other fields in ImGuiMultiSelectIO. +// - Most applications will store an object INDEX, hence the chosen name and type. Storing an index is natural, because +// SetRange requests will give you two end-points and you will need to iterate/interpolate between them to update your selection. +// - However it is perfectly possible to store a POINTER or another IDENTIFIER inside ImGuiSelectionUserData. +// Our system never assume that you identify items by indices, it never attempts to interpolate between two values. +// - If you enable ImGuiMultiSelectFlags_NoRangeSelect then it is guaranteed that you will never have to interpolate +// between two ImGuiSelectionUserData, which may be a convenient way to use part of the feature with less code work. +// - As most users will want to store an index, for convenience and to reduce confusion we use ImS64 instead of void*, +// being syntactically easier to downcast. Feel free to reinterpret_cast and store a pointer inside. + +// Flags for BeginMultiSelect() +enum ImGuiMultiSelectFlags_ +{ + ImGuiMultiSelectFlags_None = 0, + ImGuiMultiSelectFlags_SingleSelect = 1 << 0, // Disable selecting more than one item. This is available to allow single-selection code to share same code/logic if desired. It essentially disables the main purpose of BeginMultiSelect() tho! + ImGuiMultiSelectFlags_NoSelectAll = 1 << 1, // Disable CTRL+A shortcut to select all. + ImGuiMultiSelectFlags_NoRangeSelect = 1 << 2, // Disable Shift+selection mouse/keyboard support (useful for unordered 2D selection). With BoxSelect is also ensure contiguous SetRange requests are not combined into one. This allows not handling interpolation in SetRange requests. + ImGuiMultiSelectFlags_NoAutoSelect = 1 << 3, // Disable selecting items when navigating (useful for e.g. supporting range-select in a list of checkboxes). + ImGuiMultiSelectFlags_NoAutoClear = 1 << 4, // Disable clearing selection when navigating or selecting another one (generally used with ImGuiMultiSelectFlags_NoAutoSelect. useful for e.g. supporting range-select in a list of checkboxes). + ImGuiMultiSelectFlags_NoAutoClearOnReselect = 1 << 5, // Disable clearing selection when clicking/selecting an already selected item. + ImGuiMultiSelectFlags_BoxSelect1d = 1 << 6, // Enable box-selection with same width and same x pos items (e.g. full row Selectable()). Box-selection works better with little bit of spacing between items hit-box in order to be able to aim at empty space. + ImGuiMultiSelectFlags_BoxSelect2d = 1 << 7, // Enable box-selection with varying width or varying x pos items support (e.g. different width labels, or 2D layout/grid). This is slower: alters clipping logic so that e.g. horizontal movements will update selection of normally clipped items. + ImGuiMultiSelectFlags_BoxSelectNoScroll = 1 << 8, // Disable scrolling when box-selecting near edges of scope. + ImGuiMultiSelectFlags_ClearOnEscape = 1 << 9, // Clear selection when pressing Escape while scope is focused. + ImGuiMultiSelectFlags_ClearOnClickVoid = 1 << 10, // Clear selection when clicking on empty location within scope. + ImGuiMultiSelectFlags_ScopeWindow = 1 << 11, // Scope for _BoxSelect and _ClearOnClickVoid is whole window (Default). Use if BeginMultiSelect() covers a whole window or used a single time in same window. + ImGuiMultiSelectFlags_ScopeRect = 1 << 12, // Scope for _BoxSelect and _ClearOnClickVoid is rectangle encompassing BeginMultiSelect()/EndMultiSelect(). Use if BeginMultiSelect() is called multiple times in same window. + ImGuiMultiSelectFlags_SelectOnClick = 1 << 13, // Apply selection on mouse down when clicking on unselected item. (Default) + ImGuiMultiSelectFlags_SelectOnClickRelease = 1 << 14, // Apply selection on mouse release when clicking an unselected item. Allow dragging an unselected item without altering selection. + //ImGuiMultiSelectFlags_RangeSelect2d = 1 << 15, // Shift+Selection uses 2d geometry instead of linear sequence, so possible to use Shift+up/down to select vertically in grid. Analogous to what BoxSelect does. + ImGuiMultiSelectFlags_NavWrapX = 1 << 16, // [Temporary] Enable navigation wrapping on X axis. Provided as a convenience because we don't have a design for the general Nav API for this yet. When the more general feature be public we may obsolete this flag in favor of new one. +}; + +// Main IO structure returned by BeginMultiSelect()/EndMultiSelect(). +// This mainly contains a list of selection requests. +// - Use 'Demo->Tools->Debug Log->Selection' to see requests as they happen. +// - Some fields are only useful if your list is dynamic and allows deletion (getting post-deletion focus/state right is shown in the demo) +// - Below: who reads/writes each fields? 'r'=read, 'w'=write, 'ms'=multi-select code, 'app'=application/user code. +struct ImGuiMultiSelectIO +{ + //------------------------------------------// BeginMultiSelect / EndMultiSelect + ImVector Requests; // ms:w, app:r / ms:w app:r // Requests to apply to your selection data. + ImGuiSelectionUserData RangeSrcItem; // ms:w app:r / // (If using clipper) Begin: Source item (often the first selected item) must never be clipped: use clipper.IncludeItemByIndex() to ensure it is submitted. + ImGuiSelectionUserData NavIdItem; // ms:w, app:r / // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items). + bool NavIdSelected; // ms:w, app:r / app:r // (If using deletion) Last known selection state for NavId (if part of submitted items). + bool RangeSrcReset; // app:w / ms:r // (If using deletion) Set before EndMultiSelect() to reset ResetSrcItem (e.g. if deleted selection). + int ItemsCount; // ms:w, app:r / app:r // 'int items_count' parameter to BeginMultiSelect() is copied here for convenience, allowing simpler calls to your ApplyRequests handler. Not used internally. +}; + +// Selection request type +enum ImGuiSelectionRequestType +{ + ImGuiSelectionRequestType_None = 0, + ImGuiSelectionRequestType_SetAll, // Request app to clear selection (if Selected==false) or select all items (if Selected==true). We cannot set RangeFirstItem/RangeLastItem as its contents is entirely up to user (not necessarily an index) + ImGuiSelectionRequestType_SetRange, // Request app to select/unselect [RangeFirstItem..RangeLastItem] items (inclusive) based on value of Selected. Only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false. +}; + +// Selection request item +struct ImGuiSelectionRequest +{ + //------------------------------------------// BeginMultiSelect / EndMultiSelect + ImGuiSelectionRequestType Type; // ms:w, app:r / ms:w, app:r // Request type. You'll most often receive 1 Clear + 1 SetRange with a single-item range. + bool Selected; // ms:w, app:r / ms:w, app:r // Parameter for SetAll/SetRange requests (true = select, false = unselect) + ImS8 RangeDirection; // / ms:w app:r // Parameter for SetRange request: +1 when RangeFirstItem comes before RangeLastItem, -1 otherwise. Useful if you want to preserve selection order on a backward Shift+Click. + ImGuiSelectionUserData RangeFirstItem; // / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from top to bottom). + ImGuiSelectionUserData RangeLastItem; // / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from bottom to top). Inclusive! +}; + +// Optional helper to store multi-selection state + apply multi-selection requests. +// - Used by our demos and provided as a convenience to easily implement basic multi-selection. +// - Iterate selection with 'void* it = NULL; ImGuiID id; while (selection.GetNextSelectedItem(&it, &id)) { ... }' +// Or you can check 'if (Contains(id)) { ... }' for each possible object if their number is not too high to iterate. +// - USING THIS IS NOT MANDATORY. This is only a helper and not a required API. +// To store a multi-selection, in your application you could: +// - Use this helper as a convenience. We use our simple key->value ImGuiStorage as a std::set replacement. +// - Use your own external storage: e.g. std::set, std::vector, interval trees, intrusively stored selection etc. +// In ImGuiSelectionBasicStorage we: +// - always use indices in the multi-selection API (passed to SetNextItemSelectionUserData(), retrieved in ImGuiMultiSelectIO) +// - use the AdapterIndexToStorageId() indirection layer to abstract how persistent selection data is derived from an index. +// - use decently optimized logic to allow queries and insertion of very large selection sets. +// - do not preserve selection order. +// Many combinations are possible depending on how you prefer to store your items and how you prefer to store your selection. +// Large applications are likely to eventually want to get rid of this indirection layer and do their own thing. +// See https://github.com/ocornut/imgui/wiki/Multi-Select for details and pseudo-code using this helper. +struct ImGuiSelectionBasicStorage +{ + // Members + int Size; // // Number of selected items, maintained by this helper. + bool PreserveOrder; // = false // GetNextSelectedItem() will return ordered selection (currently implemented by two additional sorts of selection. Could be improved) + void* UserData; // = NULL // User data for use by adapter function // e.g. selection.UserData = (void*)my_items; + ImGuiID (*AdapterIndexToStorageId)(ImGuiSelectionBasicStorage* self, int idx); // e.g. selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { return ((MyItems**)self->UserData)[idx]->ID; }; + int _SelectionOrder;// [Internal] Increasing counter to store selection order + ImGuiStorage _Storage; // [Internal] Selection set. Think of this as similar to e.g. std::set. Prefer not accessing directly: iterate with GetNextSelectedItem(). + + // Methods + ImGuiSelectionBasicStorage(); + IMGUI_API void ApplyRequests(ImGuiMultiSelectIO* ms_io); // Apply selection requests coming from BeginMultiSelect() and EndMultiSelect() functions. It uses 'items_count' passed to BeginMultiSelect() + IMGUI_API bool Contains(ImGuiID id) const; // Query if an item id is in selection. + IMGUI_API void Clear(); // Clear selection + IMGUI_API void Swap(ImGuiSelectionBasicStorage& r); // Swap two selections + IMGUI_API void SetItemSelected(ImGuiID id, bool selected); // Add/remove an item from selection (generally done by ApplyRequests() function) + IMGUI_API bool GetNextSelectedItem(void** opaque_it, ImGuiID* out_id); // Iterate selection with 'void* it = NULL; ImGuiId id; while (selection.GetNextSelectedItem(&it, &id)) { ... }' + inline ImGuiID GetStorageIdFromIndex(int idx) { return AdapterIndexToStorageId(this, idx); } // Convert index to item id based on provided adapter. +}; + +// Optional helper to apply multi-selection requests to existing randomly accessible storage. +// Convenient if you want to quickly wire multi-select API on e.g. an array of bool or items storing their own selection state. +struct ImGuiSelectionExternalStorage +{ + // Members + void* UserData; // User data for use by adapter function // e.g. selection.UserData = (void*)my_items; + void (*AdapterSetItemSelected)(ImGuiSelectionExternalStorage* self, int idx, bool selected); // e.g. AdapterSetItemSelected = [](ImGuiSelectionExternalStorage* self, int idx, bool selected) { ((MyItems**)self->UserData)[idx]->Selected = selected; } + + // Methods + IMGUI_API ImGuiSelectionExternalStorage(); + IMGUI_API void ApplyRequests(ImGuiMultiSelectIO* ms_io); // Apply selection requests by using AdapterSetItemSelected() calls +}; + //----------------------------------------------------------------------------- // [SECTION] Drawing API (ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawListFlags, ImDrawList, ImDrawData) // Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList. @@ -2826,15 +3147,15 @@ struct ImDrawList // [Internal, used while building lists] unsigned int _VtxCurrentIdx; // [Internal] generally == VtxBuffer.Size unless we are past 64K vertices, in which case this gets reset to 0. ImDrawListSharedData* _Data; // Pointer to shared draw data (you can use ImGui::GetDrawListSharedData() to get the one from current ImGui context) - const char* _OwnerName; // Pointer to owner window's name for debugging ImDrawVert* _VtxWritePtr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) ImDrawIdx* _IdxWritePtr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) - ImVector _ClipRectStack; // [Internal] - ImVector _TextureIdStack; // [Internal] ImVector _Path; // [Internal] current path building ImDrawCmdHeader _CmdHeader; // [Internal] template of active commands. Fields should match those of CmdBuffer.back(). ImDrawListSplitter _Splitter; // [Internal] for channels api (note: prefer using your own persistent instance of ImDrawListSplitter!) + ImVector _ClipRectStack; // [Internal] + ImVector _TextureIdStack; // [Internal] float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content + const char* _OwnerName; // Pointer to owner window's name for debugging // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData() or create and use your own ImDrawListSharedData (so you can use ImDrawList without ImGui) ImDrawList(ImDrawListSharedData* shared_data) { memset(this, 0, sizeof(*this)); _Data = shared_data; } @@ -2867,15 +3188,20 @@ struct ImDrawList 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 AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f); - IMGUI_API void AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0); + IMGUI_API void AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f); + IMGUI_API void AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0); 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) + // General polygon + // - Only simple polygons are supported by filling functions (no self-intersections, no holes). + // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience fo user but not used by main library. + IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness); + IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); + IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col); + // Image primitives // - Read FAQ to understand what ImTextureID is. // - "p_min" and "p_max" represent the upper-left and lower-right corners of the rectangle. @@ -2891,10 +3217,11 @@ struct ImDrawList 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 PathFillConcave(ImU32 col) { AddConcavePolyFilled(_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 PathEllipticalArcTo(const ImVec2& center, float radius_x, float radius_y, float rot, float a_min, float a_max, int num_segments = 0); // Ellipse + IMGUI_API void PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, float rot, float a_min, float a_max, int num_segments = 0); // Ellipse 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); @@ -2927,6 +3254,9 @@ struct ImDrawList inline void PrimVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) { PrimWriteIdx((ImDrawIdx)_VtxCurrentIdx); PrimWriteVtx(pos, uv, col); } // Write vertex with unique index // Obsolete names + //inline void AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f) { AddEllipse(center, ImVec2(radius_x, radius_y), col, rot, num_segments, thickness); } // OBSOLETED in 1.90.5 (Mar 2024) + //inline void AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0) { AddEllipseFilled(center, ImVec2(radius_x, radius_y), col, rot, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) + //inline void PathEllipticalArcTo(const ImVec2& center, float radius_x, float radius_y, float rot, float a_min, float a_max, int num_segments = 0) { PathEllipticalArcTo(center, ImVec2(radius_x, radius_y), rot, a_min, a_max, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void AddBezierCurve(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0) { AddBezierCubic(p1, p2, p3, p4, col, thickness, num_segments); } // OBSOLETED in 1.80 (Jan 2021) //inline void PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0) { PathBezierCubicCurveTo(p2, p3, p4, num_segments); } // OBSOLETED in 1.80 (Jan 2021) @@ -3115,7 +3445,7 @@ struct ImFontAtlas // 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. + // - If you render colored output, set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. // - You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), // so you can render e.g. custom colorful icons and use them as regular glyphs. // - Read docs/FONTS.md for more details about using colorful icons. @@ -3192,7 +3522,7 @@ struct ImFont float EllipsisCharStep; // 4 // out // Step between characters when EllipsisCount > 0 bool DirtyLookupTables; // 1 // out // float Scale; // 4 // in // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with SetWindowFontScale() - float Ascent, Descent; // 4+4 // out // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] + float Ascent, Descent; // 4+4 // out // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) int MetricsTotalSurface;// 4 // out // // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) 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. @@ -3274,7 +3604,7 @@ struct ImGuiViewport // The library never uses those fields, they are merely storage to facilitate backend implementation. void* RendererUserData; // void* to hold custom data structure for the renderer (e.g. swap chain, framebuffers etc.). generally set by your Renderer_CreateWindow function. void* PlatformUserData; // void* to hold custom data structure for the OS / platform (e.g. windowing info, render context). generally set by your Platform_CreateWindow function. - void* PlatformHandle; // void* for FindViewportByPlatformHandle(). (e.g. suggested to use natural platform handle such as HWND, GLFWWindow*, SDL_Window*) + void* PlatformHandle; // void* to hold higher-level, platform window handle (e.g. HWND, GLFWWindow*, SDL_Window*), for FindViewportByPlatformHandle(). void* PlatformHandleRaw; // void* to hold lower-level, platform-native window handle (under Win32 this is expected to be a HWND, unused for other platforms), when using an abstraction layer like GLFW or SDL (where PlatformHandle would be a SDL_Window*) bool PlatformWindowCreated; // Platform window has been created (Platform_CreateWindow() has been called). This is false during the first frame where a viewport is being created. bool PlatformRequestMove; // Platform window requested move (e.g. window was moved by the OS / host window manager, authoritative position will be OS window position) @@ -3411,7 +3741,7 @@ struct ImGuiPlatformMonitor ImGuiPlatformMonitor() { MainPos = MainSize = WorkPos = WorkSize = ImVec2(0, 0); DpiScale = 1.0f; PlatformHandle = NULL; } }; -// (Optional) Support for IME (Input Method Editor) via the io.SetPlatformImeDataFn() function. +// (Optional) Support for IME (Input Method Editor) via the io.PlatformSetImeDataFn() function. struct ImGuiPlatformImeData { bool WantVisible; // A widget wants the IME to be visible @@ -3427,38 +3757,40 @@ struct ImGuiPlatformImeData // Please keep your copy of dear imgui up to date! Occasionally set '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' in imconfig.h to stay ahead. //----------------------------------------------------------------------------- -namespace ImGui -{ -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - IMGUI_API ImGuiKey GetKeyIndex(ImGuiKey key); // map ImGuiKey_* values into legacy native key index. == io.KeyMap[key] -#else - static inline ImGuiKey GetKeyIndex(ImGuiKey key) { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END && "ImGuiKey and native_index was merged together and native_index is disabled by IMGUI_DISABLE_OBSOLETE_KEYIO. Please switch to ImGuiKey."); return key; } -#endif -} - #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS namespace ImGui { + // OBSOLETED in 1.91.0 (from July 2024) + static inline void PushButtonRepeat(bool repeat) { PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); } + static inline void PopButtonRepeat() { PopItemFlag(); } + static inline void PushTabStop(bool tab_stop) { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); } + static inline void PopTabStop() { PopItemFlag(); } + IMGUI_API ImVec2 GetContentRegionMax(); // Content boundaries max (e.g. window boundaries including scrolling, or current column boundaries). You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()! + IMGUI_API ImVec2 GetWindowContentRegionMin(); // Content boundaries min for the window (roughly (0,0)-Scroll), in window-local coordinates. You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()! + IMGUI_API ImVec2 GetWindowContentRegionMax(); // Content boundaries max for the window (roughly (0,0)+Size-Scroll), in window-local coordinates. You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()! // OBSOLETED in 1.90.0 (from September 2023) - static inline bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags window_flags = 0) { return BeginChild(id, size, ImGuiChildFlags_FrameStyle, window_flags); } - static inline void EndChildFrame() { EndChild(); } + static inline bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags window_flags = 0) { return BeginChild(id, size, ImGuiChildFlags_FrameStyle, window_flags); } + static inline void EndChildFrame() { EndChild(); } //static inline bool BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags window_flags){ return BeginChild(str_id, size_arg, border ? ImGuiChildFlags_Border : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Border //static inline bool BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags window_flags) { return BeginChild(id, size_arg, border ? ImGuiChildFlags_Border : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Border - static inline void ShowStackToolWindow(bool* p_open = NULL) { ShowIDStackToolWindow(p_open); } - IMGUI_API bool ListBox(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int height_in_items = -1); + static inline void ShowStackToolWindow(bool* p_open = NULL) { ShowIDStackToolWindow(p_open); } IMGUI_API bool Combo(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int popup_max_height_in_items = -1); + IMGUI_API bool ListBox(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int height_in_items = -1); // OBSOLETED in 1.89.7 (from June 2023) - IMGUI_API void SetItemAllowOverlap(); // Use SetNextItemAllowOverlap() before item. + IMGUI_API void SetItemAllowOverlap(); // Use SetNextItemAllowOverlap() before item. // OBSOLETED in 1.89.4 (from March 2023) - static inline void PushAllowKeyboardFocus(bool tab_stop) { PushTabStop(tab_stop); } - static inline void PopAllowKeyboardFocus() { PopTabStop(); } + static inline void PushAllowKeyboardFocus(bool tab_stop) { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); } + static inline void PopAllowKeyboardFocus() { PopItemFlag(); } // OBSOLETED in 1.89 (from August 2022) IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); // Use new ImageButton() signature (explicit item id, regular FramePadding) - // 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.87 (from February 2022 but more formally obsoleted April 2024) + IMGUI_API ImGuiKey GetKeyIndex(ImGuiKey key); // Map ImGuiKey_* values into legacy native key index. == io.KeyMap[key]. When using a 1.87+ backend using io.AddKeyEvent(), calling GetKeyIndex() with ANY ImGuiKey_XXXX values will return the same value! + //static inline ImGuiKey GetKeyIndex(ImGuiKey key) { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END); return key; } // Some of the older obsolete names along with their replacement (commented out so they are not reported in IDE) + //-- 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); // Code removed, see 1.90 for last version of the code. Calculate range of visible items for large list of evenly sized items. Prefer using ImGuiListClipper. //-- OBSOLETED in 1.85 (from August 2021) @@ -3531,8 +3863,8 @@ namespace ImGui // RENAMED and MERGED both ImGuiKey_ModXXX and ImGuiModFlags_XXX into ImGuiMod_XXX (from September 2022) // RENAMED ImGuiKeyModFlags -> ImGuiModFlags in 1.88 (from April 2022). Exceptionally commented out ahead of obscolescence schedule to reduce confusion and because they were not meant to be used in the first place. -typedef ImGuiKeyChord ImGuiModFlags; // == int. We generally use ImGuiKeyChord to mean "a ImGuiKey or-ed with any number of ImGuiMod_XXX value", but you may store only mods in there. -enum ImGuiModFlags_ { ImGuiModFlags_None = 0, ImGuiModFlags_Ctrl = ImGuiMod_Ctrl, ImGuiModFlags_Shift = ImGuiMod_Shift, ImGuiModFlags_Alt = ImGuiMod_Alt, ImGuiModFlags_Super = ImGuiMod_Super }; +//typedef ImGuiKeyChord ImGuiModFlags; // == int. We generally use ImGuiKeyChord to mean "a ImGuiKey or-ed with any number of ImGuiMod_XXX value", so you may store mods in there. +//enum ImGuiModFlags_ { ImGuiModFlags_None = 0, ImGuiModFlags_Ctrl = ImGuiMod_Ctrl, ImGuiModFlags_Shift = ImGuiMod_Shift, ImGuiModFlags_Alt = ImGuiMod_Alt, ImGuiModFlags_Super = ImGuiMod_Super }; //typedef ImGuiKeyChord ImGuiKeyModFlags; // == int //enum ImGuiKeyModFlags_ { ImGuiKeyModFlags_None = 0, ImGuiKeyModFlags_Ctrl = ImGuiMod_Ctrl, ImGuiKeyModFlags_Shift = ImGuiMod_Shift, ImGuiKeyModFlags_Alt = ImGuiMod_Alt, ImGuiKeyModFlags_Super = ImGuiMod_Super }; diff --git a/libs/imgui/imgui_demo.cpp b/libs/imgui/imgui_demo.cpp index 9b4ba87..0d512d1 100644 --- a/libs/imgui/imgui_demo.cpp +++ b/libs/imgui/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.4 +// dear imgui, v1.91.0 // (demo code) // Help: @@ -7,9 +7,14 @@ // - Need help integrating Dear ImGui in your codebase? // - Read Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started // - Read 'Programmer guide' in imgui.cpp for notes on how to setup Dear ImGui in your codebase. -// Read imgui.cpp for more details, documentation and comments. +// Read top of imgui.cpp and imgui.h for many details, documentation, comments, links. // Get the latest version at https://github.com/ocornut/imgui +// How to easily locate code? +// - Use Tools->Item Picker to debug break in code by clicking any widgets: https://github.com/ocornut/imgui/wiki/Debug-Tools +// - Browse an online version the demo with code linked to hovered widgets: https://pthom.github.io/imgui_manual_online/manual/imgui_manual.html +// - Find a visible string and search for it in the code! + //--------------------------------------------------- // PLEASE DO NOT REMOVE THIS FILE FROM YOUR PROJECT! //--------------------------------------------------- @@ -54,8 +59,10 @@ // Because we can't assume anything about your support of maths operators, we cannot use them in imgui_demo.cpp. // Navigating this file: -// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. -// - With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. +// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. +// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. +// - You can search/grep for all sections listed in the index to find the section. /* @@ -63,13 +70,15 @@ Index of this file: // [SECTION] Forward Declarations // [SECTION] Helpers +// [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor & Multi-Select demos) // [SECTION] Demo Window / ShowDemoWindow() -// - ShowDemoWindow() -// - sub section: ShowDemoWindowWidgets() -// - sub section: ShowDemoWindowLayout() -// - sub section: ShowDemoWindowPopups() -// - sub section: ShowDemoWindowTables() -// - sub section: ShowDemoWindowInputs() +// [SECTION] ShowDemoWindowMenuBar() +// [SECTION] ShowDemoWindowWidgets() +// [SECTION] ShowDemoWindowMultiSelect() +// [SECTION] ShowDemoWindowLayout() +// [SECTION] ShowDemoWindowPopups() +// [SECTION] ShowDemoWindowTables() +// [SECTION] ShowDemoWindowInputs() // [SECTION] About Window / ShowAboutWindow() // [SECTION] Style Editor / ShowStyleEditor() // [SECTION] User Guide / ShowUserGuide() @@ -87,6 +96,7 @@ Index of this file: // [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() // [SECTION] Example App: Docking, DockSpace / ShowExampleAppDockSpace() // [SECTION] Example App: Documents Handling / ShowExampleAppDocuments() +// [SECTION] Example App: Assets Browser / ShowExampleAppAssetsBrowser() */ @@ -131,6 +141,7 @@ Index of this file: #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. #pragma clang diagnostic ignored "-Wreserved-id-macro" // warning: macro name is a reserved identifier #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #elif defined(__GNUC__) #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size @@ -183,20 +194,22 @@ Index of this file: #endif //----------------------------------------------------------------------------- -// [SECTION] Forward Declarations, Helpers +// [SECTION] Forward Declarations //----------------------------------------------------------------------------- #if !defined(IMGUI_DISABLE_DEMO_WINDOWS) // Forward Declarations +struct ImGuiDemoWindowData; static void ShowExampleAppMainMenuBar(); +static void ShowExampleAppAssetsBrowser(bool* p_open); static void ShowExampleAppConsole(bool* p_open); static void ShowExampleAppCustomRendering(bool* p_open); static void ShowExampleAppDockSpace(bool* p_open); static void ShowExampleAppDocuments(bool* p_open); static void ShowExampleAppLog(bool* p_open); static void ShowExampleAppLayout(bool* p_open); -static void ShowExampleAppPropertyEditor(bool* p_open); +static void ShowExampleAppPropertyEditor(bool* p_open, ImGuiDemoWindowData* demo_data); static void ShowExampleAppSimpleOverlay(bool* p_open); static void ShowExampleAppAutoResize(bool* p_open); static void ShowExampleAppConstrainedResize(bool* p_open); @@ -206,8 +219,10 @@ static void ShowExampleAppWindowTitles(bool* p_open); static void ShowExampleMenuFile(); // We split the contents of the big ShowDemoWindow() function into smaller functions -// (because the link time of very large functions grow non-linearly) -static void ShowDemoWindowWidgets(); +// (because the link time of very large functions tends to grow non-linearly) +static void ShowDemoWindowMenuBar(ImGuiDemoWindowData* demo_data); +static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data); +static void ShowDemoWindowMultiSelect(ImGuiDemoWindowData* demo_data); static void ShowDemoWindowLayout(); static void ShowDemoWindowPopups(); static void ShowDemoWindowTables(); @@ -250,17 +265,122 @@ ImGuiDemoMarkerCallback GImGuiDemoMarkerCallback = NULL; void* GImGuiDemoMarkerCallbackUserData = NULL; #define IMGUI_DEMO_MARKER(section) do { if (GImGuiDemoMarkerCallback != NULL) GImGuiDemoMarkerCallback(__FILE__, __LINE__, section, GImGuiDemoMarkerCallbackUserData); } while (0) +//----------------------------------------------------------------------------- +// [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor etc.) +//----------------------------------------------------------------------------- + +// Simple representation for a tree +// (this is designed to be simple to understand for our demos, not to be fancy or efficient etc.) +struct ExampleTreeNode +{ + // Tree structure + char Name[28] = ""; + int UID = 0; + ExampleTreeNode* Parent = NULL; + ImVector Childs; + unsigned short IndexInParent = 0; // Maintaining this allows us to implement linear traversal more easily + + // Leaf Data + bool HasData = false; // All leaves have data + bool DataMyBool = true; + int DataMyInt = 128; + ImVec2 DataMyVec2 = ImVec2(0.0f, 3.141592f); +}; + +// Simple representation of struct metadata/serialization data. +// (this is a minimal version of what a typical advanced application may provide) +struct ExampleMemberInfo +{ + const char* Name; // Member name + ImGuiDataType DataType; // Member type + int DataCount; // Member count (1 when scalar) + int Offset; // Offset inside parent structure +}; + +// Metadata description of ExampleTreeNode struct. +static const ExampleMemberInfo ExampleTreeNodeMemberInfos[] +{ + { "MyBool", ImGuiDataType_Bool, 1, offsetof(ExampleTreeNode, DataMyBool) }, + { "MyInt", ImGuiDataType_S32, 1, offsetof(ExampleTreeNode, DataMyInt) }, + { "MyVec2", ImGuiDataType_Float, 2, offsetof(ExampleTreeNode, DataMyVec2) }, +}; + +static ExampleTreeNode* ExampleTree_CreateNode(const char* name, int uid, ExampleTreeNode* parent) +{ + ExampleTreeNode* node = IM_NEW(ExampleTreeNode); + snprintf(node->Name, IM_ARRAYSIZE(node->Name), "%s", name); + node->UID = uid; + node->Parent = parent; + node->IndexInParent = parent ? (unsigned short)parent->Childs.Size : 0; + if (parent) + parent->Childs.push_back(node); + return node; +} + +// Create example tree data +// (this allocates _many_ more times than most other code in either Dear ImGui or others demo) +static ExampleTreeNode* ExampleTree_CreateDemoTree() +{ + static const char* root_names[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pear", "Pineapple", "Strawberry", "Watermelon" }; + char name_buf[32]; + int uid = 0; + ExampleTreeNode* node_L0 = ExampleTree_CreateNode("", ++uid, NULL); + const int root_items_multiplier = 2; + for (int idx_L0 = 0; idx_L0 < IM_ARRAYSIZE(root_names) * root_items_multiplier; idx_L0++) + { + snprintf(name_buf, 32, "%s %d", root_names[idx_L0 / root_items_multiplier], idx_L0 % root_items_multiplier); + ExampleTreeNode* node_L1 = ExampleTree_CreateNode(name_buf, ++uid, node_L0); + const int number_of_childs = (int)strlen(node_L1->Name); + for (int idx_L1 = 0; idx_L1 < number_of_childs; idx_L1++) + { + snprintf(name_buf, 32, "Child %d", idx_L1); + ExampleTreeNode* node_L2 = ExampleTree_CreateNode(name_buf, ++uid, node_L1); + node_L2->HasData = true; + if (idx_L1 == 0) + { + snprintf(name_buf, 32, "Sub-child %d", 0); + ExampleTreeNode* node_L3 = ExampleTree_CreateNode(name_buf, ++uid, node_L2); + node_L3->HasData = true; + } + } + } + return node_L0; +} + //----------------------------------------------------------------------------- // [SECTION] Demo Window / ShowDemoWindow() //----------------------------------------------------------------------------- -// - ShowDemoWindow() -// - ShowDemoWindowWidgets() -// - ShowDemoWindowLayout() -// - ShowDemoWindowPopups() -// - ShowDemoWindowTables() -// - ShowDemoWindowColumns() -// - ShowDemoWindowInputs() -//----------------------------------------------------------------------------- + +// Data to be shared accross different functions of the demo. +struct ImGuiDemoWindowData +{ + // Examples Apps (accessible from the "Examples" menu) + bool ShowMainMenuBar = false; + bool ShowAppAssetsBrowser = false; + bool ShowAppConsole = false; + bool ShowAppCustomRendering = false; + bool ShowAppDocuments = false; + bool ShowAppDockSpace = false; + bool ShowAppLog = false; + bool ShowAppLayout = false; + bool ShowAppPropertyEditor = false; + bool ShowAppSimpleOverlay = false; + bool ShowAppAutoResize = false; + bool ShowAppConstrainedResize = false; + bool ShowAppFullscreen = false; + bool ShowAppLongText = false; + bool ShowAppWindowTitles = false; + + // Dear ImGui Tools (accessible from the "Tools" menu) + bool ShowMetrics = false; + bool ShowDebugLog = false; + bool ShowIDStackTool = false; + bool ShowStyleEditor = false; + bool ShowAbout = false; + + // Other data + ExampleTreeNode* DemoTree = NULL; +}; // Demonstrate most Dear ImGui features (this is big function!) // You may execute this function to experiment with the UI and understand what it does. @@ -271,58 +391,40 @@ void ImGui::ShowDemoWindow(bool* p_open) // Most functions would normally just assert/crash if the context is missing. IM_ASSERT(ImGui::GetCurrentContext() != NULL && "Missing Dear ImGui context. Refer to examples app!"); - // Examples Apps (accessible from the "Examples" menu) - static bool show_app_main_menu_bar = false; - static bool show_app_console = false; - static bool show_app_custom_rendering = false; - static bool show_app_dockspace = false; - static bool show_app_documents = false; - static bool show_app_log = false; - static bool show_app_layout = false; - static bool show_app_property_editor = false; - static bool show_app_simple_overlay = false; - static bool show_app_auto_resize = false; - static bool show_app_constrained_resize = false; - static bool show_app_fullscreen = false; - static bool show_app_long_text = false; - static bool show_app_window_titles = false; + // Verify ABI compatibility between caller code and compiled version of Dear ImGui. This helps detects some build issues. + IMGUI_CHECKVERSION(); - if (show_app_main_menu_bar) ShowExampleAppMainMenuBar(); - if (show_app_dockspace) ShowExampleAppDockSpace(&show_app_dockspace); // Process the Docking app first, as explicit DockSpace() nodes needs to be submitted early (read comments near the DockSpace function) - if (show_app_documents) ShowExampleAppDocuments(&show_app_documents); // Process the Document app next, as it may also use a DockSpace() - if (show_app_console) ShowExampleAppConsole(&show_app_console); - if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering); - if (show_app_log) ShowExampleAppLog(&show_app_log); - if (show_app_layout) ShowExampleAppLayout(&show_app_layout); - if (show_app_property_editor) ShowExampleAppPropertyEditor(&show_app_property_editor); - if (show_app_simple_overlay) ShowExampleAppSimpleOverlay(&show_app_simple_overlay); - if (show_app_auto_resize) ShowExampleAppAutoResize(&show_app_auto_resize); - if (show_app_constrained_resize) ShowExampleAppConstrainedResize(&show_app_constrained_resize); - if (show_app_fullscreen) ShowExampleAppFullscreen(&show_app_fullscreen); - if (show_app_long_text) ShowExampleAppLongText(&show_app_long_text); - if (show_app_window_titles) ShowExampleAppWindowTitles(&show_app_window_titles); + // Stored data + static ImGuiDemoWindowData demo_data; + + // Examples Apps (accessible from the "Examples" menu) + if (demo_data.ShowMainMenuBar) { ShowExampleAppMainMenuBar(); } + if (demo_data.ShowAppDockSpace) { ShowExampleAppDockSpace(&demo_data.ShowAppDockSpace); } // Important: Process the Docking app first, as explicit DockSpace() nodes needs to be submitted early (read comments near the DockSpace function) + if (demo_data.ShowAppDocuments) { ShowExampleAppDocuments(&demo_data.ShowAppDocuments); } // ...process the Document app next, as it may also use a DockSpace() + if (demo_data.ShowAppAssetsBrowser) { ShowExampleAppAssetsBrowser(&demo_data.ShowAppAssetsBrowser); } + if (demo_data.ShowAppConsole) { ShowExampleAppConsole(&demo_data.ShowAppConsole); } + if (demo_data.ShowAppCustomRendering) { ShowExampleAppCustomRendering(&demo_data.ShowAppCustomRendering); } + if (demo_data.ShowAppLog) { ShowExampleAppLog(&demo_data.ShowAppLog); } + if (demo_data.ShowAppLayout) { ShowExampleAppLayout(&demo_data.ShowAppLayout); } + if (demo_data.ShowAppPropertyEditor) { ShowExampleAppPropertyEditor(&demo_data.ShowAppPropertyEditor, &demo_data); } + if (demo_data.ShowAppSimpleOverlay) { ShowExampleAppSimpleOverlay(&demo_data.ShowAppSimpleOverlay); } + if (demo_data.ShowAppAutoResize) { ShowExampleAppAutoResize(&demo_data.ShowAppAutoResize); } + if (demo_data.ShowAppConstrainedResize) { ShowExampleAppConstrainedResize(&demo_data.ShowAppConstrainedResize); } + if (demo_data.ShowAppFullscreen) { ShowExampleAppFullscreen(&demo_data.ShowAppFullscreen); } + if (demo_data.ShowAppLongText) { ShowExampleAppLongText(&demo_data.ShowAppLongText); } + if (demo_data.ShowAppWindowTitles) { ShowExampleAppWindowTitles(&demo_data.ShowAppWindowTitles); } // Dear ImGui Tools (accessible from the "Tools" menu) - static bool show_tool_metrics = false; - static bool show_tool_debug_log = false; - static bool show_tool_id_stack_tool = false; - static bool show_tool_style_editor = false; - static bool show_tool_about = false; - - if (show_tool_metrics) - ImGui::ShowMetricsWindow(&show_tool_metrics); - if (show_tool_debug_log) - ImGui::ShowDebugLogWindow(&show_tool_debug_log); - if (show_tool_id_stack_tool) - ImGui::ShowIDStackToolWindow(&show_tool_id_stack_tool); - if (show_tool_style_editor) + if (demo_data.ShowMetrics) { ImGui::ShowMetricsWindow(&demo_data.ShowMetrics); } + if (demo_data.ShowDebugLog) { ImGui::ShowDebugLogWindow(&demo_data.ShowDebugLog); } + if (demo_data.ShowIDStackTool) { ImGui::ShowIDStackToolWindow(&demo_data.ShowIDStackTool); } + if (demo_data.ShowAbout) { ImGui::ShowAboutWindow(&demo_data.ShowAbout); } + if (demo_data.ShowStyleEditor) { - ImGui::Begin("Dear ImGui Style Editor", &show_tool_style_editor); + ImGui::Begin("Dear ImGui Style Editor", &demo_data.ShowStyleEditor); ImGui::ShowStyleEditor(); ImGui::End(); } - if (show_tool_about) - ImGui::ShowAboutWindow(&show_tool_about); // Demonstrate the various window flags. Typically you would just use the default! static bool no_titlebar = false; @@ -367,68 +469,11 @@ void ImGui::ShowDemoWindow(bool* p_open) } // Most "big" widgets share a common width settings by default. See 'Demo->Layout->Widgets Width' for details. - // e.g. Use 2/3 of the space for widgets and 1/3 for labels (right align) - //ImGui::PushItemWidth(-ImGui::GetWindowWidth() * 0.35f); - // e.g. Leave a fixed amount of width for labels (by passing a negative value), the rest goes to widgets. - ImGui::PushItemWidth(ImGui::GetFontSize() * -12); + ImGui::PushItemWidth(ImGui::GetFontSize() * -12); // e.g. Leave a fixed amount of width for labels (by passing a negative value), the rest goes to widgets. + //ImGui::PushItemWidth(-ImGui::GetWindowWidth() * 0.35f); // e.g. Use 2/3 of the space for widgets and 1/3 for labels (right align) // Menu Bar - if (ImGui::BeginMenuBar()) - { - if (ImGui::BeginMenu("Menu")) - { - IMGUI_DEMO_MARKER("Menu/File"); - ShowExampleMenuFile(); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Examples")) - { - IMGUI_DEMO_MARKER("Menu/Examples"); - ImGui::MenuItem("Main menu bar", NULL, &show_app_main_menu_bar); - - ImGui::SeparatorText("Mini apps"); - ImGui::MenuItem("Console", NULL, &show_app_console); - ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering); - ImGui::MenuItem("Dockspace", NULL, &show_app_dockspace); - ImGui::MenuItem("Documents", NULL, &show_app_documents); - ImGui::MenuItem("Log", NULL, &show_app_log); - ImGui::MenuItem("Property editor", NULL, &show_app_property_editor); - ImGui::MenuItem("Simple layout", NULL, &show_app_layout); - ImGui::MenuItem("Simple overlay", NULL, &show_app_simple_overlay); - - ImGui::SeparatorText("Concepts"); - ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize); - ImGui::MenuItem("Constrained-resizing window", NULL, &show_app_constrained_resize); - ImGui::MenuItem("Fullscreen window", NULL, &show_app_fullscreen); - ImGui::MenuItem("Long text display", NULL, &show_app_long_text); - ImGui::MenuItem("Manipulating window titles", NULL, &show_app_window_titles); - - ImGui::EndMenu(); - } - //if (ImGui::MenuItem("MenuItem")) {} // You can also use MenuItem() inside a menu bar! - if (ImGui::BeginMenu("Tools")) - { - IMGUI_DEMO_MARKER("Menu/Tools"); -#ifndef IMGUI_DISABLE_DEBUG_TOOLS - const bool has_debug_tools = true; -#else - const bool has_debug_tools = false; -#endif - ImGui::MenuItem("Metrics/Debugger", NULL, &show_tool_metrics, has_debug_tools); - ImGui::MenuItem("Debug Log", NULL, &show_tool_debug_log, has_debug_tools); - ImGui::MenuItem("ID Stack Tool", NULL, &show_tool_id_stack_tool, has_debug_tools); - ImGui::MenuItem("Style Editor", NULL, &show_tool_style_editor); - bool is_debugger_present = ImGui::GetIO().ConfigDebugIsDebuggerPresent; - if (ImGui::MenuItem("Item Picker", NULL, false, has_debug_tools && is_debugger_present)) - ImGui::DebugStartItemPicker(); - if (!is_debugger_present) - ImGui::SetItemTooltip("Requires io.ConfigDebugIsDebuggerPresent=true to be set.\n\nWe otherwise disable the menu option to avoid casual users crashing the application.\n\nYou can however always access the Item Picker in Metrics->Tools."); - ImGui::Separator(); - ImGui::MenuItem("About Dear ImGui", NULL, &show_tool_about); - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - } + ShowDemoWindowMenuBar(&demo_data); ImGui::Text("dear imgui says hello! (%s) (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); ImGui::Spacing(); @@ -446,7 +491,9 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::BulletText("See the ShowDemoWindow() code in imgui_demo.cpp. <- you are here!"); ImGui::BulletText("See comments in imgui.cpp."); ImGui::BulletText("See example applications in the examples/ folder."); - ImGui::BulletText("Read the FAQ at https://www.dearimgui.com/faq/"); + ImGui::BulletText("Read the FAQ at "); + ImGui::SameLine(0, 0); + ImGui::TextLinkOpenURL("https://www.dearimgui.com/faq/"); ImGui::BulletText("Set 'io.ConfigFlags |= NavEnableKeyboard' for keyboard controls."); ImGui::BulletText("Set 'io.ConfigFlags |= NavEnableGamepad' for gamepad controls."); @@ -469,19 +516,25 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::CheckboxFlags("io.ConfigFlags: NavEnableSetMousePos", &io.ConfigFlags, ImGuiConfigFlags_NavEnableSetMousePos); ImGui::SameLine(); HelpMarker("Instruct navigation to move the mouse cursor. See comment for ImGuiConfigFlags_NavEnableSetMousePos."); ImGui::CheckboxFlags("io.ConfigFlags: NoMouse", &io.ConfigFlags, ImGuiConfigFlags_NoMouse); + ImGui::SameLine(); HelpMarker("Instruct dear imgui to disable mouse inputs and interactions."); + + // The "NoMouse" option can get us stuck with a disabled mouse! Let's provide an alternative way to fix it: if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) { - // The "NoMouse" option can get us stuck with a disabled mouse! Let's provide an alternative way to fix it: if (fmodf((float)ImGui::GetTime(), 0.40f) < 0.20f) { ImGui::SameLine(); ImGui::Text("<>"); } - if (ImGui::IsKeyPressed(ImGuiKey_Space)) + // Prevent both being checked + if (ImGui::IsKeyPressed(ImGuiKey_Space) || (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard)) io.ConfigFlags &= ~ImGuiConfigFlags_NoMouse; } - ImGui::CheckboxFlags("io.ConfigFlags: NoMouseCursorChange", &io.ConfigFlags, ImGuiConfigFlags_NoMouseCursorChange); + + ImGui::CheckboxFlags("io.ConfigFlags: NoMouseCursorChange", &io.ConfigFlags, ImGuiConfigFlags_NoMouseCursorChange); ImGui::SameLine(); HelpMarker("Instruct backend to not alter mouse cursor shape and visibility."); + ImGui::CheckboxFlags("io.ConfigFlags: NoKeyboard", &io.ConfigFlags, ImGuiConfigFlags_NoKeyboard); + ImGui::SameLine(); HelpMarker("Instruct dear imgui to disable keyboard inputs and interactions."); ImGui::Checkbox("io.ConfigInputTrickleEventQueue", &io.ConfigInputTrickleEventQueue); ImGui::SameLine(); HelpMarker("Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates."); @@ -537,6 +590,7 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("Enable resizing of windows from their edges and from the lower-left corner.\nThis requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback."); ImGui::Checkbox("io.ConfigWindowsMoveFromTitleBarOnly", &io.ConfigWindowsMoveFromTitleBarOnly); ImGui::Checkbox("io.ConfigMacOSXBehaviors", &io.ConfigMacOSXBehaviors); + ImGui::SameLine(); HelpMarker("Swap Cmd<>Ctrl keys, enable various MacOS style behaviors."); ImGui::Text("Also see Style->Rendering for rendering options."); ImGui::SeparatorText("Debug"); @@ -582,8 +636,9 @@ void ImGui::ShowDemoWindow(bool* p_open) IMGUI_DEMO_MARKER("Configuration/Style"); if (ImGui::TreeNode("Style")) { + ImGui::Checkbox("Style Editor", &demo_data.ShowStyleEditor); + ImGui::SameLine(); HelpMarker("The same contents can be accessed in 'Tools->Style Editor' or by calling the ShowStyleEditor() function."); - ImGui::ShowStyleEditor(); ImGui::TreePop(); ImGui::Spacing(); } @@ -630,7 +685,7 @@ void ImGui::ShowDemoWindow(bool* p_open) } // All demo contents - ShowDemoWindowWidgets(); + ShowDemoWindowWidgets(&demo_data); ShowDemoWindowLayout(); ShowDemoWindowPopups(); ShowDemoWindowTables(); @@ -641,9 +696,80 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::End(); } -static void ShowDemoWindowWidgets() +//----------------------------------------------------------------------------- +// [SECTION] ShowDemoWindowMenuBar() +//----------------------------------------------------------------------------- + +static void ShowDemoWindowMenuBar(ImGuiDemoWindowData* demo_data) +{ + IMGUI_DEMO_MARKER("Menu"); + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Menu")) + { + IMGUI_DEMO_MARKER("Menu/File"); + ShowExampleMenuFile(); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Examples")) + { + IMGUI_DEMO_MARKER("Menu/Examples"); + ImGui::MenuItem("Main menu bar", NULL, &demo_data->ShowMainMenuBar); + + ImGui::SeparatorText("Mini apps"); + ImGui::MenuItem("Assets Browser", NULL, &demo_data->ShowAppAssetsBrowser); + ImGui::MenuItem("Console", NULL, &demo_data->ShowAppConsole); + ImGui::MenuItem("Custom rendering", NULL, &demo_data->ShowAppCustomRendering); + ImGui::MenuItem("Documents", NULL, &demo_data->ShowAppDocuments); + ImGui::MenuItem("Dockspace", NULL, &demo_data->ShowAppDockSpace); + ImGui::MenuItem("Log", NULL, &demo_data->ShowAppLog); + ImGui::MenuItem("Property editor", NULL, &demo_data->ShowAppPropertyEditor); + ImGui::MenuItem("Simple layout", NULL, &demo_data->ShowAppLayout); + ImGui::MenuItem("Simple overlay", NULL, &demo_data->ShowAppSimpleOverlay); + + ImGui::SeparatorText("Concepts"); + ImGui::MenuItem("Auto-resizing window", NULL, &demo_data->ShowAppAutoResize); + ImGui::MenuItem("Constrained-resizing window", NULL, &demo_data->ShowAppConstrainedResize); + ImGui::MenuItem("Fullscreen window", NULL, &demo_data->ShowAppFullscreen); + ImGui::MenuItem("Long text display", NULL, &demo_data->ShowAppLongText); + ImGui::MenuItem("Manipulating window titles", NULL, &demo_data->ShowAppWindowTitles); + + ImGui::EndMenu(); + } + //if (ImGui::MenuItem("MenuItem")) {} // You can also use MenuItem() inside a menu bar! + if (ImGui::BeginMenu("Tools")) + { + IMGUI_DEMO_MARKER("Menu/Tools"); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + const bool has_debug_tools = true; +#else + const bool has_debug_tools = false; +#endif + ImGui::MenuItem("Metrics/Debugger", NULL, &demo_data->ShowMetrics, has_debug_tools); + ImGui::MenuItem("Debug Log", NULL, &demo_data->ShowDebugLog, has_debug_tools); + ImGui::MenuItem("ID Stack Tool", NULL, &demo_data->ShowIDStackTool, has_debug_tools); + ImGui::MenuItem("Style Editor", NULL, &demo_data->ShowStyleEditor); + bool is_debugger_present = ImGui::GetIO().ConfigDebugIsDebuggerPresent; + if (ImGui::MenuItem("Item Picker", NULL, false, has_debug_tools && is_debugger_present)) + ImGui::DebugStartItemPicker(); + if (!is_debugger_present) + ImGui::SetItemTooltip("Requires io.ConfigDebugIsDebuggerPresent=true to be set.\n\nWe otherwise disable the menu option to avoid casual users crashing the application.\n\nYou can however always access the Item Picker in Metrics->Tools."); + ImGui::Separator(); + ImGui::MenuItem("About Dear ImGui", NULL, &demo_data->ShowAbout); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] ShowDemoWindowWidgets() +//----------------------------------------------------------------------------- + +static void ShowDemoWindowWidgets(ImGuiDemoWindowData* demo_data) { IMGUI_DEMO_MARKER("Widgets"); + //ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (!ImGui::CollapsingHeader("Widgets")) return; @@ -702,11 +828,11 @@ static void ShowDemoWindowWidgets() IMGUI_DEMO_MARKER("Widgets/Basic/Buttons (Repeating)"); static int counter = 0; float spacing = ImGui::GetStyle().ItemInnerSpacing.x; - ImGui::PushButtonRepeat(true); + ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); if (ImGui::ArrowButton("##left", ImGuiDir_Left)) { counter--; } ImGui::SameLine(0.0f, spacing); if (ImGui::ArrowButton("##right", ImGuiDir_Right)) { counter++; } - ImGui::PopButtonRepeat(); + ImGui::PopItemFlag(); ImGui::SameLine(); ImGui::Text("%d", counter); @@ -763,18 +889,19 @@ static void ShowDemoWindowWidgets() { IMGUI_DEMO_MARKER("Widgets/Basic/DragInt, DragFloat"); - static int i1 = 50, i2 = 42; + static int i1 = 50, i2 = 42, i3 = 128; ImGui::DragInt("drag int", &i1, 1); ImGui::SameLine(); HelpMarker( "Click and drag to edit value.\n" "Hold SHIFT/ALT for faster/slower edit.\n" "Double-click or CTRL+click to input value."); - ImGui::DragInt("drag int 0..100", &i2, 1, 0, 100, "%d%%", ImGuiSliderFlags_AlwaysClamp); + ImGui::DragInt("drag int wrap 100..200", &i3, 1, 100, 200, "%d", ImGuiSliderFlags_WrapAround); static float f1 = 1.00f, f2 = 0.0067f; ImGui::DragFloat("drag float", &f1, 0.005f); ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns"); + //ImGui::DragFloat("drag wrap -1..1", &f3, 0.005f, -1.0f, 1.0f, NULL, ImGuiSliderFlags_WrapAround); } ImGui::SeparatorText("Sliders"); @@ -931,12 +1058,11 @@ static void ShowDemoWindowWidgets() // Using ImGuiHoveredFlags_ForTooltip will pull flags from 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav', // which default value include the ImGuiHoveredFlags_AllowWhenDisabled flag. - // As a result, Set ImGui::BeginDisabled(); ImGui::Button("Disabled item", sz); - ImGui::EndDisabled(); if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) ImGui::SetTooltip("I am a a tooltip for a disabled item."); + ImGui::EndDisabled(); ImGui::TreePop(); } @@ -960,13 +1086,18 @@ static void ShowDemoWindowWidgets() if (i == 0) ImGui::SetNextItemOpen(true, ImGuiCond_Once); - if (ImGui::TreeNode((void*)(intptr_t)i, "Child %d", i)) + // Here we use PushID() to generate a unique base ID, and then the "" used as TreeNode id won't conflict. + // An alternative to using 'PushID() + TreeNode("", ...)' to generate a unique ID is to use 'TreeNode((void*)(intptr_t)i, ...)', + // aka generate a dummy pointer-sized value to be hashed. The demo below uses that technique. Both are fine. + ImGui::PushID(i); + if (ImGui::TreeNode("", "Child %d", i)) { ImGui::Text("blah blah"); ImGui::SameLine(); if (ImGui::SmallButton("button")) {} ImGui::TreePop(); } + ImGui::PopID(); } ImGui::TreePop(); } @@ -984,7 +1115,11 @@ static void ShowDemoWindowWidgets() ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnDoubleClick", &base_flags, ImGuiTreeNodeFlags_OpenOnDoubleClick); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAvailWidth", &base_flags, ImGuiTreeNodeFlags_SpanAvailWidth); ImGui::SameLine(); HelpMarker("Extend hit area to all available width instead of allowing more items to be laid out after the node."); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanFullWidth", &base_flags, ImGuiTreeNodeFlags_SpanFullWidth); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanTextWidth", &base_flags, ImGuiTreeNodeFlags_SpanTextWidth); ImGui::SameLine(); HelpMarker("Reduce hit area to the text label and a bit of margin."); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAllColumns", &base_flags, ImGuiTreeNodeFlags_SpanAllColumns); ImGui::SameLine(); HelpMarker("For use in Tables only."); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_AllowOverlap", &base_flags, ImGuiTreeNodeFlags_AllowOverlap); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_Framed", &base_flags, ImGuiTreeNodeFlags_Framed); ImGui::SameLine(); HelpMarker("Draw frame with background (e.g. for CollapsingHeader)"); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_NavLeftJumpsBackHere", &base_flags, ImGuiTreeNodeFlags_NavLeftJumpsBackHere); ImGui::Checkbox("Align label with current X position", &align_label_with_current_x_position); ImGui::Checkbox("Test tree node as drag source", &test_drag_and_drop); ImGui::Text("Hello!"); @@ -1017,9 +1152,17 @@ static void ShowDemoWindowWidgets() ImGui::Text("This is a drag and drop source"); ImGui::EndDragDropSource(); } + if (i == 2 && (base_flags & ImGuiTreeNodeFlags_SpanTextWidth)) + { + // Item 2 has an additional inline button to help demonstrate SpanTextWidth. + ImGui::SameLine(); + if (ImGui::SmallButton("button")) {} + } if (node_open) { ImGui::BulletText("Blah blah\nBlah Blah"); + ImGui::SameLine(); + ImGui::SmallButton("Button"); ImGui::TreePop(); } } @@ -1281,18 +1424,18 @@ static void ShowDemoWindowWidgets() // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively // stored in the object itself, etc.) const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" }; - static int item_current_idx = 0; // Here we store our selection data as an index. + static int item_selected_idx = 0; // Here we store our selection data as an index. // Pass in the preview value visible before opening the combo (it could technically be different contents or not pulled from items[]) - const char* combo_preview_value = items[item_current_idx]; + const char* combo_preview_value = items[item_selected_idx]; if (ImGui::BeginCombo("combo 1", combo_preview_value, flags)) { for (int n = 0; n < IM_ARRAYSIZE(items); n++) { - const bool is_selected = (item_current_idx == n); + const bool is_selected = (item_selected_idx == n); if (ImGui::Selectable(items[n], is_selected)) - item_current_idx = n; + item_selected_idx = n; // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) if (is_selected) @@ -1334,14 +1477,22 @@ static void ShowDemoWindowWidgets() // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively // stored in the object itself, etc.) const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" }; - static int item_current_idx = 0; // Here we store our selection data as an index. + static int item_selected_idx = 0; // Here we store our selected data as an index. + + static bool item_highlight = false; + int item_highlighted_idx = -1; // Here we store our highlighted data as an index. + ImGui::Checkbox("Highlight hovered item in second listbox", &item_highlight); + if (ImGui::BeginListBox("listbox 1")) { for (int n = 0; n < IM_ARRAYSIZE(items); n++) { - const bool is_selected = (item_current_idx == n); + const bool is_selected = (item_selected_idx == n); if (ImGui::Selectable(items[n], is_selected)) - item_current_idx = n; + item_selected_idx = n; + + if (item_highlight && ImGui::IsItemHovered()) + item_highlighted_idx = n; // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) if (is_selected) @@ -1349,6 +1500,7 @@ static void ShowDemoWindowWidgets() } ImGui::EndListBox(); } + ImGui::SameLine(); HelpMarker("Here we are sharing selection state between both boxes."); // Custom size: use all width, 5 items tall ImGui::Text("Full-width:"); @@ -1356,9 +1508,10 @@ static void ShowDemoWindowWidgets() { for (int n = 0; n < IM_ARRAYSIZE(items); n++) { - const bool is_selected = (item_current_idx == n); - if (ImGui::Selectable(items[n], is_selected)) - item_current_idx = n; + bool is_selected = (item_selected_idx == n); + ImGuiSelectableFlags flags = (item_highlighted_idx == n) ? ImGuiSelectableFlags_Highlight : 0; + if (ImGui::Selectable(items[n], is_selected, flags)) + item_selected_idx = n; // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) if (is_selected) @@ -1371,6 +1524,7 @@ static void ShowDemoWindowWidgets() } IMGUI_DEMO_MARKER("Widgets/Selectables"); + //ImGui::SetNextItemOpen(true, ImGuiCond_Once); if (ImGui::TreeNode("Selectables")) { // Selectable() has 2 overloads: @@ -1392,37 +1546,6 @@ static void ShowDemoWindowWidgets() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Selectables/Single Selection"); - if (ImGui::TreeNode("Selection State: Single Selection")) - { - static int selected = -1; - for (int n = 0; n < 5; n++) - { - char buf[32]; - sprintf(buf, "Object %d", n); - if (ImGui::Selectable(buf, selected == n)) - selected = n; - } - ImGui::TreePop(); - } - IMGUI_DEMO_MARKER("Widgets/Selectables/Multiple Selection"); - if (ImGui::TreeNode("Selection State: Multiple Selection")) - { - HelpMarker("Hold CTRL and click to select multiple items."); - static bool selection[5] = { false, false, false, false, false }; - for (int n = 0; n < 5; n++) - { - char buf[32]; - sprintf(buf, "Object %d", n); - if (ImGui::Selectable(buf, selection[n])) - { - if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held - memset(selection, 0, sizeof(selection)); - selection[n] ^= 1; - } - } - ImGui::TreePop(); - } IMGUI_DEMO_MARKER("Widgets/Selectables/Rendering more items on the same line"); if (ImGui::TreeNode("Rendering more items on the same line")) { @@ -1435,8 +1558,8 @@ static void ShowDemoWindowWidgets() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Widgets/Selectables/In columns"); - if (ImGui::TreeNode("In columns")) + IMGUI_DEMO_MARKER("Widgets/Selectables/In Tables"); + if (ImGui::TreeNode("In Tables")) { static bool selected[10] = {}; @@ -1530,6 +1653,8 @@ static void ShowDemoWindowWidgets() ImGui::TreePop(); } + ShowDemoWindowMultiSelect(demo_data); + // To wire InputText() with std::string or any other custom string type, // see the "Text Input > Resize Callback" section of this demo, and the misc/cpp/imgui_stdlib.h file. IMGUI_DEMO_MARKER("Widgets/Text Input"); @@ -1765,6 +1890,7 @@ static void ShowDemoWindowWidgets() ImGui::CheckboxFlags("ImGuiTabBarFlags_AutoSelectNewTabs", &tab_bar_flags, ImGuiTabBarFlags_AutoSelectNewTabs); ImGui::CheckboxFlags("ImGuiTabBarFlags_TabListPopupButton", &tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton); ImGui::CheckboxFlags("ImGuiTabBarFlags_NoCloseWithMiddleMouseButton", &tab_bar_flags, ImGuiTabBarFlags_NoCloseWithMiddleMouseButton); + ImGui::CheckboxFlags("ImGuiTabBarFlags_DrawSelectedOverline", &tab_bar_flags, ImGuiTabBarFlags_DrawSelectedOverline); if ((tab_bar_flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0) tab_bar_flags |= ImGuiTabBarFlags_FittingPolicyDefault_; if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown)) @@ -1773,11 +1899,13 @@ static void ShowDemoWindowWidgets() tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll); // Tab Bar + ImGui::AlignTextToFramePadding(); + ImGui::Text("Opened:"); const char* names[4] = { "Artichoke", "Beetroot", "Celery", "Daikon" }; static bool opened[4] = { true, true, true, true }; // Persistent user state for (int n = 0; n < IM_ARRAYSIZE(opened); n++) { - if (n > 0) { ImGui::SameLine(); } + ImGui::SameLine(); ImGui::Checkbox(names[n], &opened[n]); } @@ -1879,10 +2007,10 @@ static void ShowDemoWindowWidgets() ImGui::Checkbox("Animate", &animate); // Plot as lines and plot as histogram - IMGUI_DEMO_MARKER("Widgets/Plotting/PlotLines, PlotHistogram"); static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0, 80.0f)); + //ImGui::SameLine(); HelpMarker("Consider using ImPlot instead!"); // Fill an array of contiguous float values to plot // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float @@ -1932,15 +2060,17 @@ static void ShowDemoWindowWidgets() ImGui::PlotHistogram("Histogram", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); ImGui::Separator(); + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Progress Bars"); + if (ImGui::TreeNode("Progress Bars")) + { // Animate a simple progress bar - IMGUI_DEMO_MARKER("Widgets/Plotting/ProgressBar"); static float progress = 0.0f, progress_dir = 1.0f; - if (animate) - { - progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; - if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } - if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } - } + progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; + if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } + if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } // Typically we would use ImVec2(-1.0f,0.0f) or ImVec2(-FLT_MIN,0.0f) to use all available width, // or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth. @@ -1952,6 +2082,13 @@ static void ShowDemoWindowWidgets() char buf[32]; sprintf(buf, "%d/%d", (int)(progress_saturated * 1753), 1753); ImGui::ProgressBar(progress, ImVec2(0.f, 0.f), buf); + + // Pass an animated negative value, e.g. -1.0f * (float)ImGui::GetTime() is the recommended value. + // Adjust the factor if you want to adjust the animation speed. + ImGui::ProgressBar(-1.0f * (float)ImGui::GetTime(), ImVec2(0.0f, 0.0f), "Searching.."); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::Text("Indeterminate"); + ImGui::TreePop(); } @@ -2161,6 +2298,8 @@ static void ShowDemoWindowWidgets() ImGui::SameLine(); HelpMarker("Disable rounding underlying value to match precision of the format string (e.g. %.3f values are rounded to those 3 digits)."); ImGui::CheckboxFlags("ImGuiSliderFlags_NoInput", &flags, ImGuiSliderFlags_NoInput); ImGui::SameLine(); HelpMarker("Disable CTRL+Click or Enter key allowing to input text directly into the widget."); + ImGui::CheckboxFlags("ImGuiSliderFlags_WrapAround", &flags, ImGuiSliderFlags_WrapAround); + ImGui::SameLine(); HelpMarker("Enable wrapping around from max to min and from min to max (only supported by DragXXX() functions)"); // Drags static float drag_f = 0.5f; @@ -2175,9 +2314,10 @@ static void ShowDemoWindowWidgets() // Sliders static float slider_f = 0.5f; static int slider_i = 50; + const ImGuiSliderFlags flags_for_sliders = flags & ~ImGuiSliderFlags_WrapAround; ImGui::Text("Underlying float value: %f", slider_f); - ImGui::SliderFloat("SliderFloat (0 -> 1)", &slider_f, 0.0f, 1.0f, "%.3f", flags); - ImGui::SliderInt("SliderInt (0 -> 100)", &slider_i, 0, 100, "%d", flags); + ImGui::SliderFloat("SliderFloat (0 -> 1)", &slider_f, 0.0f, 1.0f, "%.3f", flags_for_sliders); + ImGui::SliderInt("SliderInt (0 -> 100)", &slider_i, 0, 100, "%d", flags_for_sliders); ImGui::TreePop(); } @@ -2299,20 +2439,24 @@ static void ShowDemoWindowWidgets() IMGUI_DEMO_MARKER("Widgets/Data Types/Inputs"); static bool inputs_step = true; + static ImGuiInputTextFlags flags = ImGuiInputTextFlags_None; ImGui::SeparatorText("Inputs"); ImGui::Checkbox("Show step buttons", &inputs_step); - ImGui::InputScalar("input s8", ImGuiDataType_S8, &s8_v, inputs_step ? &s8_one : NULL, NULL, "%d"); - ImGui::InputScalar("input u8", ImGuiDataType_U8, &u8_v, inputs_step ? &u8_one : NULL, NULL, "%u"); - ImGui::InputScalar("input s16", ImGuiDataType_S16, &s16_v, inputs_step ? &s16_one : NULL, NULL, "%d"); - ImGui::InputScalar("input u16", ImGuiDataType_U16, &u16_v, inputs_step ? &u16_one : NULL, NULL, "%u"); - ImGui::InputScalar("input s32", ImGuiDataType_S32, &s32_v, inputs_step ? &s32_one : NULL, NULL, "%d"); - ImGui::InputScalar("input s32 hex", ImGuiDataType_S32, &s32_v, inputs_step ? &s32_one : NULL, NULL, "%04X"); - ImGui::InputScalar("input u32", ImGuiDataType_U32, &u32_v, inputs_step ? &u32_one : NULL, NULL, "%u"); - ImGui::InputScalar("input u32 hex", ImGuiDataType_U32, &u32_v, inputs_step ? &u32_one : NULL, NULL, "%08X"); - ImGui::InputScalar("input s64", ImGuiDataType_S64, &s64_v, inputs_step ? &s64_one : NULL); - ImGui::InputScalar("input u64", ImGuiDataType_U64, &u64_v, inputs_step ? &u64_one : NULL); - ImGui::InputScalar("input float", ImGuiDataType_Float, &f32_v, inputs_step ? &f32_one : NULL); - ImGui::InputScalar("input double", ImGuiDataType_Double, &f64_v, inputs_step ? &f64_one : NULL); + ImGui::CheckboxFlags("ImGuiInputTextFlags_ReadOnly", &flags, ImGuiInputTextFlags_ReadOnly); + ImGui::CheckboxFlags("ImGuiInputTextFlags_ParseEmptyRefVal", &flags, ImGuiInputTextFlags_ParseEmptyRefVal); + ImGui::CheckboxFlags("ImGuiInputTextFlags_DisplayEmptyRefVal", &flags, ImGuiInputTextFlags_DisplayEmptyRefVal); + ImGui::InputScalar("input s8", ImGuiDataType_S8, &s8_v, inputs_step ? &s8_one : NULL, NULL, "%d", flags); + ImGui::InputScalar("input u8", ImGuiDataType_U8, &u8_v, inputs_step ? &u8_one : NULL, NULL, "%u", flags); + ImGui::InputScalar("input s16", ImGuiDataType_S16, &s16_v, inputs_step ? &s16_one : NULL, NULL, "%d", flags); + ImGui::InputScalar("input u16", ImGuiDataType_U16, &u16_v, inputs_step ? &u16_one : NULL, NULL, "%u", flags); + ImGui::InputScalar("input s32", ImGuiDataType_S32, &s32_v, inputs_step ? &s32_one : NULL, NULL, "%d", flags); + ImGui::InputScalar("input s32 hex", ImGuiDataType_S32, &s32_v, inputs_step ? &s32_one : NULL, NULL, "%04X", flags); + ImGui::InputScalar("input u32", ImGuiDataType_U32, &u32_v, inputs_step ? &u32_one : NULL, NULL, "%u", flags); + ImGui::InputScalar("input u32 hex", ImGuiDataType_U32, &u32_v, inputs_step ? &u32_one : NULL, NULL, "%08X", flags); + ImGui::InputScalar("input s64", ImGuiDataType_S64, &s64_v, inputs_step ? &s64_one : NULL, NULL, NULL, flags); + ImGui::InputScalar("input u64", ImGuiDataType_U64, &u64_v, inputs_step ? &u64_one : NULL, NULL, NULL, flags); + ImGui::InputScalar("input float", ImGuiDataType_Float, &f32_v, inputs_step ? &f32_one : NULL, NULL, NULL, flags); + ImGui::InputScalar("input double", ImGuiDataType_Double, &f64_v, inputs_step ? &f64_one : NULL, NULL, NULL, flags); ImGui::TreePop(); } @@ -2542,9 +2686,7 @@ static void ShowDemoWindowWidgets() { IM_UNUSED(payload); ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed); - ImGui::BeginTooltip(); - ImGui::Text("Cannot drop here!"); - ImGui::EndTooltip(); + ImGui::SetTooltip("Cannot drop here!"); } ImGui::EndDragDropTarget(); } @@ -2586,7 +2728,7 @@ static void ShowDemoWindowWidgets() ImGui::BeginDisabled(true); if (item_type == 0) { ImGui::Text("ITEM: Text"); } // Testing text items with no identifier/interaction if (item_type == 1) { ret = ImGui::Button("ITEM: Button"); } // Testing button - if (item_type == 2) { ImGui::PushButtonRepeat(true); ret = ImGui::Button("ITEM: Button"); ImGui::PopButtonRepeat(); } // Testing button (with repeater) + if (item_type == 2) { ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); ret = ImGui::Button("ITEM: Button"); ImGui::PopItemFlag(); } // Testing button (with repeater) if (item_type == 3) { ret = ImGui::Checkbox("ITEM: Checkbox", &b); } // Testing checkbox if (item_type == 4) { ret = ImGui::SliderFloat("ITEM: SliderFloat", &col4f[0], 0.0f, 1.0f); } // Testing basic item if (item_type == 5) { ret = ImGui::InputText("ITEM: InputText", &str[0], IM_ARRAYSIZE(str)); } // Testing input text (which handles tabbing) @@ -2804,6 +2946,977 @@ static void ShowDemoWindowWidgets() } } +static const char* ExampleNames[] = +{ + "Artichoke", "Arugula", "Asparagus", "Avocado", "Bamboo Shoots", "Bean Sprouts", "Beans", "Beet", "Belgian Endive", "Bell Pepper", + "Bitter Gourd", "Bok Choy", "Broccoli", "Brussels Sprouts", "Burdock Root", "Cabbage", "Calabash", "Capers", "Carrot", "Cassava", + "Cauliflower", "Celery", "Celery Root", "Celcuce", "Chayote", "Chinese Broccoli", "Corn", "Cucumber" +}; + +// Extra functions to add deletion support to ImGuiSelectionBasicStorage +struct ExampleSelectionWithDeletion : ImGuiSelectionBasicStorage +{ + // Find which item should be Focused after deletion. + // Call _before_ item submission. Retunr an index in the before-deletion item list, your item loop should call SetKeyboardFocusHere() on it. + // The subsequent ApplyDeletionPostLoop() code will use it to apply Selection. + // - We cannot provide this logic in core Dear ImGui because we don't have access to selection data. + // - We don't actually manipulate the ImVector<> here, only in ApplyDeletionPostLoop(), but using similar API for consistency and flexibility. + // - Important: Deletion only works if the underlying ImGuiID for your items are stable: aka not depend on their index, but on e.g. item id/ptr. + // FIXME-MULTISELECT: Doesn't take account of the possibility focus target will be moved during deletion. Need refocus or scroll offset. + int ApplyDeletionPreLoop(ImGuiMultiSelectIO* ms_io, int items_count) + { + if (Size == 0) + return -1; + + // If focused item is not selected... + const int focused_idx = (int)ms_io->NavIdItem; // Index of currently focused item + if (ms_io->NavIdSelected == false) // This is merely a shortcut, == Contains(adapter->IndexToStorage(items, focused_idx)) + { + ms_io->RangeSrcReset = true; // Request to recover RangeSrc from NavId next frame. Would be ok to reset even when NavIdSelected==true, but it would take an extra frame to recover RangeSrc when deleting a selected item. + return focused_idx; // Request to focus same item after deletion. + } + + // If focused item is selected: land on first unselected item after focused item. + for (int idx = focused_idx + 1; idx < items_count; idx++) + if (!Contains(GetStorageIdFromIndex(idx))) + return idx; + + // If focused item is selected: otherwise return last unselected item before focused item. + for (int idx = IM_MIN(focused_idx, items_count) - 1; idx >= 0; idx--) + if (!Contains(GetStorageIdFromIndex(idx))) + return idx; + + return -1; + } + + // Rewrite item list (delete items) + update selection. + // - Call after EndMultiSelect() + // - We cannot provide this logic in core Dear ImGui because we don't have access to your items, nor to selection data. + template + void ApplyDeletionPostLoop(ImGuiMultiSelectIO* ms_io, ImVector& items, int item_curr_idx_to_select) + { + // Rewrite item list (delete items) + convert old selection index (before deletion) to new selection index (after selection). + // If NavId was not part of selection, we will stay on same item. + ImVector new_items; + new_items.reserve(items.Size - Size); + int item_next_idx_to_select = -1; + for (int idx = 0; idx < items.Size; idx++) + { + if (!Contains(GetStorageIdFromIndex(idx))) + new_items.push_back(items[idx]); + if (item_curr_idx_to_select == idx) + item_next_idx_to_select = new_items.Size - 1; + } + items.swap(new_items); + + // Update selection + Clear(); + if (item_next_idx_to_select != -1 && ms_io->NavIdSelected) + SetItemSelected(GetStorageIdFromIndex(item_next_idx_to_select), true); + } +}; + +// Example: Implement dual list box storage and interface +struct ExampleDualListBox +{ + ImVector Items[2]; // ID is index into ExampleName[] + ImGuiSelectionBasicStorage Selections[2]; // Store ExampleItemId into selection + bool OptKeepSorted = true; + + void MoveAll(int src, int dst) + { + IM_ASSERT((src == 0 && dst == 1) || (src == 1 && dst == 0)); + for (ImGuiID item_id : Items[src]) + Items[dst].push_back(item_id); + Items[src].clear(); + SortItems(dst); + Selections[src].Swap(Selections[dst]); + Selections[src].Clear(); + } + void MoveSelected(int src, int dst) + { + for (int src_n = 0; src_n < Items[src].Size; src_n++) + { + ImGuiID item_id = Items[src][src_n]; + if (!Selections[src].Contains(item_id)) + continue; + Items[src].erase(&Items[src][src_n]); // FIXME-OPT: Could be implemented more optimally (rebuild src items and swap) + Items[dst].push_back(item_id); + src_n--; + } + if (OptKeepSorted) + SortItems(dst); + Selections[src].Swap(Selections[dst]); + Selections[src].Clear(); + } + void ApplySelectionRequests(ImGuiMultiSelectIO* ms_io, int side) + { + // In this example we store item id in selection (instead of item index) + Selections[side].UserData = Items[side].Data; + Selections[side].AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { ImGuiID* items = (ImGuiID*)self->UserData; return items[idx]; }; + Selections[side].ApplyRequests(ms_io); + } + static int IMGUI_CDECL CompareItemsByValue(const void* lhs, const void* rhs) + { + const int* a = (const int*)lhs; + const int* b = (const int*)rhs; + return (*a - *b) > 0 ? +1 : -1; + } + void SortItems(int n) + { + qsort(Items[n].Data, (size_t)Items[n].Size, sizeof(Items[n][0]), CompareItemsByValue); + } + void Show() + { + //ImGui::Checkbox("Sorted", &OptKeepSorted); + if (ImGui::BeginTable("split", 3, ImGuiTableFlags_None)) + { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); // Left side + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); // Buttons + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); // Right side + ImGui::TableNextRow(); + + int request_move_selected = -1; + int request_move_all = -1; + float child_height_0 = 0.0f; + for (int side = 0; side < 2; side++) + { + // FIXME-MULTISELECT: Dual List Box: Add context menus + // FIXME-NAV: Using ImGuiWindowFlags_NavFlattened exhibit many issues. + ImVector& items = Items[side]; + ImGuiSelectionBasicStorage& selection = Selections[side]; + + ImGui::TableSetColumnIndex((side == 0) ? 0 : 2); + ImGui::Text("%s (%d)", (side == 0) ? "Available" : "Basket", items.Size); + + // Submit scrolling range to avoid glitches on moving/deletion + const float items_height = ImGui::GetTextLineHeightWithSpacing(); + ImGui::SetNextWindowContentSize(ImVec2(0.0f, items.Size * items_height)); + + bool child_visible; + if (side == 0) + { + // Left child is resizable + ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, ImGui::GetFrameHeightWithSpacing() * 4), ImVec2(FLT_MAX, FLT_MAX)); + child_visible = ImGui::BeginChild("0", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY); + child_height_0 = ImGui::GetWindowSize().y; + } + else + { + // Right child use same height as left one + child_visible = ImGui::BeginChild("1", ImVec2(-FLT_MIN, child_height_0), ImGuiChildFlags_FrameStyle); + } + if (child_visible) + { + ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_None; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size); + ApplySelectionRequests(ms_io, side); + + for (int item_n = 0; item_n < items.Size; item_n++) + { + ImGuiID item_id = items[item_n]; + bool item_is_selected = selection.Contains(item_id); + ImGui::SetNextItemSelectionUserData(item_n); + ImGui::Selectable(ExampleNames[item_id], item_is_selected, ImGuiSelectableFlags_AllowDoubleClick); + if (ImGui::IsItemFocused()) + { + // FIXME-MULTISELECT: Dual List Box: Transfer focus + if (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter)) + request_move_selected = side; + if (ImGui::IsMouseDoubleClicked(0)) // FIXME-MULTISELECT: Double-click on multi-selection? + request_move_selected = side; + } + } + + ms_io = ImGui::EndMultiSelect(); + ApplySelectionRequests(ms_io, side); + } + ImGui::EndChild(); + } + + // Buttons columns + ImGui::TableSetColumnIndex(1); + ImGui::NewLine(); + //ImVec2 button_sz = { ImGui::CalcTextSize(">>").x + ImGui::GetStyle().FramePadding.x * 2.0f, ImGui::GetFrameHeight() + padding.y * 2.0f }; + ImVec2 button_sz = { ImGui::GetFrameHeight(), ImGui::GetFrameHeight() }; + + // (Using BeginDisabled()/EndDisabled() works but feels distracting given how it is currently visualized) + if (ImGui::Button(">>", button_sz)) + request_move_all = 0; + if (ImGui::Button(">", button_sz)) + request_move_selected = 0; + if (ImGui::Button("<", button_sz)) + request_move_selected = 1; + if (ImGui::Button("<<", button_sz)) + request_move_all = 1; + + // Process requests + if (request_move_all != -1) + MoveAll(request_move_all, request_move_all ^ 1); + if (request_move_selected != -1) + MoveSelected(request_move_selected, request_move_selected ^ 1); + + // FIXME-MULTISELECT: Support action from outside + /* + if (OptKeepSorted == false) + { + ImGui::NewLine(); + if (ImGui::ArrowButton("MoveUp", ImGuiDir_Up)) {} + if (ImGui::ArrowButton("MoveDown", ImGuiDir_Down)) {} + } + */ + + ImGui::EndTable(); + } + } +}; + +//----------------------------------------------------------------------------- +// [SECTION] ShowDemoWindowMultiSelect() +//----------------------------------------------------------------------------- +// Multi-selection demos +// Also read: https://github.com/ocornut/imgui/wiki/Multi-Select +//----------------------------------------------------------------------------- + +static void ShowDemoWindowMultiSelect(ImGuiDemoWindowData* demo_data) +{ + IMGUI_DEMO_MARKER("Widgets/Selection State & Multi-Select"); + if (ImGui::TreeNode("Selection State & Multi-Select")) + { + HelpMarker("Selections can be built using Selectable(), TreeNode() or other widgets. Selection state is owned by application code/data."); + + // Without any fancy API: manage single-selection yourself. + IMGUI_DEMO_MARKER("Widgets/Selection State/Single-Select"); + if (ImGui::TreeNode("Single-Select")) + { + static int selected = -1; + for (int n = 0; n < 5; n++) + { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selected == n)) + selected = n; + } + ImGui::TreePop(); + } + + // Demonstrate implementation a most-basic form of multi-selection manually + // This doesn't support the SHIFT modifier which requires BeginMultiSelect()! + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (manual/simplified, without BeginMultiSelect)"); + if (ImGui::TreeNode("Multi-Select (manual/simplified, without BeginMultiSelect)")) + { + HelpMarker("Hold CTRL and click to select multiple items."); + static bool selection[5] = { false, false, false, false, false }; + for (int n = 0; n < 5; n++) + { + char buf[32]; + sprintf(buf, "Object %d", n); + if (ImGui::Selectable(buf, selection[n])) + { + if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held + memset(selection, 0, sizeof(selection)); + selection[n] ^= 1; // Toggle current item + } + } + ImGui::TreePop(); + } + + // Demonstrate handling proper multi-selection using the BeginMultiSelect/EndMultiSelect API. + // SHIFT+Click w/ CTRL and other standard features are supported. + // We use the ImGuiSelectionBasicStorage helper which you may freely reimplement. + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select"); + if (ImGui::TreeNode("Multi-Select")) + { + ImGui::Text("Supported features:"); + ImGui::BulletText("Keyboard navigation (arrows, page up/down, home/end, space)."); + ImGui::BulletText("Ctrl modifier to preserve and toggle selection."); + ImGui::BulletText("Shift modifier for range selection."); + ImGui::BulletText("CTRL+A to select all."); + ImGui::BulletText("Escape to clear selection."); + ImGui::BulletText("Click and drag to box-select."); + ImGui::Text("Tip: Use 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen."); + + // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection + const int ITEMS_COUNT = 50; + static ImGuiSelectionBasicStorage selection; + ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT); + + // The BeginChild() has no purpose for selection logic, other that offering a scrolling region. + if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) + { + ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT); + selection.ApplyRequests(ms_io); + + for (int n = 0; n < ITEMS_COUNT; n++) + { + char label[64]; + sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]); + bool item_is_selected = selection.Contains((ImGuiID)n); + ImGui::SetNextItemSelectionUserData(n); + ImGui::Selectable(label, item_is_selected); + } + + ms_io = ImGui::EndMultiSelect(); + selection.ApplyRequests(ms_io); + } + ImGui::EndChild(); + ImGui::TreePop(); + } + + // Demonstrate using the clipper with BeginMultiSelect()/EndMultiSelect() + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (with clipper)"); + if (ImGui::TreeNode("Multi-Select (with clipper)")) + { + // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection + static ImGuiSelectionBasicStorage selection; + + ImGui::Text("Added features:"); + ImGui::BulletText("Using ImGuiListClipper."); + + const int ITEMS_COUNT = 10000; + ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT); + if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) + { + ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT); + selection.ApplyRequests(ms_io); + + ImGuiListClipper clipper; + clipper.Begin(ITEMS_COUNT); + if (ms_io->RangeSrcItem != -1) + clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped. + while (clipper.Step()) + { + for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++) + { + char label[64]; + sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]); + bool item_is_selected = selection.Contains((ImGuiID)n); + ImGui::SetNextItemSelectionUserData(n); + ImGui::Selectable(label, item_is_selected); + } + } + + ms_io = ImGui::EndMultiSelect(); + selection.ApplyRequests(ms_io); + } + ImGui::EndChild(); + ImGui::TreePop(); + } + + // Demonstrate dynamic item list + deletion support using the BeginMultiSelect/EndMultiSelect API. + // In order to support Deletion without any glitches you need to: + // - (1) If items are submitted in their own scrolling area, submit contents size SetNextWindowContentSize() ahead of time to prevent one-frame readjustment of scrolling. + // - (2) Items needs to have persistent ID Stack identifier = ID needs to not depends on their index. PushID(index) = KO. PushID(item_id) = OK. This is in order to focus items reliably after a selection. + // - (3) BeginXXXX process + // - (4) Focus process + // - (5) EndXXXX process + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (with deletion)"); + if (ImGui::TreeNode("Multi-Select (with deletion)")) + { + // Storing items data separately from selection data. + // (you may decide to store selection data inside your item (aka intrusive storage) if you don't need multiple views over same items) + // Use a custom selection.Adapter: store item identifier in Selection (instead of index) + static ImVector items; + static ExampleSelectionWithDeletion selection; + selection.UserData = (void*)&items; + selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { ImVector* p_items = (ImVector*)self->UserData; return (*p_items)[idx]; }; // Index -> ID + + ImGui::Text("Added features:"); + ImGui::BulletText("Dynamic list with Delete key support."); + ImGui::Text("Selection size: %d/%d", selection.Size, items.Size); + + // Initialize default list with 50 items + button to add/remove items. + static ImGuiID items_next_id = 0; + if (items_next_id == 0) + for (ImGuiID n = 0; n < 50; n++) + items.push_back(items_next_id++); + if (ImGui::SmallButton("Add 20 items")) { for (int n = 0; n < 20; n++) { items.push_back(items_next_id++); } } + ImGui::SameLine(); + if (ImGui::SmallButton("Remove 20 items")) { for (int n = IM_MIN(20, items.Size); n > 0; n--) { selection.SetItemSelected(items.back(), false); items.pop_back(); } } + + // (1) Extra to support deletion: Submit scrolling range to avoid glitches on deletion + const float items_height = ImGui::GetTextLineHeightWithSpacing(); + ImGui::SetNextWindowContentSize(ImVec2(0.0f, items.Size * items_height)); + + if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) + { + ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size); + selection.ApplyRequests(ms_io); + + const bool want_delete = ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (selection.Size > 0); + const int item_curr_idx_to_focus = want_delete ? selection.ApplyDeletionPreLoop(ms_io, items.Size) : -1; + + for (int n = 0; n < items.Size; n++) + { + const ImGuiID item_id = items[n]; + char label[64]; + sprintf(label, "Object %05u: %s", item_id, ExampleNames[item_id % IM_ARRAYSIZE(ExampleNames)]); + + bool item_is_selected = selection.Contains(item_id); + ImGui::SetNextItemSelectionUserData(n); + ImGui::Selectable(label, item_is_selected); + if (item_curr_idx_to_focus == n) + ImGui::SetKeyboardFocusHere(-1); + } + + // Apply multi-select requests + ms_io = ImGui::EndMultiSelect(); + selection.ApplyRequests(ms_io); + if (want_delete) + selection.ApplyDeletionPostLoop(ms_io, items, item_curr_idx_to_focus); + } + ImGui::EndChild(); + ImGui::TreePop(); + } + + // Implement a Dual List Box (#6648) + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (dual list box)"); + if (ImGui::TreeNode("Multi-Select (dual list box)")) + { + // Init default state + static ExampleDualListBox dlb; + if (dlb.Items[0].Size == 0 && dlb.Items[1].Size == 0) + for (int item_id = 0; item_id < IM_ARRAYSIZE(ExampleNames); item_id++) + dlb.Items[0].push_back((ImGuiID)item_id); + + // Show + dlb.Show(); + + ImGui::TreePop(); + } + + // Demonstrate using the clipper with BeginMultiSelect()/EndMultiSelect() + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (in a table)"); + if (ImGui::TreeNode("Multi-Select (in a table)")) + { + static ImGuiSelectionBasicStorage selection; + + const int ITEMS_COUNT = 10000; + ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT); + if (ImGui::BeginTable("##Basket", 2, ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter)) + { + ImGui::TableSetupColumn("Object"); + ImGui::TableSetupColumn("Action"); + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableHeadersRow(); + + ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT); + selection.ApplyRequests(ms_io); + + ImGuiListClipper clipper; + clipper.Begin(ITEMS_COUNT); + if (ms_io->RangeSrcItem != -1) + clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped. + while (clipper.Step()) + { + for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + char label[64]; + sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]); + bool item_is_selected = selection.Contains((ImGuiID)n); + ImGui::SetNextItemSelectionUserData(n); + ImGui::Selectable(label, item_is_selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap); + ImGui::TableNextColumn(); + ImGui::SmallButton("hello"); + } + } + + ms_io = ImGui::EndMultiSelect(); + selection.ApplyRequests(ms_io); + ImGui::EndTable(); + } + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (checkboxes)"); + if (ImGui::TreeNode("Multi-Select (checkboxes)")) + { + ImGui::Text("In a list of checkboxes (not selectable):"); + ImGui::BulletText("Using _NoAutoSelect + _NoAutoClear flags."); + ImGui::BulletText("Shift+Click to check multiple boxes."); + ImGui::BulletText("Shift+Keyboard to copy current value to other boxes."); + + // If you have an array of checkboxes, you may want to use NoAutoSelect + NoAutoClear and the ImGuiSelectionExternalStorage helper. + static bool items[20] = {}; + static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_NoAutoSelect | ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_ClearOnEscape; + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoSelect", &flags, ImGuiMultiSelectFlags_NoAutoSelect); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoClear", &flags, ImGuiMultiSelectFlags_NoAutoClear); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect2d", &flags, ImGuiMultiSelectFlags_BoxSelect2d); // Cannot use ImGuiMultiSelectFlags_BoxSelect1d as checkboxes are varying width. + + if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeY)) + { + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, -1, IM_ARRAYSIZE(items)); + ImGuiSelectionExternalStorage storage_wrapper; + storage_wrapper.UserData = (void*)items; + storage_wrapper.AdapterSetItemSelected = [](ImGuiSelectionExternalStorage* self, int n, bool selected) { bool* array = (bool*)self->UserData; array[n] = selected; }; + storage_wrapper.ApplyRequests(ms_io); + for (int n = 0; n < 20; n++) + { + char label[32]; + sprintf(label, "Item %d", n); + ImGui::SetNextItemSelectionUserData(n); + ImGui::Checkbox(label, &items[n]); + } + ms_io = ImGui::EndMultiSelect(); + storage_wrapper.ApplyRequests(ms_io); + } + ImGui::EndChild(); + + ImGui::TreePop(); + } + + // Demonstrate individual selection scopes in same window + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (multiple scopes)"); + if (ImGui::TreeNode("Multi-Select (multiple scopes)")) + { + // Use default select: Pass index to SetNextItemSelectionUserData(), store index in Selection + const int SCOPES_COUNT = 3; + const int ITEMS_COUNT = 8; // Per scope + static ImGuiSelectionBasicStorage selections_data[SCOPES_COUNT]; + + // Use ImGuiMultiSelectFlags_ScopeRect to not affect other selections in same window. + static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ScopeRect | ImGuiMultiSelectFlags_ClearOnEscape;// | ImGuiMultiSelectFlags_ClearOnClickVoid; + if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeWindow", &flags, ImGuiMultiSelectFlags_ScopeWindow) && (flags & ImGuiMultiSelectFlags_ScopeWindow)) + flags &= ~ImGuiMultiSelectFlags_ScopeRect; + if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeRect", &flags, ImGuiMultiSelectFlags_ScopeRect) && (flags & ImGuiMultiSelectFlags_ScopeRect)) + flags &= ~ImGuiMultiSelectFlags_ScopeWindow; + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnClickVoid", &flags, ImGuiMultiSelectFlags_ClearOnClickVoid); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect1d", &flags, ImGuiMultiSelectFlags_BoxSelect1d); + + for (int selection_scope_n = 0; selection_scope_n < SCOPES_COUNT; selection_scope_n++) + { + ImGui::PushID(selection_scope_n); + ImGuiSelectionBasicStorage* selection = &selections_data[selection_scope_n]; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection->Size, ITEMS_COUNT); + selection->ApplyRequests(ms_io); + + ImGui::SeparatorText("Selection scope"); + ImGui::Text("Selection size: %d/%d", selection->Size, ITEMS_COUNT); + + for (int n = 0; n < ITEMS_COUNT; n++) + { + char label[64]; + sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]); + bool item_is_selected = selection->Contains((ImGuiID)n); + ImGui::SetNextItemSelectionUserData(n); + ImGui::Selectable(label, item_is_selected); + } + + // Apply multi-select requests + ms_io = ImGui::EndMultiSelect(); + selection->ApplyRequests(ms_io); + ImGui::PopID(); + } + ImGui::TreePop(); + } + + // See ShowExampleAppAssetsBrowser() + if (ImGui::TreeNode("Multi-Select (tiled assets browser)")) + { + ImGui::Checkbox("Assets Browser", &demo_data->ShowAppAssetsBrowser); + ImGui::Text("(also access from 'Examples->Assets Browser' in menu)"); + ImGui::TreePop(); + } + + // Demonstrate supporting multiple-selection in a tree. + // - We don't use linear indices for selection user data, but our ExampleTreeNode* pointer directly! + // This showcase how SetNextItemSelectionUserData() never assume indices! + // - The difficulty here is to "interpolate" from RangeSrcItem to RangeDstItem in the SetAll/SetRange request. + // We want this interpolation to match what the user sees: in visible order, skipping closed nodes. + // This is implemented by our TreeGetNextNodeInVisibleOrder() user-space helper. + // - Important: In a real codebase aiming to implement full-featured selectable tree with custom filtering, you + // are more likely to build an array mapping sequential indices to visible tree nodes, since your + // filtering/search + clipping process will benefit from it. Having this will make this interpolation much easier. + // - Consider this a prototype: we are working toward simplifying some of it. + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (trees)"); + if (ImGui::TreeNode("Multi-Select (trees)")) + { + HelpMarker( + "This is rather advanced and experimental. If you are getting started with multi-select," + "please don't start by looking at how to use it for a tree!\n\n" + "Future versions will try to simplify and formalize some of this."); + + struct ExampleTreeFuncs + { + static void DrawNode(ExampleTreeNode* node, ImGuiSelectionBasicStorage* selection) + { + ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; + tree_node_flags |= ImGuiTreeNodeFlags_NavLeftJumpsBackHere; // Enable pressing left to jump to parent + if (node->Childs.Size == 0) + tree_node_flags |= ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_Leaf; + if (selection->Contains((ImGuiID)node->UID)) + tree_node_flags |= ImGuiTreeNodeFlags_Selected; + + // Using SetNextItemStorageID() to specify storage id, so we can easily peek into + // the storage holding open/close stage, using our TreeNodeGetOpen/TreeNodeSetOpen() functions. + ImGui::SetNextItemSelectionUserData((ImGuiSelectionUserData)(intptr_t)node); + ImGui::SetNextItemStorageID((ImGuiID)node->UID); + if (ImGui::TreeNodeEx(node->Name, tree_node_flags)) + { + for (ExampleTreeNode* child : node->Childs) + DrawNode(child, selection); + ImGui::TreePop(); + } + else if (ImGui::IsItemToggledOpen()) + { + TreeCloseAndUnselectChildNodes(node, selection); + } + } + + static bool TreeNodeGetOpen(ExampleTreeNode* node) + { + return ImGui::GetStateStorage()->GetBool((ImGuiID)node->UID); + } + + static void TreeNodeSetOpen(ExampleTreeNode* node, bool open) + { + ImGui::GetStateStorage()->SetBool((ImGuiID)node->UID, open); + } + + // When closing a node: 1) close and unselect all child nodes, 2) select parent if any child was selected. + // FIXME: This is currently handled by user logic but I'm hoping to eventually provide tree node + // features to do this automatically, e.g. a ImGuiTreeNodeFlags_AutoCloseChildNodes etc. + static int TreeCloseAndUnselectChildNodes(ExampleTreeNode* node, ImGuiSelectionBasicStorage* selection, int depth = 0) + { + // Recursive close (the test for depth == 0 is because we call this on a node that was just closed!) + int unselected_count = selection->Contains((ImGuiID)node->UID) ? 1 : 0; + if (depth == 0 || TreeNodeGetOpen(node)) + { + for (ExampleTreeNode* child : node->Childs) + unselected_count += TreeCloseAndUnselectChildNodes(child, selection, depth + 1); + TreeNodeSetOpen(node, false); + } + + // Select root node if any of its child was selected, otherwise unselect + selection->SetItemSelected((ImGuiID)node->UID, (depth == 0 && unselected_count > 0)); + return unselected_count; + } + + // Apply multi-selection requests + static void ApplySelectionRequests(ImGuiMultiSelectIO* ms_io, ExampleTreeNode* tree, ImGuiSelectionBasicStorage* selection) + { + for (ImGuiSelectionRequest& req : ms_io->Requests) + { + if (req.Type == ImGuiSelectionRequestType_SetAll) + { + if (req.Selected) + TreeSetAllInOpenNodes(tree, selection, req.Selected); + else + selection->Clear(); + } + else if (req.Type == ImGuiSelectionRequestType_SetRange) + { + ExampleTreeNode* first_node = (ExampleTreeNode*)(intptr_t)req.RangeFirstItem; + ExampleTreeNode* last_node = (ExampleTreeNode*)(intptr_t)req.RangeLastItem; + for (ExampleTreeNode* node = first_node; node != NULL; node = TreeGetNextNodeInVisibleOrder(node, last_node)) + selection->SetItemSelected((ImGuiID)node->UID, req.Selected); + } + } + } + + static void TreeSetAllInOpenNodes(ExampleTreeNode* node, ImGuiSelectionBasicStorage* selection, bool selected) + { + if (node->Parent != NULL) // Root node isn't visible nor selectable in our scheme + selection->SetItemSelected((ImGuiID)node->UID, selected); + if (node->Parent == NULL || TreeNodeGetOpen(node)) + for (ExampleTreeNode* child : node->Childs) + TreeSetAllInOpenNodes(child, selection, selected); + } + + // Interpolate in *user-visible order* AND only *over opened nodes*. + // If you have a sequential mapping tables (e.g. generated after a filter/search pass) this would be simpler. + // Here the tricks are that: + // - we store/maintain ExampleTreeNode::IndexInParent which allows implementing a linear iterator easily, without searches, without recursion. + // this could be replaced by a search in parent, aka 'int index_in_parent = curr_node->Parent->Childs.find_index(curr_node)' + // which would only be called when crossing from child to a parent, aka not too much. + // - we call SetNextItemStorageID() before our TreeNode() calls with an ID which doesn't relate to UI stack, + // making it easier to call TreeNodeGetOpen()/TreeNodeSetOpen() from any location. + static ExampleTreeNode* TreeGetNextNodeInVisibleOrder(ExampleTreeNode* curr_node, ExampleTreeNode* last_node) + { + // Reached last node + if (curr_node == last_node) + return NULL; + + // Recurse into childs. Query storage to tell if the node is open. + if (curr_node->Childs.Size > 0 && TreeNodeGetOpen(curr_node)) + return curr_node->Childs[0]; + + // Next sibling, then into our own parent + while (curr_node->Parent != NULL) + { + if (curr_node->IndexInParent + 1 < curr_node->Parent->Childs.Size) + return curr_node->Parent->Childs[curr_node->IndexInParent + 1]; + curr_node = curr_node->Parent; + } + return NULL; + } + + }; // ExampleTreeFuncs + + static ImGuiSelectionBasicStorage selection; + if (demo_data->DemoTree == NULL) + demo_data->DemoTree = ExampleTree_CreateDemoTree(); // Create tree once + ImGui::Text("Selection size: %d", selection.Size); + + if (ImGui::BeginChild("##Tree", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) + { + ExampleTreeNode* tree = demo_data->DemoTree; + ImGuiMultiSelectFlags ms_flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect2d; + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ms_flags, selection.Size, -1); + ExampleTreeFuncs::ApplySelectionRequests(ms_io, tree, &selection); + for (ExampleTreeNode* node : tree->Childs) + ExampleTreeFuncs::DrawNode(node, &selection); + ms_io = ImGui::EndMultiSelect(); + ExampleTreeFuncs::ApplySelectionRequests(ms_io, tree, &selection); + } + ImGui::EndChild(); + + ImGui::TreePop(); + } + + // Advanced demonstration of BeginMultiSelect() + // - Showcase clipping. + // - Showcase deletion. + // - Showcase basic drag and drop. + // - Showcase TreeNode variant (note that tree node don't expand in the demo: supporting expanding tree nodes + clipping a separate thing). + // - Showcase using inside a table. + IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (advanced)"); + //ImGui::SetNextItemOpen(true, ImGuiCond_Once); + if (ImGui::TreeNode("Multi-Select (advanced)")) + { + // Options + enum WidgetType { WidgetType_Selectable, WidgetType_TreeNode }; + static bool use_clipper = true; + static bool use_deletion = true; + static bool use_drag_drop = true; + static bool show_in_table = false; + static bool show_color_button = true; + static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d; + static WidgetType widget_type = WidgetType_Selectable; + + if (ImGui::TreeNode("Options")) + { + if (ImGui::RadioButton("Selectables", widget_type == WidgetType_Selectable)) { widget_type = WidgetType_Selectable; } + ImGui::SameLine(); + if (ImGui::RadioButton("Tree nodes", widget_type == WidgetType_TreeNode)) { widget_type = WidgetType_TreeNode; } + ImGui::SameLine(); + HelpMarker("TreeNode() is technically supported but... using this correctly is more complicated (you need some sort of linear/random access to your tree, which is suited to advanced trees setups already implementing filters and clipper. We will work toward simplifying and demoing this.\n\nFor now the tree demo is actually a little bit meaningless because it is an empty tree with only root nodes."); + ImGui::Checkbox("Enable clipper", &use_clipper); + ImGui::Checkbox("Enable deletion", &use_deletion); + ImGui::Checkbox("Enable drag & drop", &use_drag_drop); + ImGui::Checkbox("Show in a table", &show_in_table); + ImGui::Checkbox("Show color button", &show_color_button); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SingleSelect", &flags, ImGuiMultiSelectFlags_SingleSelect); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoSelectAll", &flags, ImGuiMultiSelectFlags_NoSelectAll); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoRangeSelect", &flags, ImGuiMultiSelectFlags_NoRangeSelect); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoSelect", &flags, ImGuiMultiSelectFlags_NoAutoSelect); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoClear", &flags, ImGuiMultiSelectFlags_NoAutoClear); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_NoAutoClearOnReselect", &flags, ImGuiMultiSelectFlags_NoAutoClearOnReselect); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect1d", &flags, ImGuiMultiSelectFlags_BoxSelect1d); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelect2d", &flags, ImGuiMultiSelectFlags_BoxSelect2d); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_BoxSelectNoScroll", &flags, ImGuiMultiSelectFlags_BoxSelectNoScroll); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnEscape", &flags, ImGuiMultiSelectFlags_ClearOnEscape); + ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ClearOnClickVoid", &flags, ImGuiMultiSelectFlags_ClearOnClickVoid); + if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeWindow", &flags, ImGuiMultiSelectFlags_ScopeWindow) && (flags & ImGuiMultiSelectFlags_ScopeWindow)) + flags &= ~ImGuiMultiSelectFlags_ScopeRect; + if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_ScopeRect", &flags, ImGuiMultiSelectFlags_ScopeRect) && (flags & ImGuiMultiSelectFlags_ScopeRect)) + flags &= ~ImGuiMultiSelectFlags_ScopeWindow; + if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClick", &flags, ImGuiMultiSelectFlags_SelectOnClick) && (flags & ImGuiMultiSelectFlags_SelectOnClick)) + flags &= ~ImGuiMultiSelectFlags_SelectOnClickRelease; + if (ImGui::CheckboxFlags("ImGuiMultiSelectFlags_SelectOnClickRelease", &flags, ImGuiMultiSelectFlags_SelectOnClickRelease) && (flags & ImGuiMultiSelectFlags_SelectOnClickRelease)) + flags &= ~ImGuiMultiSelectFlags_SelectOnClick; + ImGui::SameLine(); HelpMarker("Allow dragging an unselected item without altering selection."); + ImGui::TreePop(); + } + + // Initialize default list with 1000 items. + // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection + static ImVector items; + static int items_next_id = 0; + if (items_next_id == 0) { for (int n = 0; n < 1000; n++) { items.push_back(items_next_id++); } } + static ExampleSelectionWithDeletion selection; + static bool request_deletion_from_menu = false; // Queue deletion triggered from context menu + + ImGui::Text("Selection size: %d/%d", selection.Size, items.Size); + + const float items_height = (widget_type == WidgetType_TreeNode) ? ImGui::GetTextLineHeight() : ImGui::GetTextLineHeightWithSpacing(); + ImGui::SetNextWindowContentSize(ImVec2(0.0f, items.Size * items_height)); + if (ImGui::BeginChild("##Basket", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY)) + { + ImVec2 color_button_sz(ImGui::GetFontSize(), ImGui::GetFontSize()); + if (widget_type == WidgetType_TreeNode) + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f)); + + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size); + selection.ApplyRequests(ms_io); + + const bool want_delete = (ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (selection.Size > 0)) || request_deletion_from_menu; + const int item_curr_idx_to_focus = want_delete ? selection.ApplyDeletionPreLoop(ms_io, items.Size) : -1; + request_deletion_from_menu = false; + + if (show_in_table) + { + if (widget_type == WidgetType_TreeNode) + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0.0f, 0.0f)); + ImGui::BeginTable("##Split", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_NoPadOuterX); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.70f); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 0.30f); + //ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, 0.0f)); + } + + ImGuiListClipper clipper; + if (use_clipper) + { + clipper.Begin(items.Size); + if (item_curr_idx_to_focus != -1) + clipper.IncludeItemByIndex(item_curr_idx_to_focus); // Ensure focused item is not clipped. + if (ms_io->RangeSrcItem != -1) + clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped. + } + + while (!use_clipper || clipper.Step()) + { + const int item_begin = use_clipper ? clipper.DisplayStart : 0; + const int item_end = use_clipper ? clipper.DisplayEnd : items.Size; + for (int n = item_begin; n < item_end; n++) + { + if (show_in_table) + ImGui::TableNextColumn(); + + const int item_id = items[n]; + const char* item_category = ExampleNames[item_id % IM_ARRAYSIZE(ExampleNames)]; + char label[64]; + sprintf(label, "Object %05d: %s", item_id, item_category); + + // IMPORTANT: for deletion refocus to work we need object ID to be stable, + // aka not depend on their index in the list. Here we use our persistent item_id + // instead of index to build a unique ID that will persist. + // (If we used PushID(index) instead, focus wouldn't be restored correctly after deletion). + ImGui::PushID(item_id); + + // Emit a color button, to test that Shift+LeftArrow landing on an item that is not part + // of the selection scope doesn't erroneously alter our selection. + if (show_color_button) + { + ImU32 dummy_col = (ImU32)((unsigned int)n * 0xC250B74B) | IM_COL32_A_MASK; + ImGui::ColorButton("##", ImColor(dummy_col), ImGuiColorEditFlags_NoTooltip, color_button_sz); + ImGui::SameLine(); + } + + // Submit item + bool item_is_selected = selection.Contains((ImGuiID)n); + bool item_is_open = false; + ImGui::SetNextItemSelectionUserData(n); + if (widget_type == WidgetType_Selectable) + { + ImGui::Selectable(label, item_is_selected, ImGuiSelectableFlags_None); + } + else if (widget_type == WidgetType_TreeNode) + { + ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; + if (item_is_selected) + tree_node_flags |= ImGuiTreeNodeFlags_Selected; + item_is_open = ImGui::TreeNodeEx(label, tree_node_flags); + } + + // Focus (for after deletion) + if (item_curr_idx_to_focus == n) + ImGui::SetKeyboardFocusHere(-1); + + // Drag and Drop + if (use_drag_drop && ImGui::BeginDragDropSource()) + { + // Create payload with full selection OR single unselected item. + // (the later is only possible when using ImGuiMultiSelectFlags_SelectOnClickRelease) + if (ImGui::GetDragDropPayload() == NULL) + { + ImVector payload_items; + void* it = NULL; + ImGuiID id = 0; + if (!item_is_selected) + payload_items.push_back(item_id); + else + while (selection.GetNextSelectedItem(&it, &id)) + payload_items.push_back((int)id); + ImGui::SetDragDropPayload("MULTISELECT_DEMO_ITEMS", payload_items.Data, (size_t)payload_items.size_in_bytes()); + } + + // Display payload content in tooltip + const ImGuiPayload* payload = ImGui::GetDragDropPayload(); + const int* payload_items = (int*)payload->Data; + const int payload_count = (int)payload->DataSize / (int)sizeof(int); + if (payload_count == 1) + ImGui::Text("Object %05d: %s", payload_items[0], ExampleNames[payload_items[0] % IM_ARRAYSIZE(ExampleNames)]); + else + ImGui::Text("Dragging %d objects", payload_count); + + ImGui::EndDragDropSource(); + } + + if (widget_type == WidgetType_TreeNode && item_is_open) + ImGui::TreePop(); + + // Right-click: context menu + if (ImGui::BeginPopupContextItem()) + { + ImGui::BeginDisabled(!use_deletion || selection.Size == 0); + sprintf(label, "Delete %d item(s)###DeleteSelected", selection.Size); + if (ImGui::Selectable(label)) + request_deletion_from_menu = true; + ImGui::EndDisabled(); + ImGui::Selectable("Close"); + ImGui::EndPopup(); + } + + // Demo content within a table + if (show_in_table) + { + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::InputText("###NoLabel", (char*)(void*)item_category, strlen(item_category), ImGuiInputTextFlags_ReadOnly); + ImGui::PopStyleVar(); + } + + ImGui::PopID(); + } + if (!use_clipper) + break; + } + + if (show_in_table) + { + ImGui::EndTable(); + if (widget_type == WidgetType_TreeNode) + ImGui::PopStyleVar(); + } + + // Apply multi-select requests + ms_io = ImGui::EndMultiSelect(); + selection.ApplyRequests(ms_io); + if (want_delete) + selection.ApplyDeletionPostLoop(ms_io, items, item_curr_idx_to_focus); + + if (widget_type == WidgetType_TreeNode) + ImGui::PopStyleVar(); + } + ImGui::EndChild(); + ImGui::TreePop(); + } + ImGui::TreePop(); + } +} + +//----------------------------------------------------------------------------- +// [SECTION] ShowDemoWindowLayout() +//----------------------------------------------------------------------------- + static void ShowDemoWindowLayout() { IMGUI_DEMO_MARKER("Layout"); @@ -3098,7 +4211,7 @@ static void ShowDemoWindowLayout() ImGui::Text("Manual wrapping:"); ImGuiStyle& style = ImGui::GetStyle(); int buttons_count = 20; - float window_visible_x2 = ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x; + float window_visible_x2 = ImGui::GetCursorScreenPos().x + ImGui::GetContentRegionAvail().x; for (int n = 0; n < buttons_count; n++) { ImGui::PushID(n); @@ -3566,8 +4679,8 @@ static void ShowDemoWindowLayout() ImGui::TreePop(); } - IMGUI_DEMO_MARKER("Layout/Clipping"); - if (ImGui::TreeNode("Clipping")) + IMGUI_DEMO_MARKER("Layout/Text Clipping"); + if (ImGui::TreeNode("Text Clipping")) { static ImVec2 size(100.0f, 100.0f); static ImVec2 offset(30.0f, 30.0f); @@ -3663,6 +4776,10 @@ static void ShowDemoWindowLayout() } } +//----------------------------------------------------------------------------- +// [SECTION] ShowDemoWindowPopups() +//----------------------------------------------------------------------------- + static void ShowDemoWindowPopups() { IMGUI_DEMO_MARKER("Popups"); @@ -3918,7 +5035,7 @@ static void ShowDemoWindowPopups() static int item = 1; static float color[4] = { 0.4f, 0.7f, 0.0f, 0.5f }; ImGui::Combo("Combo", &item, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); - ImGui::ColorEdit4("color", color); + ImGui::ColorEdit4("Color", color); if (ImGui::Button("Add another modal..")) ImGui::OpenPopup("Stacked 2"); @@ -3930,6 +5047,7 @@ static void ShowDemoWindowPopups() if (ImGui::BeginPopupModal("Stacked 2", &unused_open)) { ImGui::Text("Hello from Stacked The Second!"); + ImGui::ColorEdit4("Color", color); // Allow opening another nested popup if (ImGui::Button("Close")) ImGui::CloseCurrentPopup(); ImGui::EndPopup(); @@ -4122,6 +5240,10 @@ static void ShowTableColumnsStatusFlags(ImGuiTableColumnFlags flags) ImGui::CheckboxFlags("_IsHovered", &flags, ImGuiTableColumnFlags_IsHovered); } +//----------------------------------------------------------------------------- +// [SECTION] ShowDemoWindowTables() +//----------------------------------------------------------------------------- + static void ShowDemoWindowTables() { //ImGui::SetNextItemOpen(true, ImGuiCond_Once); @@ -4240,7 +5362,7 @@ static void ShowDemoWindowTables() PushStyleCompact(); ImGui::CheckboxFlags("ImGuiTableFlags_RowBg", &flags, ImGuiTableFlags_RowBg); ImGui::CheckboxFlags("ImGuiTableFlags_Borders", &flags, ImGuiTableFlags_Borders); - ImGui::SameLine(); HelpMarker("ImGuiTableFlags_Borders\n = ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterV\n | ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterH"); + ImGui::SameLine(); HelpMarker("ImGuiTableFlags_Borders\n = ImGuiTableFlags_BordersInnerV\n | ImGuiTableFlags_BordersOuterV\n | ImGuiTableFlags_BordersInnerH\n | ImGuiTableFlags_BordersOuterH"); ImGui::Indent(); ImGui::CheckboxFlags("ImGuiTableFlags_BordersH", &flags, ImGuiTableFlags_BordersH); @@ -5215,7 +6337,8 @@ static void ShowDemoWindowTables() static ImGuiTableFlags flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; static ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAllColumns; - ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanFullWidth", &tree_node_flags, ImGuiTreeNodeFlags_SpanFullWidth); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanFullWidth", &tree_node_flags, ImGuiTreeNodeFlags_SpanFullWidth); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanTextWidth", &tree_node_flags, ImGuiTreeNodeFlags_SpanTextWidth); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAllColumns", &tree_node_flags, ImGuiTreeNodeFlags_SpanAllColumns); HelpMarker("See \"Columns flags\" section to configure how indentation is applied to individual columns."); @@ -5404,6 +6527,17 @@ static void ShowDemoWindowTables() ImGui::SliderInt("Frozen rows", &frozen_rows, 0, 2); ImGui::CheckboxFlags("Disable header contributing to column width", &column_flags, ImGuiTableColumnFlags_NoHeaderWidth); + if (ImGui::TreeNode("Style settings")) + { + ImGui::SameLine(); + HelpMarker("Giving access to some ImGuiStyle value in this demo for convenience."); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); + ImGui::SliderAngle("style.TableAngledHeadersAngle", &ImGui::GetStyle().TableAngledHeadersAngle, -50.0f, +50.0f); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); + ImGui::SliderFloat2("style.TableAngledHeadersTextAlign", (float*)&ImGui::GetStyle().TableAngledHeadersTextAlign, 0.0f, 1.0f, "%.2f"); + ImGui::TreePop(); + } + if (ImGui::BeginTable("table_angled_headers", columns_count, table_flags, ImVec2(0.0f, TEXT_BASE_HEIGHT * 12))) { ImGui::TableSetupColumn(column_names[0], ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_NoReorder); @@ -5555,6 +6689,7 @@ static void ShowDemoWindowTables() HelpMarker("Multiple tables with the same identifier will share their settings, width, visibility, order etc."); static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings; + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", &flags, ImGuiTableFlags_ScrollY); ImGui::CheckboxFlags("ImGuiTableFlags_SizingFixedFit", &flags, ImGuiTableFlags_SizingFixedFit); ImGui::CheckboxFlags("ImGuiTableFlags_HighlightHoveredColumn", &flags, ImGuiTableFlags_HighlightHoveredColumn); @@ -5881,7 +7016,6 @@ static void ShowDemoWindowTables() // Show data // FIXME-TABLE FIXME-NAV: How we can get decent up/down even though we have the buttons here? - ImGui::PushButtonRepeat(true); #if 1 // Demonstrate using clipper for large vertical lists ImGuiListClipper clipper; @@ -5966,7 +7100,6 @@ static void ShowDemoWindowTables() ImGui::PopID(); } } - ImGui::PopButtonRepeat(); // Store some info to display debug details below table_scroll_cur = ImVec2(ImGui::GetScrollX(), ImGui::GetScrollY()); @@ -6204,6 +7337,10 @@ static void ShowDemoWindowColumns() ImGui::TreePop(); } +//----------------------------------------------------------------------------- +// [SECTION] ShowDemoWindowInputs() +//----------------------------------------------------------------------------- + static void ShowDemoWindowInputs() { IMGUI_DEMO_MARKER("Inputs & Focus"); @@ -6214,12 +7351,14 @@ static void ShowDemoWindowInputs() // Display inputs submitted to ImGuiIO IMGUI_DEMO_MARKER("Inputs & Focus/Inputs"); ImGui::SetNextItemOpen(true, ImGuiCond_Once); - if (ImGui::TreeNode("Inputs")) + bool inputs_opened = ImGui::TreeNode("Inputs"); + ImGui::SameLine(); + HelpMarker( + "This is a simplified view. See more detailed input state:\n" + "- in 'Tools->Metrics/Debugger->Inputs'.\n" + "- in 'Tools->Debug Log->IO'."); + if (inputs_opened) { - HelpMarker( - "This is a simplified view. See more detailed input state:\n" - "- in 'Tools->Metrics/Debugger->Inputs'.\n" - "- in 'Tools->Debug Log->IO'."); if (ImGui::IsMousePosValid()) ImGui::Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y); else @@ -6250,15 +7389,17 @@ static void ShowDemoWindowInputs() // Display ImGuiIO output flags IMGUI_DEMO_MARKER("Inputs & Focus/Outputs"); ImGui::SetNextItemOpen(true, ImGuiCond_Once); - if (ImGui::TreeNode("Outputs")) + bool outputs_opened = ImGui::TreeNode("Outputs"); + ImGui::SameLine(); + HelpMarker( + "The value of io.WantCaptureMouse and io.WantCaptureKeyboard are normally set by Dear ImGui " + "to instruct your application of how to route inputs. Typically, when a value is true, it means " + "Dear ImGui wants the corresponding inputs and we expect the underlying application to ignore them.\n\n" + "The most typical case is: when hovering a window, Dear ImGui set io.WantCaptureMouse to true, " + "and underlying application should ignore mouse inputs (in practice there are many and more subtle " + "rules leading to how those flags are set)."); + if (outputs_opened) { - HelpMarker( - "The value of io.WantCaptureMouse and io.WantCaptureKeyboard are normally set by Dear ImGui " - "to instruct your application of how to route inputs. Typically, when a value is true, it means " - "Dear ImGui wants the corresponding inputs and we expect the underlying application to ignore them.\n\n" - "The most typical case is: when hovering a window, Dear ImGui set io.WantCaptureMouse to true, " - "and underlying application should ignore mouse inputs (in practice there are many and more subtle " - "rules leading to how those flags are set)."); ImGui::Text("io.WantCaptureMouse: %d", io.WantCaptureMouse); ImGui::Text("io.WantCaptureMouseUnlessPopupClose: %d", io.WantCaptureMouseUnlessPopupClose); ImGui::Text("io.WantCaptureKeyboard: %d", io.WantCaptureKeyboard); @@ -6292,6 +7433,102 @@ static void ShowDemoWindowInputs() ImGui::TreePop(); } + // Demonstrate using Shortcut() and Routing Policies. + // The general flow is: + // - Code interested in a chord (e.g. "Ctrl+A") declares their intent. + // - Multiple locations may be interested in same chord! Routing helps find a winner. + // - Every frame, we resolve all claims and assign one owner if the modifiers are matching. + // - The lower-level function is 'bool SetShortcutRouting()', returns true when caller got the route. + // - Most of the times, SetShortcutRouting() is not called directly. User mostly calls Shortcut() with routing flags. + // - If you call Shortcut() WITHOUT any routing option, it uses ImGuiInputFlags_RouteFocused. + // TL;DR: Most uses will simply be: + // - Shortcut(ImGuiMod_Ctrl | ImGuiKey_A); // Use ImGuiInputFlags_RouteFocused policy. + IMGUI_DEMO_MARKER("Inputs & Focus/Shortcuts"); + if (ImGui::TreeNode("Shortcuts")) + { + static ImGuiInputFlags route_options = ImGuiInputFlags_Repeat; + static ImGuiInputFlags route_type = ImGuiInputFlags_RouteFocused; + ImGui::CheckboxFlags("ImGuiInputFlags_Repeat", &route_options, ImGuiInputFlags_Repeat); + ImGui::RadioButton("ImGuiInputFlags_RouteActive", &route_type, ImGuiInputFlags_RouteActive); + ImGui::RadioButton("ImGuiInputFlags_RouteFocused (default)", &route_type, ImGuiInputFlags_RouteFocused); + ImGui::RadioButton("ImGuiInputFlags_RouteGlobal", &route_type, ImGuiInputFlags_RouteGlobal); + ImGui::Indent(); + ImGui::BeginDisabled(route_type != ImGuiInputFlags_RouteGlobal); + ImGui::CheckboxFlags("ImGuiInputFlags_RouteOverFocused", &route_options, ImGuiInputFlags_RouteOverFocused); + ImGui::CheckboxFlags("ImGuiInputFlags_RouteOverActive", &route_options, ImGuiInputFlags_RouteOverActive); + ImGui::CheckboxFlags("ImGuiInputFlags_RouteUnlessBgFocused", &route_options, ImGuiInputFlags_RouteUnlessBgFocused); + ImGui::EndDisabled(); + ImGui::Unindent(); + ImGui::RadioButton("ImGuiInputFlags_RouteAlways", &route_type, ImGuiInputFlags_RouteAlways); + ImGuiInputFlags flags = route_type | route_options; // Merged flags + if (route_type != ImGuiInputFlags_RouteGlobal) + flags &= ~(ImGuiInputFlags_RouteOverFocused | ImGuiInputFlags_RouteOverActive | ImGuiInputFlags_RouteUnlessBgFocused); + + ImGui::SeparatorText("Using SetNextItemShortcut()"); + ImGui::Text("Ctrl+S"); + ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_S, flags | ImGuiInputFlags_Tooltip); + ImGui::Button("Save"); + ImGui::Text("Alt+F"); + ImGui::SetNextItemShortcut(ImGuiMod_Alt | ImGuiKey_F, flags | ImGuiInputFlags_Tooltip); + static float f = 0.5f; + ImGui::SliderFloat("Factor", &f, 0.0f, 1.0f); + + ImGui::SeparatorText("Using Shortcut()"); + const float line_height = ImGui::GetTextLineHeightWithSpacing(); + const ImGuiKeyChord key_chord = ImGuiMod_Ctrl | ImGuiKey_A; + + ImGui::Text("Ctrl+A"); + ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags) ? "PRESSED" : "..."); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 0.0f, 1.0f, 0.1f)); + + ImGui::BeginChild("WindowA", ImVec2(-FLT_MIN, line_height * 14), true); + ImGui::Text("Press CTRL+A and see who receives it!"); + ImGui::Separator(); + + // 1: Window polling for CTRL+A + ImGui::Text("(in WindowA)"); + ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags) ? "PRESSED" : "..."); + + // 2: InputText also polling for CTRL+A: it always uses _RouteFocused internally (gets priority when active) + // (Commmented because the owner-aware version of Shortcut() is still in imgui_internal.h) + //char str[16] = "Press CTRL+A"; + //ImGui::Spacing(); + //ImGui::InputText("InputTextB", str, IM_ARRAYSIZE(str), ImGuiInputTextFlags_ReadOnly); + //ImGuiID item_id = ImGui::GetItemID(); + //ImGui::SameLine(); HelpMarker("Internal widgets always use _RouteFocused"); + //ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags, item_id) ? "PRESSED" : "..."); + + // 3: Dummy child is not claiming the route: focusing them shouldn't steal route away from WindowA + ImGui::BeginChild("ChildD", ImVec2(-FLT_MIN, line_height * 4), true); + ImGui::Text("(in ChildD: not using same Shortcut)"); + ImGui::Text("IsWindowFocused: %d", ImGui::IsWindowFocused()); + ImGui::EndChild(); + + // 4: Child window polling for CTRL+A. It is deeper than WindowA and gets priority when focused. + ImGui::BeginChild("ChildE", ImVec2(-FLT_MIN, line_height * 4), true); + ImGui::Text("(in ChildE: using same Shortcut)"); + ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags) ? "PRESSED" : "..."); + ImGui::EndChild(); + + // 5: In a popup + if (ImGui::Button("Open Popup")) + ImGui::OpenPopup("PopupF"); + if (ImGui::BeginPopup("PopupF")) + { + ImGui::Text("(in PopupF)"); + ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags) ? "PRESSED" : "..."); + // (Commmented because the owner-aware version of Shortcut() is still in imgui_internal.h) + //ImGui::InputText("InputTextG", str, IM_ARRAYSIZE(str), ImGuiInputTextFlags_ReadOnly); + //ImGui::Text("IsWindowFocused: %d, Shortcut: %s", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags, ImGui::GetItemID()) ? "PRESSED" : "..."); + ImGui::EndPopup(); + } + ImGui::EndChild(); + ImGui::PopStyleColor(); + + ImGui::TreePop(); + } + // Display mouse cursors IMGUI_DEMO_MARKER("Inputs & Focus/Mouse Cursors"); if (ImGui::TreeNode("Mouse Cursors")) @@ -6329,10 +7566,10 @@ static void ShowDemoWindowInputs() ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); ImGui::InputText("3", buf, IM_ARRAYSIZE(buf)); - ImGui::PushTabStop(false); + ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop, true); ImGui::InputText("4 (tab skip)", buf, IM_ARRAYSIZE(buf)); ImGui::SameLine(); HelpMarker("Item won't be cycled through when using TAB or Shift+Tab."); - ImGui::PopTabStop(); + ImGui::PopItemFlag(); ImGui::InputText("5", buf, IM_ARRAYSIZE(buf)); ImGui::TreePop(); } @@ -6354,12 +7591,12 @@ static void ShowDemoWindowInputs() ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); if (ImGui::IsItemActive()) has_focus = 2; - ImGui::PushTabStop(false); + ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop, true); if (focus_3) ImGui::SetKeyboardFocusHere(); ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf)); if (ImGui::IsItemActive()) has_focus = 3; ImGui::SameLine(); HelpMarker("Item won't be cycled through when using TAB or Shift+Tab."); - ImGui::PopTabStop(); + ImGui::PopItemFlag(); if (has_focus) ImGui::Text("Item with focus: %d", has_focus); @@ -6424,10 +7661,21 @@ void ImGui::ShowAboutWindow(bool* p_open) } IMGUI_DEMO_MARKER("Tools/About Dear ImGui"); ImGui::Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM); + + ImGui::TextLinkOpenURL("Homepage", "https://github.com/ocornut/imgui"); + ImGui::SameLine(); + ImGui::TextLinkOpenURL("FAQ", "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md"); + ImGui::SameLine(); + ImGui::TextLinkOpenURL("Wiki", "https://github.com/ocornut/imgui/wiki"); + ImGui::SameLine(); + ImGui::TextLinkOpenURL("Releases", "https://github.com/ocornut/imgui/releases"); + ImGui::SameLine(); + ImGui::TextLinkOpenURL("Funding", "https://github.com/ocornut/imgui/wiki/Funding"); + ImGui::Separator(); ImGui::Text("By Omar Cornut and all Dear ImGui contributors."); ImGui::Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information."); - ImGui::Text("If your company uses this, please consider sponsoring the project!"); + ImGui::Text("If your company uses this, please consider funding the project."); static bool show_config_info = false; ImGui::Checkbox("Config/Build Information", &show_config_info); @@ -6700,6 +7948,8 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("TabBarBorderSize", &style.TabBarBorderSize, 0.0f, 2.0f, "%.0f"); + ImGui::SliderFloat("TabBarOverlineSize", &style.TabBarOverlineSize, 0.0f, 2.0f, "%.0f"); + ImGui::SameLine(); HelpMarker("Overline is only drawn over the selected tab when ImGuiTabBarFlags_DrawSelectedOverline is set."); ImGui::SeparatorText("Rounding"); ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); @@ -6713,12 +7963,13 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::SeparatorText("Tables"); ImGui::SliderFloat2("CellPadding", (float*)&style.CellPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderAngle("TableAngledHeadersAngle", &style.TableAngledHeadersAngle, -50.0f, +50.0f); + ImGui::SliderFloat2("TableAngledHeadersTextAlign", (float*)&style.TableAngledHeadersTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SeparatorText("Widgets"); ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); int window_menu_button_position = style.WindowMenuButtonPosition + 1; if (ImGui::Combo("WindowMenuButtonPosition", (int*)&window_menu_button_position, "None\0Left\0Right\0")) - style.WindowMenuButtonPosition = window_menu_button_position - 1; + style.WindowMenuButtonPosition = (ImGuiDir)(window_menu_button_position - 1); ImGui::Combo("ColorButtonPosition", (int*)&style.ColorButtonPosition, "Left\0Right\0"); ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); HelpMarker("Alignment applies when a button is larger than its text content."); @@ -6746,7 +7997,8 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) } ImGui::SeparatorText("Misc"); - ImGui::SliderFloat2("DisplaySafeAreaPadding", (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); ImGui::SameLine(); HelpMarker("Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); + ImGui::SliderFloat2("DisplayWindowPadding", (float*)&style.DisplayWindowPadding, 0.0f, 30.0f, "%.0f"); ImGui::SameLine(); HelpMarker("Apply to regular windows: amount which we enforce to keep visible when moving near edges of your screen."); + ImGui::SliderFloat2("DisplaySafeAreaPadding", (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); ImGui::SameLine(); HelpMarker("Apply to every windows, menus, popups, tooltips: amount where we avoid displaying contents. Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); ImGui::EndTabItem(); } @@ -6788,7 +8040,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) "Right-click to open edit options menu."); ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 10), ImVec2(FLT_MAX, FLT_MAX)); - ImGui::BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Border, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened); + ImGui::BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Border | ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); ImGui::PushItemWidth(ImGui::GetFontSize() * -12); for (int i = 0; i < ImGuiCol_COUNT; i++) { @@ -6869,10 +8121,10 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); if (show_samples && ImGui::BeginTooltip()) { - ImGui::TextUnformatted("(R = radius, N = number of segments)"); + ImGui::TextUnformatted("(R = radius, N = approx number of segments)"); ImGui::Spacing(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); - const float min_widget_width = ImGui::CalcTextSize("N: MMM\nR: MMM").x; + const float min_widget_width = ImGui::CalcTextSize("R: MMM\nN: MMM").x; for (int n = 0; n < 8; n++) { const float RAD_MIN = 5.0f; @@ -6881,6 +8133,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::BeginGroup(); + // N is not always exact here due to how PathArcTo() function work internally ImGui::Text("R: %.f\nN: %d", rad, draw_list->_CalcCircleAutoSegmentCount(rad)); const float canvas_width = IM_MAX(min_widget_width, rad * 2.0f); @@ -7179,6 +8432,7 @@ struct ExampleAppConsole } // Options, Filter + ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_O, ImGuiInputFlags_Tooltip); if (ImGui::Button("Options")) ImGui::OpenPopup("Options"); ImGui::SameLine(); @@ -7187,7 +8441,7 @@ struct ExampleAppConsole // Reserve enough left-over height for 1 separator + 1 input text const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); - if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar)) + if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_HorizontalScrollbar)) { if (ImGui::BeginPopupContextWindow()) { @@ -7663,57 +8917,130 @@ static void ShowExampleAppLayout(bool* p_open) //----------------------------------------------------------------------------- // [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() //----------------------------------------------------------------------------- +// Some of the interactions are a bit lack-luster: +// - We would want pressing validating or leaving the filter to somehow restore focus. +// - We may want more advanced filtering (child nodes) and clipper support: both will need extra work. +// - We would want to customize some keyboard interactions to easily keyboard navigate between the tree and the properties. +//----------------------------------------------------------------------------- -static void ShowPlaceholderObject(const char* prefix, int uid) +struct ExampleAppPropertyEditor { - // Use object uid as identifier. Most commonly you could also use the object pointer as a base ID. - ImGui::PushID(uid); + ImGuiTextFilter Filter; + ExampleTreeNode* VisibleNode = NULL; - // Text and Tree nodes are less high than framed widgets, using AlignTextToFramePadding() we add vertical spacing to make the tree lines equal high. - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::AlignTextToFramePadding(); - bool node_open = ImGui::TreeNode("Object", "%s_%u", prefix, uid); - ImGui::TableSetColumnIndex(1); - ImGui::Text("my sailor is rich"); - - if (node_open) + void Draw(ExampleTreeNode* root_node) { - static float placeholder_members[8] = { 0.0f, 0.0f, 1.0f, 3.1416f, 100.0f, 999.0f }; - for (int i = 0; i < 8; i++) + // Left side: draw tree + // - Currently using a table to benefit from RowBg feature + if (ImGui::BeginChild("##tree", ImVec2(300, 0), ImGuiChildFlags_ResizeX | ImGuiChildFlags_Border | ImGuiChildFlags_NavFlattened)) { - ImGui::PushID(i); // Use field index as identifier. - if (i < 2) - { - ShowPlaceholderObject("Child", 424242); - } - else - { - // Here we use a TreeNode to highlight on hover (we could use e.g. Selectable as well) - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::AlignTextToFramePadding(); - ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_Bullet; - ImGui::TreeNodeEx("Field", flags, "Field_%d", i); + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip); + ImGui::PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); + if (ImGui::InputTextWithHint("##Filter", "incl,-excl", Filter.InputBuf, IM_ARRAYSIZE(Filter.InputBuf), ImGuiInputTextFlags_EscapeClearsAll)) + Filter.Build(); + ImGui::PopItemFlag(); - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(-FLT_MIN); - if (i >= 5) - ImGui::InputFloat("##value", &placeholder_members[i], 1.0f); - else - ImGui::DragFloat("##value", &placeholder_members[i], 0.01f); - ImGui::NextColumn(); + if (ImGui::BeginTable("##bg", 1, ImGuiTableFlags_RowBg)) + { + for (ExampleTreeNode* node : root_node->Childs) + if (Filter.PassFilter(node->Name)) // Filter root node + DrawTreeNode(node); + ImGui::EndTable(); } - ImGui::PopID(); } - ImGui::TreePop(); - } - ImGui::PopID(); -} + ImGui::EndChild(); -// Demonstrate create a simple property editor. -// This demo is a bit lackluster nowadays, would be nice to improve. -static void ShowExampleAppPropertyEditor(bool* p_open) + // Right side: draw properties + ImGui::SameLine(); + + ImGui::BeginGroup(); // Lock X position + if (ExampleTreeNode* node = VisibleNode) + { + ImGui::Text("%s", node->Name); + ImGui::TextDisabled("UID: 0x%08X", node->UID); + ImGui::Separator(); + if (ImGui::BeginTable("##properties", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) + { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch, 2.0f); // Default twice larger + if (node->HasData) + { + // In a typical application, the structure description would be derived from a data-driven system. + // - We try to mimic this with our ExampleMemberInfo structure and the ExampleTreeNodeMemberInfos[] array. + // - Limits and some details are hard-coded to simplify the demo. + for (const ExampleMemberInfo& field_desc : ExampleTreeNodeMemberInfos) + { + ImGui::TableNextRow(); + ImGui::PushID(field_desc.Name); + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(field_desc.Name); + ImGui::TableNextColumn(); + void* field_ptr = (void*)(((unsigned char*)node) + field_desc.Offset); + switch (field_desc.DataType) + { + case ImGuiDataType_Bool: + { + IM_ASSERT(field_desc.DataCount == 1); + ImGui::Checkbox("##Editor", (bool*)field_ptr); + break; + } + case ImGuiDataType_S32: + { + int v_min = INT_MIN, v_max = INT_MAX; + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::DragScalarN("##Editor", field_desc.DataType, field_ptr, field_desc.DataCount, 1.0f, &v_min, &v_max); + break; + } + case ImGuiDataType_Float: + { + float v_min = 0.0f, v_max = 1.0f; + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::SliderScalarN("##Editor", field_desc.DataType, field_ptr, field_desc.DataCount, &v_min, &v_max); + break; + } + } + ImGui::PopID(); + } + } + ImGui::EndTable(); + } + } + ImGui::EndGroup(); + } + + void DrawTreeNode(ExampleTreeNode* node) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushID(node->UID); + ImGuiTreeNodeFlags tree_flags = ImGuiTreeNodeFlags_None; + tree_flags |= ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; // Standard opening mode as we are likely to want to add selection afterwards + tree_flags |= ImGuiTreeNodeFlags_NavLeftJumpsBackHere; // Left arrow support + if (node == VisibleNode) + tree_flags |= ImGuiTreeNodeFlags_Selected; + if (node->Childs.Size == 0) + tree_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet; + if (node->DataMyBool == false) + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]); + bool node_open = ImGui::TreeNodeEx("", tree_flags, "%s", node->Name); + if (node->DataMyBool == false) + ImGui::PopStyleColor(); + if (ImGui::IsItemFocused()) + VisibleNode = node; + if (node_open) + { + for (ExampleTreeNode* child : node->Childs) + DrawTreeNode(child); + ImGui::TreePop(); + } + ImGui::PopID(); + } +}; + +// Demonstrate creating a simple property editor. +static void ShowExampleAppPropertyEditor(bool* p_open, ImGuiDemoWindowData* demo_data) { ImGui::SetNextWindowSize(ImVec2(430, 450), ImGuiCond_FirstUseEver); if (!ImGui::Begin("Example: Property editor", p_open)) @@ -7723,25 +9050,11 @@ static void ShowExampleAppPropertyEditor(bool* p_open) } IMGUI_DEMO_MARKER("Examples/Property Editor"); - HelpMarker( - "This example shows how you may implement a property editor using two columns.\n" - "All objects/fields data are dummies here.\n"); + static ExampleAppPropertyEditor property_editor; + if (demo_data->DemoTree == NULL) + demo_data->DemoTree = ExampleTree_CreateDemoTree(); + property_editor.Draw(demo_data->DemoTree); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2)); - if (ImGui::BeginTable("##split", 2, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY)) - { - ImGui::TableSetupScrollFreeze(0, 1); - ImGui::TableSetupColumn("Object"); - ImGui::TableSetupColumn("Contents"); - ImGui::TableHeadersRow(); - - // Iterate placeholder objects (all the same data) - for (int obj_i = 0; obj_i < 4; obj_i++) - ShowPlaceholderObject("Object", obj_i); - - ImGui::EndTable(); - } - ImGui::PopStyleVar(); ImGui::End(); } @@ -7889,7 +9202,7 @@ static void ShowExampleAppConstrainedResize(bool* p_open) if (type == 2) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 0), ImVec2(-1, FLT_MAX)); // Resize vertical + lock current width if (type == 3) ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1), ImVec2(FLT_MAX, -1)); // Resize horizontal + lock current height if (type == 4) ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1), ImVec2(500, -1)); // Width Between and 400 and 500 - if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 500), ImVec2(-1, FLT_MAX)); // Height at least 400 + if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 400), ImVec2(-1, FLT_MAX)); // Height at least 400 if (type == 6) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::AspectRatio, (void*)&aspect_ratio); // Aspect ratio if (type == 7) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square); // Always Square if (type == 8) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)&fixed_step); // Fixed Step @@ -8067,6 +9380,14 @@ static void ShowExampleAppWindowTitles(bool*) // [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() //----------------------------------------------------------------------------- +// Add a |_| looking shape +static void PathConcaveShape(ImDrawList* draw_list, float x, float y, float sz) +{ + const ImVec2 pos_norms[] = { { 0.0f, 0.0f }, { 0.3f, 0.0f }, { 0.3f, 0.7f }, { 0.7f, 0.7f }, { 0.7f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f } }; + for (const ImVec2& p : pos_norms) + draw_list->PathLineTo(ImVec2(x + 0.5f + (int)(sz * p.x), y + 0.5f + (int)(sz * p.y))); +} + // Demonstrate using the low-level ImDrawList to draw custom shapes. static void ShowExampleAppCustomRendering(bool* p_open) { @@ -8150,12 +9471,14 @@ static void ShowExampleAppCustomRendering(bool* p_open) float th = (n == 0) ? 1.0f : thickness; draw_list->AddNgon(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, ngon_sides, th); x += sz + spacing; // N-gon draw_list->AddCircle(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments, th); x += sz + spacing; // Circle - draw_list->AddEllipse(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, sz*0.3f, col, -0.3f, circle_segments, th); x += sz + spacing; // Ellipse + draw_list->AddEllipse(ImVec2(x + sz*0.5f, y + sz*0.5f), ImVec2(sz*0.5f, sz*0.3f), col, -0.3f, circle_segments, th); x += sz + spacing; // Ellipse draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, ImDrawFlags_None, th); x += sz + spacing; // Square draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, ImDrawFlags_None, th); x += sz + spacing; // Square with all rounded corners draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, corners_tl_br, th); x += sz + spacing; // Square with two rounded corners draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th);x += sz + spacing; // Triangle //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin triangle + PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, ImDrawFlags_Closed, th); x += sz + spacing; // Concave Shape + //draw_list->AddPolyline(concave_shape, IM_ARRAYSIZE(concave_shape), col, ImDrawFlags_Closed, th); draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) draw_list->AddLine(ImVec2(x, y), ImVec2(x, y + sz), col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th); x += sz + spacing; // Diagonal line @@ -8179,12 +9502,13 @@ static void ShowExampleAppCustomRendering(bool* p_open) // Filled shapes draw_list->AddNgonFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, ngon_sides); x += sz + spacing; // N-gon draw_list->AddCircleFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, circle_segments); x += sz + spacing; // Circle - draw_list->AddEllipseFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, sz * 0.3f, col, -0.3f, circle_segments); x += sz + spacing;// Ellipse + draw_list->AddEllipseFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), ImVec2(sz * 0.5f, sz * 0.3f), col, -0.3f, circle_segments); x += sz + spacing;// Ellipse draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col); x += sz + spacing; // Square draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f); x += sz + spacing; // Square with all rounded corners draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f, corners_tl_br); x += sz + spacing; // Square with two rounded corners draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col); x += sz + spacing; // Triangle //draw_list->AddTriangleFilled(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col); x += sz*0.4f + spacing; // Thin triangle + PathConcaveShape(draw_list, x, y, sz); draw_list->PathFillConcave(col); x += sz + spacing; // Concave shape draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + thickness), col); x += sz + spacing; // Horizontal line (faster than AddLine, but only handle integer thickness) draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + thickness, y + sz), col); x += spacing * 2.0f;// Vertical line (faster than AddLine, but only handle integer thickness) draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + 1, y + 1), col); x += sz; // Pixel (faster than AddLine) @@ -8200,15 +9524,10 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->PathFillConvex(col); x += sz + spacing; - // Cubic Bezier Curve (4 control points): this is concave so not drawing it yet - //draw_list->PathLineTo(ImVec2(x + cp4[0].x, y + cp4[0].y)); - //draw_list->PathBezierCubicCurveTo(ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), curve_segments); - //draw_list->PathFillConvex(col); - //x += sz + spacing; - draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x + sz, y + sz), IM_COL32(0, 0, 0, 255), IM_COL32(255, 0, 0, 255), IM_COL32(255, 255, 0, 255), IM_COL32(0, 255, 0, 255)); + x += sz + spacing; - ImGui::Dummy(ImVec2((sz + spacing) * 12.2f, (sz + spacing) * 3.0f)); + ImGui::Dummy(ImVec2((sz + spacing) * 13.2f, (sz + spacing) * 3.0f)); ImGui::PopItemWidth(); ImGui::EndTabItem(); } @@ -8397,7 +9716,7 @@ void ShowExampleAppDockSpace(bool* p_open) // If we remove all options we are showcasing, this demo would become: // void ShowExampleAppDockSpace() // { - // ImGui::DockSpaceOverViewport(ImGui::GetMainViewport()); + // ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport()); // } // In most cases you should be able to just call DockSpaceOverViewport() and ignore all the code below! // In this specific demo, we are not using DockSpaceOverViewport() because: @@ -8506,92 +9825,119 @@ void ShowExampleAppDockSpace(bool* p_open) // Simplified structure to mimic a Document model struct MyDocument { - const char* Name; // Document title + char Name[32]; // Document title + int UID; // Unique ID (necessary as we can change title) bool Open; // Set when open (we keep an array of all available documents to simplify demo code!) bool OpenPrev; // Copy of Open from last update. bool Dirty; // Set when the document has been modified - bool WantClose; // Set when the document ImVec4 Color; // An arbitrary variable associated to the document - MyDocument(const char* name, bool open = true, const ImVec4& color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f)) + MyDocument(int uid, const char* name, bool open = true, const ImVec4& color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f)) { - Name = name; + UID = uid; + snprintf(Name, sizeof(Name), "%s", name); Open = OpenPrev = open; Dirty = false; - WantClose = false; Color = color; } void DoOpen() { Open = true; } - void DoQueueClose() { WantClose = true; } void DoForceClose() { Open = false; Dirty = false; } void DoSave() { Dirty = false; } +}; + +struct ExampleAppDocuments +{ + ImVector Documents; + ImVector CloseQueue; + MyDocument* RenamingDoc = NULL; + bool RenamingStarted = false; + + ExampleAppDocuments() + { + Documents.push_back(MyDocument(0, "Lettuce", true, ImVec4(0.4f, 0.8f, 0.4f, 1.0f))); + Documents.push_back(MyDocument(1, "Eggplant", true, ImVec4(0.8f, 0.5f, 1.0f, 1.0f))); + Documents.push_back(MyDocument(2, "Carrot", true, ImVec4(1.0f, 0.8f, 0.5f, 1.0f))); + Documents.push_back(MyDocument(3, "Tomato", false, ImVec4(1.0f, 0.3f, 0.4f, 1.0f))); + Documents.push_back(MyDocument(4, "A Rather Long Title", false, ImVec4(0.4f, 0.8f, 0.8f, 1.0f))); + Documents.push_back(MyDocument(5, "Some Document", false, ImVec4(0.8f, 0.8f, 1.0f, 1.0f))); + } + + // As we allow to change document name, we append a never-changing document ID so tabs are stable + void GetTabName(MyDocument* doc, char* out_buf, size_t out_buf_size) + { + snprintf(out_buf, out_buf_size, "%s###doc%d", doc->Name, doc->UID); + } // Display placeholder contents for the Document - static void DisplayContents(MyDocument* doc) + void DisplayDocContents(MyDocument* doc) { ImGui::PushID(doc); ImGui::Text("Document \"%s\"", doc->Name); ImGui::PushStyleColor(ImGuiCol_Text, doc->Color); ImGui::TextWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); ImGui::PopStyleColor(); - if (ImGui::Button("Modify", ImVec2(100, 0))) - doc->Dirty = true; + + ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_R, ImGuiInputFlags_Tooltip); + if (ImGui::Button("Rename..")) + { + RenamingDoc = doc; + RenamingStarted = true; + } ImGui::SameLine(); - if (ImGui::Button("Save", ImVec2(100, 0))) + + ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_M, ImGuiInputFlags_Tooltip); + if (ImGui::Button("Modify")) + doc->Dirty = true; + + ImGui::SameLine(); + ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_S, ImGuiInputFlags_Tooltip); + if (ImGui::Button("Save")) doc->DoSave(); + + ImGui::SameLine(); + ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_W, ImGuiInputFlags_Tooltip); + if (ImGui::Button("Close")) + CloseQueue.push_back(doc); ImGui::ColorEdit3("color", &doc->Color.x); // Useful to test drag and drop and hold-dragged-to-open-tab behavior. ImGui::PopID(); } // Display context menu for the Document - static void DisplayContextMenu(MyDocument* doc) + void DisplayDocContextMenu(MyDocument* doc) { if (!ImGui::BeginPopupContextItem()) return; char buf[256]; sprintf(buf, "Save %s", doc->Name); - if (ImGui::MenuItem(buf, "CTRL+S", false, doc->Open)) + if (ImGui::MenuItem(buf, "Ctrl+S", false, doc->Open)) doc->DoSave(); - if (ImGui::MenuItem("Close", "CTRL+W", false, doc->Open)) - doc->DoQueueClose(); + if (ImGui::MenuItem("Rename...", "Ctrl+R", false, doc->Open)) + RenamingDoc = doc; + if (ImGui::MenuItem("Close", "Ctrl+W", false, doc->Open)) + CloseQueue.push_back(doc); ImGui::EndPopup(); } -}; -struct ExampleAppDocuments -{ - ImVector Documents; - - ExampleAppDocuments() + // [Optional] Notify the system of Tabs/Windows closure that happened outside the regular tab interface. + // If a tab has been closed programmatically (aka closed from another source such as the Checkbox() in the demo, + // as opposed to clicking on the regular tab closing button) and stops being submitted, it will take a frame for + // the tab bar to notice its absence. During this frame there will be a gap in the tab bar, and if the tab that has + // disappeared was the selected one, the tab bar will report no selected tab during the frame. This will effectively + // give the impression of a flicker for one frame. + // We call SetTabItemClosed() to manually notify the Tab Bar or Docking system of removed tabs to avoid this glitch. + // Note that this completely optional, and only affect tab bars with the ImGuiTabBarFlags_Reorderable flag. + void NotifyOfDocumentsClosedElsewhere() { - Documents.push_back(MyDocument("Lettuce", true, ImVec4(0.4f, 0.8f, 0.4f, 1.0f))); - Documents.push_back(MyDocument("Eggplant", true, ImVec4(0.8f, 0.5f, 1.0f, 1.0f))); - Documents.push_back(MyDocument("Carrot", true, ImVec4(1.0f, 0.8f, 0.5f, 1.0f))); - Documents.push_back(MyDocument("Tomato", false, ImVec4(1.0f, 0.3f, 0.4f, 1.0f))); - Documents.push_back(MyDocument("A Rather Long Title", false)); - Documents.push_back(MyDocument("Some Document", false)); + for (MyDocument& doc : Documents) + { + if (!doc.Open && doc.OpenPrev) + ImGui::SetTabItemClosed(doc.Name); + doc.OpenPrev = doc.Open; + } } }; -// [Optional] Notify the system of Tabs/Windows closure that happened outside the regular tab interface. -// If a tab has been closed programmatically (aka closed from another source such as the Checkbox() in the demo, -// as opposed to clicking on the regular tab closing button) and stops being submitted, it will take a frame for -// the tab bar to notice its absence. During this frame there will be a gap in the tab bar, and if the tab that has -// disappeared was the selected one, the tab bar will report no selected tab during the frame. This will effectively -// give the impression of a flicker for one frame. -// We call SetTabItemClosed() to manually notify the Tab Bar or Docking system of removed tabs to avoid this glitch. -// Note that this completely optional, and only affect tab bars with the ImGuiTabBarFlags_Reorderable flag. -static void NotifyOfDocumentsClosedElsewhere(ExampleAppDocuments& app) -{ - for (MyDocument& doc : app.Documents) - { - if (!doc.Open && doc.OpenPrev) - ImGui::SetTabItemClosed(doc.Name); - doc.OpenPrev = doc.Open; - } -} - void ShowExampleAppDocuments(bool* p_open) { static ExampleAppDocuments app; @@ -8639,8 +9985,8 @@ void ShowExampleAppDocuments(bool* p_open) } if (ImGui::MenuItem("Close All Documents", NULL, false, open_count > 0)) for (MyDocument& doc : app.Documents) - doc.DoQueueClose(); - if (ImGui::MenuItem("Exit", "Ctrl+F4") && p_open) + app.CloseQueue.push_back(&doc); + if (ImGui::MenuItem("Exit") && p_open) *p_open = false; ImGui::EndMenu(); } @@ -8682,10 +10028,11 @@ void ShowExampleAppDocuments(bool* p_open) if (opt_target == Target_Tab) { ImGuiTabBarFlags tab_bar_flags = (opt_fitting_flags) | (opt_reorderable ? ImGuiTabBarFlags_Reorderable : 0); + tab_bar_flags |= ImGuiTabBarFlags_DrawSelectedOverline; if (ImGui::BeginTabBar("##tabs", tab_bar_flags)) { if (opt_reorderable) - NotifyOfDocumentsClosedElsewhere(app); + app.NotifyOfDocumentsClosedElsewhere(); // [DEBUG] Stress tests //if ((ImGui::GetFrameCount() % 30) == 0) docs[1].Open ^= 1; // [DEBUG] Automatically show/hide a tab. Test various interactions e.g. dragging with this on. @@ -8697,20 +10044,23 @@ void ShowExampleAppDocuments(bool* p_open) if (!doc.Open) continue; + // As we allow to change document name, we append a never-changing document id so tabs are stable + char doc_name_buf[64]; + app.GetTabName(&doc, doc_name_buf, sizeof(doc_name_buf)); ImGuiTabItemFlags tab_flags = (doc.Dirty ? ImGuiTabItemFlags_UnsavedDocument : 0); - bool visible = ImGui::BeginTabItem(doc.Name, &doc.Open, tab_flags); + bool visible = ImGui::BeginTabItem(doc_name_buf, &doc.Open, tab_flags); // Cancel attempt to close when unsaved add to save queue so we can display a popup. if (!doc.Open && doc.Dirty) { doc.Open = true; - doc.DoQueueClose(); + app.CloseQueue.push_back(&doc); } - MyDocument::DisplayContextMenu(&doc); + app.DisplayDocContextMenu(&doc); if (visible) { - MyDocument::DisplayContents(&doc); + app.DisplayDocContents(&doc); ImGui::EndTabItem(); } } @@ -8722,7 +10072,7 @@ void ShowExampleAppDocuments(bool* p_open) { if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_DockingEnable) { - NotifyOfDocumentsClosedElsewhere(app); + app.NotifyOfDocumentsClosedElsewhere(); // Create a DockSpace node where any window can be docked ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); @@ -8743,12 +10093,12 @@ void ShowExampleAppDocuments(bool* p_open) if (!doc->Open && doc->Dirty) { doc->Open = true; - doc->DoQueueClose(); + app.CloseQueue.push_back(doc); } - MyDocument::DisplayContextMenu(doc); + app.DisplayDocContextMenu(doc); if (visible) - MyDocument::DisplayContents(doc); + app.DisplayDocContents(doc); ImGui::End(); } @@ -8766,33 +10116,44 @@ void ShowExampleAppDocuments(bool* p_open) return; } - // Update closing queue - static ImVector close_queue; - if (close_queue.empty()) + // Display renaming UI + if (app.RenamingDoc != NULL) { - // Close queue is locked once we started a popup - for (MyDocument& doc : app.Documents) - if (doc.WantClose) + if (app.RenamingStarted) + ImGui::OpenPopup("Rename"); + if (ImGui::BeginPopup("Rename")) + { + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 30); + if (ImGui::InputText("###Name", app.RenamingDoc->Name, IM_ARRAYSIZE(app.RenamingDoc->Name), ImGuiInputTextFlags_EnterReturnsTrue)) { - doc.WantClose = false; - close_queue.push_back(&doc); + ImGui::CloseCurrentPopup(); + app.RenamingDoc = NULL; } + if (app.RenamingStarted) + ImGui::SetKeyboardFocusHere(-1); + ImGui::EndPopup(); + } + else + { + app.RenamingDoc = NULL; + } + app.RenamingStarted = false; } // Display closing confirmation UI - if (!close_queue.empty()) + if (!app.CloseQueue.empty()) { int close_queue_unsaved_documents = 0; - for (int n = 0; n < close_queue.Size; n++) - if (close_queue[n]->Dirty) + for (int n = 0; n < app.CloseQueue.Size; n++) + if (app.CloseQueue[n]->Dirty) close_queue_unsaved_documents++; if (close_queue_unsaved_documents == 0) { // Close documents when all are unsaved - for (int n = 0; n < close_queue.Size; n++) - close_queue[n]->DoForceClose(); - close_queue.clear(); + for (int n = 0; n < app.CloseQueue.Size; n++) + app.CloseQueue[n]->DoForceClose(); + app.CloseQueue.clear(); } else { @@ -8803,37 +10164,35 @@ void ShowExampleAppDocuments(bool* p_open) ImGui::Text("Save change to the following items?"); float item_height = ImGui::GetTextLineHeightWithSpacing(); if (ImGui::BeginChild(ImGui::GetID("frame"), ImVec2(-FLT_MIN, 6.25f * item_height), ImGuiChildFlags_FrameStyle)) - { - for (int n = 0; n < close_queue.Size; n++) - if (close_queue[n]->Dirty) - ImGui::Text("%s", close_queue[n]->Name); - } + for (MyDocument* doc : app.CloseQueue) + if (doc->Dirty) + ImGui::Text("%s", doc->Name); ImGui::EndChild(); ImVec2 button_size(ImGui::GetFontSize() * 7.0f, 0.0f); if (ImGui::Button("Yes", button_size)) { - for (int n = 0; n < close_queue.Size; n++) + for (MyDocument* doc : app.CloseQueue) { - if (close_queue[n]->Dirty) - close_queue[n]->DoSave(); - close_queue[n]->DoForceClose(); + if (doc->Dirty) + doc->DoSave(); + doc->DoForceClose(); } - close_queue.clear(); + app.CloseQueue.clear(); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("No", button_size)) { - for (int n = 0; n < close_queue.Size; n++) - close_queue[n]->DoForceClose(); - close_queue.clear(); + for (MyDocument* doc : app.CloseQueue) + doc->DoForceClose(); + app.CloseQueue.clear(); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Cancel", button_size)) { - close_queue.clear(); + app.CloseQueue.clear(); ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); @@ -8844,6 +10203,404 @@ void ShowExampleAppDocuments(bool* p_open) ImGui::End(); } +//----------------------------------------------------------------------------- +// [SECTION] Example App: Assets Browser / ShowExampleAppAssetsBrowser() +//----------------------------------------------------------------------------- + +//#include "imgui_internal.h" // NavMoveRequestTryWrapping() + +struct ExampleAsset +{ + ImGuiID ID; + int Type; + + ExampleAsset(ImGuiID id, int type) { ID = id; Type = type; } + + static const ImGuiTableSortSpecs* s_current_sort_specs; + + static void SortWithSortSpecs(ImGuiTableSortSpecs* sort_specs, ExampleAsset* items, int items_count) + { + s_current_sort_specs = sort_specs; // Store in variable accessible by the sort function. + if (items_count > 1) + qsort(items, (size_t)items_count, sizeof(items[0]), ExampleAsset::CompareWithSortSpecs); + s_current_sort_specs = NULL; + } + + // Compare function to be used by qsort() + static int IMGUI_CDECL CompareWithSortSpecs(const void* lhs, const void* rhs) + { + const ExampleAsset* a = (const ExampleAsset*)lhs; + const ExampleAsset* b = (const ExampleAsset*)rhs; + for (int n = 0; n < s_current_sort_specs->SpecsCount; n++) + { + const ImGuiTableColumnSortSpecs* sort_spec = &s_current_sort_specs->Specs[n]; + int delta = 0; + if (sort_spec->ColumnIndex == 0) + delta = ((int)a->ID - (int)b->ID); + else if (sort_spec->ColumnIndex == 1) + delta = (a->Type - b->Type); + if (delta > 0) + return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? +1 : -1; + if (delta < 0) + return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? -1 : +1; + } + return ((int)a->ID - (int)b->ID); + } +}; +const ImGuiTableSortSpecs* ExampleAsset::s_current_sort_specs = NULL; + +struct ExampleAssetsBrowser +{ + // Options + bool ShowTypeOverlay = true; + bool AllowSorting = true; + bool AllowDragUnselected = false; + bool AllowBoxSelect = true; + float IconSize = 32.0f; + int IconSpacing = 10; + int IconHitSpacing = 4; // Increase hit-spacing if you want to make it possible to clear or box-select from gaps. Some spacing is required to able to amend with Shift+box-select. Value is small in Explorer. + bool StretchSpacing = true; + + // State + ImVector Items; // Our items + ExampleSelectionWithDeletion Selection; // Our selection (ImGuiSelectionBasicStorage + helper funcs to handle deletion) + ImGuiID NextItemId = 0; // Unique identifier when creating new items + bool RequestDelete = false; // Deferred deletion request + bool RequestSort = false; // Deferred sort request + float ZoomWheelAccum = 0.0f; // Mouse wheel accumulator to handle smooth wheels better + + // Calculated sizes for layout, output of UpdateLayoutSizes(). Could be locals but our code is simpler this way. + ImVec2 LayoutItemSize; + ImVec2 LayoutItemStep; // == LayoutItemSize + LayoutItemSpacing + float LayoutItemSpacing = 0.0f; + float LayoutSelectableSpacing = 0.0f; + float LayoutOuterPadding = 0.0f; + int LayoutColumnCount = 0; + int LayoutLineCount = 0; + + // Functions + ExampleAssetsBrowser() + { + AddItems(10000); + } + void AddItems(int count) + { + if (Items.Size == 0) + NextItemId = 0; + Items.reserve(Items.Size + count); + for (int n = 0; n < count; n++, NextItemId++) + Items.push_back(ExampleAsset(NextItemId, (NextItemId % 20) < 15 ? 0 : (NextItemId % 20) < 18 ? 1 : 2)); + RequestSort = true; + } + void ClearItems() + { + Items.clear(); + Selection.Clear(); + } + + // Logic would be written in the main code BeginChild() and outputing to local variables. + // We extracted it into a function so we can call it easily from multiple places. + void UpdateLayoutSizes(float avail_width) + { + // Layout: when not stretching: allow extending into right-most spacing. + LayoutItemSpacing = (float)IconSpacing; + if (StretchSpacing == false) + avail_width += floorf(LayoutItemSpacing * 0.5f); + + // Layout: calculate number of icon per line and number of lines + LayoutItemSize = ImVec2(floorf(IconSize), floorf(IconSize)); + LayoutColumnCount = IM_MAX((int)(avail_width / (LayoutItemSize.x + LayoutItemSpacing)), 1); + LayoutLineCount = (Items.Size + LayoutColumnCount - 1) / LayoutColumnCount; + + // Layout: when stretching: allocate remaining space to more spacing. Round before division, so item_spacing may be non-integer. + if (StretchSpacing && LayoutColumnCount > 1) + LayoutItemSpacing = floorf(avail_width - LayoutItemSize.x * LayoutColumnCount) / LayoutColumnCount; + + LayoutItemStep = ImVec2(LayoutItemSize.x + LayoutItemSpacing, LayoutItemSize.y + LayoutItemSpacing); + LayoutSelectableSpacing = IM_MAX(floorf(LayoutItemSpacing) - IconHitSpacing, 0.0f); + LayoutOuterPadding = floorf(LayoutItemSpacing * 0.5f); + } + + void Draw(const char* title, bool* p_open) + { + ImGui::SetNextWindowSize(ImVec2(IconSize * 25, IconSize * 15), ImGuiCond_FirstUseEver); + if (!ImGui::Begin(title, p_open, ImGuiWindowFlags_MenuBar)) + { + ImGui::End(); + return; + } + + // Menu bar + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem("Add 10000 items")) + AddItems(10000); + if (ImGui::MenuItem("Clear items")) + ClearItems(); + ImGui::Separator(); + if (ImGui::MenuItem("Close", NULL, false, p_open != NULL)) + *p_open = false; + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Edit")) + { + if (ImGui::MenuItem("Delete", "Del", false, Selection.Size > 0)) + RequestDelete = true; + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Options")) + { + ImGui::PushItemWidth(ImGui::GetFontSize() * 10); + + ImGui::SeparatorText("Contents"); + ImGui::Checkbox("Show Type Overlay", &ShowTypeOverlay); + ImGui::Checkbox("Allow Sorting", &AllowSorting); + + ImGui::SeparatorText("Selection Behavior"); + ImGui::Checkbox("Allow dragging unselected item", &AllowDragUnselected); + ImGui::Checkbox("Allow box-selection", &AllowBoxSelect); + + ImGui::SeparatorText("Layout"); + ImGui::SliderFloat("Icon Size", &IconSize, 16.0f, 128.0f, "%.0f"); + ImGui::SameLine(); HelpMarker("Use CTRL+Wheel to zoom"); + ImGui::SliderInt("Icon Spacing", &IconSpacing, 0, 32); + ImGui::SliderInt("Icon Hit Spacing", &IconHitSpacing, 0, 32); + ImGui::Checkbox("Stretch Spacing", &StretchSpacing); + ImGui::PopItemWidth(); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + // Show a table with ONLY one header row to showcase the idea/possibility of using this to provide a sorting UI + if (AllowSorting) + { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGuiTableFlags table_flags_for_sort_specs = ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Borders; + if (ImGui::BeginTable("for_sort_specs_only", 2, table_flags_for_sort_specs, ImVec2(0.0f, ImGui::GetFrameHeight()))) + { + ImGui::TableSetupColumn("Index"); + ImGui::TableSetupColumn("Type"); + ImGui::TableHeadersRow(); + if (ImGuiTableSortSpecs* sort_specs = ImGui::TableGetSortSpecs()) + if (sort_specs->SpecsDirty || RequestSort) + { + ExampleAsset::SortWithSortSpecs(sort_specs, Items.Data, Items.Size); + sort_specs->SpecsDirty = RequestSort = false; + } + ImGui::EndTable(); + } + ImGui::PopStyleVar(); + } + + ImGuiIO& io = ImGui::GetIO(); + ImGui::SetNextWindowContentSize(ImVec2(0.0f, LayoutOuterPadding + LayoutLineCount * (LayoutItemSize.x + LayoutItemSpacing))); + if (ImGui::BeginChild("Assets", ImVec2(0.0f, -ImGui::GetTextLineHeightWithSpacing()), ImGuiChildFlags_Border, ImGuiWindowFlags_NoMove)) + { + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + const float avail_width = ImGui::GetContentRegionAvail().x; + UpdateLayoutSizes(avail_width); + + // Calculate and store start position. + ImVec2 start_pos = ImGui::GetCursorScreenPos(); + start_pos = ImVec2(start_pos.x + LayoutOuterPadding, start_pos.y + LayoutOuterPadding); + ImGui::SetCursorScreenPos(start_pos); + + // Multi-select + ImGuiMultiSelectFlags ms_flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_ClearOnClickVoid; + + // - Enable box-select (in 2D mode, so that changing box-select rectangle X1/X2 boundaries will affect clipped items) + if (AllowBoxSelect) + ms_flags |= ImGuiMultiSelectFlags_BoxSelect2d; + + // - This feature allows dragging an unselected item without selecting it (rarely used) + if (AllowDragUnselected) + ms_flags |= ImGuiMultiSelectFlags_SelectOnClickRelease; + + // - Enable keyboard wrapping on X axis + // (FIXME-MULTISELECT: We haven't designed/exposed a general nav wrapping api yet, so this flag is provided as a courtesy to avoid doing: + // ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX); + // When we finish implementing a more general API for this, we will obsolete this flag in favor of the new system) + ms_flags |= ImGuiMultiSelectFlags_NavWrapX; + + ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ms_flags, Selection.Size, Items.Size); + + // Use custom selection adapter: store ID in selection (recommended) + Selection.UserData = this; + Selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self_, int idx) { ExampleAssetsBrowser* self = (ExampleAssetsBrowser*)self_->UserData; return self->Items[idx].ID; }; + Selection.ApplyRequests(ms_io); + + const bool want_delete = (ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (Selection.Size > 0)) || RequestDelete; + const int item_curr_idx_to_focus = want_delete ? Selection.ApplyDeletionPreLoop(ms_io, Items.Size) : -1; + RequestDelete = false; + + // Push LayoutSelectableSpacing (which is LayoutItemSpacing minus hit-spacing, if we decide to have hit gaps between items) + // Altering style ItemSpacing may seem unnecessary as we position every items using SetCursorScreenPos()... + // But it is necessary for two reasons: + // - Selectables uses it by default to visually fill the space between two items. + // - The vertical spacing would be measured by Clipper to calculate line height if we didn't provide it explicitly (here we do). + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(LayoutSelectableSpacing, LayoutSelectableSpacing)); + + // Rendering parameters + const ImU32 icon_type_overlay_colors[3] = { 0, IM_COL32(200, 70, 70, 255), IM_COL32(70, 170, 70, 255) }; + const ImU32 icon_bg_color = ImGui::GetColorU32(ImGuiCol_MenuBarBg); + const ImVec2 icon_type_overlay_size = ImVec2(4.0f, 4.0f); + const bool display_label = (LayoutItemSize.x >= ImGui::CalcTextSize("999").x); + + const int column_count = LayoutColumnCount; + ImGuiListClipper clipper; + clipper.Begin(LayoutLineCount, LayoutItemStep.y); + if (item_curr_idx_to_focus != -1) + clipper.IncludeItemByIndex(item_curr_idx_to_focus / column_count); // Ensure focused item line is not clipped. + if (ms_io->RangeSrcItem != -1) + clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem / column_count); // Ensure RangeSrc item line is not clipped. + while (clipper.Step()) + { + for (int line_idx = clipper.DisplayStart; line_idx < clipper.DisplayEnd; line_idx++) + { + const int item_min_idx_for_current_line = line_idx * column_count; + const int item_max_idx_for_current_line = IM_MIN((line_idx + 1) * column_count, Items.Size); + for (int item_idx = item_min_idx_for_current_line; item_idx < item_max_idx_for_current_line; ++item_idx) + { + ExampleAsset* item_data = &Items[item_idx]; + ImGui::PushID((int)item_data->ID); + + // Position item + ImVec2 pos = ImVec2(start_pos.x + (item_idx % column_count) * LayoutItemStep.x, start_pos.y + line_idx * LayoutItemStep.y); + ImGui::SetCursorScreenPos(pos); + + ImGui::SetNextItemSelectionUserData(item_idx); + bool item_is_selected = Selection.Contains((ImGuiID)item_data->ID); + bool item_is_visible = ImGui::IsRectVisible(LayoutItemSize); + ImGui::Selectable("", item_is_selected, ImGuiSelectableFlags_None, LayoutItemSize); + + // Update our selection state immediately (without waiting for EndMultiSelect() requests) + // because we use this to alter the color of our text/icon. + if (ImGui::IsItemToggledSelection()) + item_is_selected = !item_is_selected; + + // Focus (for after deletion) + if (item_curr_idx_to_focus == item_idx) + ImGui::SetKeyboardFocusHere(-1); + + // Drag and drop + if (ImGui::BeginDragDropSource()) + { + // Create payload with full selection OR single unselected item. + // (the later is only possible when using ImGuiMultiSelectFlags_SelectOnClickRelease) + if (ImGui::GetDragDropPayload() == NULL) + { + ImVector payload_items; + void* it = NULL; + ImGuiID id = 0; + if (!item_is_selected) + payload_items.push_back(item_data->ID); + else + while (Selection.GetNextSelectedItem(&it, &id)) + payload_items.push_back(id); + ImGui::SetDragDropPayload("ASSETS_BROWSER_ITEMS", payload_items.Data, (size_t)payload_items.size_in_bytes()); + } + + // Display payload content in tooltip, by extracting it from the payload data + // (we could read from selection, but it is more correct and reusable to read from payload) + const ImGuiPayload* payload = ImGui::GetDragDropPayload(); + const int payload_count = (int)payload->DataSize / (int)sizeof(ImGuiID); + ImGui::Text("%d assets", payload_count); + + ImGui::EndDragDropSource(); + } + + // Render icon (a real app would likely display an image/thumbnail here) + // Because we use ImGuiMultiSelectFlags_BoxSelect2d, clipping vertical may occasionally be larger, so we coarse-clip our rendering as well. + if (item_is_visible) + { + ImVec2 box_min(pos.x - 1, pos.y - 1); + ImVec2 box_max(box_min.x + LayoutItemSize.x + 2, box_min.y + LayoutItemSize.y + 2); // Dubious + draw_list->AddRectFilled(box_min, box_max, icon_bg_color); // Background color + if (ShowTypeOverlay && item_data->Type != 0) + { + ImU32 type_col = icon_type_overlay_colors[item_data->Type % IM_ARRAYSIZE(icon_type_overlay_colors)]; + draw_list->AddRectFilled(ImVec2(box_max.x - 2 - icon_type_overlay_size.x, box_min.y + 2), ImVec2(box_max.x - 2, box_min.y + 2 + icon_type_overlay_size.y), type_col); + } + if (display_label) + { + ImU32 label_col = ImGui::GetColorU32(item_is_selected ? ImGuiCol_Text : ImGuiCol_TextDisabled); + char label[32]; + sprintf(label, "%d", item_data->ID); + draw_list->AddText(ImVec2(box_min.x, box_max.y - ImGui::GetFontSize()), label_col, label); + } + } + + ImGui::PopID(); + } + } + } + clipper.End(); + ImGui::PopStyleVar(); // ImGuiStyleVar_ItemSpacing + + // Context menu + if (ImGui::BeginPopupContextWindow()) + { + ImGui::Text("Selection: %d items", Selection.Size); + ImGui::Separator(); + if (ImGui::MenuItem("Delete", "Del", false, Selection.Size > 0)) + RequestDelete = true; + ImGui::EndPopup(); + } + + ms_io = ImGui::EndMultiSelect(); + Selection.ApplyRequests(ms_io); + if (want_delete) + Selection.ApplyDeletionPostLoop(ms_io, Items, item_curr_idx_to_focus); + + // Zooming with CTRL+Wheel + if (ImGui::IsWindowAppearing()) + ZoomWheelAccum = 0.0f; + if (ImGui::IsWindowHovered() && io.MouseWheel != 0.0f && ImGui::IsKeyDown(ImGuiMod_Ctrl) && ImGui::IsAnyItemActive() == false) + { + ZoomWheelAccum += io.MouseWheel; + if (fabsf(ZoomWheelAccum) >= 1.0f) + { + // Calculate hovered item index from mouse location + // FIXME: Locking aiming on 'hovered_item_idx' (with a cool-down timer) would ensure zoom keeps on it. + const float hovered_item_nx = (io.MousePos.x - start_pos.x + LayoutItemSpacing * 0.5f) / LayoutItemStep.x; + const float hovered_item_ny = (io.MousePos.y - start_pos.y + LayoutItemSpacing * 0.5f) / LayoutItemStep.y; + const int hovered_item_idx = ((int)hovered_item_ny * LayoutColumnCount) + (int)hovered_item_nx; + //ImGui::SetTooltip("%f,%f -> item %d", hovered_item_nx, hovered_item_ny, hovered_item_idx); // Move those 4 lines in block above for easy debugging + + // Zoom + IconSize *= powf(1.1f, (float)(int)ZoomWheelAccum); + IconSize = IM_CLAMP(IconSize, 16.0f, 128.0f); + ZoomWheelAccum -= (int)ZoomWheelAccum; + UpdateLayoutSizes(avail_width); + + // Manipulate scroll to that we will land at the same Y location of currently hovered item. + // - Calculate next frame position of item under mouse + // - Set new scroll position to be used in next ImGui::BeginChild() call. + float hovered_item_rel_pos_y = ((float)(hovered_item_idx / LayoutColumnCount) + fmodf(hovered_item_ny, 1.0f)) * LayoutItemStep.y; + hovered_item_rel_pos_y += ImGui::GetStyle().WindowPadding.y; + float mouse_local_y = io.MousePos.y - ImGui::GetWindowPos().y; + ImGui::SetScrollY(hovered_item_rel_pos_y - mouse_local_y); + } + } + } + ImGui::EndChild(); + + ImGui::Text("Selected: %d/%d items", Selection.Size, Items.Size); + ImGui::End(); + } +}; + +void ShowExampleAppAssetsBrowser(bool* p_open) +{ + IMGUI_DEMO_MARKER("Examples/Assets Browser"); + static ExampleAssetsBrowser assets_browser; + assets_browser.Draw("Example: Assets Browser", p_open); +} + // End of Demo code #else diff --git a/libs/imgui/imgui_draw.cpp b/libs/imgui/imgui_draw.cpp index 659d578..7749151 100644 --- a/libs/imgui/imgui_draw.cpp +++ b/libs/imgui/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.4 +// dear imgui, v1.91.0 // (drawing and font code) /* @@ -8,6 +8,7 @@ Index of this file: // [SECTION] STB libraries implementation // [SECTION] Style functions // [SECTION] ImDrawList +// [SECTION] ImTriangulator, ImDrawList concave polygon fill // [SECTION] ImDrawListSplitter // [SECTION] ImDrawData // [SECTION] Helpers ShadeVertsXXX functions @@ -64,6 +65,7 @@ Index of this file: #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wreserved-identifier" // warning: identifier '_Xxx' is reserved because it starts with '_' followed by a capital letter +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #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 @@ -209,11 +211,13 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.20f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); - colors[ImGuiCol_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_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); + colors[ImGuiCol_TabSelected] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); + colors[ImGuiCol_TabSelectedOverline] = colors[ImGuiCol_HeaderActive]; + colors[ImGuiCol_TabDimmed] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabDimmedSelected] = ImLerp(colors[ImGuiCol_TabSelected], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); colors[ImGuiCol_DockingPreview] = colors[ImGuiCol_HeaderActive] * ImVec4(1.0f, 1.0f, 1.0f, 0.7f); colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); @@ -225,6 +229,7 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) 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_TextLink] = colors[ImGuiCol_HeaderActive]; 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); @@ -271,11 +276,13 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst) colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.10f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.78f, 0.82f, 1.00f, 0.60f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.78f, 0.82f, 1.00f, 0.90f); - colors[ImGuiCol_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_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); + colors[ImGuiCol_TabSelected] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); + colors[ImGuiCol_TabSelectedOverline] = colors[ImGuiCol_HeaderActive]; + colors[ImGuiCol_TabDimmed] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabDimmedSelected] = ImLerp(colors[ImGuiCol_TabSelected], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_TabDimmedSelectedOverline] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_DockingPreview] = colors[ImGuiCol_Header] * ImVec4(1.0f, 1.0f, 1.0f, 0.7f); colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); @@ -287,6 +294,7 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst) 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_TextLink] = colors[ImGuiCol_HeaderActive]; 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]; @@ -334,11 +342,13 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_ResizeGrip] = ImVec4(0.35f, 0.35f, 0.35f, 0.17f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); - colors[ImGuiCol_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_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.90f); + colors[ImGuiCol_TabSelected] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); + colors[ImGuiCol_TabSelectedOverline] = colors[ImGuiCol_HeaderActive]; + colors[ImGuiCol_TabDimmed] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabDimmedSelected] = ImLerp(colors[ImGuiCol_TabSelected], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.26f, 0.59f, 1.00f, 1.00f); colors[ImGuiCol_DockingPreview] = colors[ImGuiCol_Header] * ImVec4(1.0f, 1.0f, 1.0f, 0.7f); colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); colors[ImGuiCol_PlotLines] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); @@ -350,6 +360,7 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) 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_TextLink] = colors[ImGuiCol_HeaderActive]; 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]; @@ -389,6 +400,7 @@ void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) } // Initialize before use in a new frame. We always have a command ready in the buffer. +// In the majority of cases, you would want to call PushClipRect() and PushTextureID() after this. void ImDrawList::_ResetForNewFrame() { // Verify that the ImDrawCmd fields we want to memcmp() are contiguous in memory. @@ -1223,10 +1235,10 @@ void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, floa } } -void ImDrawList::PathEllipticalArcTo(const ImVec2& center, float radius_x, float radius_y, float rot, float a_min, float a_max, int num_segments) +void ImDrawList::PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, float rot, float a_min, float a_max, int num_segments) { if (num_segments <= 0) - num_segments = _CalcCircleAutoSegmentCount(ImMax(radius_x, radius_y)); // A bit pessimistic, maybe there's a better computation to do here. + num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. _Path.reserve(_Path.Size + (num_segments + 1)); @@ -1235,11 +1247,10 @@ void ImDrawList::PathEllipticalArcTo(const ImVec2& center, float radius_x, float for (int i = 0; i <= num_segments; i++) { const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min); - ImVec2 point(ImCos(a) * radius_x, ImSin(a) * radius_y); - const float rel_x = (point.x * cos_rot) - (point.y * sin_rot); - const float rel_y = (point.x * sin_rot) + (point.y * cos_rot); - point.x = rel_x + center.x; - point.y = rel_y + center.y; + ImVec2 point(ImCos(a) * radius.x, ImSin(a) * radius.y); + const ImVec2 rel((point.x * cos_rot) - (point.y * sin_rot), (point.x * sin_rot) + (point.y * cos_rot)); + point.x = rel.x + center.x; + point.y = rel.y + center.y; _Path.push_back(point); } } @@ -1564,31 +1575,31 @@ void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, in } // Ellipse -void ImDrawList::AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot, int num_segments, float thickness) +void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments, float thickness) { if ((col & IM_COL32_A_MASK) == 0) return; if (num_segments <= 0) - num_segments = _CalcCircleAutoSegmentCount(ImMax(radius_x, radius_y)); // A bit pessimistic, maybe there's a better computation to do here. + num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments; - PathEllipticalArcTo(center, radius_x, radius_y, rot, 0.0f, a_max, num_segments - 1); + PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1); PathStroke(col, true, thickness); } -void ImDrawList::AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot, int num_segments) +void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments) { if ((col & IM_COL32_A_MASK) == 0) return; if (num_segments <= 0) - num_segments = _CalcCircleAutoSegmentCount(ImMax(radius_x, radius_y)); // A bit pessimistic, maybe there's a better computation to do here. + num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments; - PathEllipticalArcTo(center, radius_x, radius_y, rot, 0.0f, a_max, num_segments - 1); + PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1); PathFillConvex(col); } @@ -1619,10 +1630,11 @@ void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, if ((col & IM_COL32_A_MASK) == 0) return; + // Accept null ranges + if (text_begin == text_end || text_begin[0] == 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) @@ -1706,6 +1718,316 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi PopTextureID(); } +//----------------------------------------------------------------------------- +// [SECTION] ImTriangulator, ImDrawList concave polygon fill +//----------------------------------------------------------------------------- +// Triangulate concave polygons. Based on "Triangulation by Ear Clipping" paper, O(N^2) complexity. +// Reference: https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf +// Provided as a convenience for user but not used by main library. +//----------------------------------------------------------------------------- +// - ImTriangulator [Internal] +// - AddConcavePolyFilled() +//----------------------------------------------------------------------------- + +enum ImTriangulatorNodeType +{ + ImTriangulatorNodeType_Convex, + ImTriangulatorNodeType_Ear, + ImTriangulatorNodeType_Reflex +}; + +struct ImTriangulatorNode +{ + ImTriangulatorNodeType Type; + int Index; + ImVec2 Pos; + ImTriangulatorNode* Next; + ImTriangulatorNode* Prev; + + void Unlink() { Next->Prev = Prev; Prev->Next = Next; } +}; + +struct ImTriangulatorNodeSpan +{ + ImTriangulatorNode** Data = NULL; + int Size = 0; + + void push_back(ImTriangulatorNode* node) { Data[Size++] = node; } + void find_erase_unsorted(int idx) { for (int i = Size - 1; i >= 0; i--) if (Data[i]->Index == idx) { Data[i] = Data[Size - 1]; Size--; return; } } +}; + +struct ImTriangulator +{ + static int EstimateTriangleCount(int points_count) { return (points_count < 3) ? 0 : points_count - 2; } + static int EstimateScratchBufferSize(int points_count) { return sizeof(ImTriangulatorNode) * points_count + sizeof(ImTriangulatorNode*) * points_count * 2; } + + void Init(const ImVec2* points, int points_count, void* scratch_buffer); + void GetNextTriangle(unsigned int out_triangle[3]); // Return relative indexes for next triangle + + // Internal functions + void BuildNodes(const ImVec2* points, int points_count); + void BuildReflexes(); + void BuildEars(); + void FlipNodeList(); + bool IsEar(int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const; + void ReclassifyNode(ImTriangulatorNode* node); + + // Internal members + int _TrianglesLeft = 0; + ImTriangulatorNode* _Nodes = NULL; + ImTriangulatorNodeSpan _Ears; + ImTriangulatorNodeSpan _Reflexes; +}; + +// Distribute storage for nodes, ears and reflexes. +// FIXME-OPT: if everything is convex, we could report it to caller and let it switch to an convex renderer +// (this would require first building reflexes to bail to convex if empty, without even building nodes) +void ImTriangulator::Init(const ImVec2* points, int points_count, void* scratch_buffer) +{ + IM_ASSERT(scratch_buffer != NULL && points_count >= 3); + _TrianglesLeft = EstimateTriangleCount(points_count); + _Nodes = (ImTriangulatorNode*)scratch_buffer; // points_count x Node + _Ears.Data = (ImTriangulatorNode**)(_Nodes + points_count); // points_count x Node* + _Reflexes.Data = (ImTriangulatorNode**)(_Nodes + points_count) + points_count; // points_count x Node* + BuildNodes(points, points_count); + BuildReflexes(); + BuildEars(); +} + +void ImTriangulator::BuildNodes(const ImVec2* points, int points_count) +{ + for (int i = 0; i < points_count; i++) + { + _Nodes[i].Type = ImTriangulatorNodeType_Convex; + _Nodes[i].Index = i; + _Nodes[i].Pos = points[i]; + _Nodes[i].Next = _Nodes + i + 1; + _Nodes[i].Prev = _Nodes + i - 1; + } + _Nodes[0].Prev = _Nodes + points_count - 1; + _Nodes[points_count - 1].Next = _Nodes; +} + +void ImTriangulator::BuildReflexes() +{ + ImTriangulatorNode* n1 = _Nodes; + for (int i = _TrianglesLeft; i >= 0; i--, n1 = n1->Next) + { + if (ImTriangleIsClockwise(n1->Prev->Pos, n1->Pos, n1->Next->Pos)) + continue; + n1->Type = ImTriangulatorNodeType_Reflex; + _Reflexes.push_back(n1); + } +} + +void ImTriangulator::BuildEars() +{ + ImTriangulatorNode* n1 = _Nodes; + for (int i = _TrianglesLeft; i >= 0; i--, n1 = n1->Next) + { + if (n1->Type != ImTriangulatorNodeType_Convex) + continue; + if (!IsEar(n1->Prev->Index, n1->Index, n1->Next->Index, n1->Prev->Pos, n1->Pos, n1->Next->Pos)) + continue; + n1->Type = ImTriangulatorNodeType_Ear; + _Ears.push_back(n1); + } +} + +void ImTriangulator::GetNextTriangle(unsigned int out_triangle[3]) +{ + if (_Ears.Size == 0) + { + FlipNodeList(); + + ImTriangulatorNode* node = _Nodes; + for (int i = _TrianglesLeft; i >= 0; i--, node = node->Next) + node->Type = ImTriangulatorNodeType_Convex; + _Reflexes.Size = 0; + BuildReflexes(); + BuildEars(); + + // If we still don't have ears, it means geometry is degenerated. + if (_Ears.Size == 0) + { + // Return first triangle available, mimicking the behavior of convex fill. + IM_ASSERT(_TrianglesLeft > 0); // Geometry is degenerated + _Ears.Data[0] = _Nodes; + _Ears.Size = 1; + } + } + + ImTriangulatorNode* ear = _Ears.Data[--_Ears.Size]; + out_triangle[0] = ear->Prev->Index; + out_triangle[1] = ear->Index; + out_triangle[2] = ear->Next->Index; + + ear->Unlink(); + if (ear == _Nodes) + _Nodes = ear->Next; + + ReclassifyNode(ear->Prev); + ReclassifyNode(ear->Next); + _TrianglesLeft--; +} + +void ImTriangulator::FlipNodeList() +{ + ImTriangulatorNode* prev = _Nodes; + ImTriangulatorNode* temp = _Nodes; + ImTriangulatorNode* current = _Nodes->Next; + prev->Next = prev; + prev->Prev = prev; + while (current != _Nodes) + { + temp = current->Next; + + current->Next = prev; + prev->Prev = current; + _Nodes->Next = current; + current->Prev = _Nodes; + + prev = current; + current = temp; + } + _Nodes = prev; +} + +// A triangle is an ear is no other vertex is inside it. We can test reflexes vertices only (see reference algorithm) +bool ImTriangulator::IsEar(int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const +{ + ImTriangulatorNode** p_end = _Reflexes.Data + _Reflexes.Size; + for (ImTriangulatorNode** p = _Reflexes.Data; p < p_end; p++) + { + ImTriangulatorNode* reflex = *p; + if (reflex->Index != i0 && reflex->Index != i1 && reflex->Index != i2) + if (ImTriangleContainsPoint(v0, v1, v2, reflex->Pos)) + return false; + } + return true; +} + +void ImTriangulator::ReclassifyNode(ImTriangulatorNode* n1) +{ + // Classify node + ImTriangulatorNodeType type; + const ImTriangulatorNode* n0 = n1->Prev; + const ImTriangulatorNode* n2 = n1->Next; + if (!ImTriangleIsClockwise(n0->Pos, n1->Pos, n2->Pos)) + type = ImTriangulatorNodeType_Reflex; + else if (IsEar(n0->Index, n1->Index, n2->Index, n0->Pos, n1->Pos, n2->Pos)) + type = ImTriangulatorNodeType_Ear; + else + type = ImTriangulatorNodeType_Convex; + + // Update lists when a type changes + if (type == n1->Type) + return; + if (n1->Type == ImTriangulatorNodeType_Reflex) + _Reflexes.find_erase_unsorted(n1->Index); + else if (n1->Type == ImTriangulatorNodeType_Ear) + _Ears.find_erase_unsorted(n1->Index); + if (type == ImTriangulatorNodeType_Reflex) + _Reflexes.push_back(n1); + else if (type == ImTriangulatorNodeType_Ear) + _Ears.push_back(n1); + n1->Type = type; +} + +// Use ear-clipping algorithm to triangulate a simple polygon (no self-interaction, no holes). +// (Reminder: we don't perform any coarse clipping/culling in ImDrawList layer! +// It is up to caller to ensure not making costly calls that will be outside of visible area. +// As concave fill is noticeably more expensive than other primitives, be mindful of this... +// Caller can build AABB of points, and avoid filling if 'draw_list->_CmdHeader.ClipRect.Overlays(points_bb) == false') +void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_count, ImU32 col) +{ + if (points_count < 3 || (col & IM_COL32_A_MASK) == 0) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + ImTriangulator triangulator; + unsigned int triangle[3]; + 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; + + _Data->TempBuffer.reserve_discard((ImTriangulator::EstimateScratchBufferSize(points_count) + sizeof(ImVec2)) / sizeof(ImVec2)); + triangulator.Init(points, points_count, _Data->TempBuffer.Data); + while (triangulator._TrianglesLeft > 0) + { + triangulator.GetNextTriangle(triangle); + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (triangle[0] << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (triangle[1] << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + (triangle[2] << 1)); + _IdxWritePtr += 3; + } + + // Compute normals + _Data->TempBuffer.reserve_discard(points_count); + ImVec2* temp_normals = _Data->TempBuffer.Data; + 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++; + } + _Data->TempBuffer.reserve_discard((ImTriangulator::EstimateScratchBufferSize(points_count) + sizeof(ImVec2)) / sizeof(ImVec2)); + triangulator.Init(points, points_count, _Data->TempBuffer.Data); + while (triangulator._TrianglesLeft > 0) + { + triangulator.GetNextTriangle(triangle); + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx + triangle[0]); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + triangle[1]); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + triangle[2]); + _IdxWritePtr += 3; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } +} //----------------------------------------------------------------------------- // [SECTION] ImDrawListSplitter @@ -2678,8 +3000,8 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) int unscaled_ascent, unscaled_descent, unscaled_line_gap; stbtt_GetFontVMetrics(&src_tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); - const float ascent = ImTrunc(unscaled_ascent * font_scale + ((unscaled_ascent > 0.0f) ? +1 : -1)); - const float descent = ImTrunc(unscaled_descent * font_scale + ((unscaled_descent > 0.0f) ? +1 : -1)); + const float ascent = ImCeil(unscaled_ascent * font_scale); + const float descent = ImFloor(unscaled_descent * font_scale); 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); @@ -3774,6 +4096,8 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im { x = start_x; y += line_height; + if (y > clip_rect.w) + break; // break out of main loop word_wrap_eol = NULL; s = CalcWordWrapNextLineStartA(s, text_end); // Wrapping skips upcoming blanks continue; diff --git a/libs/imgui/imgui_internal.h b/libs/imgui/imgui_internal.h index 2d243a1..343db13 100644 --- a/libs/imgui/imgui_internal.h +++ b/libs/imgui/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.90.4 +// dear imgui, v1.91.0 // (internal structures/api) // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. @@ -14,14 +14,15 @@ Index of this file: // [SECTION] Macros // [SECTION] Generic helpers // [SECTION] ImDrawList support -// [SECTION] Widgets support: flags, enums, data structures // [SECTION] Data types support +// [SECTION] Widgets support: flags, enums, data structures // [SECTION] Popup support // [SECTION] Inputs support // [SECTION] Clipper support // [SECTION] Navigation support // [SECTION] Typing-select support // [SECTION] Columns support +// [SECTION] Box-select support // [SECTION] Multi-select support // [SECTION] Docking support // [SECTION] Viewport support @@ -87,10 +88,13 @@ Index of this file: #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' +#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #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 +#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 +#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #endif // In 1.89.4, we moved the implementation of "courtesy maths operators" from imgui_internal.h in imgui.h @@ -121,10 +125,11 @@ 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 ImGuiBoxSelectState; // Box-selection state (currently used by multi-selection, could potentially be used by others) struct ImGuiColorMod; // Stacked color modifier, backup of modified data so we can restore it struct ImGuiContext; // Main Dear ImGui context struct ImGuiContextHook; // Hook for extensions like ImGuiTestEngine -struct ImGuiDataVarInfo; // Variable information (e.g. to avoid style variables from an enum) +struct ImGuiDataVarInfo; // Variable information (e.g. to access style variables from an enum) struct ImGuiDataTypeInfo; // Type information associated to a ImGuiDataType enum struct ImGuiDockContext; // Docking system context struct ImGuiDockRequest; // Docking system dock/undock queued request @@ -136,8 +141,9 @@ struct ImGuiInputTextDeactivateData;// Short term storage to backup text of a de struct ImGuiLastItemData; // Status storage for last submitted items struct ImGuiLocEntry; // A localization entry. struct ImGuiMenuColumns; // Simple column measurement, currently used for MenuItem() only +struct ImGuiMultiSelectState; // Multi-selection persistent state (for focused selection). +struct ImGuiMultiSelectTempData; // Multi-selection temporary state (while traversing). struct ImGuiNavItemData; // Result of a gamepad/keyboard directional navigation move query result -struct ImGuiNavTreeNodeData; // Temporary storage for last TreeNode() being a Left arrow landing candidate. struct ImGuiMetricsConfig; // Storage for ShowMetricsWindow() and DebugNodeXXX() functions struct ImGuiNextWindowData; // Storage for SetNextWindow** functions struct ImGuiNextItemData; // Storage for SetNextItem** functions @@ -150,14 +156,17 @@ struct ImGuiStyleMod; // Stacked style modifier, backup of modifie 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 ImGuiTableHeaderData; // Storage for TableAngledHeadersRow() 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 ImGuiTreeNodeStackData; // Temporary storage for TreeNode(). struct ImGuiTypingSelectState; // Storage for GetTypingSelectRequest() struct ImGuiTypingSelectRequest; // Storage for GetTypingSelectRequest() (aimed to be public) struct ImGuiWindow; // Storage for one window +struct ImGuiWindowDockStyle; // Storage for window-style data which needs to be stored for docking purpose struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame, in practice we currently keep it for each window) struct ImGuiWindowSettings; // Storage for a window .ini settings (we keep one of those even if the actual window wasn't instanced during this session) @@ -171,8 +180,6 @@ typedef int ImGuiLayoutType; // -> enum ImGuiLayoutType_ // E typedef int ImGuiActivateFlags; // -> enum ImGuiActivateFlags_ // Flags: for navigation/focus function (will be for ActivateItem() later) typedef int ImGuiDebugLogFlags; // -> enum ImGuiDebugLogFlags_ // Flags: for ShowDebugLogWindow(), g.DebugLogFlags typedef int ImGuiFocusRequestFlags; // -> enum ImGuiFocusRequestFlags_ // Flags: for FocusWindow(); -typedef int ImGuiInputFlags; // -> enum ImGuiInputFlags_ // Flags: for IsKeyPressed(), IsMouseClicked(), SetKeyOwner(), SetItemKeyOwner() etc. -typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag(), g.LastItemData.InFlags typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for g.LastItemData.StatusFlags typedef int ImGuiOldColumnFlags; // -> enum ImGuiOldColumnFlags_ // Flags: for BeginColumns() typedef int ImGuiNavHighlightFlags; // -> enum ImGuiNavHighlightFlags_ // Flags: for RenderNavHighlight() @@ -184,6 +191,7 @@ typedef int ImGuiSeparatorFlags; // -> enum ImGuiSeparatorFlags_ // F typedef int ImGuiTextFlags; // -> enum ImGuiTextFlags_ // Flags: for TextEx() typedef int ImGuiTooltipFlags; // -> enum ImGuiTooltipFlags_ // Flags: for BeginTooltipEx() typedef int ImGuiTypingSelectFlags; // -> enum ImGuiTypingSelectFlags_ // Flags: for GetTypingSelectRequest() +typedef int ImGuiWindowRefreshFlags; // -> enum ImGuiWindowRefreshFlags_ // Flags: for SetNextWindowRefreshPolicy() typedef void (*ImGuiErrorLogCallback)(void* user_data, const char* fmt, ...); @@ -355,6 +363,7 @@ namespace ImStb // - Helper: ImPool<> // - Helper: ImChunkStream<> // - Helper: ImGuiTextIndex +// - Helper: ImGuiStorage //----------------------------------------------------------------------------- // Helpers: Hashing @@ -391,6 +400,7 @@ IM_MSVC_RUNTIME_CHECKS_OFF static inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c &= ~32 : c; } static inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } static inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } +static inline bool ImCharIsXdigitA(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } IM_MSVC_RUNTIME_CHECKS_RESTORE // Helpers: Formatting @@ -414,6 +424,7 @@ IMGUI_API int ImTextCountCharsFromUtf8(const char* in_text, const char 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 IMGUI_API const char* ImTextFindPreviousUtf8Codepoint(const char* in_text_start, const char* in_text_curr); // return previous UTF-8 code-point. +IMGUI_API int ImTextCountLines(const char* in_text, const char* in_text_end); // return number of lines taken by text. trailing carriage return doesn't count as an extra line. // Helpers: File System #ifdef IMGUI_DISABLE_FILE_FUNCTIONS @@ -478,7 +489,7 @@ template static inline T ImSubClampOverflow(T a, T b, T mn, T mx) // - 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 ImClamp(const ImVec2& v, const ImVec2&mn, const ImVec2&mx) { return ImVec2((v.x < mn.x) ? mn.x : (v.x > mx.x) ? mx.x : v.x, (v.y < mn.y) ? mn.y : (v.y > mx.y) ? mx.y : v.y); } static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t) { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); } static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); } static inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); } @@ -494,6 +505,7 @@ static inline int ImModPositive(int a, int b) static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } +static inline float ImLinearRemapClamp(float s0, float s1, float d0, float d1, float x) { return ImSaturate((x - s0) / (s1 - s0)) * (d1 - d0) + d0; } static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } static inline bool ImIsFloatAboveGuaranteedIntegerPrecision(float f) { return f <= -16777216 || f >= 16777216; } static inline float ImExponentialMovingAverage(float avg, float sample, int n) { avg -= avg / n; avg += sample / n; return avg; } @@ -508,7 +520,8 @@ IMGUI_API ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const 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; } +inline float ImTriangleArea(const ImVec2& a, const ImVec2& b, const ImVec2& c) { return ImFabs((a.x * (b.y - c.y)) + (b.x * (c.y - a.y)) + (c.x * (a.y - b.y))) * 0.5f; } +inline bool ImTriangleIsClockwise(const ImVec2& a, const ImVec2& b, const ImVec2& c) { return ((b.x - a.x) * (c.y - b.y)) - ((c.x - b.x) * (b.y - a.y)) > 0.0f; } // Helper: ImVec1 (1D vector) // (this odd construct is used to facilitate the transition between 1D and 2D, and the maintenance of some branches/patches) @@ -727,7 +740,7 @@ struct ImChunkStream void swap(ImChunkStream& rhs) { rhs.Buf.swap(Buf); } }; -// Helper: ImGuiTextIndex<> +// Helper: ImGuiTextIndex // Maintain a line index for a text buffer. This is a strong candidate to be moved into the public API. struct ImGuiTextIndex { @@ -741,6 +754,8 @@ struct ImGuiTextIndex void append(const char* base, int old_size, int new_size); }; +// Helper: ImGuiStorage +IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStoragePair* in_end, ImGuiID key); //----------------------------------------------------------------------------- // [SECTION] ImDrawList support //----------------------------------------------------------------------------- @@ -777,6 +792,7 @@ 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 FontScale; // Current/default font scale (== FontSize / Font->FontSize) float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() float CircleSegmentMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() @@ -803,33 +819,65 @@ struct ImDrawDataBuilder ImDrawDataBuilder() { memset(this, 0, sizeof(*this)); } }; +//----------------------------------------------------------------------------- +// [SECTION] Data types support +//----------------------------------------------------------------------------- + +struct ImGuiDataVarInfo +{ + ImGuiDataType Type; + ImU32 Count; // 1+ + ImU32 Offset; // Offset in parent structure + void* GetVarPtr(void* parent) const { return (void*)((unsigned char*)parent + Offset); } +}; + +struct ImGuiDataTypeStorage +{ + ImU8 Data[8]; // Opaque storage to fit any data up to ImGuiDataType_COUNT +}; + +// Type information associated to one ImGuiDataType. Retrieve with DataTypeGetInfo(). +struct ImGuiDataTypeInfo +{ + 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, +}; + //----------------------------------------------------------------------------- // [SECTION] Widgets support: flags, enums, data structures //----------------------------------------------------------------------------- -// Flags used by upcoming items +// Extend ImGuiItemFlags // - input: PushItemFlag() manipulates g.CurrentItemFlags, ItemAdd() calls may add extra flags. // - output: stored in g.LastItemData.InFlags -// Current window shared by all windows. -// This is going to be exposed in imgui.h when stabilized enough. -enum ImGuiItemFlags_ +enum ImGuiItemFlagsPrivate_ { // Controlled by user - ImGuiItemFlags_None = 0, - ImGuiItemFlags_NoTabStop = 1 << 0, // false // Disable keyboard tabbing. This is a "lighter" version of ImGuiItemFlags_NoNav. - ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings. - ImGuiItemFlags_Disabled = 1 << 2, // false // Disable interactions but doesn't affect visuals. See BeginDisabled()/EndDisabled(). See github.com/ocornut/imgui/issues/211 - ImGuiItemFlags_NoNav = 1 << 3, // false // Disable any form of focusing (keyboard/gamepad directional navigation and SetKeyboardFocusHere() calls) - ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false // Disable item being a candidate for default focus (e.g. used by title bar items) - ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // Disable MenuItem/Selectable() automatically closing their popup window - ImGuiItemFlags_MixedValue = 1 << 6, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets) - ImGuiItemFlags_ReadOnly = 1 << 7, // false // [ALPHA] Allow hovering interactions but underlying value is not changed. - ImGuiItemFlags_NoWindowHoverableCheck = 1 << 8, // false // Disable hoverable check in ItemHoverable() - ImGuiItemFlags_AllowOverlap = 1 << 9, // false // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame. + ImGuiItemFlags_Disabled = 1 << 10, // false // Disable interactions (DOES NOT affect visuals, see BeginDisabled()/EndDisabled() for full disable feature, and github #211). + ImGuiItemFlags_ReadOnly = 1 << 11, // false // [ALPHA] Allow hovering interactions but underlying value is not changed. + ImGuiItemFlags_MixedValue = 1 << 12, // 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_NoWindowHoverableCheck = 1 << 13, // false // Disable hoverable check in ItemHoverable() + ImGuiItemFlags_AllowOverlap = 1 << 14, // false // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame. // Controlled by widget code - ImGuiItemFlags_Inputable = 1 << 10, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature. - ImGuiItemFlags_HasSelectionUserData = 1 << 11, // false // Set by SetNextItemSelectionUserData() + ImGuiItemFlags_Inputable = 1 << 20, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature. + ImGuiItemFlags_HasSelectionUserData = 1 << 21, // false // Set by SetNextItemSelectionUserData() + ImGuiItemFlags_IsMultiSelect = 1 << 22, // false // Set by SetNextItemSelectionUserData() + + ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups, // Please don't change, use PushItemFlag() instead. + + // Obsolete + //ImGuiItemFlags_SelectableDontClosePopup = !ImGuiItemFlags_AutoClosePopups, // Can't have a redirect as we inverted the behavior }; // Status flags for an already submitted item @@ -846,7 +894,8 @@ enum ImGuiItemStatusFlags_ 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_Visible = 1 << 8, // [WIP] Set when item is overlapping the current clipping rectangle (Used internally. Please don't use yet: API/system will change as we refactor Itemadd()). - ImGuiItemStatusFlags_HasClipRect = 1 << 9, // g.LastItemData.ClipRect is valid + ImGuiItemStatusFlags_HasClipRect = 1 << 9, // g.LastItemData.ClipRect is valid. + ImGuiItemStatusFlags_HasShortcut = 1 << 10, // g.LastItemData.Shortcut valid. Set by SetNextItemShortcut() -> ItemAdd(). // Additional status + semantic for ImGuiTestEngine #ifdef IMGUI_ENABLE_TEST_ENGINE @@ -873,6 +922,7 @@ enum ImGuiInputTextFlagsPrivate_ 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. + ImGuiInputTextFlags_LocalizeDecimalPoint= 1 << 29, // For internal use by InputScalar() and TempInputScalar() }; // Extend ImGuiButtonFlags_ @@ -892,7 +942,7 @@ enum ImGuiButtonFlagsPrivate_ ImGuiButtonFlags_AlignTextBaseLine = 1 << 15, // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by SmallButton(), not possible currently because of DC.CursorPosPrevLine ImGuiButtonFlags_NoKeyModifiers = 1 << 16, // disable mouse interaction if a key modifier is held ImGuiButtonFlags_NoHoldingActiveId = 1 << 17, // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only) - ImGuiButtonFlags_NoNavFocus = 1 << 18, // don't override navigation focus when activated (FIXME: this is essentially used everytime an item uses ImGuiItemFlags_NoNav, but because legacy specs don't requires LastItemData to be set ButtonBehavior(), we can't poll g.LastItemData.InFlags) + ImGuiButtonFlags_NoNavFocus = 1 << 18, // don't override navigation focus when activated (FIXME: this is essentially used every time an item uses ImGuiItemFlags_NoNav, but because legacy specs don't requires LastItemData to be set ButtonBehavior(), we can't poll g.LastItemData.InFlags) ImGuiButtonFlags_NoHoveredOnFocus = 1 << 19, // don't report as hovered when nav focus is on this item ImGuiButtonFlags_NoSetKeyOwner = 1 << 20, // don't set key/input owner on the initial click (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) ImGuiButtonFlags_NoTestKeyOwner = 1 << 21, // don't test key/input owner when polling the key (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) @@ -930,8 +980,8 @@ enum ImGuiSelectableFlagsPrivate_ // Extend ImGuiTreeNodeFlags_ enum ImGuiTreeNodeFlagsPrivate_ { - ImGuiTreeNodeFlags_ClipLabelForTrailingButton = 1 << 20, - ImGuiTreeNodeFlags_UpsideDownArrow = 1 << 21,// (FIXME-WIP) Turn Down arrow into an Up arrow, but reversed trees (#6517) + ImGuiTreeNodeFlags_ClipLabelForTrailingButton = 1 << 28,// FIXME-WIP: Hard-coded for CollapsingHeader() + ImGuiTreeNodeFlags_UpsideDownArrow = 1 << 29,// FIXME-WIP: Turn Down arrow into an Up arrow, but reversed trees (#6517) }; enum ImGuiSeparatorFlags_ @@ -1121,6 +1171,15 @@ struct IMGUI_API ImGuiInputTextState }; +enum ImGuiWindowRefreshFlags_ +{ + ImGuiWindowRefreshFlags_None = 0, + ImGuiWindowRefreshFlags_TryToAvoidRefresh = 1 << 0, // [EXPERIMENTAL] Try to keep existing contents, USER MUST NOT HONOR BEGIN() RETURNING FALSE AND NOT APPEND. + ImGuiWindowRefreshFlags_RefreshOnHover = 1 << 1, // [EXPERIMENTAL] Always refresh on hover + ImGuiWindowRefreshFlags_RefreshOnFocus = 1 << 2, // [EXPERIMENTAL] Always refresh on focus + // Refresh policy/frequency, Load Balancing etc. +}; + enum ImGuiNextWindowDataFlags_ { ImGuiNextWindowDataFlags_None = 0, @@ -1133,9 +1192,10 @@ enum ImGuiNextWindowDataFlags_ ImGuiNextWindowDataFlags_HasBgAlpha = 1 << 6, ImGuiNextWindowDataFlags_HasScroll = 1 << 7, ImGuiNextWindowDataFlags_HasChildFlags = 1 << 8, - ImGuiNextWindowDataFlags_HasViewport = 1 << 9, - ImGuiNextWindowDataFlags_HasDock = 1 << 10, - ImGuiNextWindowDataFlags_HasWindowClass = 1 << 11, + ImGuiNextWindowDataFlags_HasRefreshPolicy = 1 << 9, + ImGuiNextWindowDataFlags_HasViewport = 1 << 10, + ImGuiNextWindowDataFlags_HasDock = 1 << 11, + ImGuiNextWindowDataFlags_HasWindowClass = 1 << 12, }; // Storage for SetNexWindow** functions @@ -1162,33 +1222,36 @@ struct ImGuiNextWindowData ImGuiID DockId; ImGuiWindowClass WindowClass; ImVec2 MenuBarOffsetMinVal; // (Always on) This is not exposed publicly, so we don't clear it and it doesn't have a corresponding flag (could we? for consistency?) + ImGuiWindowRefreshFlags RefreshFlagsVal; ImGuiNextWindowData() { memset(this, 0, sizeof(*this)); } inline void ClearFlags() { Flags = ImGuiNextWindowDataFlags_None; } }; -// Multi-Selection item index or identifier when using SetNextItemSelectionUserData()/BeginMultiSelect() -// (Most users are likely to use this store an item INDEX but this may be used to store a POINTER as well.) -typedef ImS64 ImGuiSelectionUserData; - enum ImGuiNextItemDataFlags_ { ImGuiNextItemDataFlags_None = 0, ImGuiNextItemDataFlags_HasWidth = 1 << 0, ImGuiNextItemDataFlags_HasOpen = 1 << 1, ImGuiNextItemDataFlags_HasShortcut = 1 << 2, + ImGuiNextItemDataFlags_HasRefVal = 1 << 3, + ImGuiNextItemDataFlags_HasStorageID = 1 << 4, }; struct ImGuiNextItemData { ImGuiNextItemDataFlags Flags; - ImGuiItemFlags ItemFlags; // Currently only tested/used for ImGuiItemFlags_AllowOverlap. + ImGuiItemFlags ItemFlags; // Currently only tested/used for ImGuiItemFlags_AllowOverlap and ImGuiItemFlags_HasSelectionUserData. // Non-flags members are NOT cleared by ItemAdd() meaning they are still valid during NavProcessItem() + ImGuiID FocusScopeId; // Set by SetNextItemSelectionUserData() ImGuiSelectionUserData SelectionUserData; // Set by SetNextItemSelectionUserData() (note that NULL/0 is a valid value, we use -1 == ImGuiSelectionUserData_Invalid to mark invalid values) float Width; // Set by SetNextItemWidth() ImGuiKeyChord Shortcut; // Set by SetNextItemShortcut() + ImGuiInputFlags ShortcutFlags; // Set by SetNextItemShortcut() bool OpenVal; // Set by SetNextItemOpen() - ImGuiCond OpenCond : 8; + ImU8 OpenCond; // Set by SetNextItemOpen() + ImGuiDataTypeStorage RefVal; // Not exposed yet, for ImGuiInputTextFlags_ParseEmptyAsRefVal + ImGuiID StorageId; // Set by SetNextItemStorageID() ImGuiNextItemData() { memset(this, 0, sizeof(*this)); SelectionUserData = -1; } inline void ClearFlags() { Flags = ImGuiNextItemDataFlags_None; ItemFlags = ImGuiItemFlags_None; } // Also cleared manually by ItemAdd()! @@ -1202,21 +1265,24 @@ struct ImGuiLastItemData ImGuiItemStatusFlags StatusFlags; // See ImGuiItemStatusFlags_ ImRect Rect; // Full rectangle ImRect NavRect; // Navigation scoring rectangle (not displayed) - // Rarely used fields are not explicitly cleared, only valid when the corresponding ImGuiItemStatusFlags is set. - ImRect DisplayRect; // Display rectangle (ONLY VALID IF ImGuiItemStatusFlags_HasDisplayRect is set) - ImRect ClipRect; // Clip rectangle at the time of submitting item (ONLY VALID IF ImGuiItemStatusFlags_HasClipRect is set) + // Rarely used fields are not explicitly cleared, only valid when the corresponding ImGuiItemStatusFlags ar set. + ImRect DisplayRect; // Display rectangle. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasDisplayRect) is set. + ImRect ClipRect; // Clip rectangle at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasClipRect) is set.. + ImGuiKeyChord Shortcut; // Shortcut at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasShortcut) is set.. ImGuiLastItemData() { memset(this, 0, sizeof(*this)); } }; -// Store data emitted by TreeNode() for usage by TreePop() to implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere. -// This is the minimum amount of data that we need to perform the equivalent of NavApplyItemToResult() and which we can't infer in TreePop() -// Only stored when the node is a potential candidate for landing on a Left arrow jump. -struct ImGuiNavTreeNodeData +// Store data emitted by TreeNode() for usage by TreePop() +// - To implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere: store the minimum amount of data +// which we can't infer in TreePop(), to perform the equivalent of NavApplyItemToResult(). +// Only stored when the node is a potential candidate for landing on a Left arrow jump. +struct ImGuiTreeNodeStackData { ImGuiID ID; - ImGuiItemFlags InFlags; - ImRect NavRect; + ImGuiTreeNodeFlags TreeFlags; + ImGuiItemFlags InFlags; // Used for nav landing + ImRect NavRect; // Used for nav landing }; struct IMGUI_API ImGuiStackSizes @@ -1241,7 +1307,8 @@ struct ImGuiWindowStackData { ImGuiWindow* Window; ImGuiLastItemData ParentLastItemDataBackup; - ImGuiStackSizes StackSizesOnBegin; // Store size of various stacks for asserting + ImGuiStackSizes StackSizesOnBegin; // Store size of various stacks for asserting + bool DisabledOverrideReenable; // Non-child window override disabled flag }; struct ImGuiShrinkWidthItem @@ -1260,40 +1327,6 @@ struct ImGuiPtrOrIndex ImGuiPtrOrIndex(int index) { Ptr = NULL; Index = index; } }; -//----------------------------------------------------------------------------- -// [SECTION] Data types support -//----------------------------------------------------------------------------- - -struct ImGuiDataVarInfo -{ - ImGuiDataType Type; - ImU32 Count; // 1+ - ImU32 Offset; // Offset in parent structure - void* GetVarPtr(void* parent) const { return (void*)((unsigned char*)parent + Offset); } -}; - -struct ImGuiDataTypeTempStorage -{ - ImU8 Data[8]; // Can fit any data up to ImGuiDataType_COUNT -}; - -// Type information associated to one ImGuiDataType. Retrieve with DataTypeGetInfo(). -struct ImGuiDataTypeInfo -{ - size_t Size; // Size in bytes - const char* Name; // Short descriptive name for the type, for debugging - const char* PrintFmt; // Default printf format for the type - const char* ScanFmt; // Default scanf format for the type -}; - -// Extend ImGuiDataType_ -enum ImGuiDataTypePrivate_ -{ - ImGuiDataType_String = ImGuiDataType_COUNT + 1, - ImGuiDataType_Pointer, - ImGuiDataType_ID, -}; - //----------------------------------------------------------------------------- // [SECTION] Popup support //----------------------------------------------------------------------------- @@ -1310,7 +1343,7 @@ struct ImGuiPopupData { ImGuiID PopupId; // Set on OpenPopup() ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() - ImGuiWindow* BackupNavWindow;// Set on OpenPopup(), a NavWindow that will be restored on popup close + ImGuiWindow* RestoreNavWindow;// Set on OpenPopup(), a NavWindow that will be restored on popup close int ParentNavLayer; // Resolved on BeginPopup(). Actually a ImGuiNavLayer type (declared down below), initialized to -1 which is not part of an enum, but serves well-enough as "not any of layers" value int OpenFrameCount; // Set on OpenPopup() ImGuiID OpenParentId; // Set on OpenPopup(), we need this to differentiate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) @@ -1344,8 +1377,8 @@ typedef ImBitArray ImBitAr #define ImGuiKey_NavKeyboardTweakFast ImGuiMod_Shift #define ImGuiKey_NavGamepadTweakSlow ImGuiKey_GamepadL1 #define ImGuiKey_NavGamepadTweakFast ImGuiKey_GamepadR1 -#define ImGuiKey_NavGamepadActivate ImGuiKey_GamepadFaceDown -#define ImGuiKey_NavGamepadCancel ImGuiKey_GamepadFaceRight +#define ImGuiKey_NavGamepadActivate (g.IO.ConfigNavSwapGamepadButtons ? ImGuiKey_GamepadFaceRight : ImGuiKey_GamepadFaceDown) +#define ImGuiKey_NavGamepadCancel (g.IO.ConfigNavSwapGamepadButtons ? ImGuiKey_GamepadFaceDown : ImGuiKey_GamepadFaceRight) #define ImGuiKey_NavGamepadMenu ImGuiKey_GamepadFaceLeft #define ImGuiKey_NavGamepadInput ImGuiKey_GamepadFaceUp @@ -1368,7 +1401,6 @@ enum ImGuiInputSource ImGuiInputSource_Mouse, // Note: may be Mouse or TouchScreen or Pen. See io.MouseSource to distinguish them. ImGuiInputSource_Keyboard, ImGuiInputSource_Gamepad, - ImGuiInputSource_Clipboard, // Currently only used by InputText() ImGuiInputSource_COUNT }; @@ -1404,7 +1436,8 @@ struct ImGuiInputEvent // Input function taking an 'ImGuiID owner_id' argument defaults to (ImGuiKeyOwner_Any == 0) aka don't test ownership, which matches legacy behavior. #define ImGuiKeyOwner_Any ((ImGuiID)0) // Accept key that have an owner, UNLESS a call to SetKeyOwner() explicitly used ImGuiInputFlags_LockThisFrame or ImGuiInputFlags_LockUntilRelease. -#define ImGuiKeyOwner_None ((ImGuiID)-1) // Require key to have no owner. +#define ImGuiKeyOwner_NoOwner ((ImGuiID)-1) // Require key to have no owner. +//#define ImGuiKeyOwner_None ImGuiKeyOwner_NoOwner // We previously called this 'ImGuiKeyOwner_None' but it was inconsistent with our pattern that _None values == 0 and quite dangerous. Also using _NoOwner makes the IsKeyPressed() calls more explicit. typedef ImS16 ImGuiKeyRoutingIndex; @@ -1412,13 +1445,13 @@ typedef ImS16 ImGuiKeyRoutingIndex; struct ImGuiKeyRoutingData { ImGuiKeyRoutingIndex NextEntryIndex; - ImU16 Mods; // Technically we'd only need 4-bits but for simplify we store ImGuiMod_ values which need 16-bits. ImGuiMod_Shortcut is already translated to Ctrl/Super. + ImU16 Mods; // Technically we'd only need 4-bits but for simplify we store ImGuiMod_ values which need 16-bits. ImU8 RoutingCurrScore; // [DEBUG] For debug display ImU8 RoutingNextScore; // Lower is better (0: perfect score) ImGuiID RoutingCurr; ImGuiID RoutingNext; - ImGuiKeyRoutingData() { NextEntryIndex = -1; Mods = 0; RoutingCurrScore = RoutingNextScore = 255; RoutingCurr = RoutingNext = ImGuiKeyOwner_None; } + ImGuiKeyRoutingData() { NextEntryIndex = -1; Mods = 0; RoutingCurrScore = RoutingNextScore = 255; RoutingCurr = RoutingNext = ImGuiKeyOwner_NoOwner; } }; // Routing table: maintain a desired owner for each possible key-chord (key + mods), and setup owner in NewFrame() when mods are matching. @@ -1442,73 +1475,47 @@ struct ImGuiKeyOwnerData bool LockThisFrame; // Reading this key requires explicit owner id (until end of frame). Set by ImGuiInputFlags_LockThisFrame. bool LockUntilRelease; // Reading this key requires explicit owner id (until key is released). Set by ImGuiInputFlags_LockUntilRelease. When this is true LockThisFrame is always true as well. - ImGuiKeyOwnerData() { OwnerCurr = OwnerNext = ImGuiKeyOwner_None; LockThisFrame = LockUntilRelease = false; } + ImGuiKeyOwnerData() { OwnerCurr = OwnerNext = ImGuiKeyOwner_NoOwner; LockThisFrame = LockUntilRelease = false; } }; +// Extend ImGuiInputFlags_ // Flags for extended versions of IsKeyPressed(), IsMouseClicked(), Shortcut(), SetKeyOwner(), SetItemKeyOwner() // Don't mistake with ImGuiInputTextFlags! (which is for ImGui::InputText() function) -enum ImGuiInputFlags_ +enum ImGuiInputFlagsPrivate_ { // Flags for IsKeyPressed(), IsKeyChordPressed(), IsMouseClicked(), Shortcut() - ImGuiInputFlags_None = 0, - - // Repeat mode - ImGuiInputFlags_Repeat = 1 << 0, // Enable repeat. Return true on successive repeats. Default for legacy IsKeyPressed(). NOT Default for legacy IsMouseClicked(). MUST BE == 1. - ImGuiInputFlags_RepeatRateDefault = 1 << 1, // Repeat rate: Regular (default) - ImGuiInputFlags_RepeatRateNavMove = 1 << 2, // Repeat rate: Fast - ImGuiInputFlags_RepeatRateNavTweak = 1 << 3, // Repeat rate: Faster - - // Repeat mode: Specify when repeating key pressed can be interrupted. - // In theory ImGuiInputFlags_RepeatUntilOtherKeyPress may be a desirable default, but it would break too many behavior so everything is opt-in. - ImGuiInputFlags_RepeatUntilRelease = 1 << 4, // Stop repeating when released (default for all functions except Shortcut). This only exists to allow overriding Shortcut() default behavior. - ImGuiInputFlags_RepeatUntilKeyModsChange = 1 << 5, // Stop repeating when released OR if keyboard mods are changed (default for Shortcut) + // - Repeat mode: Repeat rate selection + ImGuiInputFlags_RepeatRateDefault = 1 << 1, // Repeat rate: Regular (default) + ImGuiInputFlags_RepeatRateNavMove = 1 << 2, // Repeat rate: Fast + ImGuiInputFlags_RepeatRateNavTweak = 1 << 3, // Repeat rate: Faster + // - Repeat mode: Specify when repeating key pressed can be interrupted. + // - In theory ImGuiInputFlags_RepeatUntilOtherKeyPress may be a desirable default, but it would break too many behavior so everything is opt-in. + ImGuiInputFlags_RepeatUntilRelease = 1 << 4, // Stop repeating when released (default for all functions except Shortcut). This only exists to allow overriding Shortcut() default behavior. + ImGuiInputFlags_RepeatUntilKeyModsChange = 1 << 5, // Stop repeating when released OR if keyboard mods are changed (default for Shortcut) ImGuiInputFlags_RepeatUntilKeyModsChangeFromNone = 1 << 6, // Stop repeating when released OR if keyboard mods are leaving the None state. Allows going from Mod+Key to Key by releasing Mod. - ImGuiInputFlags_RepeatUntilOtherKeyPress = 1 << 7, // Stop repeating when released OR if any other keyboard key is pressed during the repeat - - // Flags for SetItemKeyOwner() - ImGuiInputFlags_CondHovered = 1 << 8, // Only set if item is hovered (default to both) - ImGuiInputFlags_CondActive = 1 << 9, // Only set if item is active (default to both) - ImGuiInputFlags_CondDefault_ = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive, + ImGuiInputFlags_RepeatUntilOtherKeyPress = 1 << 7, // Stop repeating when released OR if any other keyboard key is pressed during the repeat // Flags for SetKeyOwner(), SetItemKeyOwner() - // Locking is useful to make input-owner-aware code steal keys from non-input-owner-aware code. If all code is input-owner-aware locking would never be necessary. - ImGuiInputFlags_LockThisFrame = 1 << 10, // Further accesses to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared at end of frame. - ImGuiInputFlags_LockUntilRelease = 1 << 11, // Further accesses to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared when the key is released or at end of each frame if key is released. + // - Locking key away from non-input aware code. Locking is useful to make input-owner-aware code steal keys from non-input-owner-aware code. If all code is input-owner-aware locking would never be necessary. + ImGuiInputFlags_LockThisFrame = 1 << 20, // Further accesses to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared at end of frame. + ImGuiInputFlags_LockUntilRelease = 1 << 21, // Further accesses to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared when the key is released or at end of each frame if key is released. - // Routing policies for Shortcut() + low-level SetShortcutRouting() - // - The general idea is that several callers register interest in a shortcut, and only one owner gets it. - // Parent -> call Shortcut(Ctrl+S) // When Parent is focused, Parent gets the shortcut. - // Child1 -> call Shortcut(Ctrl+S) // When Child1 is focused, Child1 gets the shortcut (Child1 overrides Parent shortcuts) - // Child2 -> no call // When Child2 is focused, Parent gets the shortcut. - // The whole system is order independent, so if Child1 does it calls before Parent results will be identical. - // This is an important property as it facilitate working with foreign code or larger codebase. - // - Visualize registered routes in 'Metrics->Inputs' and submitted routes in 'Debug Log->InputRouting'. - // - When a policy (except for _RouteAlways *) is set, Shortcut() will register itself with SetShortcutRouting(), - // allowing the system to decide where to route the input among other route-aware calls. - // (* Using ImGuiInputFlags_RouteAlways is roughly equivalent to calling IsKeyChordPressed(key)). - // - Shortcut() uses ImGuiInputFlags_RouteFocused by default. Meaning that a Shortcut() call will register - // a route and only succeed when parent window is in the focus-stack and if no-one with a higher priority - // is claiming the same shortcut. - // - You can chain two unrelated windows in the focus stack using SetWindowParentWindowForFocusRoute(). - // - Priorities: GlobalHigh > Focused (when owner is active item) > Global > Focused (when focused window) > GlobalLow. - // - Can select only 1 policy among all available. - ImGuiInputFlags_RouteFocused = 1 << 12, // (Default) Honor focus route: Accept inputs if window is in focus stack. Deep-most focused window takes inputs. ActiveId takes inputs over deep-most focused window. - ImGuiInputFlags_RouteGlobalLow = 1 << 13, // Register route globally (lowest priority: unless a focused window or active item registered the route) -> recommended Global priority IF you need a Global priority. - ImGuiInputFlags_RouteGlobal = 1 << 14, // Register route globally (medium priority: unless an active item registered the route, e.g. CTRL+A registered by InputText will take priority over this). - ImGuiInputFlags_RouteGlobalHigh = 1 << 15, // Register route globally (higher priority: unlikely you need to use that: will interfere with every active items, e.g. CTRL+A registered by InputText will be overriden by this) - ImGuiInputFlags_RouteAlways = 1 << 16, // Do not register route, poll keys directly. - // Routing polices: extra options - ImGuiInputFlags_RouteUnlessBgFocused= 1 << 17, // Global routes will not be applied if underlying background/void is focused (== no Dear ImGui windows are focused). Useful for overlay applications. + // - Condition for SetItemKeyOwner() + ImGuiInputFlags_CondHovered = 1 << 22, // Only set if item is hovered (default to both) + ImGuiInputFlags_CondActive = 1 << 23, // Only set if item is active (default to both) + ImGuiInputFlags_CondDefault_ = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive, // [Internal] Mask of which function support which flags ImGuiInputFlags_RepeatRateMask_ = ImGuiInputFlags_RepeatRateDefault | ImGuiInputFlags_RepeatRateNavMove | ImGuiInputFlags_RepeatRateNavTweak, ImGuiInputFlags_RepeatUntilMask_ = ImGuiInputFlags_RepeatUntilRelease | ImGuiInputFlags_RepeatUntilKeyModsChange | ImGuiInputFlags_RepeatUntilKeyModsChangeFromNone | ImGuiInputFlags_RepeatUntilOtherKeyPress, ImGuiInputFlags_RepeatMask_ = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_ | ImGuiInputFlags_RepeatUntilMask_, ImGuiInputFlags_CondMask_ = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive, - ImGuiInputFlags_RouteMask_ = ImGuiInputFlags_RouteFocused | ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteGlobalLow | ImGuiInputFlags_RouteGlobalHigh, // _Always not part of this! + ImGuiInputFlags_RouteTypeMask_ = ImGuiInputFlags_RouteActive | ImGuiInputFlags_RouteFocused | ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteAlways, + ImGuiInputFlags_RouteOptionsMask_ = ImGuiInputFlags_RouteOverFocused | ImGuiInputFlags_RouteOverActive | ImGuiInputFlags_RouteUnlessBgFocused | ImGuiInputFlags_RouteFromRootWindow, ImGuiInputFlags_SupportedByIsKeyPressed = ImGuiInputFlags_RepeatMask_, ImGuiInputFlags_SupportedByIsMouseClicked = ImGuiInputFlags_Repeat, - ImGuiInputFlags_SupportedByShortcut = ImGuiInputFlags_RepeatMask_ | ImGuiInputFlags_RouteMask_ | ImGuiInputFlags_RouteAlways | ImGuiInputFlags_RouteUnlessBgFocused, + ImGuiInputFlags_SupportedByShortcut = ImGuiInputFlags_RepeatMask_ | ImGuiInputFlags_RouteTypeMask_ | ImGuiInputFlags_RouteOptionsMask_, + ImGuiInputFlags_SupportedBySetNextItemShortcut = ImGuiInputFlags_RepeatMask_ | ImGuiInputFlags_RouteTypeMask_ | ImGuiInputFlags_RouteOptionsMask_ | ImGuiInputFlags_Tooltip, ImGuiInputFlags_SupportedBySetKeyOwner = ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease, ImGuiInputFlags_SupportedBySetItemKeyOwner = ImGuiInputFlags_SupportedBySetKeyOwner | ImGuiInputFlags_CondMask_, }; @@ -1599,6 +1606,7 @@ enum ImGuiNavMoveFlags_ ImGuiNavMoveFlags_Activate = 1 << 12, // Activate/select target item. ImGuiNavMoveFlags_NoSelect = 1 << 13, // Don't trigger selection by not setting g.NavJustMovedTo ImGuiNavMoveFlags_NoSetNavHighlight = 1 << 14, // Do not alter the visible state of keyboard vs mouse nav highlight + ImGuiNavMoveFlags_NoClearActiveId = 1 << 15, // (Experimental) Do not clear active id when applying move result }; enum ImGuiNavLayer @@ -1608,6 +1616,7 @@ enum ImGuiNavLayer ImGuiNavLayer_COUNT }; +// Storage for navigation query/results struct ImGuiNavItemData { ImGuiWindow* Window; // Init,Move // Best candidate window (result->ItemWindow->RootWindowForNav == request->Window) @@ -1615,15 +1624,16 @@ struct ImGuiNavItemData 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 - ImGuiSelectionUserData SelectionUserData;//I+Mov // Best candidate SetNextItemSelectionData() value. 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 + ImGuiSelectionUserData SelectionUserData;//I+Mov // Best candidate SetNextItemSelectionUserData() value. Valid if (InFlags & ImGuiItemFlags_HasSelectionUserData) ImGuiNavItemData() { Clear(); } void Clear() { Window = NULL; ID = FocusScopeId = 0; InFlags = 0; SelectionUserData = -1; DistBox = DistCenter = DistAxial = FLT_MAX; } }; +// Storage for PushFocusScope() struct ImGuiFocusScopeData { ImGuiID ID; @@ -1723,6 +1733,34 @@ struct ImGuiOldColumns ImGuiOldColumns() { memset(this, 0, sizeof(*this)); } }; +//----------------------------------------------------------------------------- +// [SECTION] Box-select support +//----------------------------------------------------------------------------- + +struct ImGuiBoxSelectState +{ + // Active box-selection data (persistent, 1 active at a time) + ImGuiID ID; + bool IsActive; + bool IsStarting; + bool IsStartedFromVoid; // Starting click was not from an item. + bool IsStartedSetNavIdOnce; + bool RequestClear; + ImGuiKeyChord KeyMods : 16; // Latched key-mods for box-select logic. + ImVec2 StartPosRel; // Start position in window-contents relative space (to support scrolling) + ImVec2 EndPosRel; // End position in window-contents relative space + ImVec2 ScrollAccum; // Scrolling accumulator (to behave at high-frame spaces) + ImGuiWindow* Window; + + // Temporary/Transient data + bool UnclipMode; // (Temp/Transient, here in hot area). Set/cleared by the BeginMultiSelect()/EndMultiSelect() owning active box-select. + ImRect UnclipRect; // Rectangle where ItemAdd() clipping may be temporarily disabled. Need support by multi-select supporting widgets. + ImRect BoxSelectRectPrev; // Selection rectangle in absolute coordinates (derived every frame from BoxSelectStartPosRel and MousePos) + ImRect BoxSelectRectCurr; + + ImGuiBoxSelectState() { memset(this, 0, sizeof(*this)); } +}; + //----------------------------------------------------------------------------- // [SECTION] Multi-select support //----------------------------------------------------------------------------- @@ -1730,9 +1768,45 @@ struct ImGuiOldColumns // We always assume that -1 is an invalid value (which works for indices and pointers) #define ImGuiSelectionUserData_Invalid ((ImGuiSelectionUserData)-1) -#ifdef IMGUI_HAS_MULTI_SELECT -// -#endif // #ifdef IMGUI_HAS_MULTI_SELECT +// Temporary storage for multi-select +struct IMGUI_API ImGuiMultiSelectTempData +{ + ImGuiMultiSelectIO IO; // MUST BE FIRST FIELD. Requests are set and returned by BeginMultiSelect()/EndMultiSelect() + written to by user during the loop. + ImGuiMultiSelectState* Storage; + ImGuiID FocusScopeId; // Copied from g.CurrentFocusScopeId (unless another selection scope was pushed manually) + ImGuiMultiSelectFlags Flags; + ImVec2 ScopeRectMin; + ImVec2 BackupCursorMaxPos; + ImGuiSelectionUserData LastSubmittedItem; // Copy of last submitted item data, used to merge output ranges. + ImGuiID BoxSelectId; + ImGuiKeyChord KeyMods; + ImS8 LoopRequestSetAll; // -1: no operation, 0: clear all, 1: select all. + bool IsEndIO; // Set when switching IO from BeginMultiSelect() to EndMultiSelect() state. + bool IsFocused; // Set if currently focusing the selection scope (any item of the selection). May be used if you have custom shortcut associated to selection. + bool IsKeyboardSetRange; // Set by BeginMultiSelect() when using Shift+Navigation. Because scrolling may be affected we can't afford a frame of lag with Shift+Navigation. + bool NavIdPassedBy; + bool RangeSrcPassedBy; // Set by the item that matches RangeSrcItem. + bool RangeDstPassedBy; // Set by the item that matches NavJustMovedToId when IsSetRange is set. + + ImGuiMultiSelectTempData() { Clear(); } + void Clear() { size_t io_sz = sizeof(IO); ClearIO(); memset((void*)(&IO + 1), 0, sizeof(*this) - io_sz); } // Zero-clear except IO as we preserve IO.Requests[] buffer allocation. + void ClearIO() { IO.Requests.resize(0); IO.RangeSrcItem = IO.NavIdItem = ImGuiSelectionUserData_Invalid; IO.NavIdSelected = IO.RangeSrcReset = false; } +}; + +// Persistent storage for multi-select (as long as selection is alive) +struct IMGUI_API ImGuiMultiSelectState +{ + ImGuiWindow* Window; + ImGuiID ID; + int LastFrameActive; // Last used frame-count, for GC. + int LastSelectionSize; // Set by BeginMultiSelect() based on optional info provided by user. May be -1 if unknown. + ImS8 RangeSelected; // -1 (don't have) or true/false + ImS8 NavIdSelected; // -1 (don't have) or true/false + ImGuiSelectionUserData RangeSrcItem; // + ImGuiSelectionUserData NavIdItem; // SetNextItemSelectionUserData() value for NavId (if part of submitted items) + + ImGuiMultiSelectState() { Window = NULL; ID = 0; LastFrameActive = LastSelectionSize = 0; RangeSelected = NavIdSelected = -1; RangeSrcItem = NavIdItem = ImGuiSelectionUserData_Invalid; } +}; //----------------------------------------------------------------------------- // [SECTION] Docking support @@ -1859,14 +1933,17 @@ struct IMGUI_API ImGuiDockNode enum ImGuiWindowDockStyleCol { ImGuiWindowDockStyleCol_Text, - ImGuiWindowDockStyleCol_Tab, ImGuiWindowDockStyleCol_TabHovered, - ImGuiWindowDockStyleCol_TabActive, - ImGuiWindowDockStyleCol_TabUnfocused, - ImGuiWindowDockStyleCol_TabUnfocusedActive, + ImGuiWindowDockStyleCol_TabFocused, + ImGuiWindowDockStyleCol_TabSelected, + ImGuiWindowDockStyleCol_TabSelectedOverline, + ImGuiWindowDockStyleCol_TabDimmed, + ImGuiWindowDockStyleCol_TabDimmedSelected, + ImGuiWindowDockStyleCol_TabDimmedSelectedOverline, ImGuiWindowDockStyleCol_COUNT }; +// We don't store style.Alpha: dock_node->LastBgColor embeds it and otherwise it would only affect the docking tab, which intuitively I would say we don't want to. struct ImGuiWindowDockStyle { ImU32 Colors[ImGuiWindowDockStyleCol_COUNT]; @@ -1984,6 +2061,7 @@ enum ImGuiLocKey : int ImGuiLocKey_WindowingMainMenuBar, ImGuiLocKey_WindowingPopup, ImGuiLocKey_WindowingUntitled, + ImGuiLocKey_CopyLink, ImGuiLocKey_DockingHideTabBar, ImGuiLocKey_DockingHoldShiftToDock, ImGuiLocKey_DockingDragToUndockOrMoveNode, @@ -2114,6 +2192,8 @@ struct ImGuiContext ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() float FontSize; // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. float FontBaseSize; // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. + float FontScale; // == FontSize / Font->FontSize + float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale ImDrawListSharedData DrawListSharedData; double Time; int FrameCount; @@ -2126,6 +2206,7 @@ struct ImGuiContext bool GcCompactAll; // Request full GC bool TestEngineHookItems; // Will call test engine hooks: ImGuiTestEngineHook_ItemAdd(), ImGuiTestEngineHook_ItemInfo(), ImGuiTestEngineHook_Log() void* TestEngine; // Test engine user data + char ContextName[16]; // Storage for a context name (to facilitate debugging multi-context setups) // Inputs ImVector InputEventsQueue; // Input events which will be trickled/written into IO structure. @@ -2140,11 +2221,12 @@ struct ImGuiContext 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) + ImVec2 WindowsHoverPadding; // Padding around resizable windows for which hovering on counts as hovering the window == ImMax(style.TouchExtraPadding, WINDOWS_HOVER_PADDING). ImGuiID DebugBreakInWindow; // Set to break in Begin() call. 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* HoveredWindowBeforeClear; // Window the mouse is hovering. Filled even with _NoMouse. This is currently useful for multi-context compositors. ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actual window that is moved is generally MovingWindow->RootWindowDockTree. 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; @@ -2158,10 +2240,11 @@ struct ImGuiContext ImGuiID DebugHookIdInfo; // Will call core hooks: DebugHookIdInfo() from GetID functions, used by ID Stack Tool [next HoveredId/ActiveId to not pull in an extra cache-line] ImGuiID HoveredId; // Hovered widget, filled during the frame ImGuiID HoveredIdPreviousFrame; - bool HoveredIdAllowOverlap; - bool HoveredIdDisabled; // At least one widget passed the rect test, but has been discarded by disabled flag or popup inhibit. May be true even if HoveredId == 0. float HoveredIdTimer; // Measure contiguous hovering time float HoveredIdNotActiveTimer; // Measure contiguous hovering time where the item has not been active + bool HoveredIdAllowOverlap; + bool HoveredIdIsDisabled; // 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. + bool ItemUnclipByLog; // Disable ItemAdd() clipping, essentially a memory-locality friendly copy of LogEnabled 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; @@ -2183,9 +2266,9 @@ struct ImGuiContext ImGuiID LastActiveId; // Store the last non-zero ActiveId, useful for animation. float LastActiveIdTimer; // Store the last non-zero ActiveId timer since the beginning of activation, useful for animation. - // [EXPERIMENTAL] Key/Input Ownership + Shortcut Routing system + // Key/Input Ownership + Shortcut Routing system // - The idea is that instead of "eating" a given key, we can link to an owner. - // - Input query can then read input by specifying ImGuiKeyOwner_Any (== 0), ImGuiKeyOwner_None (== -1) or a custom ID. + // - Input query can then read input by specifying ImGuiKeyOwner_Any (== 0), ImGuiKeyOwner_NoOwner (== -1) or a custom ID. // - Routing is requested ahead of time for a given chord (Key + Mods) and granted in NewFrame(). double LastKeyModsChangeTime; // Record the last time key mods changed (affect repeat delay when using shortcut logic) double LastKeyModsChangeFromNoneTime; // Record the last time key mods changed away from being 0 (affect repeat delay when using shortcut logic) @@ -2194,11 +2277,9 @@ struct ImGuiContext ImGuiKeyOwnerData KeysOwnerData[ImGuiKey_NamedKey_COUNT]; ImGuiKeyRoutingTable KeysRoutingTable; ImU32 ActiveIdUsingNavDirMask; // Active widget will want to read those nav move requests (e.g. can activate a button and move away from it) - bool ActiveIdUsingAllKeyboardKeys; // Active widget will want to read all keyboard keys inputs. (FIXME: This is a shortcut for not taking ownership of 100+ keys but perhaps best to not have the inconsistency) + bool ActiveIdUsingAllKeyboardKeys; // Active widget will want to read all keyboard keys inputs. (this is a shortcut for not taking ownership of 100+ keys, frequently used by drag operations) ImGuiKeyChord DebugBreakInShortcutRouting; // Set to break in SetShortcutRouting()/Shortcut() calls. -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - ImU32 ActiveIdUsingNavInputMask; // If you used this. Since (IMGUI_VERSION_NUM >= 18804) : 'g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);' becomes 'SetKeyOwner(ImGuiKey_Escape, g.ActiveId) and/or SetKeyOwner(ImGuiKey_NavGamepadCancel, g.ActiveId);' -#endif + //ImU32 ActiveIdUsingNavInputMask; // [OBSOLETE] Since (IMGUI_VERSION_NUM >= 18804) : 'g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);' becomes --> 'SetKeyOwner(ImGuiKey_Escape, g.ActiveId) and/or SetKeyOwner(ImGuiKey_NavGamepadCancel, g.ActiveId);' // Next window/item data ImGuiID CurrentFocusScopeId; // Value for currently appending items == g.FocusScopeStack.back(). Not to be mistaken with g.NavFocusScopeId. @@ -2219,11 +2300,10 @@ struct ImGuiContext ImVector GroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin() ImVector OpenPopupStack; // Which popups are open (persistent) ImVector BeginPopupStack; // Which level of BeginPopup() we are in (reset every frame) - ImVector NavTreeNodeStack; // Stack for TreeNode() when a NavLeft requested is emitted. + ImVectorTreeNodeStack; // Stack for TreeNode() // Viewports ImVector Viewports; // Active viewports (always 1+, and generally 1 unless multi-viewports are enabled). Each viewports hold their copy of ImDrawData. - float CurrentDpiScale; // == CurrentViewport->DpiScale ImGuiViewportP* CurrentViewport; // We track changes of viewport (happening in Begin) so we can call Platform_OnChangedViewport() ImGuiViewportP* MouseViewport; ImGuiViewportP* MouseLastHoveredViewport; // Last known viewport that was hovered by mouse (even if we are not hovering any viewport any more) + honoring the _NoInputs flag. @@ -2238,20 +2318,17 @@ struct ImGuiContext ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusedWindow' ImGuiID NavId; // Focused item for navigation ImGuiID NavFocusScopeId; // Focused focus scope (e.g. selection code often wants to "clear other items" when landing on an item of the same scope) - ImVector NavFocusRoute; // Reversed copy focus scope stack for NavId (should contains NavFocusScopeId). This essentially follow the window->ParentWindowForFocusRoute chain. + ImGuiNavLayer NavLayer; // Focused layer (main scrolling layer, or menu/title bar layer) ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItem() ImGuiID NavActivateDownId; // ~~ IsKeyDown(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyDown(ImGuiKey_NavGamepadActivate) ? NavId : 0 ImGuiID NavActivatePressedId; // ~~ IsKeyPressed(ImGuiKey_Space) || IsKeyPressed(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate) ? NavId : 0 (no repeat) ImGuiActivateFlags NavActivateFlags; + ImVector NavFocusRoute; // Reversed copy focus scope stack for NavId (should contains NavFocusScopeId). This essentially follow the window->ParentWindowForFocusRoute chain. ImGuiID NavHighlightActivatedId; float NavHighlightActivatedTimer; - ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest). - ImGuiID NavJustMovedToFocusScopeId; // Just navigated to this focus scope id (result of a successfully MoveRequest). - ImGuiKeyChord NavJustMovedToKeyMods; ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame. ImGuiActivateFlags NavNextActivateFlags; ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource_Keyboard or ImGuiInputSource_Mouse - ImGuiNavLayer NavLayer; // Layer we are navigating on. For now the system is hard-coded for 0=main contents and 1=menu/title bar, may expose layers later. ImGuiSelectionUserData NavLastValidSelectionUserData; // Last valid data passed to SetNextItemSelectionUser(), or -1. For current window. Not reset when focusing an item that doesn't have selection data. 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) @@ -2282,9 +2359,17 @@ struct ImGuiContext ImGuiNavItemData NavMoveResultOther; // Best move request candidate within NavWindow's flattened hierarchy (when using ImGuiWindowFlags_NavFlattened flag) ImGuiNavItemData NavTabbingResultFirst; // First tabbing request candidate within NavWindow and flattened hierarchy + // Navigation: record of last move request + ImGuiID NavJustMovedFromFocusScopeId; // Just navigated from this focus scope id (result of a successfully MoveRequest). + ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest). + ImGuiID NavJustMovedToFocusScopeId; // Just navigated to this focus scope id (result of a successfully MoveRequest). + ImGuiKeyChord NavJustMovedToKeyMods; + bool NavJustMovedToIsTabbing; // Copy of ImGuiNavMoveFlags_IsTabbing. Maybe we should store whole flags. + bool NavJustMovedToHasSelectionData; // Copy of move result's InFlags & ImGuiItemFlags_HasSelectionUserData). Maybe we should just store ImGuiNavItemData. + // Navigation: Windowing (CTRL+TAB for list, or Menu button + keys or directional pads to move/resize) - ImGuiKeyChord ConfigNavWindowingKeyNext; // = ImGuiMod_Ctrl | ImGuiKey_Tab, for reconfiguration (see #4828) - ImGuiKeyChord ConfigNavWindowingKeyPrev; // = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab + ImGuiKeyChord ConfigNavWindowingKeyNext; // = ImGuiMod_Ctrl | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiKey_Tab on OS X). For reconfiguration (see #4828) + ImGuiKeyChord ConfigNavWindowingKeyPrev; // = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab on OS X) ImGuiWindow* NavWindowingTarget; // Target window when doing CTRL+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most! 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 @@ -2337,6 +2422,13 @@ struct ImGuiContext ImVector CurrentTabBarStack; ImVector ShrinkWidthBuffer; + // Multi-Select state + ImGuiBoxSelectState BoxSelectState; + ImGuiMultiSelectTempData* CurrentMultiSelect; + int MultiSelectTempDataStacked; // Temporary multi-select data size (because we leave previous instances undestructed, we generally don't use MultiSelectTempData.Size) + ImVector MultiSelectTempData; + ImPool MultiSelectStorage; + // Hover Delay system ImGuiID HoverItemDelayId; ImGuiID HoverItemDelayIdPreviousFrame; @@ -2355,6 +2447,7 @@ struct ImGuiContext ImGuiInputTextDeactivatedState InputTextDeactivatedState; ImFont InputTextPasswordFont; ImGuiID TempInputId; // Temporary text input when CTRL+clicking on a slider, etc. + ImGuiDataTypeStorage DataTypeZeroValue; // 0 for all data types int BeginMenuDepth; int BeginComboDepth; ImGuiColorEditFlags ColorEditOptions; // Store user options for color edit widgets @@ -2367,13 +2460,14 @@ struct ImGuiContext ImGuiComboPreviewData ComboPreviewData; ImRect WindowResizeBorderExpectedRect; // Expected border rect, switch to relative edit if moving bool WindowResizeRelativeMode; + short ScrollbarSeekMode; // 0: relative, -1/+1: prev/next page. + float ScrollbarClickDeltaToGrabCenter; // Distance between mouse and center of grab box, normalized in parent space. Use storage? float SliderGrabClickOffset; float SliderCurrentAccum; // Accumulated slider delta when using navigation controls. bool SliderCurrentAccumDirty; // Has the accumulated slider delta changed since last time we tried to apply it? bool DragCurrentAccumDirty; float DragCurrentAccum; // Accumulator for dragging modification. Always high-precision, not rounded by end-user precision settings float DragSpeedDefaultRatio; // If speed == 0.0f, uses (max-min) * DragSpeedDefaultRatio - float ScrollbarClickDeltaToGrabCenter; // Distance between mouse and center of grab box, normalized in parent space. Use storage? float DisabledAlphaBackup; // Backup for style.Alpha for BeginDisabled() short DisabledStackSize; short LockMarkEdited; @@ -2384,7 +2478,7 @@ struct ImGuiContext // Platform support ImGuiPlatformImeData PlatformImeData; // Data updated by current frame - ImGuiPlatformImeData PlatformImeDataPrev; // Previous frame data (when changing we will call io.SetPlatformImeDataFn + ImGuiPlatformImeData PlatformImeDataPrev; // Previous frame data. When changed we call the io.PlatformSetImeDataFn() handler. ImGuiID PlatformImeViewport; // Extensions @@ -2459,7 +2553,7 @@ struct ImGuiContext ConfigFlagsCurrFrame = ConfigFlagsLastFrame = ImGuiConfigFlags_None; FontAtlasOwnedByContext = shared_font_atlas ? false : true; Font = NULL; - FontSize = FontBaseSize = 0.0f; + FontSize = FontBaseSize = FontScale = CurrentDpiScale = 0.0f; IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); Time = 0.0f; FrameCount = 0; @@ -2468,6 +2562,7 @@ struct ImGuiContext GcCompactAll = false; TestEngineHookItems = false; TestEngine = NULL; + memset(ContextName, 0, sizeof(ContextName)); InputEventsNextMouseSource = ImGuiMouseSource_Mouse; InputEventsNextEventId = 1; @@ -2476,6 +2571,7 @@ struct ImGuiContext CurrentWindow = NULL; HoveredWindow = NULL; HoveredWindowUnderMovingWindow = NULL; + HoveredWindowBeforeClear = NULL; MovingWindow = NULL; WheelingWindow = NULL; WheelingWindowStartFrame = WheelingWindowScrolledFrame = -1; @@ -2484,8 +2580,9 @@ struct ImGuiContext DebugHookIdInfo = 0; HoveredId = HoveredIdPreviousFrame = 0; HoveredIdAllowOverlap = false; - HoveredIdDisabled = false; + HoveredIdIsDisabled = false; HoveredIdTimer = HoveredIdNotActiveTimer = 0.0f; + ItemUnclipByLog = false; ActiveId = 0; ActiveIdIsAlive = 0; ActiveIdTimer = 0.0f; @@ -2511,15 +2608,11 @@ struct ImGuiContext ActiveIdUsingNavDirMask = 0x00; ActiveIdUsingAllKeyboardKeys = false; -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - ActiveIdUsingNavInputMask = 0x00; -#endif CurrentFocusScopeId = 0; CurrentItemFlags = ImGuiItemFlags_None; DebugShowGroupRects = false; - CurrentDpiScale = 0.0f; CurrentViewport = NULL; MouseViewport = MouseLastHoveredViewport = NULL; PlatformLastFocusedViewportId = 0; @@ -2528,18 +2621,18 @@ struct ImGuiContext NavWindow = NULL; NavId = NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = 0; - NavJustMovedToId = NavJustMovedToFocusScopeId = NavNextActivateId = 0; + NavLayer = ImGuiNavLayer_Main; + NavNextActivateId = 0; NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None; NavHighlightActivatedId = 0; NavHighlightActivatedTimer = 0.0f; - NavJustMovedToKeyMods = ImGuiMod_None; NavInputSource = ImGuiInputSource_Keyboard; - NavLayer = ImGuiNavLayer_Main; NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; NavIdIsAlive = false; NavMousePosDirty = false; NavDisableHighlight = true; NavDisableMouseHover = false; + NavAnyRequest = false; NavInitRequest = false; NavInitRequestFromMove = false; @@ -2554,8 +2647,15 @@ struct ImGuiContext NavTabbingDir = 0; NavTabbingCounter = 0; - ConfigNavWindowingKeyNext = ImGuiMod_Ctrl | ImGuiKey_Tab; - ConfigNavWindowingKeyPrev = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab; + NavJustMovedFromFocusScopeId = NavJustMovedToId = NavJustMovedToFocusScopeId = 0; + NavJustMovedToKeyMods = ImGuiMod_None; + NavJustMovedToIsTabbing = false; + NavJustMovedToHasSelectionData = false; + + // All platforms use Ctrl+Tab but Ctrl<>Super are swapped on Mac... + // FIXME: Because this value is stored, it annoyingly interfere with toggling io.ConfigMacOSXBehaviors updating this.. + ConfigNavWindowingKeyNext = IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiKey_Tab) : (ImGuiMod_Ctrl | ImGuiKey_Tab); + ConfigNavWindowingKeyPrev = IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab) : (ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab); NavWindowingTarget = NavWindowingTargetAnim = NavWindowingListWindow = NULL; NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f; NavWindowingToggleLayer = false; @@ -2580,6 +2680,8 @@ struct ImGuiContext CurrentTable = NULL; TablesTempDataStacked = 0; CurrentTabBar = NULL; + CurrentMultiSelect = NULL; + MultiSelectTempDataStacked = 0; HoverItemDelayId = HoverItemDelayIdPreviousFrame = HoverItemUnlockedStationaryId = HoverWindowUnlockedStationaryId = 0; HoverItemDelayTimer = HoverItemDelayClearTimer = 0.0f; @@ -2588,19 +2690,21 @@ struct ImGuiContext MouseStationaryTimer = 0.0f; TempInputId = 0; + memset(&DataTypeZeroValue, 0, sizeof(DataTypeZeroValue)); BeginMenuDepth = BeginComboDepth = 0; ColorEditOptions = ImGuiColorEditFlags_DefaultOptions_; ColorEditCurrentID = ColorEditSavedID = 0; ColorEditSavedHue = ColorEditSavedSat = 0.0f; ColorEditSavedColor = 0; WindowResizeRelativeMode = false; + ScrollbarSeekMode = 0; + ScrollbarClickDeltaToGrabCenter = 0.0f; SliderGrabClickOffset = 0.0f; SliderCurrentAccum = 0.0f; SliderCurrentAccumDirty = false; DragCurrentAccumDirty = false; DragCurrentAccum = 0.0f; DragSpeedDefaultRatio = 1.0f / 100.0f; - ScrollbarClickDeltaToGrabCenter = 0.0f; DisabledAlphaBackup = 0.0f; DisabledStackSize = 0; LockMarkEdited = 0; @@ -2694,7 +2798,7 @@ struct IMGUI_API ImGuiWindowTempData 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. + ImU32 TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. 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 @@ -2733,6 +2837,7 @@ struct IMGUI_API ImGuiWindow ImVec2 WindowPadding; // Window padding at the time of Begin(). float WindowRounding; // Window rounding at the time of Begin(). May be clamped lower to avoid rendering artifacts with title bar, menu bar etc. float WindowBorderSize; // Window border size at the time of Begin(). + float TitleBarHeight, MenuBarHeight; // Note that those used to be function before 2024/05/28. If you have old code calling TitleBarHeight() you can change it to TitleBarHeight. float DecoOuterSizeX1, DecoOuterSizeY1; // Left/Up offsets. Sum of non-scrolling outer decorations (X1 generally == 0.0f. Y1 generally = TitleBarHeight + MenuBarHeight). Locked during Begin(). float DecoOuterSizeX2, DecoOuterSizeY2; // Right/Down offsets (X2 generally == ScrollbarSize.x, Y2 == ScrollbarSizes.y). float DecoInnerSizeX1, DecoInnerSizeY1; // Applied AFTER/OVER InnerRect. Specialized for Tables as they use specialized form of clipping and frozen rows/columns are inside InnerRect (and not part of regular decoration sizes). @@ -2740,6 +2845,7 @@ struct IMGUI_API ImGuiWindow ImGuiID MoveId; // == window->GetID("#MOVE") ImGuiID TabId; // == window->GetID("#TAB") ImGuiID ChildId; // ID of corresponding item in parent window (for navigation to return from child window to parent window) + ImGuiID PopupId; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling) 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) @@ -2754,6 +2860,7 @@ struct IMGUI_API ImGuiWindow 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 SkipRefresh; // [EXPERIMENTAL] Reuse previous frame drawn contents, Begin() returns false. 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. @@ -2766,7 +2873,6 @@ struct IMGUI_API ImGuiWindow 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; bool AutoFitOnlyGrows; ImGuiDir AutoPosLastDirection; @@ -2852,10 +2958,8 @@ public: // We don't use g.FontSize because the window may be != g.CurrentWindow. ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } float CalcFontSize() const { ImGuiContext& g = *Ctx; float scale = g.FontBaseSize * FontWindowScale * FontDpiScale; if (ParentWindow) scale *= ParentWindow->FontWindowScale; return scale; } - float TitleBarHeight() const { ImGuiContext& g = *Ctx; return (Flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : CalcFontSize() + g.Style.FramePadding.y * 2.0f; } - ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight())); } - float MenuBarHeight() const { ImGuiContext& g = *Ctx; return (Flags & ImGuiWindowFlags_MenuBar) ? DC.MenuBarOffset.y + CalcFontSize() + g.Style.FramePadding.y * 2.0f : 0.0f; } - ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight(); return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight()); } + ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight)); } + ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight; return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight); } }; //----------------------------------------------------------------------------- @@ -3012,13 +3116,24 @@ struct ImGuiTableColumn }; // Transient cell data stored per row. -// sizeof() ~ 6 +// sizeof() ~ 6 bytes struct ImGuiTableCellData { ImU32 BgColor; // Actual color ImGuiTableColumnIdx Column; // Column number }; +// Parameters for TableAngledHeadersRowEx() +// This may end up being refactored for more general purpose. +// sizeof() ~ 12 bytes +struct ImGuiTableHeaderData +{ + ImGuiTableColumnIdx Index; // Column index + ImU32 TextColor; + ImU32 BgColor0; + ImU32 BgColor1; +}; + // Per-instance data that needs preserving across frames (seemingly most others do not need to be preserved aside from debug needs. Does that means they could be moved to ImGuiTableTempData?) // sizeof() ~ 24 bytes struct ImGuiTableInstanceData @@ -3033,8 +3148,7 @@ struct ImGuiTableInstanceData ImGuiTableInstanceData() { TableInstanceID = 0; LastOuterHeight = LastTopHeadersRowHeight = LastFrozenHeight = 0.0f; HoveredRowLast = HoveredRowNext = -1; } }; -// FIXME-TABLE: more transient data could be stored in a stacked ImGuiTableTempData: e.g. SortSpecs, incoming RowData -// sizeof() ~ 580 bytes + heap allocs described in TableBeginInitMemory() +// sizeof() ~ 592 bytes + heap allocs described in TableBeginInitMemory() struct IMGUI_API ImGuiTable { ImGuiID ID; @@ -3104,7 +3218,7 @@ struct IMGUI_API ImGuiTable 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 ColumnsEnabledFixedCount; // Number of enabled columns using fixed width (<= ColumnsCount) ImGuiTableColumnIdx DeclColumnsCount; // Count calls to TableSetupColumn() ImGuiTableColumnIdx AngledHeadersCount; // Count columns with angled headers ImGuiTableColumnIdx HoveredColumnBody; // Index of column whose visible region is being hovered. Important: == ColumnsCount when hovering empty region after the right-most column! @@ -3157,12 +3271,14 @@ struct IMGUI_API ImGuiTable // Transient data that are only needed between BeginTable() and EndTable(), those buffers are shared (1 per level of stacked table). // - Accessing those requires chasing an extra pointer so for very frequently used data we leave them in the main table structure. // - We also leave out of this structure data that tend to be particularly useful for debugging/metrics. -// sizeof() ~ 120 bytes. +// FIXME-TABLE: more transient data could be stored in a stacked ImGuiTableTempData: e.g. SortSpecs. +// sizeof() ~ 136 bytes. struct IMGUI_API ImGuiTableTempData { int TableIndex; // Index in g.Tables.Buf[] pool float LastTimeActive; // Last timestamp this structure was used float AngledHeadersExtraWidth; // Used in EndTable() + ImVector AngledHeadersRequests; // Used in TableAngledHeadersRow() ImVec2 UserOuterSize; // outer_size.x passed to BeginTable() ImDrawListSplitter DrawSplitter; @@ -3226,7 +3342,7 @@ 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 + // If this ever crashes 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; } @@ -3234,6 +3350,7 @@ namespace ImGui 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 void UpdateWindowSkipRefresh(ImGuiWindow* window); IMGUI_API ImVec2 CalcWindowNextAutoFitSize(ImGuiWindow* window); IMGUI_API bool IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent, bool popup_hierarchy, bool dock_hierarchy); IMGUI_API bool IsWindowWithinBeginStackOf(ImGuiWindow* window, ImGuiWindow* potential_parent); @@ -3248,6 +3365,7 @@ namespace ImGui inline ImRect WindowRectAbsToRel(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x - off.x, r.Min.y - off.y, r.Max.x - off.x, r.Max.y - off.y); } inline ImRect WindowRectRelToAbs(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x + off.x, r.Min.y + off.y, r.Max.x + off.x, r.Max.y + off.y); } inline ImVec2 WindowPosRelToAbs(ImGuiWindow* window, const ImVec2& p) { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x + off.x, p.y + off.y); } + inline ImVec2 WindowPosAbsToRel(ImGuiWindow* window, const ImVec2& p) { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x - off.x, p.y - off.y); } // Windows: Display Order and Focus Order IMGUI_API void FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags = 0); @@ -3259,6 +3377,9 @@ namespace ImGui IMGUI_API int FindWindowDisplayIndex(ImGuiWindow* window); IMGUI_API ImGuiWindow* FindBottomMostVisibleWindowWithinBeginStack(ImGuiWindow* window); + // Windows: Idle, Refresh Policies [EXPERIMENTAL] + IMGUI_API void SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags); + // Fonts, drawing IMGUI_API void SetCurrentFont(ImFont* font); inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } @@ -3272,6 +3393,7 @@ namespace ImGui // NewFrame IMGUI_API void UpdateInputEvents(bool trickle_fast_inputs); IMGUI_API void UpdateHoveredWindowAndCaptureFlags(); + IMGUI_API void FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_viewport, ImGuiWindow** out_hovered_window, ImGuiWindow** out_hovered_window_under_moving_window); IMGUI_API void StartMouseMovingWindow(ImGuiWindow* window); IMGUI_API void StartMouseMovingWindowOrNode(ImGuiWindow* window, ImGuiDockNode* node, bool undock); IMGUI_API void UpdateMouseMovingWindowNewFrame(); @@ -3324,7 +3446,7 @@ namespace ImGui //#endif // Basic Accessors - inline ImGuiItemStatusFlags GetItemStatusFlags(){ ImGuiContext& g = *GImGui; return g.LastItemData.StatusFlags; } + 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; } @@ -3350,14 +3472,12 @@ namespace ImGui IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_w, float default_h); IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); IMGUI_API void PushMultiItemsWidths(int components, float width_full); - IMGUI_API bool IsItemToggledSelection(); // Was the last item selection toggled? (after Selectable(), TreeNode() etc. We only returns toggle _event_ in order to handle clipping correctly) - IMGUI_API ImVec2 GetContentRegionMaxAbs(); IMGUI_API void ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess); // Parameter stacks (shared) - IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); - IMGUI_API void PopItemFlag(); IMGUI_API const ImGuiDataVarInfo* GetStyleVarInfo(ImGuiStyleVar idx); + IMGUI_API void BeginDisabledOverrideReenable(); + IMGUI_API void EndDisabledOverrideReenable(); // 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. @@ -3365,16 +3485,16 @@ namespace ImGui 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 + // Childs IMGUI_API bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags); + + // Popups, Modals + IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags); IMGUI_API void OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags = ImGuiPopupFlags_None); IMGUI_API void ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup); IMGUI_API void ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to_window_under_popup); IMGUI_API void ClosePopupsExceptModals(); IMGUI_API bool IsPopupOpen(ImGuiID id, ImGuiPopupFlags popup_flags); - IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags); - IMGUI_API bool BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags extra_window_flags); - IMGUI_API bool BeginTooltipHidden(); IMGUI_API ImRect GetPopupAllowedExtentRect(ImGuiWindow* window); IMGUI_API ImGuiWindow* GetTopMostPopupModal(); IMGUI_API ImGuiWindow* GetTopMostAndVisiblePopupModal(); @@ -3382,6 +3502,10 @@ namespace ImGui IMGUI_API ImVec2 FindBestWindowPosForPopup(ImGuiWindow* window); IMGUI_API ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy); + // Tooltips + IMGUI_API bool BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags extra_window_flags); + IMGUI_API bool BeginTooltipHidden(); + // Menus IMGUI_API bool BeginViewportSideBar(const char* name, ImGuiViewport* viewport, ImGuiDir dir, float size, ImGuiWindowFlags window_flags); IMGUI_API bool BeginMenuEx(const char* label, const char* icon, bool enabled = true); @@ -3399,7 +3523,7 @@ namespace ImGui IMGUI_API void NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags); IMGUI_API void NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags); IMGUI_API void NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result); - IMGUI_API void NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiNavTreeNodeData* tree_node_data); + IMGUI_API void NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiTreeNodeStackData* tree_node_data); IMGUI_API void NavMoveRequestCancel(); IMGUI_API void NavMoveRequestApplyResult(); IMGUI_API void NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags); @@ -3419,23 +3543,21 @@ namespace ImGui // Inputs // FIXME: Eventually we should aim to move e.g. IsActiveIdUsingKey() into IsKeyXXX functions. - inline bool IsNamedKey(ImGuiKey key) { return key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END; } - inline bool IsNamedKeyOrModKey(ImGuiKey key) { return (key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END) || key == ImGuiMod_Ctrl || key == ImGuiMod_Shift || key == ImGuiMod_Alt || key == ImGuiMod_Super || key == ImGuiMod_Shortcut; } - inline bool IsLegacyKey(ImGuiKey key) { return key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_LegacyNativeKey_END; } - inline bool IsKeyboardKey(ImGuiKey key) { return key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END; } - inline bool IsGamepadKey(ImGuiKey key) { return key >= ImGuiKey_Gamepad_BEGIN && key < ImGuiKey_Gamepad_END; } - inline bool IsMouseKey(ImGuiKey key) { return key >= ImGuiKey_Mouse_BEGIN && key < ImGuiKey_Mouse_END; } - inline bool IsAliasKey(ImGuiKey key) { return key >= ImGuiKey_Aliases_BEGIN && key < ImGuiKey_Aliases_END; } - inline bool IsModKey(ImGuiKey key) { return key >= ImGuiKey_LeftCtrl && key <= ImGuiKey_RightSuper; } - ImGuiKeyChord FixupKeyChord(ImGuiContext* ctx, ImGuiKeyChord key_chord); - inline ImGuiKey ConvertSingleModFlagToKey(ImGuiContext* ctx, ImGuiKey key) + inline bool IsNamedKey(ImGuiKey key) { return key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END; } + inline bool IsNamedKeyOrMod(ImGuiKey key) { return (key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END) || key == ImGuiMod_Ctrl || key == ImGuiMod_Shift || key == ImGuiMod_Alt || key == ImGuiMod_Super; } + inline bool IsLegacyKey(ImGuiKey key) { return key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_LegacyNativeKey_END; } + inline bool IsKeyboardKey(ImGuiKey key) { return key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END; } + inline bool IsGamepadKey(ImGuiKey key) { return key >= ImGuiKey_Gamepad_BEGIN && key < ImGuiKey_Gamepad_END; } + inline bool IsMouseKey(ImGuiKey key) { return key >= ImGuiKey_Mouse_BEGIN && key < ImGuiKey_Mouse_END; } + inline bool IsAliasKey(ImGuiKey key) { return key >= ImGuiKey_Aliases_BEGIN && key < ImGuiKey_Aliases_END; } + inline bool IsModKey(ImGuiKey key) { return key >= ImGuiKey_LeftCtrl && key <= ImGuiKey_RightSuper; } + ImGuiKeyChord FixupKeyChord(ImGuiKeyChord key_chord); + inline ImGuiKey ConvertSingleModFlagToKey(ImGuiKey key) { - ImGuiContext& g = *ctx; if (key == ImGuiMod_Ctrl) return ImGuiKey_ReservedForModCtrl; if (key == ImGuiMod_Shift) return ImGuiKey_ReservedForModShift; if (key == ImGuiMod_Alt) return ImGuiKey_ReservedForModAlt; if (key == ImGuiMod_Super) return ImGuiKey_ReservedForModSuper; - if (key == ImGuiMod_Shortcut) return (g.IO.ConfigMacOSXBehaviors ? ImGuiKey_ReservedForModSuper : ImGuiKey_ReservedForModCtrl); return key; } @@ -3455,7 +3577,7 @@ namespace ImGui // [EXPERIMENTAL] Low-Level: Key/Input Ownership // - The idea is that instead of "eating" a given input, we can link to an owner id. // - Ownership is most often claimed as a result of reacting to a press/down event (but occasionally may be claimed ahead). - // - Input queries can then read input by specifying ImGuiKeyOwner_Any (== 0), ImGuiKeyOwner_None (== -1) or a custom ID. + // - Input queries can then read input by specifying ImGuiKeyOwner_Any (== 0), ImGuiKeyOwner_NoOwner (== -1) or a custom ID. // - Legacy input queries (without specifying an owner or _Any or _None) are equivalent to using ImGuiKeyOwner_Any (== 0). // - Input ownership is automatically released on the frame after a key is released. Therefore: // - for ownership registration happening as a result of a down/press event, the SetKeyOwner() call may be done once (common case). @@ -3463,12 +3585,12 @@ namespace ImGui // - SetItemKeyOwner() is a shortcut for common simple case. A custom widget will probably want to call SetKeyOwner() multiple times directly based on its interaction state. // - This is marked experimental because not all widgets are fully honoring the Set/Test idioms. We will need to move forward step by step. // Please open a GitHub Issue to submit your usage scenario or if there's a use case you need solved. - IMGUI_API ImGuiID GetKeyOwner(ImGuiKey key); - IMGUI_API void SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0); - IMGUI_API void SetKeyOwnersForKeyChord(ImGuiKeyChord key, ImGuiID owner_id, ImGuiInputFlags flags = 0); - IMGUI_API void SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags = 0); // Set key owner to last item if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'. - IMGUI_API bool TestKeyOwner(ImGuiKey key, ImGuiID owner_id); // Test that key is either not owned, either owned by 'owner_id' - inline ImGuiKeyOwnerData* GetKeyOwnerData(ImGuiContext* ctx, ImGuiKey key) { if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(ctx, key); IM_ASSERT(IsNamedKey(key)); return &ctx->KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; } + IMGUI_API ImGuiID GetKeyOwner(ImGuiKey key); + IMGUI_API void SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0); + IMGUI_API void SetKeyOwnersForKeyChord(ImGuiKeyChord key, ImGuiID owner_id, ImGuiInputFlags flags = 0); + IMGUI_API void SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags); // Set key owner to last item if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'. + IMGUI_API bool TestKeyOwner(ImGuiKey key, ImGuiID owner_id); // Test that key is either not owned, either owned by 'owner_id' + inline ImGuiKeyOwnerData* GetKeyOwnerData(ImGuiContext* ctx, ImGuiKey key) { if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); IM_ASSERT(IsNamedKey(key)); return &ctx->KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; } // [EXPERIMENTAL] High-Level: Input Access functions w/ support for Key/Input Ownership // - Important: legacy IsKeyPressed(ImGuiKey, bool repeat=true) _DEFAULTS_ to repeat, new IsKeyPressed() requires _EXPLICIT_ ImGuiInputFlags_Repeat flag. @@ -3476,32 +3598,32 @@ namespace ImGui // - Specifying a value for 'ImGuiID owner' will test that EITHER the key is NOT owned (UNLESS locked), EITHER the key is owned by 'owner'. // Legacy functions use ImGuiKeyOwner_Any meaning that they typically ignore ownership, unless a call to SetKeyOwner() explicitly used ImGuiInputFlags_LockThisFrame or ImGuiInputFlags_LockUntilRelease. // - Binding generators may want to ignore those for now, or suffix them with Ex() until we decide if this gets moved into public API. - IMGUI_API bool IsKeyDown(ImGuiKey key, ImGuiID owner_id); - IMGUI_API bool IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0); // Important: when transitioning from old to new IsKeyPressed(): old API has "bool repeat = true", so would default to repeat. New API requiress explicit ImGuiInputFlags_Repeat. - IMGUI_API bool IsKeyReleased(ImGuiKey key, ImGuiID owner_id); - IMGUI_API bool IsMouseDown(ImGuiMouseButton button, ImGuiID owner_id); - IMGUI_API bool IsMouseClicked(ImGuiMouseButton button, ImGuiID owner_id, ImGuiInputFlags flags = 0); - IMGUI_API bool IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id); - IMGUI_API bool IsMouseDoubleClicked(ImGuiMouseButton button, ImGuiID owner_id); + IMGUI_API bool IsKeyDown(ImGuiKey key, ImGuiID owner_id); + IMGUI_API bool IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id = 0); // Important: when transitioning from old to new IsKeyPressed(): old API has "bool repeat = true", so would default to repeat. New API requiress explicit ImGuiInputFlags_Repeat. + IMGUI_API bool IsKeyReleased(ImGuiKey key, ImGuiID owner_id); + IMGUI_API bool IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id = 0); + IMGUI_API bool IsMouseDown(ImGuiMouseButton button, ImGuiID owner_id); + IMGUI_API bool IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGuiID owner_id = 0); + IMGUI_API bool IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id); + IMGUI_API bool IsMouseDoubleClicked(ImGuiMouseButton button, ImGuiID owner_id); - // [EXPERIMENTAL] Shortcut Routing - // - ImGuiKeyChord = a ImGuiKey optionally OR-red with ImGuiMod_Alt/ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Super. - // ImGuiKey_C (accepted by functions taking ImGuiKey or ImGuiKeyChord) - // ImGuiKey_C | ImGuiMod_Ctrl (accepted by functions taking ImGuiKeyChord) - // ONLY ImGuiMod_XXX values are legal to 'OR' with an ImGuiKey. You CANNOT 'OR' two ImGuiKey values. - // - When using one of the routing flags (e.g. ImGuiInputFlags_RouteFocused): routes requested ahead of time given a chord (key + modifiers) and a routing policy. - // - Routes are resolved during NewFrame(): if keyboard modifiers are matching current ones: SetKeyOwner() is called + route is granted for the frame. - // - Route is granted to a single owner. When multiple requests are made we have policies to select the winning route. - // - Multiple read sites may use the same owner id and will all get the granted route. - // - For routing: when owner_id is 0 we use the current Focus Scope ID as a default owner in order to identify our location. - // - TL;DR; - // - IsKeyChordPressed() compares mods + call IsKeyPressed() -> function has no side-effect. - // - Shortcut() submits a route then if currently can be routed calls IsKeyChordPressed() -> function has (desirable) side-effects. - IMGUI_API bool IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags = 0); - IMGUI_API void SetNextItemShortcut(ImGuiKeyChord key_chord); - IMGUI_API bool Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0); - IMGUI_API bool SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags = 0); // owner_id needs to be explicit and cannot be 0 - IMGUI_API bool TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id); + // Shortcut Testing & Routing + // - Set Shortcut() and SetNextItemShortcut() in imgui.h + // - When a policy (except for ImGuiInputFlags_RouteAlways *) is set, Shortcut() will register itself with SetShortcutRouting(), + // allowing the system to decide where to route the input among other route-aware calls. + // (* using ImGuiInputFlags_RouteAlways is roughly equivalent to calling IsKeyChordPressed(key) and bypassing route registration and check) + // - When using one of the routing option: + // - The default route is ImGuiInputFlags_RouteFocused (accept inputs if window is in focus stack. Deep-most focused window takes inputs. ActiveId takes inputs over deep-most focused window.) + // - Routes are requested given a chord (key + modifiers) and a routing policy. + // - Routes are resolved during NewFrame(): if keyboard modifiers are matching current ones: SetKeyOwner() is called + route is granted for the frame. + // - Each route may be granted to a single owner. When multiple requests are made we have policies to select the winning route (e.g. deep most window). + // - Multiple read sites may use the same owner id can all access the granted route. + // - When owner_id is 0 we use the current Focus Scope ID as a owner ID in order to identify our location. + // - You can chain two unrelated windows in the focus stack using SetWindowParentWindowForFocusRoute() + // e.g. if you have a tool window associated to a document, and you want document shortcuts to run when the tool is focused. + IMGUI_API bool Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id); + IMGUI_API bool SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id); // owner_id needs to be explicit and cannot be 0 + IMGUI_API bool TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id); IMGUI_API ImGuiKeyRoutingData* GetShortcutRoutingData(ImGuiKeyChord key_chord); // Docking @@ -3584,6 +3706,18 @@ namespace ImGui IMGUI_API int TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx); IMGUI_API int TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data); + // Box-Select API + IMGUI_API bool BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags); + IMGUI_API void EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flags); + + // Multi-Select API + IMGUI_API void MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags); + IMGUI_API void MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed); + IMGUI_API void MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected); + IMGUI_API void MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item); + inline ImGuiBoxSelectState* GetBoxSelectState(ImGuiID id) { ImGuiContext& g = *GImGui; return (id != 0 && g.BoxSelectState.ID == id && g.BoxSelectState.IsActive) ? &g.BoxSelectState : NULL; } + inline ImGuiMultiSelectState* GetMultiSelectState(ImGuiID id) { ImGuiContext& g = *GImGui; return g.MultiSelectStorage.GetByKey(id); } + // Internal Columns API (this is not exposed because we will encourage transitioning to the Tables API) IMGUI_API void SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect); IMGUI_API void BeginColumns(const char* str_id, int count, ImGuiOldColumnFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns(). @@ -3600,13 +3734,12 @@ namespace ImGui IMGUI_API void TableOpenContextMenu(int column_n = -1); IMGUI_API void TableSetColumnWidth(int column_n, float width); IMGUI_API void TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs); - IMGUI_API int TableGetHoveredColumn(); // May use (TableGetColumnFlags() & ImGuiTableColumnFlags_IsHovered) instead. Return hovered column. return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. IMGUI_API int TableGetHoveredRow(); // Retrieve *PREVIOUS FRAME* hovered row. This difference with TableGetHoveredColumn() is the reason why this is not public yet. IMGUI_API float TableGetHeaderRowHeight(); IMGUI_API float TableGetHeaderAngledMaxLabelWidth(); IMGUI_API void TablePushBackgroundChannel(); IMGUI_API void TablePopBackgroundChannel(); - IMGUI_API void TableAngledHeadersRowEx(float angle, float max_label_width = 0.0f); + IMGUI_API void TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label_width, const ImGuiTableHeaderData* data, int data_count); // Tables: Internals inline ImGuiTable* GetCurrentTable() { ImGuiContext& g = *GImGui; return g.CurrentTable; } @@ -3725,11 +3858,13 @@ namespace ImGui IMGUI_API bool DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags); IMGUI_API bool SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb); IMGUI_API bool SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f, float hover_visibility_delay = 0.0f, ImU32 bg_col = 0); + + // Widgets: Tree Nodes IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); IMGUI_API void TreePushOverrideID(ImGuiID id); - IMGUI_API void TreeNodeSetOpen(ImGuiID id, bool open); - IMGUI_API bool TreeNodeUpdateNextOpen(ImGuiID id, ImGuiTreeNodeFlags flags); // Return open state. Consume previous SetNextItemOpen() data, if any. May return true when logging. - IMGUI_API void SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data); + IMGUI_API bool TreeNodeGetOpen(ImGuiID storage_id); + IMGUI_API void TreeNodeSetOpen(ImGuiID storage_id, bool open); + IMGUI_API bool TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags); // Return open state. Consume previous SetNextItemOpen() data, if any. May return true when logging. // Template functions are instantiated in imgui_widgets.cpp for a finite number of types. // To use them externally (for custom widget) you may need an "extern template" statement in your code in order to link to existing instances and silence Clang warnings (see #2036). @@ -3745,7 +3880,7 @@ namespace ImGui 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 bool DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format, void* p_data_when_empty = NULL); 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); @@ -3756,6 +3891,7 @@ namespace ImGui IMGUI_API bool TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min = NULL, const void* p_clamp_max = NULL); inline bool TempInputIsActive(ImGuiID id) { ImGuiContext& g = *GImGui; return (g.ActiveId == id && g.TempInputId == id); } inline ImGuiInputTextState* GetInputTextState(ImGuiID id) { ImGuiContext& g = *GImGui; return (id != 0 && g.InputTextState.ID == id) ? &g.InputTextState : NULL; } // Get input text state if active + IMGUI_API void SetNextItemRefVal(ImGuiDataType data_type, void* p_data); // Color IMGUI_API void ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags); @@ -3775,18 +3911,15 @@ namespace ImGui 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); - IMGUI_API void DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr, size_t size); // size >= 0 : alloc, size = -1 : free - // Debug Tools + IMGUI_API void DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr, size_t size); // size >= 0 : alloc, size = -1 : free IMGUI_API void ErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback, void* user_data = NULL); IMGUI_API void ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, void* user_data = NULL); IMGUI_API void ErrorCheckUsingSetCursorPosToExtendParentBoundaries(); IMGUI_API void DebugDrawCursorPos(ImU32 col = IM_COL32(255, 0, 0, 255)); IMGUI_API void DebugDrawLineExtents(ImU32 col = IM_COL32(255, 0, 0, 255)); IMGUI_API void DebugDrawItemRect(ImU32 col = IM_COL32(255, 0, 0, 255)); + IMGUI_API void DebugTextUnformattedWithLocateItem(const char* line_begin, const char* line_end); IMGUI_API void DebugLocateItem(ImGuiID target_id); // Call sparingly: only 1 at the same time! IMGUI_API void DebugLocateItemOnHover(ImGuiID target_id); // Only call on reaction to a mouse Hover: because only 1 at the same time! IMGUI_API void DebugLocateItemResolveWithLastItem(); @@ -3807,11 +3940,13 @@ namespace ImGui IMGUI_API void DebugNodeTableSettings(ImGuiTableSettings* settings); IMGUI_API void DebugNodeInputTextState(ImGuiInputTextState* state); IMGUI_API void DebugNodeTypingSelectState(ImGuiTypingSelectState* state); + IMGUI_API void DebugNodeMultiSelectState(ImGuiMultiSelectState* 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 DebugNodePlatformMonitor(ImGuiPlatformMonitor* monitor, const char* label, int idx); IMGUI_API void DebugRenderKeyboardPreview(ImDrawList* draw_list); IMGUI_API void DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* viewport, const ImRect& bb); @@ -3820,6 +3955,8 @@ namespace ImGui inline void SetItemUsingMouseWheel() { SetItemKeyOwner(ImGuiKey_MouseWheelY); } // Changed in 1.89 inline bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0) { return TreeNodeUpdateNextOpen(id, flags); } // Renamed in 1.89 + //inline bool IsKeyPressedMap(ImGuiKey key, bool repeat = true) { IM_ASSERT(IsNamedKey(key)); return IsKeyPressed(key, repeat); } // Removed in 1.87: Mapping from named key is always identity! + // Refactored focus/nav/tabbing system in 1.82 and 1.84. If you have old/custom copy-and-pasted widgets which used FocusableItemRegister(): // (Old) IMGUI_VERSION_NUM < 18209: using 'ItemAdd(....)' and 'bool tab_focused = FocusableItemRegister(...)' // (Old) IMGUI_VERSION_NUM >= 18209: using 'ItemAdd(..., ImGuiItemAddFlags_Focusable)' and 'bool tab_focused = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Focused) != 0' @@ -3827,9 +3964,6 @@ namespace ImGui //inline bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id) // -> pass ImGuiItemAddFlags_Inputable flag to ItemAdd() //inline void FocusableItemUnregister(ImGuiWindow* window) // -> unnecessary: TempInputText() uses ImGuiInputTextFlags_MergedItem #endif -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - inline bool IsKeyPressedMap(ImGuiKey key, bool repeat = true) { IM_ASSERT(IsNamedKey(key)); return IsKeyPressed(key, repeat); } // Removed in 1.87: Mapping from named key is always identity! -#endif } // namespace ImGui diff --git a/libs/imgui/imgui_tables.cpp b/libs/imgui/imgui_tables.cpp index 260df1a..37dd703 100644 --- a/libs/imgui/imgui_tables.cpp +++ b/libs/imgui/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.4 +// dear imgui, v1.91.0 // (tables and columns code) /* @@ -24,8 +24,9 @@ Index of this file: */ // 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. +// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. +// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. //----------------------------------------------------------------------------- // [SECTION] Commentary @@ -227,6 +228,7 @@ Index of this file: #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 +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #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 @@ -318,6 +320,12 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG IM_ASSERT(inner_width >= 0.0f); // If an outer size is specified ahead we will be able to early out when not visible. Exact clipping criteria may evolve. + // FIXME: coarse clipping because access to table data causes two issues: + // - instance numbers varying/unstable. may not be a direct problem for users, but could make outside access broken or confusing, e.g. TestEngine. + // - can't implement support for ImGuiChildFlags_ResizeY as we need to somehow pull the height data from somewhere. this also needs stable instance numbers. + // The side-effects of accessing table data on coarse clip would be: + // - always reserving the pooled ImGuiTable data ahead for a fully clipped table (minor IMHO). Also the 'outer_window_is_measuring_size' criteria may already be defeating this in some situations. + // - always performing the GetOrAddByKey() O(log N) query in g.Tables.Map[]. const bool use_child_window = (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0; const ImVec2 avail_size = GetContentRegionAvail(); const ImVec2 actual_outer_size = CalcItemSize(outer_size, ImMax(avail_size.x, 1.0f), use_child_window ? ImMax(avail_size.y, 1.0f) : 0.0f); @@ -326,6 +334,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (use_child_window && IsClippedEx(outer_rect, 0) && !outer_window_is_measuring_size) { ItemSize(outer_rect); + ItemAdd(outer_rect, id); return false; } @@ -335,7 +344,6 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Acquire storage for the table ImGuiTable* table = g.Tables.GetOrAddByKey(id); - const ImGuiTableFlags table_last_flags = table->Flags; // Acquire temporary buffers const int table_idx = g.Tables.GetIndex(table); @@ -353,6 +361,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Initialize const int previous_frame_active = table->LastFrameActive; const int instance_no = (previous_frame_active != g.FrameCount) ? 0 : table->InstanceCurrent + 1; + const ImGuiTableFlags previous_flags = table->Flags; table->ID = id; table->Flags = flags; table->LastFrameActive = g.FrameCount; @@ -399,12 +408,12 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG 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) + if ((previous_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); + ImGuiWindowFlags child_window_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; + BeginChildEx(name, instance_id, outer_rect.GetSize(), ImGuiChildFlags_None, child_window_flags); table->InnerWindow = g.CurrentWindow; table->WorkRect = table->InnerWindow->WorkRect; table->OuterRect = table->InnerWindow->Rect(); @@ -428,6 +437,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // 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; + table->HasScrollbarYPrev = table->HasScrollbarYCurr = false; } // Push a standardized ID for both child-using and not-child-using tables @@ -498,6 +508,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->DeclColumnsCount = table->AngledHeadersCount = 0; if (previous_frame_active + 1 < g.FrameCount) table->IsActiveIdInTable = false; + table->AngledHeadersHeight = 0.0f; temp_data->AngledHeadersExtraWidth = 0.0f; // Using opaque colors facilitate overlapping lines of the grid, otherwise we'd need to improve TableDrawBorders() @@ -511,7 +522,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG 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) + if ((previous_flags & ImGuiTableFlags_Reorderable) && (flags & ImGuiTableFlags_Reorderable) == 0) table->IsResetDisplayOrderRequest = true; // Mark as used to avoid GC @@ -1066,6 +1077,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // - 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. + const float previous_instance_work_min_x = column->WorkMinX; column->WorkMinX = column->MinX + table->CellPaddingX + table->CellSpacingX1; column->WorkMaxX = column->MaxX - table->CellPaddingX - table->CellSpacingX2; // Expected max column->ItemWidth = ImTrunc(column->WidthGiven * 0.65f); @@ -1118,8 +1130,22 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // 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; + if (table->InstanceCurrent == 0) + { + column->ContentMaxXFrozen = column->WorkMinX; + column->ContentMaxXUnfrozen = column->WorkMinX; + column->ContentMaxXHeadersUsed = column->WorkMinX; + column->ContentMaxXHeadersIdeal = column->WorkMinX; + } + else + { + // As we store an absolute value to make per-cell updates faster, we need to offset values used for width computation. + const float offset_from_previous_instance = column->WorkMinX - previous_instance_work_min_x; + column->ContentMaxXFrozen += offset_from_previous_instance; + column->ContentMaxXUnfrozen += offset_from_previous_instance; + column->ContentMaxXHeadersUsed += offset_from_previous_instance; + column->ContentMaxXHeadersIdeal += offset_from_previous_instance; + } // Don't decrement auto-fit counters until container window got a chance to submit its items if (table->HostSkipItems == false) @@ -1238,9 +1264,9 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) // 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_half_width = ImTrunc(TABLE_RESIZE_SEPARATOR_HALF_THICKNESS * g.CurrentDpiScale); const float hit_y1 = (table->FreezeRowsCount >= 1 ? table->OuterRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight; - const float hit_y2_body = ImMax(table->OuterRect.Max.y, hit_y1 + table_instance->LastOuterHeight); + const float hit_y2_body = ImMax(table->OuterRect.Max.y, hit_y1 + table_instance->LastOuterHeight - table->AngledHeadersHeight); const float hit_y2_head = hit_y1 + table_instance->LastTopHeadersRowHeight; for (int order_n = 0; order_n < table->ColumnsCount; order_n++) @@ -1415,7 +1441,7 @@ void ImGui::EndTable() 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_x2 = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + ImTrunc(TABLE_RESIZE_SEPARATOR_HALF_THICKNESS * g.CurrentDpiScale)); const float new_width = ImTrunc(new_x2 - column->MinX - table->CellSpacingX1 - table->CellPaddingX * 2.0f); table->ResizedColumnNextWidth = new_width; } @@ -1444,7 +1470,10 @@ void ImGui::EndTable() // CursorPosPrevLine and CursorMaxPos manually. That should be a more general layout feature, see same problem e.g. #3414) if (inner_window != outer_window) { + short backup_nav_layers_active_mask = inner_window->DC.NavLayersActiveMask; + inner_window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main; // So empty table don't appear to navigate differently. EndChild(); + inner_window->DC.NavLayersActiveMask = backup_nav_layers_active_mask; } else { @@ -1462,9 +1491,13 @@ void ImGui::EndTable() } else if (temp_data->UserOuterSize.x <= 0.0f) { - const float decoration_size = table->TempData->AngledHeadersExtraWidth + ((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)); + // Some references for this: #7651 + tests "table_reported_size", "table_reported_size_outer" equivalent Y block + // - Checking for ImGuiTableFlags_ScrollX/ScrollY flag makes us a frame ahead when disabling those flags. + // - FIXME-TABLE: Would make sense to pre-compute expected scrollbar visibility/sizes to generally save a frame of feedback. + const float inner_content_max_x = table->OuterRect.Min.x + table->ColumnsAutoFitWidth; // Slightly misleading name but used for code symmetry with inner_content_max_y + const float decoration_size = table->TempData->AngledHeadersExtraWidth + ((table->Flags & ImGuiTableFlags_ScrollY) ? inner_window->ScrollbarSizes.x : 0.0f); + outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, inner_content_max_x + decoration_size - temp_data->UserOuterSize.x); + outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table->OuterRect.Max.x, inner_content_max_x + decoration_size)); } else { @@ -1472,9 +1505,9 @@ void ImGui::EndTable() } if (temp_data->UserOuterSize.y <= 0.0f) { - const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollY) ? inner_window->ScrollbarSizes.y : 0.0f; + const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollX) ? 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)); + outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table->OuterRect.Max.y, inner_content_max_y + decoration_size)); } else { @@ -1890,7 +1923,7 @@ void ImGui::TableEndRow(ImGuiTable* table) if (is_visible) { // Update data for TableGetHoveredRow() - if (table->HoveredColumnBody != -1 && g.IO.MousePos.y >= bg_y1 && g.IO.MousePos.y < bg_y2) + if (table->HoveredColumnBody != -1 && g.IO.MousePos.y >= bg_y1 && g.IO.MousePos.y < bg_y2 && table_instance->HoveredRowNext < 0) table_instance->HoveredRowNext = table->CurrentRow; // Decide of background color for the row @@ -1945,7 +1978,8 @@ void ImGui::TableEndRow(ImGuiTable* table) 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); + if (cell_bg_rect.Min.y < cell_bg_rect.Max.y) + window->DrawList->AddRectFilled(cell_bg_rect.Min, cell_bg_rect.Max, cell_data->BgColor); } } @@ -1962,34 +1996,37 @@ void ImGui::TableEndRow(ImGuiTable* table) // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and // get the new cursor position. if (unfreeze_rows_request) + { for (int column_n = 0; column_n < table->ColumnsCount; column_n++) table->Columns[column_n].NavLayerCurrent = ImGuiNavLayer_Main; - if (unfreeze_rows_actual) - { - IM_ASSERT(table->IsUnfrozenRows == false); const float y0 = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y); - table->IsUnfrozenRows = true; table_instance->LastFrozenHeight = y0 - table->OuterRect.Min.y; - // BgClipRect starts as table->InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect - table->BgClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y = ImMin(y0, window->InnerClipRect.Max.y); - table->BgClipRect.Max.y = table->Bg2ClipRectForDrawCmd.Max.y = window->InnerClipRect.Max.y; - table->Bg2DrawChannelCurrent = table->Bg2DrawChannelUnfrozen; - IM_ASSERT(table->Bg2ClipRectForDrawCmd.Min.y <= table->Bg2ClipRectForDrawCmd.Max.y); - - float row_height = table->RowPosY2 - table->RowPosY1; - table->RowPosY2 = window->DC.CursorPos.y = table->WorkRect.Min.y + table->RowPosY2 - table->OuterRect.Min.y; - table->RowPosY1 = table->RowPosY2 - row_height; - for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + if (unfreeze_rows_actual) { - ImGuiTableColumn* column = &table->Columns[column_n]; - column->DrawChannelCurrent = column->DrawChannelUnfrozen; - column->ClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y; - } + IM_ASSERT(table->IsUnfrozenRows == false); + table->IsUnfrozenRows = true; - // 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); + // BgClipRect starts as table->InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect + table->BgClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y = ImMin(y0, window->InnerClipRect.Max.y); + table->BgClipRect.Max.y = table->Bg2ClipRectForDrawCmd.Max.y = window->InnerClipRect.Max.y; + table->Bg2DrawChannelCurrent = table->Bg2DrawChannelUnfrozen; + IM_ASSERT(table->Bg2ClipRectForDrawCmd.Min.y <= table->Bg2ClipRectForDrawCmd.Max.y); + + float row_height = table->RowPosY2 - table->RowPosY1; + table->RowPosY2 = window->DC.CursorPos.y = table->WorkRect.Min.y + table->RowPosY2 - table->OuterRect.Min.y; + table->RowPosY1 = table->RowPosY2 - row_height; + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + column->DrawChannelCurrent = column->DrawChannelUnfrozen; + column->ClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y; + } + + // Update cliprect ahead of TableBeginCell() so clipper can access to new ClipRect->Min.y + SetWindowClipRectBeforeSetChannel(window, table->Columns[0].ClipRect); + table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Columns[0].DrawChannelCurrent); + } } if (!(table->RowFlags & ImGuiTableRowFlags_Headers)) @@ -2766,7 +2803,7 @@ ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() static inline ImGuiSortDirection TableGetColumnAvailSortDirection(ImGuiTableColumn* column, int n) { IM_ASSERT(n < column->SortDirectionsAvailCount); - return (column->SortDirectionsAvailList >> (n << 1)) & 0x03; + return (ImGuiSortDirection)((column->SortDirectionsAvailList >> (n << 1)) & 0x03); } // Fix sort direction if currently set on a value which is unavailable (e.g. activating NoSortAscending/NoSortDescending) @@ -2907,6 +2944,7 @@ void ImGui::TableSortSpecsBuild(ImGuiTable* table) } // Write output + // May be able to move all SortSpecs data from table (48 bytes) to ImGuiTableTempData if we decide to write it back on every BeginTable() ImGuiTableColumnSortSpecs* sort_specs = (table->SortSpecsCount == 0) ? NULL : (table->SortSpecsCount == 1) ? &table->SortSpecsSingle : table->SortSpecsMulti.Data; if (dirty && sort_specs != NULL) for (int column_n = 0; column_n < table->ColumnsCount; column_n++) @@ -2919,7 +2957,7 @@ void ImGui::TableSortSpecsBuild(ImGuiTable* table) sort_spec->ColumnUserID = column->UserID; sort_spec->ColumnIndex = (ImGuiTableColumnIdx)column_n; sort_spec->SortOrder = (ImGuiTableColumnIdx)column->SortOrder; - sort_spec->SortDirection = column->SortDirection; + sort_spec->SortDirection = (ImGuiSortDirection)column->SortDirection; } table->SortSpecs.Specs = sort_specs; @@ -2967,7 +3005,7 @@ float ImGui::TableGetHeaderAngledMaxLabelWidth() // [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. +// and can create their own! For example: TableHeader() may be preceded 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. @@ -3153,15 +3191,43 @@ void ImGui::TableHeader(const char* label) } // Unlike TableHeadersRow() it is not expected that you can reimplement or customize this with custom widgets. -// FIXME: highlight without ImGuiTableFlags_HighlightHoveredColumn // FIXME: No hit-testing/button on the angled header. void ImGui::TableAngledHeadersRow() { ImGuiContext& g = *GImGui; - TableAngledHeadersRowEx(g.Style.TableAngledHeadersAngle, 0.0f); + ImGuiTable* table = g.CurrentTable; + ImGuiTableTempData* temp_data = table->TempData; + temp_data->AngledHeadersRequests.resize(0); + temp_data->AngledHeadersRequests.reserve(table->ColumnsEnabledCount); + + // Which column needs highlight? + const ImGuiID row_id = GetID("##AngledHeaders"); + ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); + int highlight_column_n = table->HighlightColumnHeader; + if (highlight_column_n == -1 && table->HoveredColumnBody != -1) + if (table_instance->HoveredRowLast == 0 && table->HoveredColumnBorder == -1 && (g.ActiveId == 0 || g.ActiveId == row_id || (table->IsActiveIdInTable || g.DragDropActive))) + highlight_column_n = table->HoveredColumnBody; + + // Build up request + ImU32 col_header_bg = GetColorU32(ImGuiCol_TableHeaderBg); + ImU32 col_text = GetColorU32(ImGuiCol_Text); + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + if (IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n)) + { + const int column_n = table->DisplayOrderToIndex[order_n]; + ImGuiTableColumn* column = &table->Columns[column_n]; + if ((column->Flags & ImGuiTableColumnFlags_AngledHeader) == 0) // Note: can't rely on ImGuiTableColumnFlags_IsVisible test here. + continue; + ImGuiTableHeaderData request = { (ImGuiTableColumnIdx)column_n, col_text, col_header_bg, (column_n == highlight_column_n) ? GetColorU32(ImGuiCol_Header) : 0 }; + temp_data->AngledHeadersRequests.push_back(request); + } + + // Render row + TableAngledHeadersRowEx(row_id, g.Style.TableAngledHeadersAngle, 0.0f, temp_data->AngledHeadersRequests.Data, temp_data->AngledHeadersRequests.Size); } -void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) +// Important: data must be fed left to right +void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label_width, const ImGuiTableHeaderData* data, int data_count) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; @@ -3185,7 +3251,7 @@ void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) // Calculate our base metrics and set angled headers data _before_ the first call to TableNextRow() // FIXME-STYLE: Would it be better for user to submit 'max_label_width' or 'row_height' ? One can be derived from the other. const float header_height = g.FontSize + g.Style.CellPadding.x * 2.0f; - const float row_height = ImFabs(ImRotate(ImVec2(max_label_width, flip_label ? +header_height : -header_height), cos_a, sin_a).y); + const float row_height = ImTrunc(ImFabs(ImRotate(ImVec2(max_label_width, flip_label ? +header_height : -header_height), cos_a, sin_a).y)); table->AngledHeadersHeight = row_height; table->AngledHeadersSlope = (sin_a != 0.0f) ? (cos_a / sin_a) : 0.0f; const ImVec2 header_angled_vector = unit_right * (row_height / -sin_a); // vector from bottom-left to top-left, and from bottom-right to top-right @@ -3203,28 +3269,22 @@ void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) draw_list->AddRectFilled(ImVec2(table->BgClipRect.Min.x, row_r.Min.y), ImVec2(table->BgClipRect.Max.x, row_r.Max.y), GetColorU32(ImGuiCol_TableHeaderBg, 0.25f)); // FIXME-STYLE: Change row background with an arbitrary color. PushClipRect(ImVec2(clip_rect_min_x, table->BgClipRect.Min.y), table->BgClipRect.Max, true); // Span all columns - const ImGuiID row_id = GetID("##AngledHeaders"); ButtonBehavior(row_r, row_id, NULL, NULL); KeepAliveID(row_id); - ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); - int highlight_column_n = table->HighlightColumnHeader; - if (highlight_column_n == -1 && table->HoveredColumnBody != -1) - if (table_instance->HoveredRowLast == 0 && table->HoveredColumnBorder == -1 && (g.ActiveId == 0 || g.ActiveId == row_id || (table->IsActiveIdInTable || g.DragDropActive))) - highlight_column_n = table->HoveredColumnBody; + const float ascent_scaled = g.Font->Ascent * g.FontScale; // FIXME: Standardize those scaling factors better + const float line_off_for_ascent_x = (ImMax((g.FontSize - ascent_scaled) * 0.5f, 0.0f) / -sin_a) * (flip_label ? -1.0f : 1.0f); + const ImVec2 padding = g.Style.CellPadding; // We will always use swapped component + const ImVec2 align = g.Style.TableAngledHeadersTextAlign; // Draw background and labels in first pass, then all borders. float max_x = 0.0f; - ImVec2 padding = g.Style.CellPadding; // We will always use swapped component for (int pass = 0; pass < 2; pass++) - for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + for (int order_n = 0; order_n < data_count; order_n++) { - if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n)) - continue; - const int column_n = table->DisplayOrderToIndex[order_n]; + const ImGuiTableHeaderData* request = &data[order_n]; + const int column_n = request->Index; ImGuiTableColumn* column = &table->Columns[column_n]; - if ((column->Flags & ImGuiTableColumnFlags_AngledHeader) == 0) // Note: can't rely on ImGuiTableColumnFlags_IsVisible test here. - continue; ImVec2 bg_shape[4]; bg_shape[0] = ImVec2(column->MaxX, row_r.Max.y); @@ -3234,9 +3294,8 @@ void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) if (pass == 0) { // Draw shape - draw_list->AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], GetColorU32(ImGuiCol_TableHeaderBg)); - if (column_n == highlight_column_n) - draw_list->AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], GetColorU32(ImGuiCol_Header)); // Highlight on hover + draw_list->AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], request->BgColor0); + draw_list->AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], request->BgColor1); // Optional highlight max_x = ImMax(max_x, bg_shape[3].x); // Draw label @@ -3244,8 +3303,17 @@ void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) // - Handle multiple lines manually, as we want each lines to follow on the horizontal border, rather than see a whole block rotated. const char* label_name = TableGetColumnName(table, column_n); const char* label_name_end = FindRenderedTextEnd(label_name); - const float line_off_step_x = g.FontSize / -sin_a; - float line_off_curr_x = 0.0f; + const float line_off_step_x = (g.FontSize / -sin_a); + const int label_lines = ImTextCountLines(label_name, label_name_end); + + // Left<>Right alignment + float line_off_curr_x = flip_label ? (label_lines - 1) * line_off_step_x : 0.0f; + float line_off_for_align_x = ImMax((((column->MaxX - column->MinX) - padding.x * 2.0f) - (label_lines * line_off_step_x)), 0.0f) * align.x; + line_off_curr_x += line_off_for_align_x - line_off_for_ascent_x; + + // Register header width + column->ContentMaxXHeadersUsed = column->ContentMaxXHeadersIdeal = column->WorkMinX + ImCeil(label_lines * line_off_step_x - line_off_for_align_x); + while (label_name < label_name_end) { const char* label_name_eol = strchr(label_name, '\n'); @@ -3254,26 +3322,30 @@ void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) // FIXME: Individual line clipping for right-most column is broken for negative angles. ImVec2 label_size = CalcTextSize(label_name, label_name_eol); - float clip_width = max_label_width - padding.y; // Using padding.y*2.0f would be symetrical but hide more text. + float clip_width = max_label_width - padding.y; // Using padding.y*2.0f would be symmetrical but hide more text. float clip_height = ImMin(label_size.y, column->ClipRect.Max.x - column->WorkMinX - line_off_curr_x); ImRect clip_r(window->ClipRect.Min, window->ClipRect.Min + ImVec2(clip_width, clip_height)); int vtx_idx_begin = draw_list->_VtxCurrentIdx; + PushStyleColor(ImGuiCol_Text, request->TextColor); RenderTextEllipsis(draw_list, clip_r.Min, clip_r.Max, clip_r.Max.x, clip_r.Max.x, label_name, label_name_eol, &label_size); + PopStyleColor(); int vtx_idx_end = draw_list->_VtxCurrentIdx; + // Up<>Down alignment + const float available_space = ImMax(clip_width - label_size.x + ImAbs(padding.x * cos_a) * 2.0f - ImAbs(padding.y * sin_a) * 2.0f, 0.0f); + const float vertical_offset = available_space * align.y * (flip_label ? -1.0f : 1.0f); + // Rotate and offset label - ImVec2 pivot_in = ImVec2(window->ClipRect.Min.x, window->ClipRect.Min.y + label_size.y); + ImVec2 pivot_in = ImVec2(window->ClipRect.Min.x - vertical_offset, window->ClipRect.Min.y + label_size.y); ImVec2 pivot_out = ImVec2(column->WorkMinX, row_r.Max.y); - line_off_curr_x += line_off_step_x; + line_off_curr_x += flip_label ? -line_off_step_x : line_off_step_x; pivot_out += unit_right * padding.y; if (flip_label) pivot_out += unit_right * (clip_width - ImMax(0.0f, clip_width - label_size.x)); - pivot_out.x += flip_label ? line_off_curr_x - line_off_step_x : line_off_curr_x; + pivot_out.x += flip_label ? line_off_curr_x + line_off_step_x : line_off_curr_x; ShadeVertsTransformPos(draw_list, vtx_idx_begin, vtx_idx_end, pivot_in, label_cos_a, label_sin_a, pivot_out); // Rotate and offset - //if (g.IO.KeyShift) { ImDrawList* fg_dl = GetForegroundDrawList(); vtx_idx_begin = fg_dl->_VtxCurrentIdx; fg_dl->AddRect(clip_r.Min, clip_r.Max, IM_COL32(0, 255, 0, 255), 0.0f, 0, 2.0f); ShadeVertsTransformPos(fg_dl, vtx_idx_begin, fg_dl->_VtxCurrentIdx, pivot_in, label_cos_a, label_sin_a, pivot_out); } + //if (g.IO.KeyShift) { ImDrawList* fg_dl = GetForegroundDrawList(); vtx_idx_begin = fg_dl->_VtxCurrentIdx; fg_dl->AddRect(clip_r.Min, clip_r.Max, IM_COL32(0, 255, 0, 255), 0.0f, 0, 1.0f); ShadeVertsTransformPos(fg_dl, vtx_idx_begin, fg_dl->_VtxCurrentIdx, pivot_in, label_cos_a, label_sin_a, pivot_out); } - // Register header width - column->ContentMaxXHeadersUsed = column->ContentMaxXHeadersIdeal = column->WorkMinX + ImCeil(line_off_curr_x); label_name = label_name_eol + 1; } } @@ -3402,7 +3474,7 @@ void ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags Separator(); want_separator = true; - PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true); + PushItemFlag(ImGuiItemFlags_AutoClosePopups, false); for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++) { ImGuiTableColumn* other_column = &table->Columns[other_column_n]; @@ -3988,7 +4060,7 @@ float ImGui::GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offse return offset / (columns->OffMaxX - columns->OffMinX); } -static const float COLUMNS_HIT_RECT_HALF_WIDTH = 4.0f; +static const float COLUMNS_HIT_RECT_HALF_THICKNESS = 4.0f; static float GetDraggedColumnOffset(ImGuiOldColumns* columns, int column_index) { @@ -3999,7 +4071,7 @@ static float GetDraggedColumnOffset(ImGuiOldColumns* columns, int column_index) 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; + float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + ImTrunc(COLUMNS_HIT_RECT_HALF_THICKNESS * g.CurrentDpiScale) - window->Pos.x; x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing); if ((columns->Flags & ImGuiOldColumnFlags_NoPreserveWidths)) x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing); @@ -4314,7 +4386,7 @@ void ImGui::EndColumns() 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 float column_hit_hw = ImTrunc(COLUMNS_HIT_RECT_HALF_THICKNESS * g.CurrentDpiScale); const ImRect column_hit_rect(ImVec2(x - column_hit_hw, y1), ImVec2(x + column_hit_hw, y2)); if (!ItemAdd(column_hit_rect, column_id, NULL, ImGuiItemFlags_NoNav)) continue; diff --git a/libs/imgui/imgui_widgets.cpp b/libs/imgui/imgui_widgets.cpp index 1ddf4c7..26bb80d 100644 --- a/libs/imgui/imgui_widgets.cpp +++ b/libs/imgui/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.4 +// dear imgui, v1.91.0 // (widgets code) /* @@ -19,7 +19,9 @@ Index of this file: // [SECTION] Widgets: TreeNode, CollapsingHeader, etc. // [SECTION] Widgets: Selectable // [SECTION] Widgets: Typing-Select support +// [SECTION] Widgets: Box-Select support // [SECTION] Widgets: Multi-Select support +// [SECTION] Widgets: Multi-Select helpers // [SECTION] Widgets: ListBox // [SECTION] Widgets: PlotLines, PlotHistogram // [SECTION] Widgets: Value helpers @@ -70,11 +72,13 @@ Index of this file: #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 "-Wunused-macros" // warning: macro is not used // we define snprintf/vsnprintf on Windows so they are available, but not always used. #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. #pragma clang diagnostic ignored "-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 +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #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 @@ -122,9 +126,9 @@ static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); //------------------------------------------------------------------------- // For InputTextEx() -static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source); -static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); -static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); +static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false); +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); +static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); //------------------------------------------------------------------------- // [SECTION] Widgets: Text, etc. @@ -421,6 +425,7 @@ void ImGui::BulletTextV(const char* fmt, va_list args) // - RadioButton() // - ProgressBar() // - Bullet() +// - Hyperlink() //------------------------------------------------------------------------- // The ButtonBehavior() function is key to many interactions and used by many/most widgets. @@ -487,7 +492,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool // Default only reacts to left mouse button if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0) - flags |= ImGuiButtonFlags_MouseButtonDefault_; + flags |= ImGuiButtonFlags_MouseButtonLeft; // Default behavior requires click + release inside bounding box if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0) @@ -508,7 +513,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool #ifdef IMGUI_ENABLE_TEST_ENGINE // Alternate registration spot, for when caller didn't use ItemAdd() - if (id != 0 && g.LastItemData.ID != id) + if (g.LastItemData.ID != id) IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, NULL); #endif @@ -536,6 +541,8 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id; if (hovered) { + IM_ASSERT(id != 0); // Lazily check inside rare path. + // Poll mouse buttons // - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId. // - Technically we only need some values in one code path, but since this is gated by hovered test this is fine. @@ -544,7 +551,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool for (int button = 0; button < 3; button++) if (flags & (ImGuiButtonFlags_MouseButtonLeft << button)) // Handle ImGuiButtonFlags_MouseButtonRight and ImGuiButtonFlags_MouseButtonMiddle here. { - if (IsMouseClicked(button, test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; } + if (IsMouseClicked(button, ImGuiInputFlags_None, test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; } if (IsMouseReleased(button, test_owner_id) && mouse_button_released == -1) { mouse_button_released = button; } } @@ -560,8 +567,14 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool SetActiveID(id, window); g.ActiveIdMouseButton = mouse_button_clicked; if (!(flags & ImGuiButtonFlags_NoNavFocus)) + { SetFocusID(id, window); - FocusWindow(window); + FocusWindow(window); + } + else + { + FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child + } } if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2)) { @@ -570,10 +583,16 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool 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_NoNavFocus)) + { + SetFocusID(id, window); + FocusWindow(window); + } + else + { + FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child + } } } if (flags & ImGuiButtonFlags_PressedOnRelease) @@ -592,7 +611,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. if (g.ActiveId == id && (item_flags & ImGuiItemFlags_ButtonRepeat)) - if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, test_owner_id, ImGuiInputFlags_Repeat)) + if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, ImGuiInputFlags_Repeat, test_owner_id)) pressed = true; } @@ -831,17 +850,15 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) return pressed; // Render - // FIXME: Clarify this mess - ImU32 col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); - ImVec2 center = bb.GetCenter(); + ImU32 bg_col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); if (hovered) - window->DrawList->AddCircleFilled(center, ImMax(2.0f, g.FontSize * 0.5f + 1.0f), col); - - float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; + window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact); 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); + ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); + float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; + window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f); + window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f); return pressed; } @@ -864,7 +881,8 @@ bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_no 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); + window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact); if (dock_node) RenderArrowDockMenu(window->DrawList, bb.Min, g.FontSize, text_col); @@ -919,10 +937,10 @@ void ImGui::Scrollbar(ImGuiAxis axis) if (!window->ScrollbarX) rounding_corners |= ImDrawFlags_RoundCornersBottomRight; } - float size_avail = window->InnerRect.Max[axis] - window->InnerRect.Min[axis]; + float size_visible = 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); + ScrollbarEx(bb, id, axis, &scroll, (ImS64)size_visible, (ImS64)size_contents, rounding_corners); window->Scroll[axis] = (float)scroll; } @@ -932,7 +950,7 @@ void ImGui::Scrollbar(ImGuiAxis axis) // - 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) +bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; @@ -962,9 +980,9 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 // 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); + IM_ASSERT(ImMax(size_contents_v, size_visible_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers. + const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_visible_v), (ImS64)1); + const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_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(). @@ -973,7 +991,7 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 ItemAdd(bb_frame, id, NULL, ImGuiItemFlags_NoNav); ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus); - const ImS64 scroll_max = ImMax((ImS64)1, size_contents_v - size_avail_v); + const ImS64 scroll_max = ImMax((ImS64)1, size_contents_v - size_visible_v); float scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max); float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space if (held && allow_interaction && grab_h_norm < 1.0f) @@ -984,29 +1002,39 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 // 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); - bool seek_absolute = false; + const int held_dir = (clicked_v_norm < grab_v_norm) ? -1 : (clicked_v_norm > grab_v_norm + grab_h_norm) ? +1 : 0; if (g.ActiveIdIsJustActivated) { // 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; + g.ScrollbarSeekMode = (short)held_dir; + g.ScrollbarClickDeltaToGrabCenter = (g.ScrollbarSeekMode == 0.0f) ? clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f : 0.0f; } // Apply scroll (p_scroll_v will generally point on one member of window->Scroll) // It is ok to modify Scroll here because we are being called in Begin() after the calculation of ContentSize and before setting up our starting position - 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); + if (g.ScrollbarSeekMode == 0) + { + // Absolute seeking + 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); + } + else + { + // Page by page + if (IsMouseClicked(ImGuiMouseButton_Left, ImGuiInputFlags_Repeat) && held_dir == g.ScrollbarSeekMode) + { + float page_dir = (g.ScrollbarSeekMode > 0.0f) ? +1.0f : -1.0f; + *p_scroll_v = ImClamp(*p_scroll_v + (ImS64)(page_dir * size_visible_v), (ImS64)0, scroll_max); + } + } // Update values for rendering scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max); grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; - // Update distance to grab now that we have seeked and saturated - if (seek_absolute) - g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f; + // Update distance to grab now that we have seek'ed and saturated + //if (seek_absolute) + // g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f; } // Render @@ -1125,42 +1153,60 @@ bool ImGui::Checkbox(const char* label, bool* v) 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; - } + const bool is_visible = ItemAdd(total_bb, id); + const bool is_multi_select = (g.LastItemData.InFlags & ImGuiItemFlags_IsMultiSelect) != 0; + if (!is_visible) + if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(total_bb)) // Extra layer of "no logic clip" for box-select support + { + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); + return false; + } + + // Range-Selection/Multi-selection support (header) + bool checked = *v; + if (is_multi_select) + MultiSelectItemHeader(id, &checked, NULL); bool hovered, held; bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); - if (pressed) + + // Range-Selection/Multi-selection support (footer) + if (is_multi_select) + MultiSelectItemFooter(id, &checked, &pressed); + else if (pressed) + checked = !checked; + + if (*v != checked) { - *v = !(*v); + *v = checked; + pressed = true; // return value 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) + const bool mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0; + if (is_visible) { - // 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_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(square_sz / 3.6f))); - window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding); + 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); + 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_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(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_TRUNC(square_sz / 6.0f)); + RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f); + } } - else if (*v) - { - const float pad = ImMax(1.0f, IM_TRUNC(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); + const 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) + if (is_visible && label_size.x > 0.0f) RenderText(label_pos, label); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); @@ -1294,24 +1340,47 @@ void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* over 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); + // Fraction < 0.0f will display an indeterminate progress bar animation + // The value must be animated along with time, so e.g. passing '-1.0f * ImGui::GetTime()' as fraction works. + const bool is_indeterminate = (fraction < 0.0f); + if (!is_indeterminate) + fraction = ImSaturate(fraction); - // Default displaying the fraction as percentage string, but user can override it - char overlay_buf[32]; - if (!overlay) + // Out of courtesy we accept a NaN fraction without crashing + float fill_n0 = 0.0f; + float fill_n1 = (fraction == fraction) ? fraction : 0.0f; + + if (is_indeterminate) { - ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f); - overlay = overlay_buf; + const float fill_width_n = 0.2f; + fill_n0 = ImFmod(-fraction, 1.0f) * (1.0f + fill_width_n) - fill_width_n; + fill_n1 = ImSaturate(fill_n0 + fill_width_n); + fill_n0 = ImSaturate(fill_n0); } - 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); + // Render + RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); + RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_n0, fill_n1, style.FrameRounding); + + // Default displaying the fraction as percentage string, but user can override it + // Don't display text for indeterminate bars by default + char overlay_buf[32]; + if (!is_indeterminate || overlay != NULL) + { + 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) + { + float text_x = is_indeterminate ? (bb.Min.x + bb.Max.x - overlay_size.x) * 0.5f : ImLerp(bb.Min.x, bb.Max.x, fill_n1) + style.ItemSpacing.x; + RenderTextClipped(ImVec2(ImClamp(text_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() @@ -1337,6 +1406,76 @@ void ImGui::Bullet() SameLine(0, style.FramePadding.x * 2.0f); } +// This is provided as a convenience for being an often requested feature. +// FIXME-STYLE: we delayed adding as there is a larger plan to revamp the styling system. +// Because of this we currently don't provide many styling options for this widget +// (e.g. hovered/active colors are automatically inferred from a single color). +bool ImGui::TextLink(const char* label) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiID id = window->GetID(label); + const char* label_end = FindRenderedTextEnd(label); + + ImVec2 pos = window->DC.CursorPos; + ImVec2 size = CalcTextSize(label, label_end, true); + ImRect bb(pos, pos + size); + ItemSize(size, 0.0f); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_None); + + ImVec4 text_colf = g.Style.Colors[ImGuiCol_TextLink]; + ImVec4 line_colf = text_colf; + { + // FIXME-STYLE: Read comments above. This widget is NOT written in the same style as some earlier widgets, + // as we are currently experimenting/planning a different styling system. + float h, s, v; + ColorConvertRGBtoHSV(text_colf.x, text_colf.y, text_colf.z, h, s, v); + if (held || hovered) + { + v = ImSaturate(v + (held ? 0.4f : 0.3f)); + h = ImFmod(h + 0.02f, 1.0f); + } + ColorConvertHSVtoRGB(h, s, v, text_colf.x, text_colf.y, text_colf.z); + v = ImSaturate(v - 0.20f); + ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z); + } + + float line_y = bb.Max.y + ImFloor(g.Font->Descent * g.FontScale * 0.20f); + window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode. + + PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf)); + RenderText(bb.Min, label, label_end); + PopStyleColor(); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + return pressed; +} + +void ImGui::TextLinkOpenURL(const char* label, const char* url) +{ + ImGuiContext& g = *GImGui; + if (url == NULL) + url = label; + if (TextLink(label)) + if (g.IO.PlatformOpenInShellFn != NULL) + g.IO.PlatformOpenInShellFn(&g, url); + SetItemTooltip("%s", url); // It is more reassuring for user to _always_ display URL when we same as label + if (BeginPopupContextItem()) + { + if (MenuItem(LocalizeGetMsg(ImGuiLocKey_CopyLink))) + SetClipboardText(url); + EndPopup(); + } +} + //------------------------------------------------------------------------- // [SECTION] Widgets: Low-level Layout helpers //------------------------------------------------------------------------- @@ -1922,28 +2061,30 @@ bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(vo 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++) - { - const char* item_text = getter(user_data, i); - if (item_text == NULL) - item_text = "*Unknown item*"; - - PushID(i); - const bool item_selected = (i == *current_item); - if (Selectable(item_text, item_selected) && *current_item != i) + ImGuiListClipper clipper; + clipper.Begin(items_count); + clipper.IncludeItemByIndex(*current_item); + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { - value_changed = true; - *current_item = i; + const char* item_text = getter(user_data, i); + if (item_text == NULL) + item_text = "*Unknown item*"; + + PushID(i); + const bool item_selected = (i == *current_item); + if (Selectable(item_text, item_selected) && *current_item != i) + { + value_changed = true; + *current_item = i; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); } - if (item_selected) - SetItemDefaultFocus(); - PopID(); - } EndCombo(); - if (value_changed) MarkItemEdited(g.LastItemData.ID); @@ -2025,6 +2166,7 @@ static const ImGuiDataTypeInfo GDataTypeInfo[] = #endif { sizeof(float), "float", "%.3f","%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg) { sizeof(double), "double","%f", "%lf" }, // ImGuiDataType_Double + { sizeof(bool), "bool", "%d", "%d" }, // ImGuiDataType_Bool }; IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); @@ -2109,17 +2251,24 @@ void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const // 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) +bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format, void* p_data_when_empty) { + // 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); + ImGuiDataTypeStorage data_backup; + memcpy(&data_backup, p_data, type_info->Size); + while (ImCharIsBlankA(*buf)) buf++; if (!buf[0]) + { + if (p_data_when_empty != NULL) + { + memcpy(p_data, p_data_when_empty, type_info->Size); + return memcmp(&data_backup, p_data, type_info->Size) != 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 @@ -2266,12 +2415,13 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const { ImGuiContext& g = *GImGui; const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; - const bool is_clamped = (v_min < v_max); + const bool is_bounded = (v_min < v_max); + const bool is_wrapped = is_bounded && (flags & ImGuiSliderFlags_WrapAround); const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0; const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); // Default tweak speed - if (v_speed == 0.0f && is_clamped && (v_max - v_min < FLT_MAX)) + if (v_speed == 0.0f && is_bounded && (v_max - v_min < FLT_MAX)) v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio); // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings @@ -2289,7 +2439,7 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0; const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow); const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast); - const float tweak_factor = tweak_slow ? 1.0f / 1.0f : tweak_fast ? 10.0f : 1.0f; + const float tweak_factor = tweak_slow ? 1.0f / 10.0f : tweak_fast ? 10.0f : 1.0f; adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor; v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); } @@ -2305,8 +2455,8 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const // 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)); + const bool is_just_activated = g.ActiveIdIsJustActivated; + const bool is_already_past_limits_and_pushing_outward = is_bounded && !is_wrapped && ((*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; @@ -2364,13 +2514,24 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const 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 != v_cur && is_bounded) { - 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; + if (is_wrapped) + { + // Wrap values + if (v_cur < v_min) + v_cur += v_max - v_min + (is_floating_point ? 0 : 1); + if (v_cur > v_max) + v_cur -= v_max - v_min + (is_floating_point ? 0 : 1); + } + else + { + // Clamp values + handle overflow/wrap-around for integer types. + 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 @@ -2383,7 +2544,7 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const 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."); + IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); ImGuiContext& g = *GImGui; if (g.ActiveId == id) @@ -2448,7 +2609,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, if (!temp_input_is_active) { // Tabbing or CTRL-clicking on Drag turns it into an InputText - const bool clicked = hovered && IsMouseClicked(0, id); + const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id); const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id)); const bool make_active = (clicked || double_clicked || g.NavActivateId == id); if (make_active && (clicked || double_clicked)) @@ -2976,7 +3137,8 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ 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."); + IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); + IM_ASSERT((flags & ImGuiSliderFlags_WrapAround) == 0); // Not supported by SliderXXX(), only by DragXXX() switch (data_type) { @@ -3039,7 +3201,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat if (!temp_input_is_active) { // Tabbing or CTRL-clicking on Slider turns it into an input box - const bool clicked = hovered && IsMouseClicked(0, id); + const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id); const bool make_active = (clicked || g.NavActivateId == id); if (make_active && clicked) SetKeyOwner(ImGuiKey_MouseLeft, id); @@ -3201,7 +3363,7 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d format = DataTypeGetInfo(data_type)->PrintFmt; const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags); - const bool clicked = hovered && IsMouseClicked(0, id); + const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id); if (clicked || g.NavActivateId == id) { if (clicked) @@ -3432,18 +3594,18 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format); ImStrTrimBlanks(data_buf); - ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited; + ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; bool value_changed = false; if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags)) { // Backup old value size_t data_type_size = type_info->Size; - ImGuiDataTypeTempStorage data_backup; + ImGuiDataTypeStorage data_backup; memcpy(&data_backup, p_data, data_type_size); // Apply new value (or operations) then clamp - DataTypeApplyFromText(data_buf, data_type, p_data, format); + DataTypeApplyFromText(data_buf, data_type, p_data, format, NULL); if (p_clamp_min || p_clamp_max) { if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0) @@ -3459,6 +3621,13 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG return value_changed; } +void ImGui::SetNextItemRefVal(ImGuiDataType data_type, void* p_data) +{ + ImGuiContext& g = *GImGui; + g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasRefVal; + memcpy(&g.NextItemData.RefVal, p_data, DataTypeGetInfo(data_type)->Size); +} + // Note: p_data, p_step, p_step_fast are _pointers_ to a memory address holding the data. For an Input widget, p_step and p_step_fast are optional. // Read code of e.g. InputFloat(), InputInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly. bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags) @@ -3473,16 +3642,22 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; + void* p_data_default = (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasRefVal) ? &g.NextItemData.RefVal : &g.DataTypeZeroValue; + char buf[64]; - DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format); + if ((flags & ImGuiInputTextFlags_DisplayEmptyRefVal) && DataTypeCompare(data_type, p_data, p_data_default) == 0) + buf[0] = 0; + else + DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format); flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves by comparing the actual data rather than the string. + flags |= (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; bool value_changed = false; if (p_step == NULL) { if (InputText(label, buf, IM_ARRAYSIZE(buf), flags)) - value_changed = DataTypeApplyFromText(buf, data_type, p_data, format); + value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); } else { @@ -3492,7 +3667,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data 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); + value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable); // Step buttons @@ -3710,7 +3885,7 @@ namespace ImStb static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->CurLenW; } static ImWchar STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->CurLenW); 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 IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance(c) * (g.FontSize / g.Font->FontSize); } +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 IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance(c) * g.FontScale; } 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) @@ -3728,7 +3903,7 @@ static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* ob static bool is_separator(unsigned int c) { - return c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|' || c=='\n' || c=='\r' || c=='.' || c=='!'; + return c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|' || c=='\n' || c=='\r' || c=='.' || c=='!' || c=='\\' || c=='/'; } static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx) @@ -3922,9 +4097,8 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons } // Return false to discard a character. -static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source) +static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard) { - IM_ASSERT(input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Clipboard); unsigned int c = *p_char; // Filter non-printable (NB: isprint is unreliable! see #2467) @@ -3939,7 +4113,7 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted. } - if (input_source != ImGuiInputSource_Clipboard) + if (input_source_is_clipboard == false) { // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817) if (c == 127) @@ -3955,7 +4129,7 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im return false; // Generic named filters - if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))) + if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint))) { // 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 '.'. @@ -3965,7 +4139,7 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im // Users of non-default decimal point (in particular ',') may be affected by word-selection logic (is_word_boundary_from_right/is_word_boundary_from_left) functions. ImGuiContext& g = *ctx; const unsigned c_decimal_point = (unsigned int)g.IO.PlatformLocaleDecimalPoint; - if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific)) + if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint)) if (c == '.' || c == ',') c = c_decimal_point; @@ -4145,7 +4319,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ 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); + bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), ImGuiChildFlags_Border, ImGuiWindowFlags_NoMove); g.NavActivateId = backup_activate_id; PopStyleVar(3); PopStyleColor(); @@ -4289,6 +4463,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ SetKeyOwner(ImGuiKey_PageUp, id); SetKeyOwner(ImGuiKey_PageDown, id); } + // FIXME: May be a problem to always steal Alt on OSX, would ideally still allow an uninterrupted Alt down-up to toggle menu if (is_osx) SetKeyOwner(ImGuiMod_Alt, id); } @@ -4421,15 +4596,15 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // (For Tab and Enter: Win32/SFML/Allegro are sending both keys and chars, GLFW and SDL are only sending keys. For Space they all send all threes) if ((flags & ImGuiInputTextFlags_AllowTabInput) && !is_readonly) { - if (Shortcut(ImGuiKey_Tab, id, ImGuiInputFlags_Repeat)) + if (Shortcut(ImGuiKey_Tab, ImGuiInputFlags_Repeat, id)) { unsigned int c = '\t'; // Insert TAB - if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) + if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) state->OnKeyPressed((int)c); } // FIXME: Implement Shift+Tab /* - if (Shortcut(ImGuiKey_Tab | ImGuiMod_Shift, id, ImGuiInputFlags_Repeat)) + if (Shortcut(ImGuiKey_Tab | ImGuiMod_Shift, ImGuiInputFlags_Repeat, id)) { } */ @@ -4437,7 +4612,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Process regular text input (before we check for Return because using some IME will effectively send a Return?) // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters. - const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper); + const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeyCtrl); if (io.InputQueueCharacters.Size > 0) { if (!ignore_char_inputs && !is_readonly && !input_requested_by_nav) @@ -4447,7 +4622,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ unsigned int c = (unsigned int)io.InputQueueCharacters[n]; if (c == '\t') // Skip Tab, see above. continue; - if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) + if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) state->OnKeyPressed((int)c); } @@ -4467,25 +4642,26 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl - const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End + const bool is_startend_key_down = is_osx && io.KeyCtrl && !io.KeySuper && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End // Using Shortcut() with ImGuiInputFlags_RouteFocused (default policy) to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: formet would be handled by InputText) // Otherwise we could simply assume that we own the keys as we are active. const ImGuiInputFlags f_repeat = ImGuiInputFlags_Repeat; - const bool is_cut = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_X, id, f_repeat) || Shortcut(ImGuiMod_Shift | ImGuiKey_Delete, id, f_repeat)) && !is_readonly && !is_password && (!is_multiline || state->HasSelection()); - const bool is_copy = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_C, id) || Shortcut(ImGuiMod_Ctrl | ImGuiKey_Insert, id)) && !is_password && (!is_multiline || state->HasSelection()); - const bool is_paste = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_V, id, f_repeat) || Shortcut(ImGuiMod_Shift | ImGuiKey_Insert, id, f_repeat)) && !is_readonly; - const bool is_undo = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_Z, id, f_repeat)) && !is_readonly && is_undoable; - const bool is_redo = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_Y, id, f_repeat) || (is_osx && Shortcut(ImGuiMod_Shortcut | ImGuiMod_Shift | ImGuiKey_Z, id, f_repeat))) && !is_readonly && is_undoable; - const bool is_select_all = Shortcut(ImGuiMod_Shortcut | ImGuiKey_A, id); + const bool is_cut = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_X, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Delete, f_repeat, id)) && !is_readonly && !is_password && (!is_multiline || state->HasSelection()); + const bool is_copy = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, 0, id) || Shortcut(ImGuiMod_Ctrl | ImGuiKey_Insert, 0, id)) && !is_password && (!is_multiline || state->HasSelection()); + const bool is_paste = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_V, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Insert, f_repeat, id)) && !is_readonly; + const bool is_undo = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable; + const bool is_redo = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Y, f_repeat, id) || (is_osx && Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Z, f_repeat, id))) && !is_readonly && is_undoable; + const bool is_select_all = Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, 0, id); // We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful. const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; const bool is_enter_pressed = IsKeyPressed(ImGuiKey_Enter, true) || IsKeyPressed(ImGuiKey_KeypadEnter, true); const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false)); - const bool is_cancel = Shortcut(ImGuiKey_Escape, id, f_repeat) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, id, f_repeat)); + const bool is_cancel = Shortcut(ImGuiKey_Escape, f_repeat, id) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, f_repeat, id)); // FIXME: Should use more Shortcut() and reduce IsKeyPressed()+SetKeyOwner(), but requires modifiers combination to be taken account of. + // FIXME-OSX: Missing support for Alt(option)+Right/Left = go to end of line, or next line if already in end of line. if (IsKeyPressed(ImGuiKey_LeftArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } 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); } @@ -4510,7 +4686,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { 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) + else if (is_osx && io.KeyCtrl && !io.KeyAlt && !io.KeySuper) state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT); } state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); @@ -4530,7 +4706,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ else if (!is_readonly) { unsigned int c = '\n'; // Insert new line - if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) + if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) state->OnKeyPressed((int)c); } } @@ -4597,7 +4773,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { unsigned int c; s += ImTextCharFromUtf8(&c, s, NULL); - if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Clipboard)) + if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, true)) continue; clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; } @@ -4680,7 +4856,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment. ImGuiInputTextFlags event_flag = 0; ImGuiKey event_key = ImGuiKey_None; - if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, id)) + if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, 0, id)) { event_flag = ImGuiInputTextFlags_CallbackCompletion; event_key = ImGuiKey_Tab; @@ -6056,7 +6232,8 @@ bool ImGui::TreeNode(const char* label) ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; - return TreeNodeBehavior(window->GetID(label), 0, label, NULL); + ImGuiID id = window->GetID(label); + return TreeNodeBehavior(id, ImGuiTreeNodeFlags_None, label, NULL); } bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args) @@ -6074,8 +6251,8 @@ bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; - - return TreeNodeBehavior(window->GetID(label), flags, label, NULL); + ImGuiID id = window->GetID(label); + return TreeNodeBehavior(id, flags, label, NULL); } bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) @@ -6102,9 +6279,10 @@ bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char if (window->SkipItems) return false; + ImGuiID id = window->GetID(str_id); const char* label, *label_end; ImFormatStringToTempBufferV(&label, &label_end, fmt, args); - return TreeNodeBehavior(window->GetID(str_id), flags, label, label_end); + return TreeNodeBehavior(id, flags, label, label_end); } bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) @@ -6113,24 +6291,32 @@ bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char if (window->SkipItems) return false; + ImGuiID id = window->GetID(ptr_id); const char* label, *label_end; ImFormatStringToTempBufferV(&label, &label_end, fmt, args); - return TreeNodeBehavior(window->GetID(ptr_id), flags, label, label_end); + return TreeNodeBehavior(id, flags, label, label_end); } -void ImGui::TreeNodeSetOpen(ImGuiID id, bool open) +bool ImGui::TreeNodeGetOpen(ImGuiID storage_id) { ImGuiContext& g = *GImGui; ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage; - storage->SetInt(id, open ? 1 : 0); + return storage->GetInt(storage_id, 0) != 0; } -bool ImGui::TreeNodeUpdateNextOpen(ImGuiID id, ImGuiTreeNodeFlags flags) +void ImGui::TreeNodeSetOpen(ImGuiID storage_id, bool open) +{ + ImGuiContext& g = *GImGui; + ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage; + storage->SetInt(storage_id, open ? 1 : 0); +} + +bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags) { if (flags & ImGuiTreeNodeFlags_Leaf) return true; - // We only write to the tree storage if the user clicks (or explicitly use the SetNextItemOpen function) + // 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; @@ -6141,16 +6327,16 @@ bool ImGui::TreeNodeUpdateNextOpen(ImGuiID id, ImGuiTreeNodeFlags flags) if (g.NextItemData.OpenCond & ImGuiCond_Always) { is_open = g.NextItemData.OpenVal; - TreeNodeSetOpen(id, is_open); + TreeNodeSetOpen(storage_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); + const int stored_value = storage->GetInt(storage_id, -1); if (stored_value == -1) { is_open = g.NextItemData.OpenVal; - TreeNodeSetOpen(id, is_open); + TreeNodeSetOpen(storage_id, is_open); } else { @@ -6160,7 +6346,7 @@ bool ImGui::TreeNodeUpdateNextOpen(ImGuiID id, ImGuiTreeNodeFlags flags) } else { - is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0; + is_open = storage->GetInt(storage_id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0; } // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior). @@ -6171,6 +6357,23 @@ bool ImGui::TreeNodeUpdateNextOpen(ImGuiID id, ImGuiTreeNodeFlags flags) return is_open; } +// Store ImGuiTreeNodeStackData for just submitted node. +// Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase. +static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + g.TreeNodeStack.resize(g.TreeNodeStack.Size + 1); + ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.back(); + tree_node_data->ID = g.LastItemData.ID; + tree_node_data->TreeFlags = flags; + tree_node_data->InFlags = g.LastItemData.InFlags; + tree_node_data->NavRect = g.LastItemData.NavRect; + window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth); +} + +// When using public API, currently 'id == storage_id' is always true, but we separate the values to facilitate advanced user code doing storage queries outside of UI loop. bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) { ImGuiWindow* window = GetCurrentWindow(); @@ -6186,73 +6389,73 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l label_end = FindRenderedTextEnd(label); const ImVec2 label_size = CalcTextSize(label, label_end, false); + const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2); // Collapsing arrow width + Spacing + const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset); // Latch before ItemSize changes it + const float text_width = g.FontSize + label_size.x + padding.x * 2; // Include collapsing arrow + // We vertically grow up to current line height up the typical widget height. const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2); const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL); ImRect frame_bb; frame_bb.Min.x = span_all_columns ? window->ParentWorkRect.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 = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x; + frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : (flags & ImGuiTreeNodeFlags_SpanTextWidth) ? window->DC.CursorPos.x + text_width + padding.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_TRUNC(window->WindowPadding.x * 0.5f - 1.0f); - frame_bb.Max.x += IM_TRUNC(window->WindowPadding.x * 0.5f); + const float outer_extend = IM_TRUNC(window->WindowPadding.x * 0.5f); // Framed header expand a little outside of current limits + frame_bb.Min.x -= outer_extend; + frame_bb.Max.x += outer_extend; } - const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2); // Collapsing arrow width + Spacing - const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset); // Latch before ItemSize changes it - const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x * 2 : 0.0f); // Include collapsing 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 | ImGuiTreeNodeFlags_SpanAllColumns)) == 0) - interact_bb.Max.x = frame_bb.Min.x + text_width + style.ItemSpacing.x * 2.0f; - - // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel 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; - } + if ((flags & (ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanTextWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0) + interact_bb.Max.x = frame_bb.Min.x + text_width + (label_size.x > 0.0f ? style.ItemSpacing.x * 2.0f : 0.0f); // Compute open and multi-select states before ItemAdd() as it clear NextItem data. - bool is_open = TreeNodeUpdateNextOpen(id, flags); - bool item_add = ItemAdd(interact_bb, id); - g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; - g.LastItemData.DisplayRect = frame_bb; + ImGuiID storage_id = (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasStorageID) ? g.NextItemData.StorageId : id; + bool is_open = TreeNodeUpdateNextOpen(storage_id, flags); + bool is_visible; if (span_all_columns) { + // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel 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; + window->ClipRect.Min.x = window->ParentWorkRect.Min.x; + window->ClipRect.Max.x = window->ParentWorkRect.Max.x; + is_visible = ItemAdd(interact_bb, id); window->ClipRect.Min.x = backup_clip_rect_min_x; window->ClipRect.Max.x = backup_clip_rect_max_x; } + else + { + is_visible = ItemAdd(interact_bb, id); + } + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; + g.LastItemData.DisplayRect = frame_bb; // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsBackHere enabled: // Store data for the current depth to allow returning to this node from any child item. // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop(). // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsBackHere by default or move it to ImGuiStyle. - // Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase. - if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) - if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) - { - g.NavTreeNodeStack.resize(g.NavTreeNodeStack.Size + 1); - ImGuiNavTreeNodeData* nav_tree_node_data = &g.NavTreeNodeStack.back(); - nav_tree_node_data->ID = id; - nav_tree_node_data->InFlags = g.LastItemData.InFlags; - nav_tree_node_data->NavRect = g.LastItemData.NavRect; - window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth); - } + bool store_tree_node_stack_data = false; + if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + { + if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && is_open && !g.NavIdIsAlive) + if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) + store_tree_node_stack_data = true; + } const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; - if (!item_add) + if (!is_visible) { + if (store_tree_node_stack_data && is_open) + TreeNodeStoreStackData(flags); // Call before TreePushOverrideID() if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) TreePushOverrideID(id); IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); @@ -6278,8 +6481,6 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l 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. @@ -6300,6 +6501,24 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0; const bool was_selected = selected; + // Multi-selection support (header) + const bool is_multi_select = (g.LastItemData.InFlags & ImGuiItemFlags_IsMultiSelect) != 0; + if (is_multi_select) + { + // Handle multi-select + alter button flags for it + MultiSelectItemHeader(id, &selected, &button_flags); + if (is_mouse_x_over_arrow) + button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease; + + // We absolutely need to distinguish open vs select so comes by default + flags |= ImGuiTreeNodeFlags_OpenOnArrow; + } + else + { + if (window != g.HoveredWindow || !is_mouse_x_over_arrow) + button_flags |= ImGuiButtonFlags_NoKeyModifiers; + } + bool hovered, held; bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags); bool toggled = false; @@ -6307,7 +6526,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l { if (pressed && g.DragDropHoldJustPressedId != id) { - if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id)) + if ((flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id && !is_multi_select)) toggled = true; if (flags & ImGuiTreeNodeFlags_OpenOnArrow) toggled |= is_mouse_x_over_arrow && !g.NavDisableMouseHover; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job @@ -6337,64 +6556,78 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l if (toggled) { is_open = !is_open; - window->DC.StateStorage->SetInt(id, is_open); + window->DC.StateStorage->SetInt(storage_id, is_open); g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen; } } - // In this branch, TreeNodeBehavior() cannot toggle the selection so this will never trigger. - if (selected != was_selected) //-V547 + // Multi-selection support (footer) + if (is_multi_select) + { + bool pressed_copy = pressed && !toggled; + MultiSelectItemFooter(id, &selected, &pressed_copy); + if (pressed) + SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, interact_bb); + } + + if (selected != was_selected) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; // Render - const ImU32 text_col = GetColorU32(ImGuiCol_Text); - ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact; - if (display_frame) { - // Framed type - const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); - RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding); - RenderNavHighlight(frame_bb, id, nav_highlight_flags); - if (flags & ImGuiTreeNodeFlags_Bullet) - RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col); - else if (!is_leaf) - RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 1.0f); - else // Leaf without bullet, left-adjusted text - text_pos.x -= text_offset_x -padding.x; - if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton) - frame_bb.Max.x -= g.FontSize + style.FramePadding.x; - - if (g.LogEnabled) - LogSetNextTextDecoration("###", "###"); - } - else - { - // Unframed typed for tree nodes - if (hovered || selected) + const ImU32 text_col = GetColorU32(ImGuiCol_Text); + ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact; + if (is_multi_select) + nav_highlight_flags |= ImGuiNavHighlightFlags_AlwaysDraw; // Always show the nav rectangle + 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, false); + RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding); + RenderNavHighlight(frame_bb, id, nav_highlight_flags); + if (flags & ImGuiTreeNodeFlags_Bullet) + RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col); + else if (!is_leaf) + RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 1.0f); + else // Leaf without bullet, left-adjusted text + text_pos.x -= text_offset_x - padding.x; + if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton) + frame_bb.Max.x -= g.FontSize + style.FramePadding.x; + if (g.LogEnabled) + LogSetNextTextDecoration("###", "###"); } - RenderNavHighlight(frame_bb, id, nav_highlight_flags); - if (flags & ImGuiTreeNodeFlags_Bullet) - RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col); - else if (!is_leaf) - RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 0.70f); - if (g.LogEnabled) - LogSetNextTextDecoration(">", NULL); + else + { + // Unframed typed for tree nodes + if (hovered || selected) + { + const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false); + } + RenderNavHighlight(frame_bb, id, nav_highlight_flags); + if (flags & ImGuiTreeNodeFlags_Bullet) + RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col); + else if (!is_leaf) + RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 0.70f); + if (g.LogEnabled) + LogSetNextTextDecoration(">", NULL); + } + + if (span_all_columns) + TablePopBackgroundChannel(); + + // Label + if (display_frame) + RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); + else + RenderText(text_pos, label, label_end, false); } - if (span_all_columns) - TablePopBackgroundChannel(); - - // Label - if (display_frame) - RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); - else - RenderText(text_pos, label, label_end, false); - + if (store_tree_node_stack_data && is_open) + TreeNodeStoreStackData(flags); // Call before TreePushOverrideID() if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) - TreePushOverrideID(id); + TreePushOverrideID(id); // Could use TreePush(label) but this avoid computing twice + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); return is_open; } @@ -6433,16 +6666,19 @@ void ImGui::TreePop() window->DC.TreeDepth--; ImU32 tree_depth_mask = (1 << window->DC.TreeDepth); - // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled) - if (window->DC.TreeJumpToParentOnPopMask & tree_depth_mask) // Only set during request + if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) // Only set during request { - ImGuiNavTreeNodeData* nav_tree_node_data = &g.NavTreeNodeStack.back(); - IM_ASSERT(nav_tree_node_data->ID == window->IDStack.back()); - if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) - NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, nav_tree_node_data); - g.NavTreeNodeStack.pop_back(); + ImGuiTreeNodeStackData* data = &g.TreeNodeStack.back(); + IM_ASSERT(data->ID == window->IDStack.back()); + if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) + { + // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled) + if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) + NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, data); + } + g.TreeNodeStack.pop_back(); + window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask; } - 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(); @@ -6463,7 +6699,17 @@ void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond) return; g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasOpen; g.NextItemData.OpenVal = is_open; - g.NextItemData.OpenCond = cond ? cond : ImGuiCond_Always; + g.NextItemData.OpenCond = (ImU8)(cond ? cond : ImGuiCond_Always); +} + +// Set next TreeNode/CollapsingHeader storage id. +void ImGui::SetNextItemStorageID(ImGuiID storage_id) +{ + ImGuiContext& g = *GImGui; + if (g.CurrentWindow->SkipItems) + return; + g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasStorageID; + g.NextItemData.StorageId = storage_id; } // CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag). @@ -6473,8 +6719,8 @@ 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); + ImGuiID id = window->GetID(label); + return TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader, label); } // p_visible == NULL : regular collapsing header @@ -6554,6 +6800,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl 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. + // FIXME: Not part of layout so not included in clipper calculation, but ItemSize currently doesn't allow offsetting CursorPos. ImRect bb(min_x, pos.y, text_max.x, text_max.y); if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0) { @@ -6568,25 +6815,29 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl } //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/PushTableBackgroundChannel 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; + const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0; + const ImGuiItemFlags extra_item_flags = disabled_item ? (ImGuiItemFlags)ImGuiItemFlags_Disabled : ImGuiItemFlags_None; + bool is_visible; if (span_all_columns) { + // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel 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; 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) - { + is_visible = ItemAdd(bb, id, NULL, extra_item_flags); window->ClipRect.Min.x = backup_clip_rect_min_x; window->ClipRect.Max.x = backup_clip_rect_max_x; } + else + { + is_visible = ItemAdd(bb, id, NULL, extra_item_flags); + } - if (!item_add) - return false; + const bool is_multi_select = (g.LastItemData.InFlags & ImGuiItemFlags_IsMultiSelect) != 0; + if (!is_visible) + if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(bb)) // Extra layer of "no logic clip" for box-select support (would be more overhead to add to ItemAdd) + return false; const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; if (disabled_item && !disabled_global) // Only testing this as an optimization @@ -6613,20 +6864,35 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; } if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; } + // Multi-selection support (header) const bool was_selected = selected; + if (is_multi_select) + { + // Handle multi-select + alter button flags for it + MultiSelectItemHeader(id, &selected, &button_flags); + } + bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); - // Auto-select when moved into - // - This will be more fully fleshed in the range-select branch - // - This is not exposed as it won't nicely work with some user side handling of shift/control - // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons - // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope()) - // - (2) usage will fail with clipped items - // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. - if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId) - if (g.NavJustMovedToId == id) - selected = pressed = true; + // Multi-selection support (footer) + if (is_multi_select) + { + MultiSelectItemFooter(id, &selected, &pressed); + } + else + { + // Auto-select when moved into + // - This will be more fully fleshed in the range-select branch + // - This is not exposed as it won't nicely work with some user side handling of shift/control + // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons + // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope()) + // - (2) usage will fail with clipped items + // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. + if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId) + if (g.NavJustMovedToId == id) + selected = pressed = true; + } // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover))) @@ -6640,18 +6906,31 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (pressed) MarkItemEdited(id); - // In this branch, Selectable() cannot toggle the selection so this will never trigger. - if (selected != was_selected) //-V547 + if (selected != was_selected) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; // Render - if (hovered || selected) + if (is_visible) { - const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); - RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + const bool highlighted = hovered || (flags & ImGuiSelectableFlags_Highlight); + if (highlighted || selected) + { + // FIXME-MULTISELECT: Styling: Color for 'selected' elements? ImGuiCol_HeaderSelected + ImU32 col; + if (selected && !highlighted) + col = GetColorU32(ImLerp(GetStyleColorVec4(ImGuiCol_Header), GetStyleColorVec4(ImGuiCol_HeaderHovered), 0.5f)); + else + col = GetColorU32((held && highlighted) ? ImGuiCol_HeaderActive : highlighted ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + } + if (g.NavId == id) + { + ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding; + if (is_multi_select) + nav_highlight_flags |= ImGuiNavHighlightFlags_AlwaysDraw; // Always show the nav rectangle + RenderNavHighlight(bb, id, nav_highlight_flags); + } } - if (g.NavId == id) - RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding); if (span_all_columns) { @@ -6661,15 +6940,19 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl PopColumnsBackground(); } - RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb); + if (is_visible) + 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)) + if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.InFlags & ImGuiItemFlags_AutoClosePopups)) CloseCurrentPopup(); if (disabled_item && !disabled_global) EndDisabled(); + // Selectable() always returns a pressed state! + // Users of BeginMultiSelect()/EndMultiSelect() scope: you may call ImGui::IsItemToggledSelection() to retrieve + // selection toggle, only useful if you need that state updated (e.g. for rendering purpose) before reaching EndMultiSelect(). IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return pressed; //-V1020 } @@ -6747,7 +7030,7 @@ ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags f g.IO.InputQueueCharacters.resize(0); // Handle backspace - if ((flags & ImGuiTypingSelectFlags_AllowBackspace) && IsKeyPressed(ImGuiKey_Backspace, 0, ImGuiInputFlags_Repeat)) + if ((flags & ImGuiTypingSelectFlags_AllowBackspace) && IsKeyPressed(ImGuiKey_Backspace, ImGuiInputFlags_Repeat)) { char* p = (char*)(void*)ImTextFindPreviousUtf8Codepoint(data->SearchBuffer, data->SearchBuffer + buffer_len); *p = 0; @@ -6805,7 +7088,7 @@ static int ImStrimatchlen(const char* s1, const char* s1_end, const char* s2) // When SingleCharMode is set: // - it is better to NOT display a tooltip of other on-screen display indicator. // - the index of the currently focused item is required. -// if your SetNextItemSelectionData() values are indices, you can obtain it from ImGuiMultiSelectIO::NavIdItem, otherwise from g.NavLastValidSelectionUserData. +// if your SetNextItemSelectionUserData() values are indices, you can obtain it from ImGuiMultiSelectIO::NavIdItem, otherwise from g.NavLastValidSelectionUserData. int ImGui::TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx) { if (req == NULL || req->SelectRequest == false) // Support NULL parameter so both calls can be done from same spot. @@ -6875,20 +7158,927 @@ void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data) #endif } +//------------------------------------------------------------------------- +// [SECTION] Widgets: Box-Select support +// This has been extracted away from Multi-Select logic in the hope that it could eventually be used elsewhere, but hasn't been yet. +//------------------------------------------------------------------------- +// Extra logic in MultiSelectItemFooter() and ImGuiListClipper::Step() +//------------------------------------------------------------------------- +// - BoxSelectPreStartDrag() [Internal] +// - BoxSelectActivateDrag() [Internal] +// - BoxSelectDeactivateDrag() [Internal] +// - BoxSelectScrollWithMouseDrag() [Internal] +// - BeginBoxSelect() [Internal] +// - EndBoxSelect() [Internal] +//------------------------------------------------------------------------- + +// Call on the initial click. +static void BoxSelectPreStartDrag(ImGuiID id, ImGuiSelectionUserData clicked_item) +{ + ImGuiContext& g = *GImGui; + ImGuiBoxSelectState* bs = &g.BoxSelectState; + bs->ID = id; + bs->IsStarting = true; // Consider starting box-select. + bs->IsStartedFromVoid = (clicked_item == ImGuiSelectionUserData_Invalid); + bs->IsStartedSetNavIdOnce = bs->IsStartedFromVoid; + bs->KeyMods = g.IO.KeyMods; + bs->StartPosRel = bs->EndPosRel = ImGui::WindowPosAbsToRel(g.CurrentWindow, g.IO.MousePos); + bs->ScrollAccum = ImVec2(0.0f, 0.0f); +} + +static void BoxSelectActivateDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Activate\n", bs->ID); + bs->IsActive = true; + bs->Window = window; + bs->IsStarting = false; + ImGui::SetActiveID(bs->ID, window); + ImGui::SetActiveIdUsingAllKeyboardKeys(); + if (bs->IsStartedFromVoid && (bs->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0) + bs->RequestClear = true; +} + +static void BoxSelectDeactivateDrag(ImGuiBoxSelectState* bs) +{ + ImGuiContext& g = *GImGui; + bs->IsActive = bs->IsStarting = false; + if (g.ActiveId == bs->ID) + { + IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Deactivate\n", bs->ID); + ImGui::ClearActiveID(); + } + bs->ID = 0; +} + +static void BoxSelectScrollWithMouseDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window, const ImRect& inner_r) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(bs->Window == window); + for (int n = 0; n < 2; n++) // each axis + { + const float mouse_pos = g.IO.MousePos[n]; + const float dist = (mouse_pos > inner_r.Max[n]) ? mouse_pos - inner_r.Max[n] : (mouse_pos < inner_r.Min[n]) ? mouse_pos - inner_r.Min[n] : 0.0f; + const float scroll_curr = window->Scroll[n]; + if (dist == 0.0f || (dist < 0.0f && scroll_curr < 0.0f) || (dist > 0.0f && scroll_curr >= window->ScrollMax[n])) + continue; + + const float speed_multiplier = ImLinearRemapClamp(g.FontSize, g.FontSize * 5.0f, 1.0f, 4.0f, ImAbs(dist)); // x1 to x4 depending on distance + const float scroll_step = g.FontSize * 35.0f * speed_multiplier * ImSign(dist) * g.IO.DeltaTime; + bs->ScrollAccum[n] += scroll_step; + + // Accumulate into a stored value so we can handle high-framerate + const float scroll_step_i = ImFloor(bs->ScrollAccum[n]); + if (scroll_step_i == 0.0f) + continue; + if (n == 0) + ImGui::SetScrollX(window, scroll_curr + scroll_step_i); + else + ImGui::SetScrollY(window, scroll_curr + scroll_step_i); + bs->ScrollAccum[n] -= scroll_step_i; + } +} + +bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiBoxSelectState* bs = &g.BoxSelectState; + KeepAliveID(box_select_id); + if (bs->ID != box_select_id) + return false; + + // IsStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry. + bs->UnclipMode = false; + bs->RequestClear = false; + if (bs->IsStarting && IsMouseDragPastThreshold(0)) + BoxSelectActivateDrag(bs, window); + else if ((bs->IsStarting || bs->IsActive) && g.IO.MouseDown[0] == false) + BoxSelectDeactivateDrag(bs); + if (!bs->IsActive) + return false; + + // Current frame absolute prev/current rectangles are used to toggle selection. + // They are derived from positions relative to scrolling space. + ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel); + ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already + ImVec2 curr_end_pos_abs = g.IO.MousePos; + if (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) // Box-select scrolling only happens with ScopeWindow + curr_end_pos_abs = ImClamp(curr_end_pos_abs, scope_rect.Min, scope_rect.Max); + bs->BoxSelectRectPrev.Min = ImMin(start_pos_abs, prev_end_pos_abs); + bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs); + bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs); + bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs); + + // Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper) + // Storing an extra rect used by widgets supporting box-select. + if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d) + if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x) + { + bs->UnclipMode = true; + bs->UnclipRect = bs->BoxSelectRectPrev; // FIXME-OPT: UnclipRect x coordinates could be intersection of Prev and Curr rect on X axis. + bs->UnclipRect.Add(bs->BoxSelectRectCurr); + } + + //GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); + //GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); + //GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f); + return true; +} + +void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiBoxSelectState* bs = &g.BoxSelectState; + IM_ASSERT(bs->IsActive); + bs->UnclipMode = false; + + // Render selection rectangle + bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view + ImRect box_select_r = bs->BoxSelectRectCurr; + box_select_r.ClipWith(scope_rect); + window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling + window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavHighlight)); // FIXME-MULTISELECT: Styling + + // Scroll + const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0; + if (enable_scroll) + { + ImRect scroll_r = scope_rect; + scroll_r.Expand(-g.FontSize); + //GetForegroundDrawList()->AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255)); + if (!scroll_r.Contains(g.IO.MousePos)) + BoxSelectScrollWithMouseDrag(bs, window, scroll_r); + } +} //------------------------------------------------------------------------- // [SECTION] Widgets: Multi-Select support //------------------------------------------------------------------------- +// - DebugLogMultiSelectRequests() [Internal] +// - CalcScopeRect() [Internal] +// - BeginMultiSelect() +// - EndMultiSelect() +// - SetNextItemSelectionUserData() +// - MultiSelectItemHeader() [Internal] +// - MultiSelectItemFooter() [Internal] +// - DebugNodeMultiSelectState() [Internal] +//------------------------------------------------------------------------- + +static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSelectIO* io) +{ + ImGuiContext& g = *GImGui; + for (const ImGuiSelectionRequest& req : io->Requests) + { + if (req.Type == ImGuiSelectionRequestType_SetAll) IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetAll %d (= %s)\n", function, req.Selected, req.Selected ? "SelectAll" : "Clear"); + if (req.Type == ImGuiSelectionRequestType_SetRange) IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetRange %" IM_PRId64 "..%" IM_PRId64 " (0x%" IM_PRIX64 "..0x%" IM_PRIX64 ") = %d (dir %d)\n", function, req.RangeFirstItem, req.RangeLastItem, req.RangeFirstItem, req.RangeLastItem, req.Selected, req.RangeDirection); + } +} + +static ImRect CalcScopeRect(ImGuiMultiSelectTempData* ms, ImGuiWindow* window) +{ + if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect) + { + // Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only + return ImRect(ms->ScopeRectMin, ImMax(window->DC.CursorMaxPos, ms->ScopeRectMin)); + } + else + { + // Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect? + ImRect scope_rect = window->InnerClipRect; + scope_rect.Min = ImMin(scope_rect.Min + ImVec2(window->DecoInnerSizeX1, window->DecoInnerSizeY1), scope_rect.Max); + return scope_rect; + } +} + +// Return ImGuiMultiSelectIO structure. +// Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect(). +// Passing 'selection_size' and 'items_count' parameters is currently optional. +// - 'selection_size' is useful to disable some shortcut routing: e.g. ImGuiMultiSelectFlags_ClearOnEscape won't claim Escape key when selection_size 0, +// allowing a first press to clear selection THEN the second press to leave child window and return to parent. +// - 'items_count' is stored in ImGuiMultiSelectIO which makes it a convenient way to pass the information to your ApplyRequest() handler (but you may pass it differently). +// - If they are costly for you to compute (e.g. external intrusive selection without maintaining size), you may avoid them and pass -1. +// - If you can easily tell if your selection is empty or not, you may pass 0/1, or you may enable ImGuiMultiSelectFlags_ClearOnEscape flag dynamically. +ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int selection_size, int items_count) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (++g.MultiSelectTempDataStacked > g.MultiSelectTempData.Size) + g.MultiSelectTempData.resize(g.MultiSelectTempDataStacked, ImGuiMultiSelectTempData()); + ImGuiMultiSelectTempData* ms = &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1]; + IM_STATIC_ASSERT(offsetof(ImGuiMultiSelectTempData, IO) == 0); // Clear() relies on that. + g.CurrentMultiSelect = ms; + if ((flags & (ImGuiMultiSelectFlags_ScopeWindow | ImGuiMultiSelectFlags_ScopeRect)) == 0) + flags |= ImGuiMultiSelectFlags_ScopeWindow; + if (flags & ImGuiMultiSelectFlags_SingleSelect) + flags &= ~(ImGuiMultiSelectFlags_BoxSelect2d | ImGuiMultiSelectFlags_BoxSelect1d); + if (flags & ImGuiMultiSelectFlags_BoxSelect2d) + flags &= ~ImGuiMultiSelectFlags_BoxSelect1d; + + // FIXME: BeginFocusScope() + const ImGuiID id = window->IDStack.back(); + ms->Clear(); + ms->FocusScopeId = id; + ms->Flags = flags; + ms->IsFocused = (ms->FocusScopeId == g.NavFocusScopeId); + ms->BackupCursorMaxPos = window->DC.CursorMaxPos; + ms->ScopeRectMin = window->DC.CursorMaxPos = window->DC.CursorPos; + PushFocusScope(ms->FocusScopeId); + if (flags & ImGuiMultiSelectFlags_ScopeWindow) // Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items. + window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main; + + // Use copy of keyboard mods at the time of the request, otherwise we would requires mods to be held for an extra frame. + ms->KeyMods = g.NavJustMovedToId ? (g.NavJustMovedToIsTabbing ? 0 : g.NavJustMovedToKeyMods) : g.IO.KeyMods; + if (flags & ImGuiMultiSelectFlags_NoRangeSelect) + ms->KeyMods &= ~ImGuiMod_Shift; + + // Bind storage + ImGuiMultiSelectState* storage = g.MultiSelectStorage.GetOrAddByKey(id); + storage->ID = id; + storage->LastFrameActive = g.FrameCount; + storage->LastSelectionSize = selection_size; + storage->Window = window; + ms->Storage = storage; + + // Output to user + ms->IO.Requests.resize(0); + ms->IO.RangeSrcItem = storage->RangeSrcItem; + ms->IO.NavIdItem = storage->NavIdItem; + ms->IO.NavIdSelected = (storage->NavIdSelected == 1) ? true : false; + ms->IO.ItemsCount = items_count; + + // Clear when using Navigation to move within the scope + // (we compare FocusScopeId so it possible to use multiple selections inside a same window) + bool request_clear = false; + bool request_select_all = false; + if (g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == ms->FocusScopeId && g.NavJustMovedToHasSelectionData) + { + if (ms->KeyMods & ImGuiMod_Shift) + ms->IsKeyboardSetRange = true; + if (ms->IsKeyboardSetRange) + IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid); // Not ready -> could clear? + if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0) + request_clear = true; + } + else if (g.NavJustMovedFromFocusScopeId == ms->FocusScopeId) + { + // Also clear on leaving scope (may be optional?) + if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0) + request_clear = true; + } + + // Box-select handling: update active state. + ImGuiBoxSelectState* bs = &g.BoxSelectState; + if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) + { + ms->BoxSelectId = GetID("##BoxSelect"); + if (BeginBoxSelect(CalcScopeRect(ms, window), window, ms->BoxSelectId, flags)) + request_clear |= bs->RequestClear; + } + + if (ms->IsFocused) + { + // Shortcut: Clear selection (Escape) + // - Only claim shortcut if selection is not empty, allowing further presses on Escape to e.g. leave current child window. + // - Box select also handle Escape and needs to pass an id to bypass ActiveIdUsingAllKeyboardKeys lock. + if (flags & ImGuiMultiSelectFlags_ClearOnEscape) + { + if (selection_size != 0 || bs->IsActive) + if (Shortcut(ImGuiKey_Escape, ImGuiInputFlags_None, bs->IsActive ? bs->ID : 0)) + { + request_clear = true; + if (bs->IsActive) + BoxSelectDeactivateDrag(bs); + } + } + + // Shortcut: Select all (CTRL+A) + if (!(flags & ImGuiMultiSelectFlags_SingleSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll)) + if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_A)) + request_select_all = true; + } + + if (request_clear || request_select_all) + { + MultiSelectAddSetAll(ms, request_select_all); + if (!request_select_all) + storage->LastSelectionSize = 0; + } + ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1; + ms->LastSubmittedItem = ImGuiSelectionUserData_Invalid; + + if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) + DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO); + + return &ms->IO; +} + +// Return updated ImGuiMultiSelectIO structure. +// Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect(). +ImGuiMultiSelectIO* ImGui::EndMultiSelect() +{ + ImGuiContext& g = *GImGui; + ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect; + ImGuiMultiSelectState* storage = ms->Storage; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(ms->FocusScopeId == g.CurrentFocusScopeId); + IM_ASSERT(g.CurrentMultiSelect != NULL && storage->Window == g.CurrentWindow); + IM_ASSERT(g.MultiSelectTempDataStacked > 0 && &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] == g.CurrentMultiSelect); + + ImRect scope_rect = CalcScopeRect(ms, window); + if (ms->IsFocused) + { + // We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here. + if (ms->IO.RangeSrcReset || (ms->RangeSrcPassedBy == false && ms->IO.RangeSrcItem != ImGuiSelectionUserData_Invalid)) // Can't read storage->RangeSrcItem here -> we want the state at begining of the scope (see tests for easy failure) + { + IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset RangeSrcItem.\n"); // Will set be to NavId. + storage->RangeSrcItem = ImGuiSelectionUserData_Invalid; + } + if (ms->NavIdPassedBy == false && storage->NavIdItem != ImGuiSelectionUserData_Invalid) + { + IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset NavIdItem.\n"); + storage->NavIdItem = ImGuiSelectionUserData_Invalid; + storage->NavIdSelected = -1; + } + + if ((ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) && GetBoxSelectState(ms->BoxSelectId)) + EndBoxSelect(scope_rect, ms->Flags); + } + + if (ms->IsEndIO == false) + ms->IO.Requests.resize(0); + + // Clear selection when clicking void? + // We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection! + // The InnerRect test is necessary for non-child/decorated windows. + bool scope_hovered = IsWindowHovered() && window->InnerRect.Contains(g.IO.MousePos); + if (scope_hovered && (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)) + scope_hovered &= scope_rect.Contains(g.IO.MousePos); + if (scope_hovered && g.HoveredId == 0 && g.ActiveId == 0) + { + if (ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) + { + if (!g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && g.IO.MouseClickedCount[0] == 1) + { + BoxSelectPreStartDrag(ms->BoxSelectId, ImGuiSelectionUserData_Invalid); + FocusWindow(window, ImGuiFocusRequestFlags_UnlessBelowModal); + SetHoveredID(ms->BoxSelectId); + if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect) + SetNavID(0, ImGuiNavLayer_Main, ms->FocusScopeId, ImRect(g.IO.MousePos, g.IO.MousePos)); // Automatically switch FocusScope for initial click from void to box-select. + } + } + + if (ms->Flags & ImGuiMultiSelectFlags_ClearOnClickVoid) + if (IsMouseReleased(0) && IsMouseDragPastThreshold(0) == false && g.IO.KeyMods == ImGuiMod_None) + MultiSelectAddSetAll(ms, false); + } + + // Courtesy nav wrapping helper flag + if (ms->Flags & ImGuiMultiSelectFlags_NavWrapX) + { + IM_ASSERT(ms->Flags & ImGuiMultiSelectFlags_ScopeWindow); // Only supported at window scope + ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX); + } + + // Unwind + window->DC.CursorMaxPos = ImMax(ms->BackupCursorMaxPos, window->DC.CursorMaxPos); + PopFocusScope(); + + if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) + DebugLogMultiSelectRequests("EndMultiSelect", &ms->IO); + + ms->FocusScopeId = 0; + ms->Flags = ImGuiMultiSelectFlags_None; + g.CurrentMultiSelect = (--g.MultiSelectTempDataStacked > 0) ? &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] : NULL; + + return &ms->IO; +} void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data) { // Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code! // This designed so widgets can also cheaply set this before calling ItemAdd(), so we are not tied to MultiSelect api. ImGuiContext& g = *GImGui; - g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData; g.NextItemData.SelectionUserData = selection_user_data; + g.NextItemData.FocusScopeId = g.CurrentFocusScopeId; + + if (ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect) + { + // Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping) + g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect; + if (ms->IO.RangeSrcItem == selection_user_data) + ms->RangeSrcPassedBy = true; + } + else + { + g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData; + } } +// In charge of: +// - Applying SetAll for submitted items. +// - Applying SetRange for submitted items and record end points. +// - Altering button behavior flags to facilitate use with drag and drop. +void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect; + + bool selected = *p_selected; + if (ms->IsFocused) + { + ImGuiMultiSelectState* storage = ms->Storage; + ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData; + IM_ASSERT(g.NextItemData.FocusScopeId == g.CurrentFocusScopeId && "Forgot to call SetNextItemSelectionUserData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope"); + + // Apply SetAll (Clear/SelectAll) requests requested by BeginMultiSelect(). + // This is only useful if the user hasn't processed them already, and this only works if the user isn't using the clipper. + // If you are using a clipper you need to process the SetAll request after calling BeginMultiSelect() + if (ms->LoopRequestSetAll != -1) + selected = (ms->LoopRequestSetAll == 1); + + // When using SHIFT+Nav: because it can incur scrolling we cannot afford a frame of lag with the selection highlight (otherwise scrolling would happen before selection) + // For this to work, we need someone to set 'RangeSrcPassedBy = true' at some point (either clipper either SetNextItemSelectionUserData() function) + if (ms->IsKeyboardSetRange) + { + IM_ASSERT(id != 0 && (ms->KeyMods & ImGuiMod_Shift) != 0); + const bool is_range_dst = (ms->RangeDstPassedBy == false) && g.NavJustMovedToId == id; // Assume that g.NavJustMovedToId is not clipped. + if (is_range_dst) + ms->RangeDstPassedBy = true; + if (is_range_dst && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) // If we don't have RangeSrc, assign RangeSrc = RangeDst + { + storage->RangeSrcItem = item_data; + storage->RangeSelected = selected ? 1 : 0; + } + const bool is_range_src = storage->RangeSrcItem == item_data; + if (is_range_src || is_range_dst || ms->RangeSrcPassedBy != ms->RangeDstPassedBy) + { + // Apply range-select value to visible items + IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid && storage->RangeSelected != -1); + selected = (storage->RangeSelected != 0); + } + else if ((ms->KeyMods & ImGuiMod_Ctrl) == 0 && (ms->Flags & ImGuiMultiSelectFlags_NoAutoClear) == 0) + { + // Clear other items + selected = false; + } + } + *p_selected = selected; + } + + // Alter button behavior flags + // To handle drag and drop of multiple items we need to avoid clearing selection on click. + // Enabling this test makes actions using CTRL+SHIFT delay their effect on MouseUp which is annoying, but it allows drag and drop of multiple items. + if (p_button_flags != NULL) + { + ImGuiButtonFlags button_flags = *p_button_flags; + button_flags |= ImGuiButtonFlags_NoHoveredOnFocus; + if ((!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore)) && !(ms->Flags & ImGuiMultiSelectFlags_SelectOnClickRelease)) + button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease; + else + button_flags |= ImGuiButtonFlags_PressedOnClickRelease; + *p_button_flags = button_flags; + } +} + +// In charge of: +// - Auto-select on navigation. +// - Box-select toggle handling. +// - Right-click handling. +// - Altering selection based on Ctrl/Shift modifiers, both for keyboard and mouse. +// - Record current selection state for RangeSrc +// This is all rather complex, best to run and refer to "widgets_multiselect_xxx" tests in imgui_test_suite. +void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + bool selected = *p_selected; + bool pressed = *p_pressed; + ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect; + ImGuiMultiSelectState* storage = ms->Storage; + if (pressed) + ms->IsFocused = true; + + bool hovered = false; + if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) + hovered = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); + if (!ms->IsFocused && !hovered) + return; + + ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData; + + ImGuiMultiSelectFlags flags = ms->Flags; + const bool is_singleselect = (flags & ImGuiMultiSelectFlags_SingleSelect) != 0; + bool is_ctrl = (ms->KeyMods & ImGuiMod_Ctrl) != 0; + bool is_shift = (ms->KeyMods & ImGuiMod_Shift) != 0; + + bool apply_to_range_src = false; + + if (g.NavId == id && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) + apply_to_range_src = true; + if (ms->IsEndIO == false) + { + ms->IO.Requests.resize(0); + ms->IsEndIO = true; + } + + // Auto-select as you navigate a list + if (g.NavJustMovedToId == id) + { + if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) + { + if (is_ctrl && is_shift) + pressed = true; + else if (!is_ctrl) + selected = pressed = true; + } + else + { + // With NoAutoSelect, using Shift+keyboard performs a write/copy + if (is_shift) + pressed = true; + else if (!is_ctrl) + apply_to_range_src = true; // Since if (pressed) {} main block is not running we update this + } + } + + if (apply_to_range_src) + { + storage->RangeSrcItem = item_data; + storage->RangeSelected = selected; // Will be updated at the end of this function anyway. + } + + // Box-select toggle handling + if (ms->BoxSelectId != 0) + if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId)) + { + const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(g.LastItemData.Rect); + const bool rect_overlap_prev = bs->BoxSelectRectPrev.Overlaps(g.LastItemData.Rect); + if ((rect_overlap_curr && !rect_overlap_prev && !selected) || (rect_overlap_prev && !rect_overlap_curr)) + { + if (storage->LastSelectionSize <= 0 && bs->IsStartedSetNavIdOnce) + { + pressed = true; // First item act as a pressed: code below will emit selection request and set NavId (whatever we emit here will be overridden anyway) + bs->IsStartedSetNavIdOnce = false; + } + else + { + selected = !selected; + MultiSelectAddSetRange(ms, selected, +1, item_data, item_data); + } + storage->LastSelectionSize = ImMax(storage->LastSelectionSize + 1, 1); + } + } + + // Right-click handling. + // FIXME-MULTISELECT: Currently filtered out by ImGuiMultiSelectFlags_NoAutoSelect but maybe should be moved to Selectable(). See https://github.com/ocornut/imgui/pull/5816 + if (hovered && IsMouseClicked(1) && (flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) + { + if (g.ActiveId != 0 && g.ActiveId != id) + ClearActiveID(); + SetFocusID(id, window); + if (!pressed && !selected) + { + pressed = true; + is_ctrl = is_shift = false; + } + } + + // Unlike Space, Enter doesn't alter selection (but can still return a press) unless current item is not selected. + // The later, "unless current item is not select", may become optional? It seems like a better default if Enter doesn't necessarily open something + // (unlike e.g. Windows explorer). For use case where Enter always open something, we might decide to make this optional? + const bool enter_pressed = pressed && (g.NavActivateId == id) && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput); + + // Alter selection + if (pressed && (!enter_pressed || !selected)) + { + // Box-select + ImGuiInputSource input_source = (g.NavJustMovedToId == id || g.NavActivateId == id) ? g.NavInputSource : ImGuiInputSource_Mouse; + if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) + if (selected == false && !g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1) + BoxSelectPreStartDrag(ms->BoxSelectId, item_data); + + //---------------------------------------------------------------------------------------- + // ACTION | Begin | Pressed/Activated | End + //---------------------------------------------------------------------------------------- + // Keys Navigated: | Clear | Src=item, Sel=1 SetRange 1 + // Keys Navigated: Ctrl | n/a | n/a + // Keys Navigated: Shift | n/a | Dst=item, Sel=1, => Clear + SetRange 1 + // Keys Navigated: Ctrl+Shift | n/a | Dst=item, Sel=Src => Clear + SetRange Src-Dst + // Keys Activated: | n/a | Src=item, Sel=1 => Clear + SetRange 1 + // Keys Activated: Ctrl | n/a | Src=item, Sel=!Sel => SetSange 1 + // Keys Activated: Shift | n/a | Dst=item, Sel=1 => Clear + SetSange 1 + //---------------------------------------------------------------------------------------- + // Mouse Pressed: | n/a | Src=item, Sel=1, => Clear + SetRange 1 + // Mouse Pressed: Ctrl | n/a | Src=item, Sel=!Sel => SetRange 1 + // Mouse Pressed: Shift | n/a | Dst=item, Sel=1, => Clear + SetRange 1 + // Mouse Pressed: Ctrl+Shift | n/a | Dst=item, Sel=!Sel => SetRange Src-Dst + //---------------------------------------------------------------------------------------- + + if ((flags & ImGuiMultiSelectFlags_NoAutoClear) == 0) + { + bool request_clear = false; + if (is_singleselect) + request_clear = true; + else if ((input_source == ImGuiInputSource_Mouse || g.NavActivateId == id) && !is_ctrl) + request_clear = (flags & ImGuiMultiSelectFlags_NoAutoClearOnReselect) ? !selected : true; + else if ((input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Gamepad) && is_shift && !is_ctrl) + request_clear = true; // With is_shift==false the RequestClear was done in BeginIO, not necessary to do again. + if (request_clear) + MultiSelectAddSetAll(ms, false); + } + + int range_direction; + bool range_selected; + if (is_shift && !is_singleselect) + { + //IM_ASSERT(storage->HasRangeSrc && storage->HasRangeValue); + if (storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) + storage->RangeSrcItem = item_data; + if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) + { + // Shift+Arrow always select + // Ctrl+Shift+Arrow copy source selection state (already stored by BeginMultiSelect() in storage->RangeSelected) + range_selected = (is_ctrl && storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true; + } + else + { + // Shift+Arrow copy source selection state + // Shift+Click always copy from target selection state + if (ms->IsKeyboardSetRange) + range_selected = (storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true; + else + range_selected = !selected; + } + range_direction = ms->RangeSrcPassedBy ? +1 : -1; + } + else + { + // Ctrl inverts selection, otherwise always select + if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) + selected = is_ctrl ? !selected : true; + else + selected = !selected; + storage->RangeSrcItem = item_data; + range_selected = selected; + range_direction = +1; + } + MultiSelectAddSetRange(ms, range_selected, range_direction, storage->RangeSrcItem, item_data); + } + + // Update/store the selection state of the Source item (used by CTRL+SHIFT, when Source is unselected we perform a range unselect) + if (storage->RangeSrcItem == item_data) + storage->RangeSelected = selected ? 1 : 0; + + // Update/store the selection state of focused item + if (g.NavId == id) + { + storage->NavIdItem = item_data; + storage->NavIdSelected = selected ? 1 : 0; + } + if (storage->NavIdItem == item_data) + ms->NavIdPassedBy = true; + ms->LastSubmittedItem = item_data; + + *p_selected = selected; + *p_pressed = pressed; +} + +void ImGui::MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected) +{ + ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, selected, 0, ImGuiSelectionUserData_Invalid, ImGuiSelectionUserData_Invalid }; + ms->IO.Requests.resize(0); // Can always clear previous requests + ms->IO.Requests.push_back(req); // Add new request +} + +void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item) +{ + // Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges) + if (ms->IO.Requests.Size > 0 && first_item == last_item && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0) + { + ImGuiSelectionRequest* prev = &ms->IO.Requests.Data[ms->IO.Requests.Size - 1]; + if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->LastSubmittedItem && prev->Selected == selected) + { + prev->RangeLastItem = last_item; + return; + } + } + + ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item }; + ms->IO.Requests.push_back(req); // Add new request +} + +void ImGui::DebugNodeMultiSelectState(ImGuiMultiSelectState* storage) +{ +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + const bool is_active = (storage->LastFrameActive >= GetFrameCount() - 2); // Note that fully clipped early out scrolling tables will appear as inactive here. + if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); } + bool open = TreeNode((void*)(intptr_t)storage->ID, "MultiSelect 0x%08X in '%s'%s", storage->ID, storage->Window ? storage->Window->Name : "N/A", is_active ? "" : " *Inactive*"); + if (!is_active) { PopStyleColor(); } + if (!open) + return; + Text("RangeSrcItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), RangeSelected = %d", storage->RangeSrcItem, storage->RangeSrcItem, storage->RangeSelected); + Text("NavIdItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), NavIdSelected = %d", storage->NavIdItem, storage->NavIdItem, storage->NavIdSelected); + Text("LastSelectionSize = %d", storage->LastSelectionSize); // Provided by user + TreePop(); +#else + IM_UNUSED(storage); +#endif +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Multi-Select helpers +//------------------------------------------------------------------------- +// - ImGuiSelectionBasicStorage +// - ImGuiSelectionExternalStorage +//------------------------------------------------------------------------- + +ImGuiSelectionBasicStorage::ImGuiSelectionBasicStorage() +{ + Size = 0; + PreserveOrder = false; + UserData = NULL; + AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; }; + _SelectionOrder = 1; // Always >0 +} + +void ImGuiSelectionBasicStorage::Clear() +{ + Size = 0; + _SelectionOrder = 1; // Always >0 + _Storage.Data.resize(0); +} + +void ImGuiSelectionBasicStorage::Swap(ImGuiSelectionBasicStorage& r) +{ + ImSwap(Size, r.Size); + ImSwap(_SelectionOrder, r._SelectionOrder); + _Storage.Data.swap(r._Storage.Data); +} + +bool ImGuiSelectionBasicStorage::Contains(ImGuiID id) const +{ + return _Storage.GetInt(id, 0) != 0; +} + +static int IMGUI_CDECL PairComparerByValueInt(const void* lhs, const void* rhs) +{ + int lhs_v = ((const ImGuiStoragePair*)lhs)->val_i; + int rhs_v = ((const ImGuiStoragePair*)rhs)->val_i; + return (lhs_v > rhs_v ? +1 : lhs_v < rhs_v ? -1 : 0); +} + +// GetNextSelectedItem() is an abstraction allowing us to change our underlying actual storage system without impacting user. +// (e.g. store unselected vs compact down, compact down on demand, use raw ImVector instead of ImGuiStorage...) +bool ImGuiSelectionBasicStorage::GetNextSelectedItem(void** opaque_it, ImGuiID* out_id) +{ + ImGuiStoragePair* it = (ImGuiStoragePair*)*opaque_it; + ImGuiStoragePair* it_end = _Storage.Data.Data + _Storage.Data.Size; + if (PreserveOrder && it == NULL && it_end != NULL) + ImQsort(_Storage.Data.Data, (size_t)_Storage.Data.Size, sizeof(ImGuiStoragePair), PairComparerByValueInt); // ~ImGuiStorage::BuildSortByValueInt() + if (it == NULL) + it = _Storage.Data.Data; + IM_ASSERT(it >= _Storage.Data.Data && it <= it_end); + if (it != it_end) + while (it->val_i == 0 && it < it_end) + it++; + const bool has_more = (it != it_end); + *opaque_it = has_more ? (void**)(it + 1) : (void**)(it); + *out_id = has_more ? it->key : 0; + if (PreserveOrder && !has_more) + _Storage.BuildSortByKey(); + return has_more; +} + +void ImGuiSelectionBasicStorage::SetItemSelected(ImGuiID id, bool selected) +{ + int* p_int = _Storage.GetIntRef(id, 0); + if (selected && *p_int == 0) { *p_int = _SelectionOrder++; Size++; } + else if (!selected && *p_int != 0) { *p_int = 0; Size--; } +} + +// Optimized for batch edits (with same value of 'selected') +static void ImGuiSelectionBasicStorage_BatchSetItemSelected(ImGuiSelectionBasicStorage* selection, ImGuiID id, bool selected, int size_before_amends, int selection_order) +{ + ImGuiStorage* storage = &selection->_Storage; + ImGuiStoragePair* it = ImLowerBound(storage->Data.Data, storage->Data.Data + size_before_amends, id); + const bool is_contained = (it != storage->Data.Data + size_before_amends) && (it->key == id); + if (selected == (is_contained && it->val_i != 0)) + return; + if (selected && !is_contained) + storage->Data.push_back(ImGuiStoragePair(id, selection_order)); // Push unsorted at end of vector, will be sorted in SelectionMultiAmendsFinish() + else if (is_contained) + it->val_i = selected ? selection_order : 0; // Modify in-place. + selection->Size += selected ? +1 : -1; +} + +static void ImGuiSelectionBasicStorage_BatchFinish(ImGuiSelectionBasicStorage* selection, bool selected, int size_before_amends) +{ + ImGuiStorage* storage = &selection->_Storage; + if (selected && selection->Size != size_before_amends) + storage->BuildSortByKey(); // When done selecting: sort everything +} + +// Apply requests coming from BeginMultiSelect() and EndMultiSelect(). +// - Enable 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen. +// - Honoring SetRange requests requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem. +// - In this demo we often submit indices to SetNextItemSelectionUserData() + store the same indices in persistent selection. +// - Your code may do differently. If you store pointers or objects ID in ImGuiSelectionUserData you may need to perform +// a lookup in order to have some way to iterate/interpolate between two items. +// - A full-featured application is likely to allow search/filtering which is likely to lead to using indices +// and constructing a view index <> object id/ptr data structure anyway. +// WHEN YOUR APPLICATION SETTLES ON A CHOICE, YOU WILL PROBABLY PREFER TO GET RID OF THIS UNNECESSARY 'ImGuiSelectionBasicStorage' INDIRECTION LOGIC. +// Notice that with the simplest adapter (using indices everywhere), all functions return their parameters. +// The most simple implementation (using indices everywhere) would look like: +// for (ImGuiSelectionRequest& req : ms_io->Requests) +// { +// if (req.Type == ImGuiSelectionRequestType_SetAll) { Clear(); if (req.Selected) { for (int n = 0; n < items_count; n++) { SetItemSelected(n, true); } } +// if (req.Type == ImGuiSelectionRequestType_SetRange) { for (int n = (int)ms_io->RangeFirstItem; n <= (int)ms_io->RangeLastItem; n++) { SetItemSelected(n, ms_io->Selected); } } +// } +void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) +{ + // For convenience we obtain ItemsCount as passed to BeginMultiSelect(), which is optional. + // It makes sense when using ImGuiSelectionBasicStorage to simply pass your items count to BeginMultiSelect(). + // Other scheme may handle SetAll differently. + IM_ASSERT(ms_io->ItemsCount != -1 && "Missing value for items_count in BeginMultiSelect() call!"); + IM_ASSERT(AdapterIndexToStorageId != NULL); + + // This is optimized/specialized to cope with very large selections (e.g. 100k+ items) + // - A simpler version could call SetItemSelected() directly instead of ImGuiSelectionBasicStorage_BatchSetItemSelected() + ImGuiSelectionBasicStorage_BatchFinish(). + // - Optimized select can append unsorted, then sort in a second pass. Optimized unselect can clear in-place then compact in a second pass. + // - A more optimal version wouldn't even use ImGuiStorage but directly a ImVector to reduce bandwidth, but this is a reasonable trade off to reuse code. + // - There are many ways this could be better optimized. The worse case scenario being: using BoxSelect2d in a grid, box-select scrolling down while wiggling + // left and right: it affects coarse clipping + can emit multiple SetRange with 1 item each.) + // FIXME-OPT: For each block of consecutive SetRange request: + // - add all requests to a sorted list, store ID, selected, offset in ImGuiStorage. + // - rewrite sorted storage a single time. + for (ImGuiSelectionRequest& req : ms_io->Requests) + { + if (req.Type == ImGuiSelectionRequestType_SetAll) + { + Clear(); + if (req.Selected) + { + _Storage.Data.reserve(ms_io->ItemsCount); + const int size_before_amends = _Storage.Data.Size; + for (int idx = 0; idx < ms_io->ItemsCount; idx++, _SelectionOrder++) + ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, _SelectionOrder); + ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends); + } + } + else if (req.Type == ImGuiSelectionRequestType_SetRange) + { + const int selection_changes = (int)req.RangeLastItem - (int)req.RangeFirstItem + 1; + //ImGuiContext& g = *GImGui; IMGUI_DEBUG_LOG_SELECTION("Req %d/%d: set %d to %d\n", ms_io->Requests.index_from_ptr(&req), ms_io->Requests.Size, selection_changes, req.Selected); + if (selection_changes == 1 || (selection_changes < Size / 100)) + { + // Multiple sorted insertion + copy likely to be faster. + // Technically we could do a single copy with a little more work (sort sequential SetRange requests) + for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++) + SetItemSelected(GetStorageIdFromIndex(idx), req.Selected); + } + else + { + // Append insertion + single sort likely be faster. + // Use req.RangeDirection to set order field so that shift+clicking from 1 to 5 is different than shift+clicking from 5 to 1 + const int size_before_amends = _Storage.Data.Size; + int selection_order = _SelectionOrder + ((req.RangeDirection < 0) ? selection_changes - 1 : 0); + for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++, selection_order += req.RangeDirection) + ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, selection_order); + if (req.Selected) + _SelectionOrder += selection_changes; + ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends); + } + } + } +} + +//------------------------------------------------------------------------- + +ImGuiSelectionExternalStorage::ImGuiSelectionExternalStorage() +{ + UserData = NULL; + AdapterSetItemSelected = NULL; +} + +// Apply requests coming from BeginMultiSelect() and EndMultiSelect(). +// We also pull 'ms_io->ItemsCount' as passed for BeginMultiSelect() for consistency with ImGuiSelectionBasicStorage +// This makes no assumption about underlying storage. +void ImGuiSelectionExternalStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) +{ + IM_ASSERT(AdapterSetItemSelected); + for (ImGuiSelectionRequest& req : ms_io->Requests) + { + if (req.Type == ImGuiSelectionRequestType_SetAll) + for (int idx = 0; idx < ms_io->ItemsCount; idx++) + AdapterSetItemSelected(this, idx, req.Selected); + if (req.Type == ImGuiSelectionRequestType_SetRange) + for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++) + AdapterSetItemSelected(this, idx, req.Selected); + } +} //------------------------------------------------------------------------- // [SECTION] Widgets: ListBox @@ -6935,6 +8125,7 @@ bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) 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); + AlignTextToFramePadding(); } BeginChild(id, frame_bb.GetSize(), ImGuiChildFlags_FrameStyle); @@ -6978,6 +8169,7 @@ bool ImGui::ListBox(const char* label, int* current_item, const char* (*getter)( 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. + clipper.IncludeItemByIndex(*current_item); while (clipper.Step()) for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { @@ -7281,7 +8473,7 @@ bool ImGui::BeginMenuBar() // 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)); + ImRect clip_rect(ImFloor(bar_rect.Min.x + window->WindowBorderSize), ImFloor(bar_rect.Min.y + window->WindowBorderSize), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize))), ImFloor(bar_rect.Max.y)); clip_rect.ClipWith(window->OuterRectClipped); PushClipRect(clip_rect.Min, clip_rect.Max, false); @@ -7494,13 +8686,13 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) bool pressed; // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another. - const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_DontClosePopups; + const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups; 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_TRUNC(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight()); + popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight); window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f); PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 2.0f, style.ItemSpacing.y)); float w = label_size.x; @@ -7872,7 +9064,9 @@ bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags) tab_bar->ID = id; tab_bar->SeparatorMinX = tab_bar->BarRect.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f); tab_bar->SeparatorMaxX = tab_bar->BarRect.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f); - return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused); + //if (g.NavWindow && IsWindowChildOf(g.NavWindow, window, false, false)) + flags |= ImGuiTabBarFlags_IsFocused; + return BeginTabBarEx(tab_bar, tab_bar_bb, flags); } bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags) @@ -7927,7 +9121,7 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG // Draw separator // (it would be misleading to draw this in EndTabBar() suggesting that it may be drawn over tabs, as tab bar are appendable) - const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive); + const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected); if (g.Style.TabBarBorderSize > 0.0f) { const float y = tab_bar->BarRect.Max.y; @@ -8287,7 +9481,7 @@ ImGuiTabItem* ImGui::TabBarFindMostRecentlySelectedTabForActiveWindow(ImGuiTabBa ImGuiTabItem* ImGui::TabBarGetCurrentTab(ImGuiTabBar* tab_bar) { - if (tab_bar->LastTabItemIdx <= 0 || tab_bar->LastTabItemIdx >= tab_bar->Tabs.Size) + if (tab_bar->LastTabItemIdx < 0 || tab_bar->LastTabItemIdx >= tab_bar->Tabs.Size) return NULL; return &tab_bar->Tabs[tab_bar->LastTabItemIdx]; } @@ -8883,8 +10077,16 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // 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)); + const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabDimmed)); TabItemBackground(display_draw_list, bb, flags, tab_col); + if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f) + { + float x_offset = IM_TRUNC(0.4f * style.TabRounding); + if (x_offset < 2.0f * g.CurrentDpiScale) + x_offset = 0.0f; + float y_offset = 1.0f * g.CurrentDpiScale; + display_draw_list->AddLine(bb.GetTL() + ImVec2(x_offset, y_offset), bb.GetTR() + ImVec2(-x_offset, y_offset), GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline), style.TabBarOverlineSize); + } RenderNavHighlight(bb, id); // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget. diff --git a/libs/imgui/imstb_textedit.h b/libs/imgui/imstb_textedit.h index c30f1a1..783054a 100644 --- a/libs/imgui/imstb_textedit.h +++ b/libs/imgui/imstb_textedit.h @@ -41,7 +41,7 @@ // 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.10 (2016-10-25) suppress 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 diff --git a/libs/imgui/imstb_truetype.h b/libs/imgui/imstb_truetype.h index 35c827e..976f09c 100644 --- a/libs/imgui/imstb_truetype.h +++ b/libs/imgui/imstb_truetype.h @@ -656,7 +656,7 @@ STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h 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, +// codepoints without a glyph received the font's "missing character" glyph, // typically an empty box by convention. STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above diff --git a/libs/imgui_test_engine/LICENSE.txt b/libs/imgui_test_engine/LICENSE.txt index 12cacfe..933f1b8 100644 --- a/libs/imgui_test_engine/LICENSE.txt +++ b/libs/imgui_test_engine/LICENSE.txt @@ -1,5 +1,5 @@ -Dear ImGui Test Engine License (v1.03) -Copyright (c) 2018-2023 Omar Cornut +Dear ImGui Test Engine License (v1.04) +Copyright (c) 2018-2024 Omar Cornut This document is a legal agreement ("License") between you ("Licensee") and DISCO HELLO ("Licensor") that governs your use of Dear ImGui Test Engine ("Software"). @@ -31,9 +31,10 @@ information are available at the following URL: http://www.dearimgui.com/license 2.1. License scope -A limited and non-exclusive license is hereby granted, to the Licensee, to reproduce, -execute, publicly perform, and display, use, copy, modify, merge, distribute, or create -derivative works based on and/or derived from the Software ("Derivative Software"). +A limited and non-exclusive worldwide license is hereby granted, to the Licensee, +to reproduce, execute, publicly perform, and display, use, copy, modify, merge, +distribute, or create derivative works based on and/or derived from the Software +("Derivative Software"). 2.2. Right of distribution diff --git a/libs/imgui_test_engine/imgui_capture_tool.cpp b/libs/imgui_test_engine/imgui_capture_tool.cpp index eb56e0e..fa947d4 100644 --- a/libs/imgui_test_engine/imgui_capture_tool.cpp +++ b/libs/imgui_test_engine/imgui_capture_tool.cpp @@ -30,6 +30,7 @@ Index of this file: #define IMGUI_DEFINE_MATH_OPERATORS #include "imgui.h" #include "imgui_internal.h" +#include "imgui_te_engine.h" #include "imgui_capture_tool.h" #include "imgui_te_utils.h" // ImPathFindFilename, ImPathFindExtension, ImPathFixSeparatorsForCurrentOS, ImFileCreateDirectoryChain, ImOsOpenInShell #include "thirdparty/Str/Str.h" @@ -50,7 +51,10 @@ Index of this file: #pragma warning (push) #pragma warning (disable: 4456) // declaration of 'xx' hides previous local declaration #pragma warning (disable: 4457) // declaration of 'xx' hides function parameter -#else +#elif defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations"// warning: 'sprintf' has been explicitly marked deprecated here +#elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-conversion" #endif @@ -76,7 +80,9 @@ using namespace IMGUI_STB_NAMESPACE; #ifdef _MSC_VER #pragma warning (pop) -#else +#elif defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) #pragma GCC diagnostic pop #endif @@ -365,7 +371,7 @@ ImGuiCaptureStatus ImGuiCaptureContext::CaptureUpdate(ImGuiCaptureArgs* args) // FIXME-CAPTURE: Window width change may affect vertical content size if window contains text that wraps. To accurately position mouse cursor for capture we avoid horizontal resize. // Instead window width should be set manually before capture, as it is simple to do and most of the time we already have a window of desired width. //full_size.x = ImMax(window->SizeFull.x, window->ContentSize.x + (window->WindowPadding.x + window->WindowBorderSize) * 2); - full_size.y = ImMax(window->SizeFull.y, window->ContentSize.y + (window->WindowPadding.y + window->WindowBorderSize) * 2 + window->TitleBarHeight() + window->MenuBarHeight()); + full_size.y = ImMax(window->SizeFull.y, window->ContentSize.y + (window->WindowPadding.y + window->WindowBorderSize) * 2 + window->DecoOuterSizeY1); ImGui::SetWindowSize(window, full_size); _HoveredWindow = g.HoveredWindow; } @@ -732,11 +738,10 @@ void ImGuiCaptureToolUI::_CaptureWindowsSelector(ImGuiCaptureContext* context, I if (!allow_capture) ImGui::BeginDisabled(); bool do_capture = ImGui::Button(label, button_sz); - do_capture |= io.KeyAlt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C)); + do_capture |= io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_C); if (!allow_capture) ImGui::EndDisabled(); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Alternatively press Alt+C to capture selection."); + ImGui::SetItemTooltip("Alternatively press Alt+C to capture selection."); if (do_capture && _InitializeOutputFile()) _StateIsCapturing = true; } @@ -889,8 +894,8 @@ void ImGuiCaptureToolUI::ShowCaptureToolWindow(ImGuiCaptureContext* context, boo ImOsOpenInShell(OutputLastFilename); if (!has_last_file_name) ImGui::EndDisabled(); - if (has_last_file_name && ImGui::IsItemHovered()) - ImGui::SetTooltip("Open %s", OutputLastFilename); + if (has_last_file_name) + ImGui::SetItemTooltip("Open %s", OutputLastFilename); ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); } @@ -908,8 +913,7 @@ void ImGuiCaptureToolUI::ShowCaptureToolWindow(ImGuiCaptureContext* context, boo ImPathFixSeparatorsForCurrentOS(output_dir); ImOsOpenInShell(output_dir); } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Open %s/", output_dir); + ImGui::SetItemTooltip("Open %s/", output_dir); } const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; @@ -917,18 +921,16 @@ void ImGuiCaptureToolUI::ShowCaptureToolWindow(ImGuiCaptureContext* context, boo ImGui::PushItemWidth(BUTTON_WIDTH); ImGui::InputText("Output template", _OutputFileTemplate, IM_ARRAYSIZE(_OutputFileTemplate)); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Output template should contain one %%d (or variation of it) format variable. " - "Multiple captures will be saved with an increasing number to avoid overwriting same file."); + ImGui::SetItemTooltip( + "Output template should contain one %%d (or variation of it) format variable. " + "Multiple captures will be saved with an increasing number to avoid overwriting same file."); _ShowEncoderConfigFields(context); ImGui::DragFloat("Padding", &_CaptureArgs.InPadding, 0.1f, 0, 32, "%.0f"); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Extra padding around captured area."); + ImGui::SetItemTooltip("Extra padding around captured area."); ImGui::DragInt("Video FPS", &_CaptureArgs.InRecordFPSTarget, 0.1f, 10, 100, "%d fps"); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Target FPS for video captures."); + ImGui::SetItemTooltip("Target FPS for video captures."); if (ImGui::Button("Snap Windows To Grid", ImVec2(BUTTON_WIDTH, 0))) _SnapWindowsToGrid(SnapGridSize); @@ -945,13 +947,12 @@ void ImGuiCaptureToolUI::ShowCaptureToolWindow(ImGuiCaptureContext* context, boo ImGui::BeginDisabled(!content_stitching_available); ImGui::CheckboxFlags("Stitch full contents height", &_CaptureArgs.InFlags, ImGuiCaptureFlags_StitchAll); ImGui::EndDisabled(); - if (!content_stitching_available && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) - ImGui::SetTooltip("Content stitching is not possible when using viewports."); + if (!content_stitching_available) + ImGui::SetItemTooltip("Content stitching is not possible when using viewports."); ImGui::CheckboxFlags("Include other windows", &_CaptureArgs.InFlags, ImGuiCaptureFlags_IncludeOtherWindows); ImGui::CheckboxFlags("Include tooltips & popups", &_CaptureArgs.InFlags, ImGuiCaptureFlags_IncludeTooltipsAndPopups); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Capture area will be expanded to include visible tooltips."); + ImGui::SetItemTooltip("Capture area will be expanded to include visible tooltips."); ImGui::PopItemWidth(); ImGui::TreePop(); @@ -1024,8 +1025,7 @@ bool ImGuiCaptureToolUI::_ShowEncoderConfigFields(ImGuiCaptureContext* context) const bool encoder_exe_missing = !ImFileExist(context->VideoCaptureEncoderPath); if (encoder_exe_missing) ImGui::ItemErrorFrame(IM_COL32(255, 0, 0, 255)); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Absolute or relative path to video encoder executable (e.g. \"path/to/ffmpeg.exe\"). Required for video recording.%s", encoder_exe_missing ? "\nFile does not exist!" : ""); + ImGui::SetItemTooltip("Absolute or relative path to video encoder executable (e.g. \"path/to/ffmpeg.exe\"). Required for video recording.%s", encoder_exe_missing ? "\nFile does not exist!" : ""); } struct CmdLineParamsInfo @@ -1105,8 +1105,7 @@ bool ImGuiCaptureToolUI::_ShowEncoderConfigFields(ImGuiCaptureContext* context) } ImGui::EndCombo(); } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("File extension for captured video file."); + ImGui::SetItemTooltip("File extension for captured video file."); } return modified; } diff --git a/libs/imgui_test_engine/imgui_capture_tool.h b/libs/imgui_test_engine/imgui_capture_tool.h index e5489c5..c44429b 100644 --- a/libs/imgui_test_engine/imgui_capture_tool.h +++ b/libs/imgui_test_engine/imgui_capture_tool.h @@ -4,7 +4,7 @@ #pragma once -#include "imgui_te_utils.h" // ImFuncPtr +// Need "imgui_te_engine.h" included for ImFuncPtr //----------------------------------------------------------------------------- // Forward declarations diff --git a/libs/imgui_test_engine/imgui_te_context.cpp b/libs/imgui_test_engine/imgui_te_context.cpp index 7493321..e92abc9 100644 --- a/libs/imgui_test_engine/imgui_te_context.cpp +++ b/libs/imgui_test_engine/imgui_te_context.cpp @@ -20,12 +20,24 @@ // [SECTION] ImGuiTestRefDesc //------------------------------------------------------------------------- -ImGuiTestRefDesc::ImGuiTestRefDesc(const ImGuiTestRef& ref, const ImGuiTestItemInfo* item) +ImGuiTestRefDesc::ImGuiTestRefDesc(const ImGuiTestRef& ref) { - if (ref.Path) - ImFormatString(Buf, IM_ARRAYSIZE(Buf), "'%s' > %08X", ref.Path, ref.ID); + if (ref.Path && ref.ID != 0) + ImFormatString(Buf, IM_ARRAYSIZE(Buf), "'%s' (id 0x%08X)", ref.Path, ref.ID); + else if (ref.Path) + ImFormatString(Buf, IM_ARRAYSIZE(Buf), "'%s'", ref.Path); else - ImFormatString(Buf, IM_ARRAYSIZE(Buf), "%08X > '%s'", ref.ID, item ? item->DebugLabel : "NULL"); + ImFormatString(Buf, IM_ARRAYSIZE(Buf), "0x%08X", ref.ID); +} + +ImGuiTestRefDesc::ImGuiTestRefDesc(const ImGuiTestRef& ref, const ImGuiTestItemInfo& item) +{ + if (ref.Path && item.ID != 0) + ImFormatString(Buf, IM_ARRAYSIZE(Buf), "'%s' (id 0x%08X)", ref.Path, item.ID); + else if (ref.Path) + ImFormatString(Buf, IM_ARRAYSIZE(Buf), "'%s'", ref.Path); + else + ImFormatString(Buf, IM_ARRAYSIZE(Buf), "0x%08X (label \"%s\")", ref.ID, item.DebugLabel); } //------------------------------------------------------------------------- @@ -336,12 +348,6 @@ void ImGuiTestContext::Yield(int count) } } -void ImGuiTestContext::YieldUntil(int frame_count) -{ - while (FrameCount < frame_count) - ImGuiTestEngine_Yield(Engine); -} - // Supported values for ImGuiTestRunFlags: // - ImGuiTestRunFlags_NoError: if child test fails, return false and do not mark parent test as failed. // - ImGuiTestRunFlags_ShareVars: share generic vars and custom vars between child and parent tests. @@ -369,7 +375,7 @@ ImGuiTestStatus ImGuiTestContext::RunChildTest(const char* child_test_name, ImGu TestOutput->Status = ImGuiTestStatus_Error; // Return child status - LogWarning("(returning to parent test)"); + LogDebug("(returning to parent test)"); return child_status; } @@ -380,11 +386,15 @@ bool ImGuiTestContext::SuspendTestFunc(const char* file, int line) if (IsError()) return false; - file = ImPathFindFilename(file); if (file != NULL) + { + file = ImPathFindFilename(file); LogError("SuspendTestFunc() at %s:%d", file, line); + } else + { LogError("SuspendTestFunc()"); + } // Save relevant state. // FIXME-TESTS: Saving/restoring window z-order could be desirable. @@ -440,6 +450,7 @@ void ImGuiTestContext::SleepNoSkip(float time, float framestep_in_second) if (IsError()) return; + LogDebug("SleepNoSkip %f seconds (in %f increments)", time, framestep_in_second); while (time > 0.0f && !Abort) { ImGuiTestEngine_SetDeltaTime(Engine, framestep_in_second); @@ -480,11 +491,12 @@ void ImGuiTestContext::SetInputMode(ImGuiInputSource input_mode) } } +// Shortcut for when we have a window pointer, avoid mistakes with slashes in child names. void ImGuiTestContext::SetRef(ImGuiWindow* window) { IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); IM_CHECK_SILENT(window != NULL); - LogDebug("SetRef '%s' %08X", window->Name, window->ID); + LogDebug("SetRef '%s' 0x%08X", window->Name, window->ID); // We grab the ID directly and avoid ImHashDecoratedPath so "/" in window names are not ignored. size_t len = strlen(window->Name); @@ -499,13 +511,13 @@ void ImGuiTestContext::SetRef(ImGuiWindow* window) WindowCollapse(window->ID, false); } -// SetRef() ok in GUI Func ONLY if pointer to a pointer. +// It is exceptionally OK to call SetRef() in GuiFunc, as a facility to call seeded ctx->GetId() in GuiFunc. // FIXME-TESTS: May be good to focus window when docked? Otherwise locate request won't even see an item? void ImGuiTestContext::SetRef(ImGuiTestRef ref) { IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); if (ActiveFunc == ImGuiTestActiveFunc_TestFunc) - LogDebug("SetRef '%s' %08X", ref.Path ? ref.Path : "NULL", ref.ID); + LogDebug("SetRef '%s' 0x%08X", ref.Path ? ref.Path : "NULL", ref.ID); if (ref.Path) { @@ -522,13 +534,23 @@ void ImGuiTestContext::SetRef(ImGuiTestRef ref) } RefWindowID = 0; + // Early out + if (ref.IsEmpty()) + return; + // Try to infer window - // (1) Try first element of ref path, it is most likely a window name and item lookup won't be necessary. + // (This is in order to set viewport, uncollapse window, and store its base id for leading "/" operator) + + // (0) Windows is fully specified in path? ImGuiWindow* window = GetWindowByRef(""); + + // (1) Try first element of ref path, it is most likely a window name and item lookup won't be necessary. if (window == NULL && ref.Path != NULL) { + // "Window/SomeItem" -> search for "Window" const char* name_begin = ref.Path; - while (*name_begin == '/') name_begin++; + while (*name_begin == '/') // Skip leading slashes + name_begin++; const char* name_end = name_begin - 1; do { @@ -543,10 +565,13 @@ void ImGuiTestContext::SetRef(ImGuiTestRef ref) // (2) Ref was specified as an ID and points to an item therefore item lookup is unavoidable. // FIXME: Maybe display something in log when that happens? if (window == NULL) - if (ImGuiTestItemInfo* item_info = ItemInfo(RefID, ImGuiTestOpFlags_NoError)) - if (item_info->ID != 0) - window = item_info->Window; + { + ImGuiTestItemInfo item_info = ItemInfo(RefID, ImGuiTestOpFlags_NoError); + if (item_info.ID != 0) + window = item_info.Window; + } + // Set viewport and base ID for single "/" operator. if (window) { RefWindowID = window->ID; @@ -563,11 +588,9 @@ ImGuiTestRef ImGuiTestContext::GetRef() return RefID; } -// Turn ref into a root ref unless ref is empty -// FIXME: This seems inconsistent? Clarify? ImGuiWindow* ImGuiTestContext::GetWindowByRef(ImGuiTestRef ref) { - ImGuiID window_id = ref.IsEmpty() ? GetID(ref) : GetID(ref, "//"); + ImGuiID window_id = GetID(ref); ImGuiWindow* window = ImGui::FindWindowByID(window_id); return window; } @@ -685,6 +708,7 @@ ImVec2 ImGuiTestContext::GetMainMonitorWorkSize() return ImGui::GetMainViewport()->WorkSize; } +#if IMGUI_TEST_ENGINE_ENABLE_CAPTURE static bool ImGuiTestContext_CanCaptureScreenshot(ImGuiTestContext* ctx) { ImGuiTestEngineIO* io = ctx->EngineIO; @@ -696,6 +720,7 @@ static bool ImGuiTestContext_CanCaptureVideo(ImGuiTestContext* ctx) ImGuiTestEngineIO* io = ctx->EngineIO; return io->ConfigCaptureEnabled && ImFileExist(io->VideoCaptureEncoderPath); } +#endif bool ImGuiTestContext::CaptureAddWindow(ImGuiTestRef ref) { @@ -750,7 +775,7 @@ bool ImGuiTestContext::CaptureScreenshot(int capture_flags) return ret; #else IM_UNUSED(args); - LogWarning("Skipped capturing screenshot: capture disabled by IMGUI_TEST_ENGINE_ENABLE_CAPTURE=0."); + LogWarning("Skipped screenshot capture: disabled by IMGUI_TEST_ENGINE_ENABLE_CAPTURE=0."); return false; #endif } @@ -803,7 +828,7 @@ bool ImGuiTestContext::CaptureBeginVideo() return ImGuiTestEngine_CaptureBeginVideo(Engine, args); #else IM_UNUSED(args); - LogWarning("Skipped recording GIF: capture disabled by IMGUI_TEST_ENGINE_ENABLE_CAPTURE."); + LogWarning("Skipped recording GIF: disabled by IMGUI_TEST_ENGINE_ENABLE_CAPTURE=0."); return false; #endif } @@ -818,6 +843,7 @@ bool ImGuiTestContext::CaptureEndVideo() if (!ret) return false; +#if IMGUI_TEST_ENGINE_ENABLE_CAPTURE // In-progress capture was canceled by user. Delete incomplete file. if (IsError()) { @@ -836,6 +862,7 @@ bool ImGuiTestContext::CaptureEndVideo() else LogWarning("Skipped saving '%s' video because: Video Encoder not found.", args->InOutputFile); } +#endif return ret; } @@ -883,8 +910,8 @@ ImGuiID ImGuiTestContext::ItemInfoHandleWildcardSearch(const char* wildcard_pref // mode without the need for scrolling. if (task->OutItemId == 0) { - ImGuiTestItemInfo* base_item = ItemInfo(task->InPrefixId, ImGuiTestOpFlags_NoError); - ImGuiWindow* window = (base_item->ID != 0) ? base_item->Window : GetWindowByRef(task->InPrefixId); + ImGuiTestItemInfo base_item = ItemInfo(task->InPrefixId, ImGuiTestOpFlags_NoError); + ImGuiWindow* window = (base_item.ID != 0) ? base_item.Window : GetWindowByRef(task->InPrefixId); if (window) { ImVec2 rect_size = window->InnerRect.GetSize(); @@ -921,13 +948,6 @@ ImGuiID ImGuiTestContext::ItemInfoHandleWildcardSearch(const char* wildcard_pref return full_id; } -// Return an empty instance so ItemInfo() never returns a NULL pointer by default (unless requested) -ImGuiTestItemInfo* ImGuiTestContext::ItemInfoNull() -{ - DummyItemInfoNull = ImGuiTestItemInfo(); - return &DummyItemInfoNull; -} - static void ItemInfoErrorLog(ImGuiTestContext* ctx, ImGuiTestRef ref, ImGuiID full_id, ImGuiTestOpFlags flags) { if (flags & ImGuiTestOpFlags_NoError) @@ -952,11 +972,14 @@ static void ItemInfoErrorLog(ImGuiTestContext* ctx, ImGuiTestRef ref, ImGuiID fu // Supported values for ImGuiTestOpFlags: // - ImGuiTestOpFlags_NoError -ImGuiTestItemInfo* ImGuiTestContext::ItemInfo(ImGuiTestRef ref, ImGuiTestOpFlags flags) +ImGuiTestItemInfo ImGuiTestContext::ItemInfo(ImGuiTestRef ref, ImGuiTestOpFlags flags) { if (IsError()) return ItemInfoNull(); + const ImGuiTestOpFlags SUPPORTED_FLAGS = ImGuiTestOpFlags_NoError; + IM_ASSERT((flags & ~SUPPORTED_FLAGS) == 0); + ImGuiID full_id = 0; if (const char* p = ref.Path ? strstr(ref.Path, "**/") : NULL) @@ -994,7 +1017,7 @@ ImGuiTestItemInfo* ImGuiTestContext::ItemInfo(ImGuiTestRef ref, ImGuiTestOpFlags } if (item) - return item; + return *item; ImGuiTestEngine_Yield(Engine); retries++; } @@ -1006,12 +1029,12 @@ ImGuiTestItemInfo* ImGuiTestContext::ItemInfo(ImGuiTestRef ref, ImGuiTestOpFlags // Supported values for ImGuiTestOpFlags: // - ImGuiTestOpFlags_NoError -ImGuiTestItemInfo* ImGuiTestContext::ItemInfoOpenFullPath(ImGuiTestRef ref, ImGuiTestOpFlags flags) +ImGuiTestItemInfo ImGuiTestContext::ItemInfoOpenFullPath(ImGuiTestRef ref, ImGuiTestOpFlags flags) { // First query bool can_open_full_path = (ref.Path != NULL); - ImGuiTestItemInfo* item = ItemInfo(ref, (can_open_full_path ? ImGuiTestOpFlags_NoError : ImGuiTestOpFlags_None) | (flags & ImGuiTestOpFlags_NoError)); - if (item->ID != 0) + ImGuiTestItemInfo item = ItemInfo(ref, (can_open_full_path ? ImGuiTestOpFlags_NoError : ImGuiTestOpFlags_None) | (flags & ImGuiTestOpFlags_NoError)); + if (item.ID != 0) return item; if (!can_open_full_path) return ItemInfoNull(); @@ -1029,26 +1052,26 @@ ImGuiTestItemInfo* ImGuiTestContext::ItemInfoOpenFullPath(ImGuiTestRef ref, ImGu Str128 parent_id; parent_id.set(ref.Path, parent_end); - ImGuiTestItemInfo* parent_item = ItemInfo(parent_id.c_str(), ImGuiTestOpFlags_NoError); - if (parent_item->ID != 0) + ImGuiTestItemInfo parent_item = ItemInfo(parent_id.c_str(), ImGuiTestOpFlags_NoError); + if (parent_item.ID != 0) { #ifdef IMGUI_HAS_DOCK - ImGuiWindow* parent_window = parent_item->Window; + ImGuiWindow* parent_window = parent_item.Window; #endif - if ((parent_item->StatusFlags & ImGuiItemStatusFlags_Openable) != 0 && (parent_item->StatusFlags & ImGuiItemStatusFlags_Opened) == 0) + if ((parent_item.StatusFlags & ImGuiItemStatusFlags_Openable) != 0 && (parent_item.StatusFlags & ImGuiItemStatusFlags_Opened) == 0) { // Open intermediary item - if ((parent_item->InFlags & ImGuiItemFlags_Disabled) == 0) // FIXME: Report disabled state in log? + if ((parent_item.InFlags & ImGuiItemFlags_Disabled) == 0) // FIXME: Report disabled state in log? { - ItemAction(ImGuiTestAction_Open, parent_item->ID, ImGuiTestOpFlags_NoAutoOpenFullPath); + ItemAction(ImGuiTestAction_Open, parent_item.ID, ImGuiTestOpFlags_NoAutoOpenFullPath); opened_parents++; } } #ifdef IMGUI_HAS_DOCK - else if (parent_window->ID == parent_item->ID && parent_window->DockIsActive && parent_window->DockTabIsVisible == false) + else if (parent_window->ID == parent_item.ID && parent_window->DockIsActive && parent_window->DockTabIsVisible == false) { // Make tab visible - ItemClick(parent_item->ID); + ItemClick(parent_item.ID); opened_parents++; } #endif @@ -1057,7 +1080,7 @@ ImGuiTestItemInfo* ImGuiTestContext::ItemInfoOpenFullPath(ImGuiTestRef ref, ImGu if (opened_parents > 0) item = ItemInfo(ref, (flags & ImGuiTestOpFlags_NoError)); - if (item->ID == 0) + if (item.ID == 0) ItemInfoErrorLog(this, ref, 0, flags); return item; @@ -1081,28 +1104,30 @@ ImGuiTestItemInfo* ImGuiTestContext::ItemInfoOpenFullPath(ImGuiTestRef ref, ImGu // - Other fields correspond to the title-bar/tab item of a window, so likely not what you want (same as using IsItemXXX after Begin) // - If you want other fields simply get them via the window-> pointer. // - Likely you may want to feed the return value into SetRef(): e.g. 'ctx->SetRef(item->ID)' or 'ctx->SetRef(WindowInfo("//Window/Child")->ID);' +// Supported values for ImGuiTestOpFlags: +// - ImGuiTestOpFlags_NoError // Todos: // - FIXME: Missing support for wildcards. -ImGuiTestItemInfo* ImGuiTestContext::WindowInfo(ImGuiTestRef ref, ImGuiTestOpFlags flags) +ImGuiTestItemInfo ImGuiTestContext::WindowInfo(ImGuiTestRef ref, ImGuiTestOpFlags flags) { if (IsError()) return ItemInfoNull(); IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - ImGuiTestVerboseLevel log_level = (flags & ImGuiTestOpFlags_NoError) ? ImGuiTestVerboseLevel_Info : ImGuiTestVerboseLevel_Error; // Query by ID (not very useful but supported) if (ref.ID != 0) { - LogDebug("WindowInfo: by id: %08X", ref.ID); + LogDebug("WindowInfo: by id: 0x%08X", ref.ID); IM_ASSERT(ref.Path == NULL); ImGuiWindow* window = GetWindowByRef(ref); if (window == NULL) { - LogEx(log_level, 0, "WindowInfo: error: cannot find window by ID!"); // FIXME: What if we want to query a not-yet-existing window by ID? + if ((flags & ImGuiTestOpFlags_NoError) == 0) + LogError("WindowInfo: cannot find window by ID!"); // FIXME: What if we want to query a not-yet-existing window by ID? return ItemInfoNull(); } - return ItemInfo(window->ID); + return ItemInfo(window->ID, flags & ImGuiTestOpFlags_NoError); } // Query by Path: this is where the meat of our work is. @@ -1192,7 +1217,8 @@ ImGuiTestItemInfo* ImGuiTestContext::WindowInfo(ImGuiTestRef ref, ImGuiTestOpFla // FIXME: What if we want to query a not-yet-existing window by ID? if (window == NULL) { - LogEx(log_level, 0, "WindowInfo: error: element \"%s\" doesn't seem to exist.", part_name.c_str()); + if ((flags & ImGuiTestOpFlags_NoError) == 0) + LogError("WindowInfo: element \"%s\" doesn't seem to exist.", part_name.c_str()); return ItemInfoNull(); } } @@ -1203,11 +1229,12 @@ ImGuiTestItemInfo* ImGuiTestContext::WindowInfo(ImGuiTestRef ref, ImGuiTestOpFla // Stopped on "window/node/" if (window_idstack_back != 0 && window_idstack_back != window->ID) { - LogEx(log_level, 0, "WindowInfo: error: element doesn't seem to exist or isn't a window."); + if ((flags & ImGuiTestOpFlags_NoError) == 0) + LogError("WindowInfo: element doesn't seem to exist or isn't a window."); return ItemInfoNull(); } - return ItemInfo(window->ID); + return ItemInfo(window->ID, flags & ImGuiTestOpFlags_NoError); } void ImGuiTestContext::ScrollToTop(ImGuiTestRef ref) @@ -1297,6 +1324,47 @@ static ImVec2 GetWindowScrollbarMousePositionForScroll(ImGuiWindow* window, ImGu #define ImTrunc ImFloor #endif +static bool ScrollToWithScrollbar(ImGuiTestContext* ctx, ImGuiWindow* window, ImGuiAxis axis, float scroll_target) +{ + ImGuiContext& g = *ctx->UiContext; + ctx->Yield(); + ctx->WindowFocus(window->ID); + if (window->ScrollbarSizes[axis ^ 1] <= 0.0f) // Verify if still exists after yield + return false; + + const ImRect scrollbar_rect = ImGui::GetWindowScrollbarRect(window, axis); + const float scrollbar_size_v = scrollbar_rect.Max[axis] - scrollbar_rect.Min[axis]; + const float window_resize_grip_size = ImTrunc(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f)); + + // In case of a very small window, directly use SetScrollX/Y function to prevent resizing it + // FIXME-TESTS: GetWindowScrollbarMousePositionForScroll() doesn't return the exact value when scrollbar grip is too small + if (scrollbar_size_v < window_resize_grip_size) + return false; + + ctx->MouseSetViewport(window); + + const float scroll_src = window->Scroll[axis]; + ImVec2 scrollbar_src_pos = GetWindowScrollbarMousePositionForScroll(window, axis, scroll_src); + scrollbar_src_pos[axis] = ImMin(scrollbar_src_pos[axis], scrollbar_rect.Min[axis] + scrollbar_size_v - window_resize_grip_size); + ctx->MouseMoveToPos(scrollbar_src_pos); + ctx->MouseDown(0); + ctx->SleepStandard(); + + ImVec2 scrollbar_dst_pos = GetWindowScrollbarMousePositionForScroll(window, axis, scroll_target); + ctx->MouseMoveToPos(scrollbar_dst_pos); + ctx->MouseUp(0); + ctx->SleepStandard(); + + // Verify that things worked + const float scroll_result = window->Scroll[axis]; + if (ImFabs(scroll_result - scroll_target) < 1.0f) + return true; + + // FIXME-TESTS: Investigate + ctx->LogWarning("Failed to set Scroll%c. Requested %.2f, got %.2f.", 'X' + axis, scroll_target, scroll_result); + return true; +} + // Supported values for ImGuiTestOpFlags: // - ImGuiTestOpFlags_NoFocusWindow void ImGuiTestContext::ScrollTo(ImGuiTestRef ref, ImGuiAxis axis, float scroll_target, ImGuiTestOpFlags flags) @@ -1321,33 +1389,11 @@ void ImGuiTestContext::ScrollTo(ImGuiTestRef ref, ImGuiAxis axis, float scrol SleepStandard(); // Try to use Scrollbar if available - const ImGuiTestItemInfo* scrollbar_item = ItemInfo(ImGui::GetWindowScrollbarID(window, axis), ImGuiTestOpFlags_NoError); - if (scrollbar_item->ID != 0 && EngineIO->ConfigRunSpeed != ImGuiTestRunSpeed_Fast && !(flags & ImGuiTestOpFlags_NoFocusWindow)) + const ImGuiTestItemInfo scrollbar_item = ItemInfo(ImGui::GetWindowScrollbarID(window, axis), ImGuiTestOpFlags_NoError); + if (scrollbar_item.ID != 0 && /*EngineIO->ConfigRunSpeed != ImGuiTestRunSpeed_Fast && */ !(flags & ImGuiTestOpFlags_NoFocusWindow)) { - WindowFocus(window->ID); - - const ImRect scrollbar_rect = ImGui::GetWindowScrollbarRect(window, axis); - const float scrollbar_size_v = scrollbar_rect.Max[axis] - scrollbar_rect.Min[axis]; - const float window_resize_grip_size = ImTrunc(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f)); - - // In case of a very small window, directly use SetScrollX/Y function to prevent resizing it - // FIXME-TESTS: GetWindowScrollbarMousePositionForScroll doesn't return the exact value when scrollbar grip is too small - if (scrollbar_size_v >= window_resize_grip_size) + if (ScrollToWithScrollbar(this, window, axis, scroll_target_clamp)) { - MouseSetViewport(window); - - const float scroll_src = window->Scroll[axis]; - ImVec2 scrollbar_src_pos = GetWindowScrollbarMousePositionForScroll(window, axis, scroll_src); - scrollbar_src_pos[axis] = ImMin(scrollbar_src_pos[axis], scrollbar_rect.Min[axis] + scrollbar_size_v - window_resize_grip_size); - MouseMoveToPos(scrollbar_src_pos); - MouseDown(0); - SleepStandard(); - - ImVec2 scrollbar_dst_pos = GetWindowScrollbarMousePositionForScroll(window, axis, scroll_target_clamp); - MouseMoveToPos(scrollbar_dst_pos); - MouseUp(0); - SleepStandard(); - // Verify that things worked const float scroll_result = window->Scroll[axis]; if (ImFabs(scroll_result - scroll_target_clamp) < 1.0f) @@ -1392,11 +1438,11 @@ void ImGuiTestContext::ScrollToItem(ImGuiTestRef ref, ImGuiAxis axis, ImGuiTe // If the item is not currently visible, scroll to get it in the center of our window IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - ImGuiTestItemInfo* item = ItemInfo(ref); + ImGuiTestItemInfo item = ItemInfo(ref); ImGuiTestRefDesc desc(ref, item); LogDebug("ScrollToItem %c %s", 'X' + axis, desc.c_str()); - if (item->ID == 0) + if (item.ID == 0) return; // Ensure window size and ScrollMax are up-to-date @@ -1406,15 +1452,15 @@ void ImGuiTestContext::ScrollToItem(ImGuiTestRef ref, ImGuiAxis axis, ImGuiTe // FIXME-TESTS: Consider moving to its own function. ImGuiContext& g = *UiContext; if (axis == ImGuiAxis_X) - if (ImGuiTabBar* tab_bar = g.TabBars.GetByKey(item->ParentID)) + if (ImGuiTabBar* tab_bar = g.TabBars.GetByKey(item.ParentID)) if (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) { - ScrollToTabItem(tab_bar, item->ID); + ScrollToTabItem(tab_bar, item.ID); return; } - ImGuiWindow* window = item->Window; - float item_curr = ImFloor(item->RectFull.GetCenter()[axis]); + ImGuiWindow* window = item.Window; + float item_curr = ImFloor(item.RectFull.GetCenter()[axis]); float item_target = ImFloor(window->InnerClipRect.GetCenter()[axis]); float scroll_delta = item_target - item_curr; float scroll_target = ImClamp(window->Scroll[axis] - scroll_delta, 0.0f, window->ScrollMax[axis]); @@ -1502,42 +1548,37 @@ void ImGuiTestContext::NavMoveTo(ImGuiTestRef ref) IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); ImGuiContext& g = *UiContext; - ImGuiTestItemInfo* item = ItemInfo(ref); + ImGuiTestItemInfo item = ItemInfo(ref); ImGuiTestRefDesc desc(ref, item); LogDebug("NavMove to %s", desc.c_str()); - if (item->ID == 0) + if (item.ID == 0) return; - item->RefCount++; if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Cinematic) SleepStandard(); // Focus window before scrolling/moving so things are nicely visible - WindowFocus(item->Window->ID); + WindowFocus(item.Window->ID); // Teleport // FIXME-NAV: We should have a nav request feature that does this, // except it'll have to queue the request to find rect, then set scrolling, which would incur a 2 frame delay :/ // FIXME-TESTS-NOT_SAME_AS_END_USER IM_ASSERT(g.NavMoveSubmitted == false); - ImRect rect_rel = item->RectFull; - rect_rel.Translate(ImVec2(-item->Window->Pos.x, -item->Window->Pos.y)); - ImGui::SetNavID(item->ID, (ImGuiNavLayer)item->NavLayer, 0, rect_rel); + ImRect rect_rel = item.RectFull; + rect_rel.Translate(ImVec2(-item.Window->Pos.x, -item.Window->Pos.y)); + ImGui::SetNavID(item.ID, (ImGuiNavLayer)item.NavLayer, 0, rect_rel); g.NavDisableHighlight = false; g.NavDisableMouseHover = g.NavMousePosDirty = true; - ImGui::ScrollToBringRectIntoView(item->Window, item->RectFull); + ImGui::ScrollToBringRectIntoView(item.Window, item.RectFull); while (g.NavMoveSubmitted) Yield(); Yield(); if (!Abort) - { - if (g.NavId != item->ID) + if (g.NavId != item.ID) IM_ERRORF_NOHDR("Unable to set NavId to %s", desc.c_str()); - } - - item->RefCount--; } void ImGuiTestContext::NavActivate() @@ -1566,9 +1607,9 @@ void ImGuiTestContext::NavInput() // - ImGuiTestOpFlags_MoveToEdgeR // - ImGuiTestOpFlags_MoveToEdgeU // - ImGuiTestOpFlags_MoveToEdgeD -static ImVec2 GetMouseAimingPos(ImGuiTestItemInfo* item, ImGuiTestOpFlags flags) +static ImVec2 GetMouseAimingPos(const ImGuiTestItemInfo& item, ImGuiTestOpFlags flags) { - ImRect r = item->RectClipped; + ImRect r = item.RectClipped; ImVec2 pos; if (flags & ImGuiTestOpFlags_MoveToEdgeL) pos.x = (r.Min.x + 1.0f); @@ -1601,7 +1642,7 @@ void ImGuiTestContext::MouseMove(ImGuiTestRef ref, ImGuiTestOpFlags flags) IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); ImGuiContext& g = *UiContext; - ImGuiTestItemInfo* item; + ImGuiTestItemInfo item; if (flags & ImGuiTestOpFlags_NoAutoOpenFullPath) item = ItemInfo(ref); else @@ -1609,106 +1650,115 @@ void ImGuiTestContext::MouseMove(ImGuiTestRef ref, ImGuiTestOpFlags flags) ImGuiTestRefDesc desc(ref, item); LogDebug("MouseMove to %s", desc.c_str()); - if (item->ID == 0) + if (item.ID == 0) return; - if (!item->Window->WasActive) + ImGuiWindow* window = item.Window; + if (!window->WasActive) { - LogError("Window '%s' is not active!", item->Window->Name); + LogError("Window '%s' is not active!", window->Name); return; } - item->RefCount++; - // FIXME-TESTS: If window was not brought to front (because of either ImGuiWindowFlags_NoBringToFrontOnFocus or ImGuiTestOpFlags_NoFocusWindow) // then we need to make space by moving other windows away. // An easy to reproduce this bug is to run "docking_dockspace_tab_amend" with Test Engine UI over top-left corner, covering the Tools menu. // Check visibility and scroll if necessary - ImGuiWindow* window = item->Window; - if (item->NavLayer == ImGuiNavLayer_Main) { ImRect window_r = window->InnerClipRect; window_r.Expand(ImVec2(-g.WindowsHoverPadding.x, -g.WindowsHoverPadding.y)); ImRect item_r_clipped; - item_r_clipped.Min.x = ImClamp(item->RectFull.Min.x, window_r.Min.x, window_r.Max.x); - item_r_clipped.Min.y = ImClamp(item->RectFull.Min.y, window_r.Min.y, window_r.Max.y); - item_r_clipped.Max.x = ImClamp(item->RectFull.Max.x, window_r.Min.x, window_r.Max.x); - item_r_clipped.Max.y = ImClamp(item->RectFull.Max.y, window_r.Min.y, window_r.Max.y); + item_r_clipped.Min.x = ImClamp(item.RectFull.Min.x, window_r.Min.x, window_r.Max.x); + item_r_clipped.Min.y = ImClamp(item.RectFull.Min.y, window_r.Min.y, window_r.Max.y); + item_r_clipped.Max.x = ImClamp(item.RectFull.Max.x, window_r.Min.x, window_r.Max.x); + item_r_clipped.Max.y = ImClamp(item.RectFull.Max.y, window_r.Min.y, window_r.Max.y); // In theory all we need is one visible point, but it is generally nicer if we scroll toward visibility. // Bias toward reducing amount of horizontal scroll. - float visibility_ratio_x = (item_r_clipped.GetWidth() + 1.0f) / (item->RectFull.GetWidth() + 1.0f); - float visibility_ratio_y = (item_r_clipped.GetHeight() + 1.0f) / (item->RectFull.GetHeight() + 1.0f); - if (visibility_ratio_x < 0.70f) - ScrollToItem(ref, ImGuiAxis_X, ImGuiTestOpFlags_NoFocusWindow); - if (visibility_ratio_y < 0.90f) - ScrollToItem(ref, ImGuiAxis_Y, ImGuiTestOpFlags_NoFocusWindow); - } - else - { - // Menu layer is not scrollable: attempt to resize window. - // FIXME-TESTS: ImGuiItemStatusFlags_Visible is currently not usable for test engine as it relies on ITEM_INFO hook, need moving in ItemAdd(). - //if ((item->StatusFlags & ImGuiItemStatusFlags_Visible) == 0) + float visibility_ratio_x = (item_r_clipped.GetWidth() + 1.0f) / (item.RectFull.GetWidth() + 1.0f); + float visibility_ratio_y = (item_r_clipped.GetHeight() + 1.0f) / (item.RectFull.GetHeight() + 1.0f); + + if (item.NavLayer == ImGuiNavLayer_Main) { - // FIXME-TESTS: We designed RectClipped as being within RectFull which is not what we want here. Approximate using window's Max.x - ImRect window_r = window->Rect(); - if (item->RectFull.Min.x > window_r.Max.x) + if (visibility_ratio_x < 0.70f) + ScrollToItem(ref, ImGuiAxis_X, ImGuiTestOpFlags_NoFocusWindow); + if (visibility_ratio_y < 0.90f) + ScrollToItem(ref, ImGuiAxis_Y, ImGuiTestOpFlags_NoFocusWindow); + } + // FIXME: Scroll parent window + } + + // Menu layer is not scrollable: attempt to resize window. + if (item.NavLayer == ImGuiNavLayer_Menu) + { + // FIXME-TESTS: We designed RectClipped as being within RectFull which is not what we want here. Approximate using window's Max.x + ImRect window_r = window->Rect(); + if (item.RectFull.Min.x > window_r.Max.x) + { + float extra_width_desired = item.RectFull.Max.x - window_r.Max.x; // item->RectClipped.Max.x; + if (extra_width_desired > 0.0f && (flags & ImGuiTestOpFlags_IsSecondAttempt) == 0) { - float extra_width_desired = item->RectFull.Max.x - window_r.Max.x; // item->RectClipped.Max.x; - if (extra_width_desired > 0.0f && (flags & ImGuiTestOpFlags_IsSecondAttempt) == 0) - { - LogDebug("Will attempt to resize window to make item in menu layer visible."); - WindowResize(window->ID, window->Size + ImVec2(extra_width_desired, 0.0f)); - } + LogDebug("Will attempt to resize window to make item in menu layer visible."); + WindowResize(window->ID, window->Size + ImVec2(extra_width_desired, 0.0f)); } } } - // FIXME-TESTS-NOT_SAME_AS_END_USER - ImVec2 pos = item->RectFull.GetCenter(); - WindowTeleportToMakePosVisible(window->ID, pos); + // Update item + item = ItemInfo(item.ID); - // Keep a deep copy of item info since item-> will be kept updated as we set a RefCount on it. - ImGuiTestItemInfo item_initial_state = *item; + // FIXME-TESTS-NOT_SAME_AS_END_USER + ImVec2 pos = item.RectFull.GetCenter(); + if (WindowTeleportToMakePosVisible(window->ID, pos)) + item = ItemInfo(item.ID); + + // Keep a copy of item info + const ImGuiTestItemInfo item_initial_state = item; // Target point pos = GetMouseAimingPos(item, flags); // Focus window - if (!(flags & ImGuiTestOpFlags_NoFocusWindow)) + if (!(flags & ImGuiTestOpFlags_NoFocusWindow) && item.Window != NULL) { // Avoid unnecessary focus // While this is generally desirable and much more consistent with user behavior, // it make test-engine behavior a little less deterministic. // Incorrectly written tests could possibly succeed or fail based on position of other windows. - bool is_covered = FindHoveredWindowAtPos(pos) != item->Window; - bool is_inhibited = ImGui::IsWindowContentHoverable(item->Window) == false; - + bool is_covered = FindHoveredWindowAtPos(pos) != item.Window; +#if IMGUI_VERSION_NUM >= 18944 + bool is_inhibited = ImGui::IsWindowContentHoverable(item.Window) == false; +#else + bool is_inhibited = false; +#endif // FIXME-TESTS-NOT_SAME_AS_END_USER: This has too many side effect, could we do without? // - e.g. This can close a modal. if (is_covered || is_inhibited) - WindowBringToFront(item->Window->ID); + WindowBringToFront(item.Window->ID); } // Another is window active test (in the case focus change has a side effect but also as we have yield an extra frame) - if (!item->Window->WasActive) + if (!item.Window->WasActive) { - LogError("Window '%s' is not active (after aiming)", item->Window->Name); + LogError("Window '%s' is not active (after aiming)", item.Window->Name); return; } - MouseSetViewport(item->Window); + MouseSetViewport(item.Window); MouseMoveToPos(pos); // Focus again in case something made us lost focus (which could happen on a simple hover) if (!(flags & ImGuiTestOpFlags_NoFocusWindow)) { // Avoid unnecessary focus - bool is_covered = FindHoveredWindowAtPos(pos) != item->Window; - bool is_inhibited = ImGui::IsWindowContentHoverable(item->Window) == false; - + bool is_covered = FindHoveredWindowAtPos(pos) != item.Window; +#if IMGUI_VERSION_NUM >= 18944 + bool is_inhibited = ImGui::IsWindowContentHoverable(item.Window) == false; +#else + bool is_inhibited = false; +#endif if (is_covered || is_inhibited) WindowBringToFront(window->ID); } @@ -1725,7 +1775,7 @@ void ImGuiTestContext::MouseMove(ImGuiTestRef ref, ImGuiTestOpFlags flags) for (int remaining_attempts = 3; remaining_attempts > 0; remaining_attempts--) { hovered_id = g.HoveredIdPreviousFrame; - is_hovered_item = (hovered_id == item->ID); + is_hovered_item = (hovered_id == item.ID); if (is_hovered_item) break; Yield(); @@ -1734,7 +1784,7 @@ void ImGuiTestContext::MouseMove(ImGuiTestRef ref, ImGuiTestOpFlags flags) bool is_hovered_window = is_hovered_item ? true : false; if (!is_hovered_item) for (ImGuiWindow* hovered_window = g.HoveredWindow; hovered_window != NULL && !is_hovered_window; hovered_window = hovered_window->ParentWindow) - if (hovered_window->ID == item->ID && hovered_window == item->Window) + if (hovered_window->ID == item.ID && hovered_window == item.Window) is_hovered_window = true; if (!is_hovered_item && !is_hovered_window) @@ -1751,31 +1801,32 @@ void ImGuiTestContext::MouseMove(ImGuiTestRef ref, ImGuiTestOpFlags flags) float extra_size = window->CalcFontSize() * 3.0f; WindowResize(window->ID, window->Size + ImVec2(extra_size, extra_size)); MouseMove(ref, flags | ImGuiTestOpFlags_IsSecondAttempt); - item->RefCount--; return; } } + // Update item + item = ItemInfo(item.ID); + + // Log message ImVec2 pos_old = item_initial_state.RectFull.Min; - ImVec2 pos_new = item->RectFull.Min; + ImVec2 pos_new = item.RectFull.Min; ImVec2 size_old = item_initial_state.RectFull.GetSize(); - ImVec2 size_new = item->RectFull.GetSize(); + ImVec2 size_new = item.RectFull.GetSize(); Str256f error_message( "Unable to Hover %s:\n" - "- Expected item %08X in window '%s', targeted position: (%.1f,%.1f)'\n" - "- Hovered id was %08X in '%s'.\n" - "- Item Pos: Before mouse move (%6.1f,%6.1f) vs Now (%6.1f,%6.1f) (%s)\n" - "- Item Size: Before mouse move (%6.1f,%6.1f) vs Now (%6.1f,%6.1f) (%s)", + "- Expected item 0x%08X in window '%s', targeted position: (%.1f,%.1f)'\n" + "- Hovered id was 0x%08X in '%s'.\n" + "- Before mouse move: Item Pos (%6.1f,%6.1f) Size (%6.1f,%6.1f)\n" + "- After mouse move: Item Pos (%6.1f,%6.1f) Size (%6.1f,%6.1f)", desc.c_str(), - item->ID, item->Window ? item->Window->Name : "", pos.x, pos.y, + item.ID, item.Window ? item.Window->Name : "", pos.x, pos.y, hovered_id, g.HoveredWindow ? g.HoveredWindow->Name : "", - pos_old.x, pos_old.y, pos_new.x, pos_new.y, (pos_old.x == pos_new.x && pos_old.y == pos_new.y) ? "Same" : "Changed", - size_old.x, size_old.y, size_new.x, size_new.y, (size_old.x == size_new.x && size_old.y == size_new.y) ? "Same" : "Changed"); + pos_old.x, pos_old.y, size_old.x, size_old.y, + pos_new.x, pos_new.y, size_new.x, size_new.y); IM_ERRORF_NOHDR("%s", error_message.c_str()); } } - - item->RefCount--; } void ImGuiTestContext::MouseSetViewport(ImGuiWindow* window) @@ -1841,7 +1892,7 @@ bool ImGuiTestContext::WindowTeleportToMakePosVisible(ImGuiTestRef ref, ImVec delta.x = (pos.x < visible_r.Min.x) ? (visible_r.Min.x - pos.x + pad) : (pos.x > visible_r.Max.x) ? (visible_r.Max.x - pos.x - pad) : 0.0f; delta.y = (pos.y < visible_r.Min.y) ? (visible_r.Min.y - pos.y + pad) : (pos.y > visible_r.Max.y) ? (visible_r.Max.y - pos.y - pad) : 0.0f; ImGui::SetWindowPos(window, window->Pos + delta, ImGuiCond_Always); - LogDebug("WindowTeleportToMakePosVisible %s delta (%.1f,%.1f)", window->Name, delta.x, delta.y); + LogDebug("WindowTeleportToMakePosVisible '%s' delta (%.1f,%.1f)", window->Name, delta.x, delta.y); Yield(); return true; } @@ -2013,7 +2064,9 @@ void ImGuiTestContext::MouseMoveToPos(ImVec2 target) } // This always teleport the mouse regardless of fast/slow mode. Useful e.g. to set initial mouse position for a GIF recording. -void ImGuiTestContext::MouseTeleportToPos(ImVec2 target) +// Supported values for ImGuiTestOpFlags: +// - ImGuiTestOpFlags_NoYield +void ImGuiTestContext::MouseTeleportToPos(ImVec2 target, ImGuiTestOpFlags flags) { if (IsError()) return; @@ -2022,8 +2075,11 @@ void ImGuiTestContext::MouseTeleportToPos(ImVec2 target) LogDebug("MouseTeleportToPos from (%.0f,%.0f) to (%.0f,%.0f)", Inputs->MousePosValue.x, Inputs->MousePosValue.y, target.x, target.y); Inputs->MousePosValue = target; - ImGuiTestEngine_Yield(Engine); - ImGuiTestEngine_Yield(Engine); + if ((flags & ImGuiTestOpFlags_NoYield) == 0) + { + ImGuiTestEngine_Yield(Engine); + ImGuiTestEngine_Yield(Engine); + } } void ImGuiTestContext::MouseDown(ImGuiMouseButton button) @@ -2120,16 +2176,21 @@ void ImGuiTestContext::MouseLiftDragThreshold(ImGuiMouseButton button) g.IO.MouseDragMaxDistanceSqr[button] = (g.IO.MouseDragThreshold * g.IO.MouseDragThreshold) + (g.IO.MouseDragThreshold * g.IO.MouseDragThreshold); } -// Modeled on FindHoveredWindow() in imgui.cpp. -// Ideally that core function would be refactored to avoid this copy. -// - Need to take account of MovingWindow specificities and early out. -// - Need to be able to skip viewport compare. -// So for now we use a custom function. ImGuiWindow* ImGuiTestContext::FindHoveredWindowAtPos(const ImVec2& pos) { +#if IMGUI_VERSION_NUM >= 19062 + ImGuiWindow* hovered_window = NULL; + ImGui::FindHoveredWindowEx(pos, true, &hovered_window, NULL); + return hovered_window; +#else + // Modeled on FindHoveredWindow() in imgui.cpp. + // Ideally that core function would be refactored to avoid this copy. + // - Need to take account of MovingWindow specificities and early out. + // - Need to be able to skip viewport compare. + // So for now we use a custom function. ImGuiContext& g = *UiContext; - const ImVec2 padding_regular = g.Style.TouchExtraPadding; - const ImVec2 padding_for_resize = g.IO.ConfigWindowsResizeFromEdges ? g.WindowsHoverPadding : padding_regular; + 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]; @@ -2139,12 +2200,10 @@ ImGuiWindow* ImGuiTestContext::FindHoveredWindowAtPos(const ImVec2& pos) 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(pos)) + ImVec2 hit_padding = (window->Flags & (ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) ? padding_regular : padding_for_resize; + ImRect r = window->OuterRectClipped; + r.Expand(hit_padding); + if (!r.Contains(pos)) continue; // Support for one rectangular hole in any given window @@ -2160,6 +2219,7 @@ ImGuiWindow* ImGuiTestContext::FindHoveredWindowAtPos(const ImVec2& pos) return window; } return NULL; +#endif } static bool IsPosOnVoid(ImGuiContext& g, const ImVec2& pos) @@ -2261,7 +2321,11 @@ ImVec2 ImGuiTestContext::GetWindowTitlebarPoint(ImGuiTestRef window_ref) else #endif { +#if IMGUI_VERSION_NUM >= 19071 + const float h = window->TitleBarHeight; +#else const float h = window->TitleBarHeight(); +#endif drag_pos = ImFloor(window->Pos + ImVec2(window->Size.x, h) * 0.5f); } @@ -2533,7 +2597,11 @@ void ImGuiTestContext::KeyCharsReplace(const char* chars) IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); LogDebug("KeyCharsReplace('%s')", chars); +#if IMGUI_VERSION_NUM < 19063 KeyPress(ImGuiKey_A | ImGuiMod_Shortcut); +#else + KeyPress(ImGuiKey_A | ImGuiMod_Ctrl); +#endif if (chars[0]) KeyChars(chars); else @@ -2547,7 +2615,11 @@ void ImGuiTestContext::KeyCharsReplaceEnter(const char* chars) IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); LogDebug("KeyCharsReplaceEnter('%s')", chars); +#if IMGUI_VERSION_NUM < 19063 KeyPress(ImGuiKey_A | ImGuiMod_Shortcut); +#else + KeyPress(ImGuiKey_A | ImGuiMod_Ctrl); +#endif if (chars[0]) KeyChars(chars); else @@ -2594,7 +2666,7 @@ void ImGuiTestContext::GatherItems(ImGuiTestItemList* out_list, ImGuiTestRef // FIXME-TESTS: To support filter we'd need to process the list here, // Because ImGuiTestItemList is a pool (ImVector + map ID->index) we'll need to filter, rewrite, rebuild map - ImGuiTestItemInfo* parent_item = ItemInfo(parent, ImGuiTestOpFlags_NoError); + ImGuiTestItemInfo parent_item = ItemInfo(parent, ImGuiTestOpFlags_NoError); LogDebug("GatherItems from %s, %d deep: found %d items.", ImGuiTestRefDesc(parent, parent_item).c_str(), depth, end_gather_size - begin_gather_size); task->Clear(); @@ -2628,7 +2700,7 @@ void ImGuiTestContext::ItemAction(ImGuiTestAction action, ImGuiTestRef ref, I } // Find item - ImGuiTestItemInfo* item; + ImGuiTestItemInfo item; if (flags & ImGuiTestOpFlags_NoAutoOpenFullPath) item = ItemInfo(ref, (flags & ImGuiTestOpFlags_NoError)); else @@ -2636,7 +2708,7 @@ void ImGuiTestContext::ItemAction(ImGuiTestAction action, ImGuiTestRef ref, I ImGuiTestRefDesc desc(ref, item); LogDebug("Item%s %s%s", GetActionName(action), desc.c_str(), (InputMode == ImGuiInputSource_Mouse) ? "" : " (w/ Nav)"); - if (item->ID == 0) + if (item.ID == 0) { if (flags & ImGuiTestOpFlags_NoError) LogDebug("Action skipped: Item doesn't exist + used ImGuiTestOpFlags_NoError."); @@ -2644,8 +2716,8 @@ void ImGuiTestContext::ItemAction(ImGuiTestAction action, ImGuiTestRef ref, I } // Automatically uncollapse by default - if (item->Window && !(OpFlags & ImGuiTestOpFlags_NoAutoUncollapse)) - WindowCollapse(item->Window->ID, false); + if (item.Window && !(OpFlags & ImGuiTestOpFlags_NoAutoUncollapse)) + WindowCollapse(item.Window->ID, false); if (action == ImGuiTestAction_Hover) { @@ -2696,48 +2768,49 @@ void ImGuiTestContext::ItemAction(ImGuiTestAction action, ImGuiTestRef ref, I else if (action == ImGuiTestAction_Open) { IM_ASSERT(action_arg == NULL); // Unused - if ((item->StatusFlags & ImGuiItemStatusFlags_Opened) == 0) + if ((item.StatusFlags & ImGuiItemStatusFlags_Opened) == 0) { - item->RefCount++; MouseMove(ref, flags); // Some item may open just by hovering, give them that chance - if ((item->StatusFlags & ImGuiItemStatusFlags_Opened) == 0) + item = ItemInfo(item.ID); + if ((item.StatusFlags & ImGuiItemStatusFlags_Opened) == 0) { MouseClick(0); - if ((item->StatusFlags & ImGuiItemStatusFlags_Opened) == 0) + item = ItemInfo(item.ID); + if ((item.StatusFlags & ImGuiItemStatusFlags_Opened) == 0) { MouseDoubleClick(0); // Attempt a double-click // FIXME-TESTS: let's not start doing those fuzzy things.. - if ((item->StatusFlags & ImGuiItemStatusFlags_Opened) == 0) - IM_ERRORF_NOHDR("Unable to Open item: '%s' in '%s'", desc.c_str(), item->Window ? item->Window->Name : "N/A"); + item = ItemInfo(item.ID); + if ((item.StatusFlags & ImGuiItemStatusFlags_Opened) == 0) + IM_ERRORF_NOHDR("Unable to Open item: '%s' in '%s'", desc.c_str(), item.Window ? item.Window->Name : "N/A"); } } - item->RefCount--; //Yield(); } } else if (action == ImGuiTestAction_Close) { IM_ASSERT(action_arg == NULL); // Unused - if ((item->StatusFlags & ImGuiItemStatusFlags_Opened) != 0) + if ((item.StatusFlags & ImGuiItemStatusFlags_Opened) != 0) { - item->RefCount++; ItemClick(ref, 0, flags); - if ((item->StatusFlags & ImGuiItemStatusFlags_Opened) != 0) + item = ItemInfo(item.ID); + if ((item.StatusFlags & ImGuiItemStatusFlags_Opened) != 0) { ItemDoubleClick(ref, flags); // Attempt a double-click // FIXME-TESTS: let's not start doing those fuzzy things.. widget should give direction of how to close/open... e.g. do you we close a TabItem? - if ((item->StatusFlags & ImGuiItemStatusFlags_Opened) != 0) + item = ItemInfo(item.ID); + if ((item.StatusFlags & ImGuiItemStatusFlags_Opened) != 0) IM_ERRORF_NOHDR("Unable to Close item: %s", ImGuiTestRefDesc(ref, item).c_str()); } - item->RefCount--; Yield(); } } else if (action == ImGuiTestAction_Check) { IM_ASSERT(action_arg == NULL); // Unused - if ((item->StatusFlags & ImGuiItemStatusFlags_Checkable) && !(item->StatusFlags & ImGuiItemStatusFlags_Checked)) + if ((item.StatusFlags & ImGuiItemStatusFlags_Checkable) && !(item.StatusFlags & ImGuiItemStatusFlags_Checked)) { ItemClick(ref, 0, flags); } @@ -2746,7 +2819,7 @@ void ImGuiTestContext::ItemAction(ImGuiTestAction action, ImGuiTestRef ref, I else if (action == ImGuiTestAction_Uncheck) { IM_ASSERT(action_arg == NULL); // Unused - if ((item->StatusFlags & ImGuiItemStatusFlags_Checkable) && (item->StatusFlags & ImGuiItemStatusFlags_Checked)) + if ((item.StatusFlags & ImGuiItemStatusFlags_Checkable) && (item.StatusFlags & ImGuiItemStatusFlags_Checked)) { ItemClick(ref, 0, flags); } @@ -2773,12 +2846,12 @@ void ImGuiTestContext::ItemActionAll(ImGuiTestAction action, ImGuiTestRef ref if (!ref_parent.IsEmpty()) { // Open parent's parents - ImGuiTestItemInfo* parent_info = ItemInfoOpenFullPath(ref_parent); - if (parent_info->ID != 0) + ImGuiTestItemInfo parent_info = ItemInfoOpenFullPath(ref_parent); + if (parent_info.ID != 0) { // Open parent if (action == ImGuiTestAction_Open) - if ((parent_info->StatusFlags & ImGuiItemStatusFlags_Openable) && (parent_info->InFlags & ImGuiItemFlags_Disabled) == 0) + if ((parent_info.StatusFlags & ImGuiItemStatusFlags_Openable) && (parent_info.InFlags & ImGuiItemFlags_Disabled) == 0) ItemOpen(ref_parent, ImGuiTestOpFlags_NoError); } } @@ -2920,6 +2993,7 @@ void ImGuiTestContext::ItemInputValue(ImGuiTestRef ref, int value) ItemInput(ref); KeyCharsReplaceEnter(buf); } + void ImGuiTestContext::ItemInputValue(ImGuiTestRef ref, float value) { char buf[32]; @@ -2934,13 +3008,66 @@ void ImGuiTestContext::ItemInputValue(ImGuiTestRef ref, const char* value) KeyCharsReplaceEnter(value); } +// Supported values for ImGuiTestOpFlags: +// - ImGuiTestOpFlags_NoError +bool ImGuiTestContext::ItemSelectAndReadValue(ImGuiTestRef ref, ImGuiDataType data_type, void* out_data, ImGuiTestOpFlags flags) +{ + if (IsError()) + return false; + + const ImGuiDataTypeInfo* data_type_info = ImGui::DataTypeGetInfo(data_type); + const ImGuiTestOpFlags SUPPORTED_FLAGS = ImGuiTestOpFlags_NoError; + IM_ASSERT((flags & ~SUPPORTED_FLAGS) == 0); + + IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); + LogDebug("ItemSelectReadValue '%s' 0x%08X as %s", ref.Path ? ref.Path : "NULL", ref.ID, data_type_info->Name); + IM_CHECK_SILENT_RETV(out_data != NULL, false); + + Str256 backup_clipboard = ImGui::GetClipboardText(); + + ItemInput(ref, flags); +#if IMGUI_VERSION_NUM < 19063 + KeyPress(ImGuiKey_A | ImGuiMod_Shortcut); + KeyPress(ImGuiKey_C | ImGuiMod_Shortcut); // Copy to clipboard +#else + KeyPress(ImGuiKey_A | ImGuiMod_Ctrl); + KeyPress(ImGuiKey_C | ImGuiMod_Ctrl); // Copy to clipboard +#endif + KeyPress(ImGuiKey_Enter); + + const char* clipboard = ImGui::GetClipboardText(); + bool ret = ImGui::DataTypeApplyFromText(clipboard, data_type, out_data, data_type_info->ScanFmt); + if (ret == false) + { + if ((flags & ImGuiTestOpFlags_NoError) == 0) + { + LogError("Unable to parse buffer '%s' as %s", clipboard, data_type_info->Name); + IM_CHECK_RETV(ret, false); + } + } + ImGui::SetClipboardText(backup_clipboard.c_str()); + + return ret; +} + +void ImGuiTestContext::ItemSelectAndReadValue(ImGuiTestRef ref, int* out_v) +{ + ItemSelectAndReadValue(ref, ImGuiDataType_S32, (void*)out_v); +} + +void ImGuiTestContext::ItemSelectAndReadValue(ImGuiTestRef ref, float* out_v) +{ + ItemSelectAndReadValue(ref, ImGuiDataType_Float, (void*)out_v); +} + void ImGuiTestContext::ItemHold(ImGuiTestRef ref, float time) { if (IsError()) return; IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("ItemHold '%s' %08X", ref.Path ? ref.Path : "NULL", ref.ID); + ImGuiTestRefDesc desc(ref); + LogDebug("ItemHold %s", desc.c_str()); MouseMove(ref); @@ -2957,7 +3084,8 @@ void ImGuiTestContext::ItemHoldForFrames(ImGuiTestRef ref, int frames) return; IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("ItemHoldForFrames '%s' %08X", ref.Path ? ref.Path : "NULL", ref.ID); + ImGuiTestRefDesc desc(ref); + LogDebug("ItemHoldForFrames %s", desc.c_str()); MouseMove(ref); Yield(); @@ -2974,8 +3102,8 @@ void ImGuiTestContext::ItemDragOverAndHold(ImGuiTestRef ref_src, ImGuiTestRef return; IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - ImGuiTestItemInfo* item_src = ItemInfo(ref_src); - ImGuiTestItemInfo* item_dst = ItemInfo(ref_dst); + ImGuiTestItemInfo item_src = ItemInfo(ref_src); + ImGuiTestItemInfo item_dst = ItemInfo(ref_dst); ImGuiTestRefDesc desc_src(ref_src, item_src); ImGuiTestRefDesc desc_dst(ref_dst, item_dst); LogDebug("ItemDragOverAndHold %s to %s", desc_src.c_str(), desc_dst.c_str()); @@ -2985,9 +3113,11 @@ void ImGuiTestContext::ItemDragOverAndHold(ImGuiTestRef ref_src, ImGuiTestRef MouseDown(0); // Enforce lifting drag threshold even if both item are exactly at the same location. + // Don't lift the threshold in the same frame as calling MouseDown() as it can trigger two actions. + Yield(); MouseLiftDragThreshold(); - MouseMove(ref_dst, ImGuiTestOpFlags_NoCheckHoveredId); + SleepNoSkip(1.0f, 1.0f / 10.0f); MouseUp(0); } @@ -2998,8 +3128,8 @@ void ImGuiTestContext::ItemDragAndDrop(ImGuiTestRef ref_src, ImGuiTestRef ref return; IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - ImGuiTestItemInfo* item_src = ItemInfo(ref_src); - ImGuiTestItemInfo* item_dst = ItemInfo(ref_dst); + ImGuiTestItemInfo item_src = ItemInfo(ref_src); + ImGuiTestItemInfo item_dst = ItemInfo(ref_dst); ImGuiTestRefDesc desc_src(ref_src, item_src); ImGuiTestRefDesc desc_dst(ref_dst, item_dst); LogDebug("ItemDragAndDrop %s to %s", desc_src.c_str(), desc_dst.c_str()); @@ -3007,18 +3137,20 @@ void ImGuiTestContext::ItemDragAndDrop(ImGuiTestRef ref_src, ImGuiTestRef ref // Try to keep destination window above other windows. MouseMove() operation will avoid focusing destination window // as that may steal ActiveID and break operation. // FIXME-TESTS: This does not handle a case where source and destination windows overlap. - if (item_dst->Window != NULL) - WindowBringToFront(item_dst->Window->ID); + if (item_dst.Window != NULL) + WindowBringToFront(item_dst.Window->ID); // Use item_src/item_dst instead of ref_src/ref_dst so references with e.g. //$FOCUSED are latched once in the ItemInfo() call. - MouseMove(item_src->ID, ImGuiTestOpFlags_NoCheckHoveredId); + MouseMove(item_src.ID, ImGuiTestOpFlags_NoCheckHoveredId); SleepStandard(); MouseDown(button); // Enforce lifting drag threshold even if both item are exactly at the same location. + // Don't lift the threshold in the same frame as calling MouseDown() as it can trigger two actions. + Yield(); MouseLiftDragThreshold(); + MouseMove(item_dst.ID, ImGuiTestOpFlags_NoCheckHoveredId | ImGuiTestOpFlags_NoFocusWindow); - MouseMove(item_dst->ID, ImGuiTestOpFlags_NoCheckHoveredId | ImGuiTestOpFlags_NoFocusWindow); SleepStandard(); MouseUp(button); } @@ -3029,7 +3161,7 @@ void ImGuiTestContext::ItemDragWithDelta(ImGuiTestRef ref_src, ImVec2 pos_del return; IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - ImGuiTestItemInfo* item_src = ItemInfo(ref_src); + ImGuiTestItemInfo item_src = ItemInfo(ref_src); ImGuiTestRefDesc desc_src(ref_src, item_src); LogDebug("ItemDragWithDelta %s to (%f, %f)", desc_src.c_str(), pos_delta.x, pos_delta.y); @@ -3044,22 +3176,22 @@ void ImGuiTestContext::ItemDragWithDelta(ImGuiTestRef ref_src, ImVec2 pos_del bool ImGuiTestContext::ItemExists(ImGuiTestRef ref) { - ImGuiTestItemInfo* item = ItemInfo(ref, ImGuiTestOpFlags_NoError); - return item->ID != 0; + ImGuiTestItemInfo item = ItemInfo(ref, ImGuiTestOpFlags_NoError); + return item.ID != 0; } // May want to add support for ImGuiTestOpFlags_NoError if item does not exist? bool ImGuiTestContext::ItemIsChecked(ImGuiTestRef ref) { - ImGuiTestItemInfo* item = ItemInfo(ref); - return (item->StatusFlags & ImGuiItemStatusFlags_Checked) != 0; + ImGuiTestItemInfo item = ItemInfo(ref); + return (item.StatusFlags & ImGuiItemStatusFlags_Checked) != 0; } // May want to add support for ImGuiTestOpFlags_NoError if item does not exist? bool ImGuiTestContext::ItemIsOpened(ImGuiTestRef ref) { - ImGuiTestItemInfo* item = ItemInfo(ref); - return (item->StatusFlags & ImGuiItemStatusFlags_Opened) != 0; + ImGuiTestItemInfo item = ItemInfo(ref); + return (item.StatusFlags & ImGuiItemStatusFlags_Opened) != 0; } void ImGuiTestContext::ItemVerifyCheckedIfAlive(ImGuiTestRef ref, bool checked) @@ -3068,11 +3200,11 @@ void ImGuiTestContext::ItemVerifyCheckedIfAlive(ImGuiTestRef ref, bool checke // e.g. a checkable menu item in a popup which closes when checked. // Otherwise ItemInfo() data is preserved for an additional frame. Yield(); - ImGuiTestItemInfo* item = ItemInfo(ref, ImGuiTestOpFlags_NoError); - if (item->ID == 0) + ImGuiTestItemInfo item = ItemInfo(ref, ImGuiTestOpFlags_NoError); + if (item.ID == 0) return; - if (item->TimestampMain + 1 >= ImGuiTestEngine_GetFrameCount(Engine) && item->TimestampStatus == item->TimestampMain) - IM_CHECK_SILENT(((item->StatusFlags & ImGuiItemStatusFlags_Checked) != 0) == checked); + if (item.TimestampMain + 1 >= ImGuiTestEngine_GetFrameCount(Engine) && item.TimestampStatus == item.TimestampMain) + IM_CHECK_SILENT(((item.StatusFlags & ImGuiItemStatusFlags_Checked) != 0) == checked); } // FIXME-TESTS: Could this be handled by ItemClose()? @@ -3082,7 +3214,8 @@ void ImGuiTestContext::TabClose(ImGuiTestRef ref) return; IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("TabClose '%s' %08X", ref.Path ? ref.Path : "NULL", ref.ID); + ImGuiTestRefDesc desc(ref); + LogDebug("TabClose %s", desc.c_str()); // Move into first, then click close button as it appears MouseMove(ref); @@ -3135,7 +3268,8 @@ void ImGuiTestContext::MenuAction(ImGuiTestAction action, ImGuiTestRef ref) return; IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("MenuAction '%s' %08X", ref.Path ? ref.Path : "NULL", ref.ID); + ImGuiTestRefDesc desc(ref); + LogDebug("MenuAction %s", desc.c_str()); IM_ASSERT(ref.Path != NULL); @@ -3202,22 +3336,20 @@ void ImGuiTestContext::MenuAction(ImGuiTestAction action, ImGuiTestRef ref) depth++; #endif - ImGuiTestItemInfo* item = ItemInfo(buf.c_str()); - IM_CHECK_SILENT(item->ID != 0); - if ((item->StatusFlags & ImGuiItemStatusFlags_Opened) == 0) // Open menus can be ignored completely. + ImGuiTestItemInfo item = ItemInfo(buf.c_str()); + IM_CHECK_SILENT(item.ID != 0); + bool has_latest_status = (item.TimestampStatus == UiContext->FrameCount); + if ((item.StatusFlags & ImGuiItemStatusFlags_Opened) == 0 || !has_latest_status) // Open menus can be ignored completely. { // We cannot move diagonally to a menu item because depending on the angle and other items we cross on our path we could close our target menu. // First move horizontally into the menu, then vertically! if (depth > 0) { - IM_CHECK_SILENT(item != NULL); - item->RefCount++; - MouseSetViewport(item->Window); - if (depth > 1 && (Inputs->MousePosValue.x <= item->RectFull.Min.x || Inputs->MousePosValue.x >= item->RectFull.Max.x)) - MouseMoveToPos(ImVec2(item->RectFull.GetCenter().x, Inputs->MousePosValue.y)); - if (depth > 0 && (Inputs->MousePosValue.y <= item->RectFull.Min.y || Inputs->MousePosValue.y >= item->RectFull.Max.y)) - MouseMoveToPos(ImVec2(Inputs->MousePosValue.x, item->RectFull.GetCenter().y)); - item->RefCount--; + MouseSetViewport(item.Window); + if (Inputs->MousePosValue.x <= item.RectFull.Min.x || Inputs->MousePosValue.x >= item.RectFull.Max.x) + MouseMoveToPos(ImVec2(item.RectFull.GetCenter().x, Inputs->MousePosValue.y)); + if (Inputs->MousePosValue.y <= item.RectFull.Min.y || Inputs->MousePosValue.y >= item.RectFull.Max.y) + MouseMoveToPos(ImVec2(Inputs->MousePosValue.x, item.RectFull.GetCenter().y)); } if (is_target_item) @@ -3232,7 +3364,7 @@ void ImGuiTestContext::MenuAction(ImGuiTestAction action, ImGuiTestRef ref) ItemAction(Inputs->MouseButtonsValue ? ImGuiTestAction_Hover : ImGuiTestAction_Click, buf.c_str()); } } - current_window = GetWindowByRef(Str16f("##Menu_%02d", depth).c_str()); + current_window = GetWindowByRef(Str16f("//##Menu_%02d", depth).c_str()); IM_CHECK_SILENT(current_window != NULL); path = p + 1; @@ -3253,10 +3385,10 @@ void ImGuiTestContext::MenuActionAll(ImGuiTestAction action, ImGuiTestRef ref if (action == ImGuiTestAction_Check || action == ImGuiTestAction_Uncheck) { - ImGuiTestItemInfo* info2 = ItemInfo(item.ID); // refresh info - if ((info2->InFlags & ImGuiItemFlags_Disabled) != 0) // FIXME: Report disabled state in log? Make that optional? + ImGuiTestItemInfo info2 = ItemInfo(item.ID); // refresh info + if ((info2.InFlags & ImGuiItemFlags_Disabled) != 0) // FIXME: Report disabled state in log? Make that optional? continue; - if ((info2->StatusFlags & ImGuiItemStatusFlags_Checkable) == 0) + if ((info2.StatusFlags & ImGuiItemStatusFlags_Checkable) == 0) continue; } @@ -3273,20 +3405,27 @@ static bool IsWindowACombo(ImGuiWindow* window) return true; } +// Usage: ComboClick("ComboName/ItemName"); void ImGuiTestContext::ComboClick(ImGuiTestRef ref) { if (IsError()) return; IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("ComboClick '%s' %08X", ref.Path ? ref.Path : "NULL", ref.ID); + ImGuiTestRefDesc desc(ref); + LogDebug("ComboClick %s", desc.c_str()); - IM_ASSERT(ref.Path != NULL); + IM_ASSERT(ref.Path != NULL); // Should always pass an actual path, not an ID. const char* path = ref.Path; const char* path_end = path + strlen(path); - const char* p = ImStrchrRangeWithEscaping(path, path_end, '/'); + if (p == NULL) + { + LogError("Error: path should contains a / separator, e.g. ComboClick(\"mycombo/myitem\")"); + IM_CHECK(p != NULL); + } + Str128f combo_popup_buf = Str128f("%.*s", (int)(p-path), path); ItemClick(combo_popup_buf.c_str()); @@ -3327,7 +3466,8 @@ void ImGuiTestContext::TableOpenContextMenu(ImGuiTestRef ref, int column_n) return; IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("TableOpenContextMenu '%s' %08X", ref.Path ? ref.Path : "NULL", ref.ID); + ImGuiTestRefDesc desc(ref); + LogDebug("TableOpenContextMenu %s", desc.c_str()); ImGuiTable* table = ImGui::TableFindByID(GetID(ref)); IM_CHECK_SILENT(table != NULL); @@ -3355,7 +3495,7 @@ ImGuiSortDirection ImGuiTestContext::TableClickHeader(ImGuiTestRef ref, const ch if (key_mods != ImGuiMod_None) KeyUp(key_mods); - return (ImGuiSortDirection_)column->SortDirection; + return (ImGuiSortDirection)column->SortDirection; } void ImGuiTestContext::TableSetColumnEnabled(ImGuiTestRef ref, const char* label, bool enabled) @@ -3364,7 +3504,8 @@ void ImGuiTestContext::TableSetColumnEnabled(ImGuiTestRef ref, const char* label return; IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("TableSetColumnEnabled '%s' %08X = %d", ref.Path ? ref.Path : "NULL", ref.ID, enabled); + ImGuiTestRefDesc desc(ref); + LogDebug("TableSetColumnEnabled %s label '%s' enabled = %d", desc.c_str(), label, enabled); TableOpenContextMenu(ref); @@ -3384,7 +3525,8 @@ void ImGuiTestContext::TableResizeColumn(ImGuiTestRef ref, int column_n, float w return; IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("TableResizeColumn '%s' %08X column %d width %.2f", ref.Path ? ref.Path : "NULL", ref.ID, column_n, width); + ImGuiTestRefDesc desc(ref); + LogDebug("TableResizeColumn %s column %d width %.2f", desc.c_str(), column_n, width); ImGuiTable* table = ImGui::TableFindByID(GetID(ref)); IM_CHECK_SILENT(table != NULL); @@ -3458,7 +3600,7 @@ void ImGuiTestContext::WindowCollapse(ImGuiTestRef window_ref, bool collapsed void ImGuiTestContext::WindowFocus(ImGuiTestRef ref, ImGuiTestOpFlags flags) { IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - ImGuiTestRefDesc desc(ref, NULL); + ImGuiTestRefDesc desc(ref); LogDebug("WindowFocus('%s')", desc.c_str()); ImGuiWindow* window = GetWindowByRef(ref); @@ -3522,7 +3664,7 @@ void ImGuiTestContext::WindowMove(ImGuiTestRef ref, ImVec2 input_pos, ImVec2 IM_CHECK_SILENT(window != NULL); IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("WindowMove %s (%.1f,%.1f) ", window->Name, input_pos.x, input_pos.y); + LogDebug("WindowMove '%s' (%.1f,%.1f) ", window->Name, input_pos.x, input_pos.y); ImVec2 target_pos = ImFloor(input_pos - pivot * window->Size); if (ImLengthSqr(target_pos - window->Pos) < 0.001f) { @@ -3568,7 +3710,7 @@ void ImGuiTestContext::WindowResize(ImGuiTestRef ref, ImVec2 size) size = ImFloor(size); IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("WindowResize %s (%.1f,%.1f)", window->Name, size.x, size.y); + LogDebug("WindowResize '%s' (%.1f,%.1f)", window->Name, size.x, size.y); if (ImLengthSqr(size - window->Size) < 0.001f) return; @@ -3578,7 +3720,17 @@ void ImGuiTestContext::WindowResize(ImGuiTestRef ref, ImVec2 size) // Extra yield as newly created window that have AutoFitFramesX/AutoFitFramesY set are temporarily not submitting their resize widgets. Give them a bit of slack. Yield(); - ImGuiID id = ImGui::GetWindowResizeCornerID(window, 0); + // Aim at resize border or resize corner + ImGuiID border_x2 = ImGui::GetWindowResizeBorderID(window, ImGuiDir_Right); + ImGuiID border_y2 = ImGui::GetWindowResizeBorderID(window, ImGuiDir_Down); + ImGuiID resize_br = ImGui::GetWindowResizeCornerID(window, 0); + ImGuiID id; + if (ImAbs(size.x - window->Size.x) < 0.0001f && ItemExists(border_y2)) + id = border_y2; + else if (ImAbs(size.y - window->Size.y) < 0.0001f && ItemExists(border_x2)) + id = border_x2; + else + id = resize_br; MouseMove(id, ImGuiTestOpFlags_IsSecondAttempt); if (size.x <= 0.0f || size.y <= 0.0f) @@ -3719,9 +3871,12 @@ void ImGuiTestContext::DockInto(ImGuiTestRef src_id, ImGuiTestRef dst_id, ImG MouseDown(0); if (g.IO.ConfigDockingWithShift) KeyDown(ImGuiMod_Shift); + Yield(); MouseLiftDragThreshold(); if (window_src->DockIsActive) MouseMoveToPos(g.IO.MousePos + ImVec2(0, ImGui::GetFrameHeight() * 2.0f)); + else + Yield(); // A yield is necessary to start the moving, otherwise if by the time we call MouseSetViewport() no frame has elapsed and the viewports differs, dragging will fail. // (Button still held) // Locate target @@ -3853,7 +4008,11 @@ void ImGuiTestContext::UndockNode(ImGuiID dock_id) if (node->Windows.empty()) return; +#if IMGUI_VERSION_NUM >= 19071 + const float h = node->Windows[0]->TitleBarHeight; +#else const float h = node->Windows[0]->TitleBarHeight(); +#endif if (!UiContext->IO.ConfigDockingWithShift) KeyDown(ImGuiMod_Shift); // Disable docking ItemDragWithDelta(ImGui::DockNodeGetWindowMenuButtonId(node), ImVec2(h, h) * -2); @@ -3872,7 +4031,11 @@ void ImGuiTestContext::UndockWindow(const char* window_name) if (!window->DockIsActive) return; +#if IMGUI_VERSION_NUM >= 19071 + const float h = window->TitleBarHeight; +#else const float h = window->TitleBarHeight(); +#endif if (!UiContext->IO.ConfigDockingWithShift) KeyDown(ImGuiMod_Shift); ItemDragWithDelta(window->TabId, ImVec2(h, h) * -2); diff --git a/libs/imgui_test_engine/imgui_te_context.h b/libs/imgui_test_engine/imgui_te_context.h index a469f60..5d5261c 100644 --- a/libs/imgui_test_engine/imgui_te_context.h +++ b/libs/imgui_test_engine/imgui_te_context.h @@ -90,7 +90,8 @@ struct IMGUI_API ImGuiTestRefDesc char Buf[80]; const char* c_str() { return Buf; } - ImGuiTestRefDesc(const ImGuiTestRef& ref, const ImGuiTestItemInfo* item); + ImGuiTestRefDesc(const ImGuiTestRef& ref); + ImGuiTestRefDesc(const ImGuiTestRef& ref, const ImGuiTestItemInfo& item); }; //------------------------------------------------------------------------- @@ -114,6 +115,7 @@ enum ImGuiTestAction }; // Generic flags for many ImGuiTestContext functions +// Some flags are only supported by a handful of functions. Check function headers for list of supported flags. enum ImGuiTestOpFlags_ { ImGuiTestOpFlags_None = 0, @@ -122,11 +124,12 @@ enum ImGuiTestOpFlags_ ImGuiTestOpFlags_NoFocusWindow = 1 << 3, // Don't focus window when aiming at an item ImGuiTestOpFlags_NoAutoUncollapse = 1 << 4, // Disable automatically uncollapsing windows (useful when specifically testing Collapsing behaviors) ImGuiTestOpFlags_NoAutoOpenFullPath = 1 << 5, // Disable automatically opening intermediaries (e.g. ItemClick("Hello/OK") will automatically first open "Hello" if "OK" isn't found. Only works if ref is a string path. - ImGuiTestOpFlags_IsSecondAttempt = 1 << 6, // Used by recursing functions to indicate a second attempt - ImGuiTestOpFlags_MoveToEdgeL = 1 << 7, // Simple Dumb aiming helpers to test widget that care about clicking position. May need to replace will better functionalities. - ImGuiTestOpFlags_MoveToEdgeR = 1 << 8, - ImGuiTestOpFlags_MoveToEdgeU = 1 << 9, - ImGuiTestOpFlags_MoveToEdgeD = 1 << 10, + ImGuiTestOpFlags_NoYield = 1 << 6, // Don't yield (only supported by a few functions), in case you need to manage rigorous per-frame timing. + ImGuiTestOpFlags_IsSecondAttempt = 1 << 7, // Used by recursing functions to indicate a second attempt + ImGuiTestOpFlags_MoveToEdgeL = 1 << 8, // Simple Dumb aiming helpers to test widget that care about clicking position. May need to replace will better functionalities. + ImGuiTestOpFlags_MoveToEdgeR = 1 << 9, + ImGuiTestOpFlags_MoveToEdgeU = 1 << 10, + ImGuiTestOpFlags_MoveToEdgeD = 1 << 11, }; // Advanced filtering for ItemActionAll() @@ -176,6 +179,7 @@ struct IMGUI_API ImGuiTestGenericVars int Count; ImGuiID DockId; ImGuiID OwnerId; + ImVec2 WindowSize; ImGuiWindowFlags WindowFlags; ImGuiTableFlags TableFlags; ImGuiPopupFlags PopupFlags; @@ -185,8 +189,8 @@ struct IMGUI_API ImGuiTestGenericVars bool UseViewports; float Width; ImVec2 Pos; - ImVec2 Size; ImVec2 Pivot; + ImVec2 ItemSize; ImVec4 Color1, Color2; // Generic unnamed storage @@ -283,7 +287,6 @@ struct IMGUI_API ImGuiTestContext // Yield, Timing void Yield(int count = 1); - void YieldUntil(int frame_count); void Sleep(float time_in_second); // Sleep for a given simulation time, unless in Fast mode void SleepShort(); // Standard short delay of io.ActionDelayShort (~0.15f), unless in Fast mode. void SleepStandard(); // Standard regular delay of io.ActionDelayStandard (~0.40f), unless in Fast mode. @@ -297,12 +300,15 @@ struct IMGUI_API ImGuiTestContext // - SetRef("//$FOCUSED"), ItemClick("Button") --> click "Button" in focused window. // See https://github.com/ocornut/imgui_test_engine/wiki/Named-References about using ImGuiTestRef in all ImGuiTestContext functions. // Note: SetRef() may take multiple frames to complete if specified ref is an item id. + // Note: SetRef() ignores current reference, so they are always absolute path. void SetRef(ImGuiTestRef ref); void SetRef(ImGuiWindow* window); // Shortcut to SetRef(window->Name) which works for ChildWindow (see code) ImGuiTestRef GetRef(); // Windows - ImGuiTestItemInfo* WindowInfo(ImGuiTestRef window_ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); + // - Use WindowInfo() to access path to child windows, since the paths are internally mangled. + // - SetRef(WindowInfo("Parent/Child")->Window) --> set ref to child window. + ImGuiTestItemInfo WindowInfo(ImGuiTestRef window_ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); void WindowClose(ImGuiTestRef window_ref); void WindowCollapse(ImGuiTestRef window_ref, bool collapsed); void WindowFocus(ImGuiTestRef window_ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); @@ -340,7 +346,7 @@ struct IMGUI_API ImGuiTestContext // Mouse inputs void MouseMove(ImGuiTestRef ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); void MouseMoveToPos(ImVec2 pos); - void MouseTeleportToPos(ImVec2 pos); + void MouseTeleportToPos(ImVec2 pos, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); void MouseClick(ImGuiMouseButton button = 0); void MouseClickMulti(ImGuiMouseButton button, int count); void MouseDoubleClick(ImGuiMouseButton button = 0); @@ -400,10 +406,10 @@ struct IMGUI_API ImGuiTestContext // Low-level queries // - ItemInfo queries never returns a NULL pointer, instead they return an empty instance (info->IsEmpty(), info->ID == 0) and set contexted as errored. // - You can use ImGuiTestOpFlags_NoError to do a query without marking context as errored. This is what ItemExists() does. - ImGuiTestItemInfo* ItemInfo(ImGuiTestRef ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); - ImGuiTestItemInfo* ItemInfoOpenFullPath(ImGuiTestRef ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); + ImGuiTestItemInfo ItemInfo(ImGuiTestRef ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); + ImGuiTestItemInfo ItemInfoOpenFullPath(ImGuiTestRef ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); ImGuiID ItemInfoHandleWildcardSearch(const char* wildcard_prefix_start, const char* wildcard_prefix_end, const char* wildcard_suffix_start); - ImGuiTestItemInfo* ItemInfoNull(); + ImGuiTestItemInfo ItemInfoNull() { return ImGuiTestItemInfo(); } void GatherItems(ImGuiTestItemList* out_list, ImGuiTestRef parent, int depth = -1); // Item/Widgets manipulation @@ -428,12 +434,12 @@ struct IMGUI_API ImGuiTestContext void ItemInputValue(ImGuiTestRef ref, float f); void ItemInputValue(ImGuiTestRef ref, const char* str); - // Item/Widgets: Drag and Mouse operations - void ItemHold(ImGuiTestRef ref, float time); - void ItemHoldForFrames(ImGuiTestRef ref, int frames); - void ItemDragOverAndHold(ImGuiTestRef ref_src, ImGuiTestRef ref_dst); - void ItemDragAndDrop(ImGuiTestRef ref_src, ImGuiTestRef ref_dst, ImGuiMouseButton button = 0); - void ItemDragWithDelta(ImGuiTestRef ref_src, ImVec2 pos_delta); + // Item/Widgets: Helpers to easily read a value by selecting Slider/Drag/Input text, copying into clipboard and parsing it. + // - This requires the item to be selectable (we will later provide helpers that works in more general manner) + // - Original clipboard value is restored afterward. + bool ItemSelectAndReadValue(ImGuiTestRef ref, ImGuiDataType data_type, void* out_data, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); + void ItemSelectAndReadValue(ImGuiTestRef ref, int* out_v); + void ItemSelectAndReadValue(ImGuiTestRef ref, float* out_v); // Item/Widgets: Status query bool ItemExists(ImGuiTestRef ref); @@ -441,6 +447,13 @@ struct IMGUI_API ImGuiTestContext bool ItemIsOpened(ImGuiTestRef ref); void ItemVerifyCheckedIfAlive(ImGuiTestRef ref, bool checked); + // Item/Widgets: Drag and Mouse operations + void ItemHold(ImGuiTestRef ref, float time); + void ItemHoldForFrames(ImGuiTestRef ref, int frames); + void ItemDragOverAndHold(ImGuiTestRef ref_src, ImGuiTestRef ref_dst); + void ItemDragAndDrop(ImGuiTestRef ref_src, ImGuiTestRef ref_dst, ImGuiMouseButton button = 0); + void ItemDragWithDelta(ImGuiTestRef ref_src, ImVec2 pos_delta); + // Helpers for Tab Bars widgets void TabClose(ImGuiTestRef ref); bool TabBarCompareOrder(ImGuiTabBar* tab_bar, const char** tab_order); @@ -493,15 +506,17 @@ struct IMGUI_API ImGuiTestContext // Obsolete functions #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // Obsoleted 2024/05/21 + void YieldUntil(int frame_count) { while (FrameCount < frame_count) { Yield(); } } // Obsoleted 2022/10/11 ImGuiID GetIDByInt(int n); // Prefer using "$$123" ImGuiID GetIDByInt(int n, ImGuiTestRef seed_ref); ImGuiID GetIDByPtr(void* p); // Prefer using "$$(ptr)0xFFFFFFFF" ImGuiID GetIDByPtr(void* p, ImGuiTestRef seed_ref); // Obsoleted 2022/09/26 - void KeyModDown(ImGuiModFlags mods) { KeyDown(mods); } - void KeyModUp(ImGuiModFlags mods) { KeyUp(mods); } - void KeyModPress(ImGuiModFlags mods) { KeyPress(mods); } + //void KeyModDown(ImGuiModFlags mods) { KeyDown(mods); } + //void KeyModUp(ImGuiModFlags mods) { KeyUp(mods); } + //void KeyModPress(ImGuiModFlags mods) { KeyPress(mods); } #endif // [Internal] @@ -604,6 +619,7 @@ template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, Im // Floating point compares #define IM_CHECK_FLOAT_EQ_EPS(_LHS, _RHS) IM_CHECK_LE(ImFabs(_LHS - (_RHS)), FLT_EPSILON) // Float Equal +#define IM_CHECK_FLOAT_NE_EPS(_LHS, _RHS) IM_CHECK_GT(ImFabs(_LHS - (_RHS)), FLT_EPSILON) // Float Not Equal #define IM_CHECK_FLOAT_NEAR(_LHS, _RHS, _EPS) IM_CHECK_LE(ImFabs(_LHS - (_RHS)), _EPS) #define IM_CHECK_FLOAT_NEAR_NO_RET(_LHS, _RHS, _E) IM_CHECK_LE_NO_RET(ImFabs(_LHS - (_RHS)), _E) diff --git a/libs/imgui_test_engine/imgui_te_engine.cpp b/libs/imgui_test_engine/imgui_te_engine.cpp index f5cf37f..ecac901 100644 --- a/libs/imgui_test_engine/imgui_te_engine.cpp +++ b/libs/imgui_test_engine/imgui_te_engine.cpp @@ -592,17 +592,34 @@ void ImGuiTestEngine_ApplyInputToImGuiContext(ImGuiTestEngine* engine) case ImGuiTestInputType_Key: { ImGuiKeyChord key_chord = input.KeyChord; -#if IMGUI_VERSION_NUM >= 19016 +#if IMGUI_VERSION_NUM >= 19016 && IMGUI_VERSION_NUM < 19063 key_chord = ImGui::FixupKeyChord(&g, key_chord); // This will add ImGuiMod_Alt when pressing ImGuiKey_LeftAlt or ImGuiKey_LeftRight +#endif +#if IMGUI_VERSION_NUM >= 19063 + key_chord = ImGui::FixupKeyChord(key_chord); // This will add ImGuiMod_Alt when pressing ImGuiKey_LeftAlt or ImGuiKey_LeftRight #endif ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); ImGuiKeyChord mods = (key_chord & ImGuiMod_Mask_); if (mods != 0x00) { // OSX conversion -#if IMGUI_VERSION_NUM >= 18912 +#if IMGUI_VERSION_NUM >= 18912 && IMGUI_VERSION_NUM < 19063 if (mods & ImGuiMod_Shortcut) mods = (mods & ~ImGuiMod_Shortcut) | (g.IO.ConfigMacOSXBehaviors ? ImGuiMod_Super : ImGuiMod_Ctrl); +#endif +#if IMGUI_VERSION_NUM >= 19063 + // MacOS: swap Cmd(Super) and Ctrl WILL BE SWAPPED BACK BY io.AddKeyEvent() + if (g.IO.ConfigMacOSXBehaviors) + { + if ((mods & (ImGuiMod_Ctrl | ImGuiMod_Super)) == ImGuiMod_Super) + mods = (mods & ~ImGuiMod_Super) | ImGuiMod_Ctrl; + else if ((mods & (ImGuiMod_Ctrl | ImGuiMod_Super)) == ImGuiMod_Ctrl) + mods = (mods & ~ImGuiMod_Ctrl) | ImGuiMod_Super; + if (key == ImGuiKey_LeftSuper) { key = ImGuiKey_LeftCtrl; } + else if (key == ImGuiKey_LeftSuper) { key = ImGuiKey_RightCtrl; } + else if (key == ImGuiKey_LeftCtrl) { key = ImGuiKey_LeftSuper; } + else if (key == ImGuiKey_LeftCtrl) { key = ImGuiKey_RightSuper; } + } #endif // Submitting a ImGuiMod_XXX without associated key needs to add at least one of the key. if (mods & ImGuiMod_Ctrl) @@ -805,7 +822,7 @@ static void ImGuiTestEngine_PostNewFrame(ImGuiTestEngine* engine, ImGuiContext* for (int task_n = 0; task_n < engine->InfoTasks.Size; task_n++) { ImGuiTestInfoTask* task = engine->InfoTasks[task_n]; - if (task->FrameCount < engine->FrameCount - LOCATION_TASK_ELAPSE_FRAMES && task->Result.RefCount == 0) + if (task->FrameCount < engine->FrameCount - LOCATION_TASK_ELAPSE_FRAMES) { IM_DELETE(task); engine->InfoTasks.erase(engine->InfoTasks.Data + task_n); @@ -950,7 +967,7 @@ int ImGuiTestEngine_GetFrameCount(ImGuiTestEngine* engine) const char* ImGuiTestEngine_GetStatusName(ImGuiTestStatus v) { - static const char* names[ImGuiTestStatus_COUNT] = { "Success", "Queued", "Running", "Error", "Suspended" }; + static const char* names[ImGuiTestStatus_COUNT] = { "Unknown", "Success", "Queued", "Running", "Error", "Suspended" }; IM_STATIC_ASSERT(IM_ARRAYSIZE(names) == ImGuiTestStatus_COUNT); if (v >= 0 && v < IM_ARRAYSIZE(names)) return names[v]; @@ -1172,6 +1189,32 @@ ImGuiTest* ImGuiTestEngine_RegisterTest(ImGuiTestEngine* engine, const char* cat return t; } +void ImGuiTestEngine_UnregisterTest(ImGuiTestEngine* engine, ImGuiTest* test) +{ + // Cannot unregister a running test. Please contact us if you need this. + if (engine->TestContext != NULL) + IM_ASSERT(engine->TestContext->Test != test); + + // Remove from lists + bool found = engine->TestsAll.find_erase(test); + IM_ASSERT(found); // Calling ImGuiTestEngine_UnregisterTest() on an unknown test. + for (int n = 0; n < engine->TestsQueue.Size; n++) + { + ImGuiTestRunTask& task = engine->TestsQueue[n]; + if (task.Test == test) + { + engine->TestsQueue.erase(&task); + n--; + } + } + if (engine->UiSelectAndScrollToTest == test) + engine->UiSelectAndScrollToTest = NULL; + if (engine->UiSelectedTest == test) + engine->UiSelectedTest = NULL; + + IM_DELETE(test); +} + ImGuiPerfTool* ImGuiTestEngine_GetPerfTool(ImGuiTestEngine* engine) { return engine->PerfTool; @@ -1297,8 +1340,9 @@ void ImGuiTestEngine_QueueTests(ImGuiTestEngine* engine, ImGuiTestGroup group, c if (group != ImGuiTestGroup_Unknown && test->Group != group) continue; - if (!ImGuiTestEngine_PassFilter(test, filter_str)) - continue; + if (filter_str != NULL) + if (!ImGuiTestEngine_PassFilter(test, filter_str)) + continue; ImGuiTestEngine_QueueTest(engine, test, run_flags); } @@ -1386,8 +1430,10 @@ struct ImGuiTestContextUiContextBackup IO = g.IO; Style = g.Style; DebugLogFlags = g.DebugLogFlags; +#if IMGUI_VERSION_NUM >= 18837 ConfigNavWindowingKeyNext = g.ConfigNavWindowingKeyNext; ConfigNavWindowingKeyPrev = g.ConfigNavWindowingKeyPrev; +#endif memset(IO.MouseDown, 0, sizeof(IO.MouseDown)); for (int n = 0; n < IM_ARRAYSIZE(IO.KeysData); n++) IO.KeysData[n].Down = false; @@ -1400,8 +1446,10 @@ struct ImGuiTestContextUiContextBackup g.IO = IO; g.Style = Style; g.DebugLogFlags = DebugLogFlags; +#if IMGUI_VERSION_NUM >= 18837 g.ConfigNavWindowingKeyNext = ConfigNavWindowingKeyNext; g.ConfigNavWindowingKeyPrev = ConfigNavWindowingKeyPrev; +#endif } void RestoreClipboardFuncs(ImGuiContext& g) { @@ -1517,19 +1565,12 @@ void ImGuiTestEngine_RunTest(ImGuiTestEngine* engine, ImGuiTestContext* parent_c else { ctx->LogWarning("Child Test: '%s' '%s'..", test->Category, test->Name); - ctx->LogWarning("(ShareVars=%d ShareTestContext=%d)", (run_flags & ImGuiTestRunFlags_ShareVars) ? 1 : 0, (run_flags & ImGuiTestRunFlags_ShareTestContext) ? 1 : 0); + ctx->LogDebug("(ShareVars=%d ShareTestContext=%d)", (run_flags & ImGuiTestRunFlags_ShareVars) ? 1 : 0, (run_flags & ImGuiTestRunFlags_ShareTestContext) ? 1 : 0); } // Clear ImGui inputs to avoid key/mouse leaks from one test to another ImGuiTestEngine_ClearInput(engine); - ctx->FrameCount = parent_ctx ? parent_ctx->FrameCount : 0; - ctx->ErrorCounter = 0; - ctx->SetRef(""); - ctx->SetInputMode(ImGuiInputSource_Mouse); - ctx->UiContext->NavInputSource = ImGuiInputSource_Keyboard; - ctx->Clipboard.clear(); - // Backup entire IO and style. Allows tests modifying them and not caring about restoring state. ImGuiTestContextUiContextBackup backup_ui_context; backup_ui_context.Backup(*ctx->UiContext); @@ -1570,6 +1611,12 @@ void ImGuiTestEngine_RunTest(ImGuiTestEngine* engine, ImGuiTestContext* parent_c ImGuiTestActiveFunc backup_active_func = ctx->ActiveFunc; ctx->ActiveFunc = ImGuiTestActiveFunc_TestFunc; ctx->FirstGuiFrame = (test->GuiFunc != NULL) ? true : false; + ctx->FrameCount = parent_ctx ? parent_ctx->FrameCount : 0; + ctx->ErrorCounter = 0; + ctx->SetRef(""); + ctx->SetInputMode(ImGuiInputSource_Mouse); + ctx->UiContext->NavInputSource = ImGuiInputSource_Keyboard; + ctx->Clipboard.clear(); // Warm up GUI // - We need one mandatory frame running GuiFunc before running TestFunc @@ -2055,14 +2102,14 @@ void ImGuiTestEngineHook_Log(ImGuiContext* ui_ctx, const char* fmt, ...) // Your custom assert code may optionally want to call this. void ImGuiTestEngine_AssertLog(const char* expr, const char* file, const char* function, int line) { - ImGuiTestEngine* engine = GImGuiTestEngine; - if (ImGuiTestContext* ctx = engine->TestContext) - { - ctx->LogError("Assert: '%s'", expr); - ctx->LogWarning("In %s:%d, function %s()", file, line, function); - if (ImGuiTest* test = ctx->Test) - ctx->LogWarning("While running test: %s %s", test->Category, test->Name); - } + if (ImGuiTestEngine* engine = GImGuiTestEngine) + if (ImGuiTestContext* ctx = engine->TestContext) + { + ctx->LogError("Assert: '%s'", expr); + ctx->LogWarning("In %s:%d, function %s()", file, line, function); + if (ImGuiTest* test = ctx->Test) + ctx->LogWarning("While running test: %s %s", test->Category, test->Name); + } } // Used by IM_CHECK_OP() macros diff --git a/libs/imgui_test_engine/imgui_te_engine.h b/libs/imgui_test_engine/imgui_te_engine.h index c5a585d..434b685 100644 --- a/libs/imgui_test_engine/imgui_te_engine.h +++ b/libs/imgui_test_engine/imgui_te_engine.h @@ -7,7 +7,28 @@ #include "imgui.h" #include "imgui_internal.h" // ImPool<>, ImRect, ImGuiItemStatusFlags, ImFormatString -#include "imgui_te_utils.h" // ImFuncPtr + +#if defined(__clang__) +#pragma clang diagnostic push +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#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 +#ifdef Status // X11 headers +#undef Status +#endif + +//----------------------------------------------------------------------------- +// Function Pointers +//----------------------------------------------------------------------------- + +#if IMGUI_TEST_ENGINE_ENABLE_STD_FUNCTION +#include +#define ImFuncPtr(FUNC_TYPE) std::function +#else +#define ImFuncPtr(FUNC_TYPE) FUNC_TYPE* +#endif + #include "imgui_capture_tool.h" // ImGuiScreenCaptureFunc //------------------------------------------------------------------------- @@ -70,12 +91,12 @@ enum ImGuiTestVerboseLevel : int // Test status (stored in ImGuiTest) enum ImGuiTestStatus : int { - ImGuiTestStatus_Unknown = -1, - ImGuiTestStatus_Success = 0, - ImGuiTestStatus_Queued = 1, - ImGuiTestStatus_Running = 2, - ImGuiTestStatus_Error = 3, - ImGuiTestStatus_Suspended = 4, + ImGuiTestStatus_Unknown = 0, + ImGuiTestStatus_Success = 1, + ImGuiTestStatus_Queued = 2, + ImGuiTestStatus_Running = 3, + ImGuiTestStatus_Error = 4, + ImGuiTestStatus_Suspended = 5, ImGuiTestStatus_COUNT }; @@ -168,6 +189,7 @@ IMGUI_API ImGuiTestEngineIO& ImGuiTestEngine_GetIO(ImGuiTestEngine* engine); // Macros: Register Test #define IM_REGISTER_TEST(_ENGINE, _CATEGORY, _NAME) ImGuiTestEngine_RegisterTest(_ENGINE, _CATEGORY, _NAME, __FILE__, __LINE__) IMGUI_API ImGuiTest* ImGuiTestEngine_RegisterTest(ImGuiTestEngine* engine, const char* category, const char* name, const char* src_file = NULL, int src_line = 0); // Prefer calling IM_REGISTER_TEST() +IMGUI_API void ImGuiTestEngine_UnregisterTest(ImGuiTestEngine* engine, ImGuiTest* test); // Functions: Main IMGUI_API void ImGuiTestEngine_QueueTest(ImGuiTestEngine* engine, ImGuiTest* test, ImGuiTestRunFlags run_flags = 0); @@ -275,22 +297,20 @@ struct IMGUI_API ImGuiTestEngineIO // Information about a given item or window, result of an ItemInfo() or WindowInfo() query struct ImGuiTestItemInfo { - int RefCount : 8; // User can increment this if they want to hold on the result pointer across frames, otherwise the task will be GC-ed. + ImGuiID ID = 0; // Item ID + char DebugLabel[32] = {}; // Shortened/truncated label for debugging and convenience purpose + ImGuiWindow* Window = NULL; // Item Window unsigned int NavLayer : 1; // Nav layer of the item (ImGuiNavLayer) int Depth : 16; // Depth from requested parent id. 0 == ID is immediate child of requested parent id. - int TimestampMain = -1; // Timestamp of main result (all fields) - int TimestampStatus = -1; // Timestamp of StatusFlags - ImGuiID ID = 0; // Item ID + int TimestampMain; // Timestamp of main result (all fields) + int TimestampStatus; // Timestamp of StatusFlags ImGuiID ParentID = 0; // Item Parent ID (value at top of the ID stack) - ImGuiWindow* Window = NULL; // Item Window ImRect RectFull = ImRect(); // Item Rectangle ImRect RectClipped = ImRect(); // Item Rectangle (clipped with window->ClipRect at time of item submission) ImGuiItemFlags InFlags = 0; // Item flags ImGuiItemStatusFlags StatusFlags = 0; // Item Status flags (fully updated for some items only, compare TimestampStatus to FrameCount) - char DebugLabel[32] = {}; // Shortened label for debugging purpose - ImGuiTestItemInfo() { RefCount = 0; NavLayer = 0; Depth = 0; } - bool IsEmpty() const { return ID == 0; } + ImGuiTestItemInfo() { memset(this, 0, sizeof(*this)); } }; // Result of an GatherItems() query @@ -427,3 +447,9 @@ struct IMGUI_API ImGuiTestRunTask }; //------------------------------------------------------------------------- + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif diff --git a/libs/imgui_test_engine/imgui_te_internal.h b/libs/imgui_test_engine/imgui_te_internal.h index 4b20438..00c1c10 100644 --- a/libs/imgui_test_engine/imgui_te_internal.h +++ b/libs/imgui_test_engine/imgui_te_internal.h @@ -138,7 +138,7 @@ struct ImGuiTestEngine float OverrideDeltaTime = -1.0f; // Inject custom delta time into imgui context to simulate clock passing faster than wall clock time. ImVector TestsAll; ImVector TestsQueue; - ImGuiTestContext* TestContext = NULL; + ImGuiTestContext* TestContext = NULL; // Running test context ImVectorInfoTasks; ImGuiTestGatherTask GatherTask; ImGuiTestFindByLabelTask FindByLabelTask; diff --git a/libs/imgui_test_engine/imgui_te_perftool.cpp b/libs/imgui_test_engine/imgui_te_perftool.cpp index 65b853a..1bedbb1 100644 --- a/libs/imgui_test_engine/imgui_te_perftool.cpp +++ b/libs/imgui_test_engine/imgui_te_perftool.cpp @@ -435,8 +435,13 @@ static bool RenderMultiSelectFilter(ImGuiPerfTool* perf, const char* filter_hint ImGui::SetTooltip("Hold CTRL to invert other items.\nHold SHIFT to close popup instantly."); // Keep popup open for multiple actions if SHIFT is pressed. +#if IMGUI_VERSION_NUM >= 19094 + if (!io.KeyShift) + ImGui::PushItemFlag(ImGuiItemFlags_AutoClosePopups, false); +#else if (!io.KeyShift) ImGui::PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true); +#endif if (ImGui::MenuItem("Show All")) { @@ -1050,8 +1055,6 @@ void ImGuiPerfTool::ShowPerfToolWindow(ImGuiTestEngine* engine, bool* p_open) if (ImGui::IsWindowAppearing() && Empty()) LoadCSV(); - ImGuiStyle& style = ImGui::GetStyle(); - // ----------------------------------------------------------------------------------------------------------------- // Render utility buttons // ----------------------------------------------------------------------------------------------------------------- @@ -1157,10 +1160,7 @@ void ImGuiPerfTool::ShowPerfToolWindow(ImGuiTestEngine* engine, bool* p_open) ImGui::SetTooltip("Generate a report and open it in the browser."); // Align help button to the right. - float help_pos = ImGui::GetWindowContentRegionMax().x - style.FramePadding.x * 2 - ImGui::CalcTextSize("(?)").x; - if (help_pos > ImGui::GetCursorPosX()) - ImGui::SetCursorPosX(help_pos); - + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImMax(0.0f, ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize("(?)").x)); ImGui::TextDisabled("(?)"); if (ImGui::IsItemHovered()) { @@ -1263,6 +1263,7 @@ void ImGuiPerfTool::ShowPerfToolWindow(ImGuiTestEngine* engine, bool* p_open) { #if IMGUI_TEST_ENGINE_ENABLE_IMPLOT // Splitter between two following child windows is rendered first. + ImGuiStyle& style = ImGui::GetStyle(); float plot_height = 0.0f; float& table_height = _InfoTableHeight; ImGui::Splitter("splitter", &plot_height, &table_height, ImGuiAxis_Y, +1); @@ -1843,7 +1844,7 @@ void RegisterTests_TestEnginePerfTool(ImGuiTestEngine* e) ctx->WindowMove("", ImVec2(50, 50)); ctx->WindowResize("", ImVec2(1400, 900)); #if IMGUI_TEST_ENGINE_ENABLE_IMPLOT - ImGuiWindow* plot_child = ctx->WindowInfo("plot")->Window; // "plot/PerfTool" prior to implot 2023/08/21 + ImGuiWindow* plot_child = ctx->WindowInfo("plot").Window; // "plot/PerfTool" prior to implot 2023/08/21 IM_CHECK(plot_child != NULL); // Move legend to right side. @@ -1910,7 +1911,7 @@ void RegisterTests_TestEnginePerfTool(ImGuiTestEngine* e) #if IMGUI_TEST_ENGINE_ENABLE_IMPLOT ctx->ItemDoubleClick("splitter"); // Hide info table - ImGuiWindow* plot_child = ctx->WindowInfo("plot")->Window; // "plot/PerfTool" prior to implot 2023/08/21 + ImGuiWindow* plot_child = ctx->WindowInfo("plot").Window; // "plot/PerfTool" prior to implot 2023/08/21 IM_CHECK(plot_child != NULL); // Move legend to right side. @@ -1928,7 +1929,7 @@ void RegisterTests_TestEnginePerfTool(ImGuiTestEngine* e) // Take a screenshot. ImGuiCaptureArgs* args = ctx->CaptureArgs; args->InCaptureRect = plot_child->Rect(); - ctx->CaptureAddWindow(window->Name); + ctx->CaptureAddWindow(window->ID); ctx->CaptureScreenshot(ImGuiCaptureFlags_HideMouseCursor); ctx->ItemDragWithDelta("splitter", ImVec2(0, -180)); // Show info table perf_report_image = args->InOutputFile; diff --git a/libs/imgui_test_engine/imgui_te_ui.cpp b/libs/imgui_test_engine/imgui_te_ui.cpp index 2d42bde..4738887 100644 --- a/libs/imgui_test_engine/imgui_te_ui.cpp +++ b/libs/imgui_test_engine/imgui_te_ui.cpp @@ -177,7 +177,11 @@ static void DrawTestLog(ImGuiTestEngine* e, ImGuiTest* test) ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32_WHITE); break; } +#if IMGUI_VERSION_NUM >= 19072 + ImGui::DebugTextUnformattedWithLocateItem(line_start, line_end); +#else ImGui::TextUnformatted(line_start, line_end); +#endif ImGui::PopStyleColor(); ImGui::PushID(line_no); @@ -277,13 +281,16 @@ static void ShowTestGroup(ImGuiTestEngine* e, ImGuiTestGroup group, Str* filter) ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetFrameHeight() + style.ItemInnerSpacing.x); //ImGui::Text("TESTS (%d)", engine->TestsAll.Size); -#if IMGUI_VERSION_NUM >= 18837 +#if IMGUI_VERSION_NUM >= 19066 + ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_R, ImGuiInputFlags_Tooltip | ImGuiInputFlags_RouteFromRootWindow); + bool run = ImGui::Button("Run"); +#elif IMGUI_VERSION_NUM >= 18837 bool run = ImGui::Button("Run") || ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_R); -#else - bool = ImGui::Button("Run"); -#endif #if IMGUI_VERSION_NUM > 18963 ImGui::SetItemTooltip("Ctrl+R"); +#endif +#else + bool run = ImGui::Button("Run"); #endif if (run) { @@ -320,7 +327,7 @@ static void ShowTestGroup(ImGuiTestEngine* e, ImGuiTestGroup group, Str* filter) ImGui::SameLine(); const char* perflog_label = "Perf Tool"; - float filter_width = ImGui::GetWindowContentRegionMax().x - ImGui::GetCursorPos().x; + float filter_width = ImGui::GetContentRegionAvail().x; float perf_stress_factor_width = (30 * dpi_scale); if (group == ImGuiTestGroup_Perfs) { @@ -329,6 +336,9 @@ static void ShowTestGroup(ImGuiTestEngine* e, ImGuiTestGroup group, Str* filter) } filter_width -= ImGui::CalcTextSize("(?)").x + style.ItemSpacing.x; ImGui::SetNextItemWidth(ImMax(20.0f, filter_width)); +#if IMGUI_VERSION_NUM >= 19066 + ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip | ImGuiInputFlags_RouteFromRootWindow); +#endif ImGui::InputText("##filter", filter); ImGui::SameLine(); ImGui::TextDisabled("(?)"); @@ -354,6 +364,7 @@ static void ShowTestGroup(ImGuiTestEngine* e, ImGuiTestGroup group, Str* filter) int tests_completed = 0; int tests_succeeded = 0; int tests_failed = 0; + ImVector tests_to_remove; if (ImGui::BeginTable("Tests", 3, ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_SizingFixedFit)) { ImGui::TableSetupScrollFreeze(0, 1); @@ -515,6 +526,11 @@ static void ShowTestGroup(ImGuiTestEngine* e, ImGuiTestGroup group, Str* filter) if (ImGui::MenuItem("Clear log", NULL, false, !test_log->IsEmpty())) test_log->Clear(); + // [DEBUG] Simple way to exercise ImGuiTestEngine_UnregisterTest() + //ImGui::Separator(); + //if (ImGui::MenuItem("Remove test")) + // tests_to_remove.push_back(test); + ImGui::EndPopup(); } @@ -571,6 +587,10 @@ static void ShowTestGroup(ImGuiTestEngine* e, ImGuiTestGroup group, Str* filter) ImGui::EndTable(); } + // Process removal + for (ImGuiTest* test : tests_to_remove) + ImGuiTestEngine_UnregisterTest(e, test); + // Display test status recap (colors match per-test run button colors defined above) { ImVec4 status_color; @@ -750,22 +770,34 @@ static void ImGuiTestEngine_ShowTestTool(ImGuiTestEngine* engine, bool* p_open) "- Cinematic: Run tests with pauses between actions (for e.g. tutorials)." ); ImGui::SameLine(); - //ImGui::Checkbox("Fast", &engine->IO.ConfigRunFast); - //ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + + // (Would be good if we exposed horizontal layout mode..) ImGui::Checkbox("Stop", &engine->IO.ConfigStopOnError); - ImGui::SetItemTooltip("Stop running tests when hitting an error."); + ImGui::SetItemTooltip("When hitting an error:\n- Stop running other tests."); ImGui::SameLine(); ImGui::Checkbox("DbgBrk", &engine->IO.ConfigBreakOnError); - ImGui::SetItemTooltip("Break in debugger when hitting an error."); + ImGui::SetItemTooltip("When hitting an error:\n- Break in debugger."); ImGui::SameLine(); - ImGui::Checkbox("KeepGUI", &engine->IO.ConfigKeepGuiFunc); - ImGui::SetItemTooltip("Keep GUI function running after a test fails, or when a single queued test is finished.\nHold ESC to abort a running GUI function."); - ImGui::SameLine(); - ImGui::Checkbox("Refocus", &engine->IO.ConfigRestoreFocusAfterTests); - ImGui::SetItemTooltip("Restore focus back after running tests."); + ImGui::Checkbox("Capture", &engine->IO.ConfigCaptureOnError); + ImGui::SetItemTooltip("When hitting an error:\n- Capture screen to PNG. Right-click filename in Test Log to open."); ImGui::SameLine(); ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); ImGui::SameLine(); + + ImGui::Checkbox("KeepGUI", &engine->IO.ConfigKeepGuiFunc); + ImGui::SetItemTooltip("After running single test or hitting an error:\n- Keep GUI function visible and interactive.\n- Hold ESC to abort a running GUI function."); + ImGui::SameLine(); + bool keep_focus = !engine->IO.ConfigRestoreFocusAfterTests; + if (ImGui::Checkbox("KeepFocus", &keep_focus)) + engine->IO.ConfigRestoreFocusAfterTests = !keep_focus; + ImGui::SetItemTooltip("After running tests:\n- Keep GUI current focus, instead of restoring focus to this window."); + + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + ImGui::SetNextItemWidth(70 * dpi_scale); if (ImGui::BeginCombo("##Verbose", ImGuiTestEngine_GetVerboseLevelName(engine->IO.ConfigVerboseLevel), ImGuiComboFlags_None)) { @@ -775,6 +807,7 @@ static void ImGuiTestEngine_ShowTestTool(ImGuiTestEngine* engine, bool* p_open) ImGui::EndCombo(); } ImGui::SetItemTooltip("Verbose level."); + //ImGui::PopStyleVar(); ImGui::Separator(); diff --git a/libs/imgui_test_engine/imgui_te_utils.cpp b/libs/imgui_test_engine/imgui_te_utils.cpp index ad473a7..ee88a5f 100644 --- a/libs/imgui_test_engine/imgui_te_utils.cpp +++ b/libs/imgui_test_engine/imgui_te_utils.cpp @@ -26,6 +26,9 @@ #include #include // stat() #endif +#ifdef __APPLE__ +#include +#endif #if defined(__linux) || defined(__linux__) || defined(__MACH__) || defined(__MSL__) || defined(__MINGW32__) #include // pthread_setname_np() @@ -280,7 +283,7 @@ bool ImFileCreateDirectoryChain(const char* path, const char* path_end) path_local[path_len] = 0; #if defined(_WIN32) - ImVector buf; + ImVector buf; #endif // Modification of passed file_name allows us to avoid extra temporary memory allocation. // strtok() pokes \0 into places where slashes are, we create a directory using directory_name and restore slash. @@ -291,10 +294,11 @@ bool ImFileCreateDirectoryChain(const char* path, const char* path_end) *(token - 1) = IM_DIR_SEPARATOR; #if defined(_WIN32) - // Use ::CreateDirectoryW() because ::CreateDirectoryA() treat filenames in the local code-page instead of UTF-8. - const int filename_wsize = ImTextCountCharsFromUtf8(path_local, NULL) + 1; + // Use ::CreateDirectoryW() because ::CreateDirectoryA() treat filenames in the local code-page instead of UTF-8 + // We cannot use ImWchar, which can be 32bits if IMGUI_USE_WCHAR32 (and CreateDirectoryW require 16bits wchar) + int filename_wsize = MultiByteToWideChar(CP_UTF8, 0, path_local, -1, NULL, 0); buf.resize(filename_wsize); - ImTextStrFromUtf8(&buf[0], filename_wsize, path_local, NULL); + MultiByteToWideChar(CP_UTF8, 0, path_local, -1, &buf[0], filename_wsize); if (!::CreateDirectoryW((wchar_t*)&buf[0], NULL) && GetLastError() != ERROR_ALREADY_EXISTS) #else if (mkdir(path_local, S_IRWXU) != 0 && errno != EEXIST) @@ -785,10 +789,11 @@ const ImBuildInfo* ImBuildGetCompilationInfo() // CPU #if defined(_M_X86) || defined(_M_IX86) || defined(__i386) || defined(__i386__) || defined(_X86_) || defined(_M_AMD64) || defined(_AMD64_) || defined(__x86_64__) build_info.Cpu = (sizeof(size_t) == 4) ? "X86" : "X64"; -#elif defined(__aarch64__) +#elif defined(__aarch64__) || (defined(_M_ARM64) && defined(_WIN64)) build_info.Cpu = "ARM64"; +#elif defined(__EMSCRIPTEN__) + build_info.Cpu = "WebAsm"; #else -#error build_info.Cpu = (sizeof(size_t) == 4) ? "Unknown32" : "Unknown64"; #endif @@ -1000,6 +1005,27 @@ bool ImOsIsDebuggerPresent() debugger_pid = atoi(tracer_pid); } return debugger_pid != 0; +#elif defined(__APPLE__) + // https://stackoverflow.com/questions/2200277/detecting-debugger-on-mac-os-x + int junk; + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + info.kp_proc.p_flag = 0; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + size = sizeof(info); + junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); + IM_ASSERT(junk == 0); + + // We're being debugged if the P_TRACED flag is set. + return (info.kp_proc.p_flag & P_TRACED) != 0; #else // FIXME return false; @@ -1212,6 +1238,7 @@ void TableDiscardInstanceAndSettings(ImGuiID table_id) // Helper to verify ImDrawData integrity of buffer count (broke before e.g. #6716) void DrawDataVerifyMatchingBufferCount(ImDrawData* draw_data) { +#if IMGUI_VERSION_NUM >= 18973 int total_vtx_count = 0; int total_idx_count = 0; for (ImDrawList* draw_list : draw_data->CmdLists) @@ -1223,6 +1250,9 @@ void DrawDataVerifyMatchingBufferCount(ImDrawData* draw_data) IM_UNUSED(total_idx_count); IM_ASSERT(total_vtx_count == draw_data->TotalVtxCount); IM_ASSERT(total_idx_count == draw_data->TotalIdxCount); +#else + IM_UNUSED(draw_data); +#endif } //----------------------------------------------------------------------------- diff --git a/libs/imgui_test_engine/imgui_te_utils.h b/libs/imgui_test_engine/imgui_te_utils.h index 1eef55c..0b9bf4d 100644 --- a/libs/imgui_test_engine/imgui_te_utils.h +++ b/libs/imgui_test_engine/imgui_te_utils.h @@ -13,17 +13,6 @@ #include "imgui.h" // ImGuiID, ImGuiKey class Str; // Str<> from thirdparty/Str/Str.h -//----------------------------------------------------------------------------- -// Function Pointers -//----------------------------------------------------------------------------- - -#if IMGUI_TEST_ENGINE_ENABLE_STD_FUNCTION -#include -#define ImFuncPtr(FUNC_TYPE) std::function -#else -#define ImFuncPtr(FUNC_TYPE) FUNC_TYPE* -#endif - //----------------------------------------------------------------------------- // Hashing Helpers //----------------------------------------------------------------------------- diff --git a/libs/imguizmo/ImGuizmo.cpp b/libs/imguizmo/ImGuizmo.cpp new file mode 100644 index 0000000..645b25b --- /dev/null +++ b/libs/imguizmo/ImGuizmo.cpp @@ -0,0 +1,2996 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// 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. +// + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif +#include "imgui.h" +#include "imgui_internal.h" +#include "ImGuizmo.h" + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#endif +#if !defined(_MSC_VER) && !defined(__MINGW64_VERSION_MAJOR) +#define _malloca(x) alloca(x) +#define _freea(x) +#endif + +// includes patches for multiview from +// https://github.com/CedricGuillemet/ImGuizmo/issues/15 + +namespace IMGUIZMO_NAMESPACE +{ + static const float ZPI = 3.14159265358979323846f; + static const float RAD2DEG = (180.f / ZPI); + static const float DEG2RAD = (ZPI / 180.f); + const float screenRotateSize = 0.06f; + // scale a bit so translate axis do not touch when in universal + const float rotationDisplayFactor = 1.2f; + + static OPERATION operator&(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + static bool operator!=(OPERATION lhs, int rhs) + { + return static_cast(lhs) != rhs; + } + + static bool Intersects(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) != 0; + } + + // True if lhs contains rhs + static bool Contains(OPERATION lhs, OPERATION rhs) + { + return (lhs & rhs) == rhs; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // utility and math + + void FPU_MatrixF_x_MatrixF(const float* a, const float* b, float* r) + { + r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; + r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; + r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; + r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; + + r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; + r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; + r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; + r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; + + r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; + r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; + r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; + r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; + + r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; + r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; + r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; + r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; + } + + void Frustum(float left, float right, float bottom, float top, float znear, float zfar, float* m16) + { + float temp, temp2, temp3, temp4; + temp = 2.0f * znear; + temp2 = right - left; + temp3 = top - bottom; + temp4 = zfar - znear; + m16[0] = temp / temp2; + m16[1] = 0.0; + m16[2] = 0.0; + m16[3] = 0.0; + m16[4] = 0.0; + m16[5] = temp / temp3; + m16[6] = 0.0; + m16[7] = 0.0; + m16[8] = (right + left) / temp2; + m16[9] = (top + bottom) / temp3; + m16[10] = (-zfar - znear) / temp4; + m16[11] = -1.0f; + m16[12] = 0.0; + m16[13] = 0.0; + m16[14] = (-temp * zfar) / temp4; + m16[15] = 0.0; + } + + void Perspective(float fovyInDegrees, float aspectRatio, float znear, float zfar, float* m16) + { + float ymax, xmax; + ymax = znear * tanf(fovyInDegrees * DEG2RAD); + xmax = ymax * aspectRatio; + Frustum(-xmax, xmax, -ymax, ymax, znear, zfar, m16); + } + + void Cross(const float* a, const float* b, float* r) + { + r[0] = a[1] * b[2] - a[2] * b[1]; + r[1] = a[2] * b[0] - a[0] * b[2]; + r[2] = a[0] * b[1] - a[1] * b[0]; + } + + float Dot(const float* a, const float* b) + { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + + void Normalize(const float* a, float* r) + { + float il = 1.f / (sqrtf(Dot(a, a)) + FLT_EPSILON); + r[0] = a[0] * il; + r[1] = a[1] * il; + r[2] = a[2] * il; + } + + void LookAt(const float* eye, const float* at, const float* up, float* m16) + { + float X[3], Y[3], Z[3], tmp[3]; + + tmp[0] = eye[0] - at[0]; + tmp[1] = eye[1] - at[1]; + tmp[2] = eye[2] - at[2]; + Normalize(tmp, Z); + Normalize(up, Y); + Cross(Y, Z, tmp); + Normalize(tmp, X); + Cross(Z, X, tmp); + Normalize(tmp, Y); + + m16[0] = X[0]; + m16[1] = Y[0]; + m16[2] = Z[0]; + m16[3] = 0.0f; + m16[4] = X[1]; + m16[5] = Y[1]; + m16[6] = Z[1]; + m16[7] = 0.0f; + m16[8] = X[2]; + m16[9] = Y[2]; + m16[10] = Z[2]; + m16[11] = 0.0f; + m16[12] = -Dot(X, eye); + m16[13] = -Dot(Y, eye); + m16[14] = -Dot(Z, eye); + m16[15] = 1.0f; + } + + template T Clamp(T x, T y, T z) { return ((x < y) ? y : ((x > z) ? z : x)); } + template T max(T x, T y) { return (x > y) ? x : y; } + template T min(T x, T y) { return (x < y) ? x : y; } + template bool IsWithin(T x, T y, T z) { return (x >= y) && (x <= z); } + + struct matrix_t; + struct vec_t + { + public: + float x, y, z, w; + + void Lerp(const vec_t& v, float t) + { + x += (v.x - x) * t; + y += (v.y - y) * t; + z += (v.z - z) * t; + w += (v.w - w) * t; + } + + void Set(float v) { x = y = z = w = v; } + void Set(float _x, float _y, float _z = 0.f, float _w = 0.f) { x = _x; y = _y; z = _z; w = _w; } + + vec_t& operator -= (const vec_t& v) { x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; } + vec_t& operator += (const vec_t& v) { x += v.x; y += v.y; z += v.z; w += v.w; return *this; } + vec_t& operator *= (const vec_t& v) { x *= v.x; y *= v.y; z *= v.z; w *= v.w; return *this; } + vec_t& operator *= (float v) { x *= v; y *= v; z *= v; w *= v; return *this; } + + vec_t operator * (float f) const; + vec_t operator - () const; + vec_t operator - (const vec_t& v) const; + vec_t operator + (const vec_t& v) const; + vec_t operator * (const vec_t& v) const; + + const vec_t& operator + () const { return (*this); } + float Length() const { return sqrtf(x * x + y * y + z * z); }; + float LengthSq() const { return (x * x + y * y + z * z); }; + vec_t Normalize() { (*this) *= (1.f / ( Length() > FLT_EPSILON ? Length() : FLT_EPSILON ) ); return (*this); } + vec_t Normalize(const vec_t& v) { this->Set(v.x, v.y, v.z, v.w); this->Normalize(); return (*this); } + vec_t Abs() const; + + void Cross(const vec_t& v) + { + vec_t res; + res.x = y * v.z - z * v.y; + res.y = z * v.x - x * v.z; + res.z = x * v.y - y * v.x; + + x = res.x; + y = res.y; + z = res.z; + w = 0.f; + } + + void Cross(const vec_t& v1, const vec_t& v2) + { + x = v1.y * v2.z - v1.z * v2.y; + y = v1.z * v2.x - v1.x * v2.z; + z = v1.x * v2.y - v1.y * v2.x; + w = 0.f; + } + + float Dot(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z) + (w * v.w); + } + + float Dot3(const vec_t& v) const + { + return (x * v.x) + (y * v.y) + (z * v.z); + } + + void Transform(const matrix_t& matrix); + void Transform(const vec_t& s, const matrix_t& matrix); + + void TransformVector(const matrix_t& matrix); + void TransformPoint(const matrix_t& matrix); + void TransformVector(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformVector(matrix); } + void TransformPoint(const vec_t& v, const matrix_t& matrix) { (*this) = v; this->TransformPoint(matrix); } + + float& operator [] (size_t index) { return ((float*)&x)[index]; } + const float& operator [] (size_t index) const { return ((float*)&x)[index]; } + bool operator!=(const vec_t& other) const { return memcmp(this, &other, sizeof(vec_t)) != 0; } + }; + + vec_t makeVect(float _x, float _y, float _z = 0.f, float _w = 0.f) { vec_t res; res.x = _x; res.y = _y; res.z = _z; res.w = _w; return res; } + vec_t makeVect(ImVec2 v) { vec_t res; res.x = v.x; res.y = v.y; res.z = 0.f; res.w = 0.f; return res; } + vec_t vec_t::operator * (float f) const { return makeVect(x * f, y * f, z * f, w * f); } + vec_t vec_t::operator - () const { return makeVect(-x, -y, -z, -w); } + vec_t vec_t::operator - (const vec_t& v) const { return makeVect(x - v.x, y - v.y, z - v.z, w - v.w); } + vec_t vec_t::operator + (const vec_t& v) const { return makeVect(x + v.x, y + v.y, z + v.z, w + v.w); } + vec_t vec_t::operator * (const vec_t& v) const { return makeVect(x * v.x, y * v.y, z * v.z, w * v.w); } + vec_t vec_t::Abs() const { return makeVect(fabsf(x), fabsf(y), fabsf(z)); } + + vec_t Normalized(const vec_t& v) { vec_t res; res = v; res.Normalize(); return res; } + vec_t Cross(const vec_t& v1, const vec_t& v2) + { + vec_t res; + res.x = v1.y * v2.z - v1.z * v2.y; + res.y = v1.z * v2.x - v1.x * v2.z; + res.z = v1.x * v2.y - v1.y * v2.x; + res.w = 0.f; + return res; + } + + float Dot(const vec_t& v1, const vec_t& v2) + { + return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z); + } + + vec_t BuildPlan(const vec_t& p_point1, const vec_t& p_normal) + { + vec_t normal, res; + normal.Normalize(p_normal); + res.w = normal.Dot(p_point1); + res.x = normal.x; + res.y = normal.y; + res.z = normal.z; + return res; + } + + struct matrix_t + { + public: + + union + { + float m[4][4]; + float m16[16]; + struct + { + vec_t right, up, dir, position; + } v; + vec_t component[4]; + }; + + operator float* () { return m16; } + operator const float* () const { return m16; } + void Translation(float _x, float _y, float _z) { this->Translation(makeVect(_x, _y, _z)); } + + void Translation(const vec_t& vt) + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(vt.x, vt.y, vt.z, 1.f); + } + + void Scale(float _x, float _y, float _z) + { + v.right.Set(_x, 0.f, 0.f, 0.f); + v.up.Set(0.f, _y, 0.f, 0.f); + v.dir.Set(0.f, 0.f, _z, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Scale(const vec_t& s) { Scale(s.x, s.y, s.z); } + + matrix_t& operator *= (const matrix_t& mat) + { + matrix_t tmpMat; + tmpMat = *this; + tmpMat.Multiply(mat); + *this = tmpMat; + return *this; + } + matrix_t operator * (const matrix_t& mat) const + { + matrix_t matT; + matT.Multiply(*this, mat); + return matT; + } + + void Multiply(const matrix_t& matrix) + { + matrix_t tmp; + tmp = *this; + + FPU_MatrixF_x_MatrixF((float*)&tmp, (float*)&matrix, (float*)this); + } + + void Multiply(const matrix_t& m1, const matrix_t& m2) + { + FPU_MatrixF_x_MatrixF((float*)&m1, (float*)&m2, (float*)this); + } + + float GetDeterminant() const + { + return m[0][0] * m[1][1] * m[2][2] + m[0][1] * m[1][2] * m[2][0] + m[0][2] * m[1][0] * m[2][1] - + m[0][2] * m[1][1] * m[2][0] - m[0][1] * m[1][0] * m[2][2] - m[0][0] * m[1][2] * m[2][1]; + } + + float Inverse(const matrix_t& srcMatrix, bool affine = false); + void SetToIdentity() + { + v.right.Set(1.f, 0.f, 0.f, 0.f); + v.up.Set(0.f, 1.f, 0.f, 0.f); + v.dir.Set(0.f, 0.f, 1.f, 0.f); + v.position.Set(0.f, 0.f, 0.f, 1.f); + } + void Transpose() + { + matrix_t tmpm; + for (int l = 0; l < 4; l++) + { + for (int c = 0; c < 4; c++) + { + tmpm.m[l][c] = m[c][l]; + } + } + (*this) = tmpm; + } + + void RotationAxis(const vec_t& axis, float angle); + + void OrthoNormalize() + { + v.right.Normalize(); + v.up.Normalize(); + v.dir.Normalize(); + } + }; + + void vec_t::Transform(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + w * matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + w * matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + w * matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + w * matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::Transform(const vec_t& s, const matrix_t& matrix) + { + *this = s; + Transform(matrix); + } + + void vec_t::TransformPoint(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0] + matrix.m[3][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1] + matrix.m[3][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2] + matrix.m[3][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3] + matrix.m[3][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + void vec_t::TransformVector(const matrix_t& matrix) + { + vec_t out; + + out.x = x * matrix.m[0][0] + y * matrix.m[1][0] + z * matrix.m[2][0]; + out.y = x * matrix.m[0][1] + y * matrix.m[1][1] + z * matrix.m[2][1]; + out.z = x * matrix.m[0][2] + y * matrix.m[1][2] + z * matrix.m[2][2]; + out.w = x * matrix.m[0][3] + y * matrix.m[1][3] + z * matrix.m[2][3]; + + x = out.x; + y = out.y; + z = out.z; + w = out.w; + } + + float matrix_t::Inverse(const matrix_t& srcMatrix, bool affine) + { + float det = 0; + + if (affine) + { + det = GetDeterminant(); + float s = 1 / det; + m[0][0] = (srcMatrix.m[1][1] * srcMatrix.m[2][2] - srcMatrix.m[1][2] * srcMatrix.m[2][1]) * s; + m[0][1] = (srcMatrix.m[2][1] * srcMatrix.m[0][2] - srcMatrix.m[2][2] * srcMatrix.m[0][1]) * s; + m[0][2] = (srcMatrix.m[0][1] * srcMatrix.m[1][2] - srcMatrix.m[0][2] * srcMatrix.m[1][1]) * s; + m[1][0] = (srcMatrix.m[1][2] * srcMatrix.m[2][0] - srcMatrix.m[1][0] * srcMatrix.m[2][2]) * s; + m[1][1] = (srcMatrix.m[2][2] * srcMatrix.m[0][0] - srcMatrix.m[2][0] * srcMatrix.m[0][2]) * s; + m[1][2] = (srcMatrix.m[0][2] * srcMatrix.m[1][0] - srcMatrix.m[0][0] * srcMatrix.m[1][2]) * s; + m[2][0] = (srcMatrix.m[1][0] * srcMatrix.m[2][1] - srcMatrix.m[1][1] * srcMatrix.m[2][0]) * s; + m[2][1] = (srcMatrix.m[2][0] * srcMatrix.m[0][1] - srcMatrix.m[2][1] * srcMatrix.m[0][0]) * s; + m[2][2] = (srcMatrix.m[0][0] * srcMatrix.m[1][1] - srcMatrix.m[0][1] * srcMatrix.m[1][0]) * s; + m[3][0] = -(m[0][0] * srcMatrix.m[3][0] + m[1][0] * srcMatrix.m[3][1] + m[2][0] * srcMatrix.m[3][2]); + m[3][1] = -(m[0][1] * srcMatrix.m[3][0] + m[1][1] * srcMatrix.m[3][1] + m[2][1] * srcMatrix.m[3][2]); + m[3][2] = -(m[0][2] * srcMatrix.m[3][0] + m[1][2] * srcMatrix.m[3][1] + m[2][2] * srcMatrix.m[3][2]); + } + else + { + // transpose matrix + float src[16]; + for (int i = 0; i < 4; ++i) + { + src[i] = srcMatrix.m16[i * 4]; + src[i + 4] = srcMatrix.m16[i * 4 + 1]; + src[i + 8] = srcMatrix.m16[i * 4 + 2]; + src[i + 12] = srcMatrix.m16[i * 4 + 3]; + } + + // calculate pairs for first 8 elements (cofactors) + float tmp[12]; // temp array for pairs + tmp[0] = src[10] * src[15]; + tmp[1] = src[11] * src[14]; + tmp[2] = src[9] * src[15]; + tmp[3] = src[11] * src[13]; + tmp[4] = src[9] * src[14]; + tmp[5] = src[10] * src[13]; + tmp[6] = src[8] * src[15]; + tmp[7] = src[11] * src[12]; + tmp[8] = src[8] * src[14]; + tmp[9] = src[10] * src[12]; + tmp[10] = src[8] * src[13]; + tmp[11] = src[9] * src[12]; + + // calculate first 8 elements (cofactors) + m16[0] = (tmp[0] * src[5] + tmp[3] * src[6] + tmp[4] * src[7]) - (tmp[1] * src[5] + tmp[2] * src[6] + tmp[5] * src[7]); + m16[1] = (tmp[1] * src[4] + tmp[6] * src[6] + tmp[9] * src[7]) - (tmp[0] * src[4] + tmp[7] * src[6] + tmp[8] * src[7]); + m16[2] = (tmp[2] * src[4] + tmp[7] * src[5] + tmp[10] * src[7]) - (tmp[3] * src[4] + tmp[6] * src[5] + tmp[11] * src[7]); + m16[3] = (tmp[5] * src[4] + tmp[8] * src[5] + tmp[11] * src[6]) - (tmp[4] * src[4] + tmp[9] * src[5] + tmp[10] * src[6]); + m16[4] = (tmp[1] * src[1] + tmp[2] * src[2] + tmp[5] * src[3]) - (tmp[0] * src[1] + tmp[3] * src[2] + tmp[4] * src[3]); + m16[5] = (tmp[0] * src[0] + tmp[7] * src[2] + tmp[8] * src[3]) - (tmp[1] * src[0] + tmp[6] * src[2] + tmp[9] * src[3]); + m16[6] = (tmp[3] * src[0] + tmp[6] * src[1] + tmp[11] * src[3]) - (tmp[2] * src[0] + tmp[7] * src[1] + tmp[10] * src[3]); + m16[7] = (tmp[4] * src[0] + tmp[9] * src[1] + tmp[10] * src[2]) - (tmp[5] * src[0] + tmp[8] * src[1] + tmp[11] * src[2]); + + // calculate pairs for second 8 elements (cofactors) + tmp[0] = src[2] * src[7]; + tmp[1] = src[3] * src[6]; + tmp[2] = src[1] * src[7]; + tmp[3] = src[3] * src[5]; + tmp[4] = src[1] * src[6]; + tmp[5] = src[2] * src[5]; + tmp[6] = src[0] * src[7]; + tmp[7] = src[3] * src[4]; + tmp[8] = src[0] * src[6]; + tmp[9] = src[2] * src[4]; + tmp[10] = src[0] * src[5]; + tmp[11] = src[1] * src[4]; + + // calculate second 8 elements (cofactors) + m16[8] = (tmp[0] * src[13] + tmp[3] * src[14] + tmp[4] * src[15]) - (tmp[1] * src[13] + tmp[2] * src[14] + tmp[5] * src[15]); + m16[9] = (tmp[1] * src[12] + tmp[6] * src[14] + tmp[9] * src[15]) - (tmp[0] * src[12] + tmp[7] * src[14] + tmp[8] * src[15]); + m16[10] = (tmp[2] * src[12] + tmp[7] * src[13] + tmp[10] * src[15]) - (tmp[3] * src[12] + tmp[6] * src[13] + tmp[11] * src[15]); + m16[11] = (tmp[5] * src[12] + tmp[8] * src[13] + tmp[11] * src[14]) - (tmp[4] * src[12] + tmp[9] * src[13] + tmp[10] * src[14]); + m16[12] = (tmp[2] * src[10] + tmp[5] * src[11] + tmp[1] * src[9]) - (tmp[4] * src[11] + tmp[0] * src[9] + tmp[3] * src[10]); + m16[13] = (tmp[8] * src[11] + tmp[0] * src[8] + tmp[7] * src[10]) - (tmp[6] * src[10] + tmp[9] * src[11] + tmp[1] * src[8]); + m16[14] = (tmp[6] * src[9] + tmp[11] * src[11] + tmp[3] * src[8]) - (tmp[10] * src[11] + tmp[2] * src[8] + tmp[7] * src[9]); + m16[15] = (tmp[10] * src[10] + tmp[4] * src[8] + tmp[9] * src[9]) - (tmp[8] * src[9] + tmp[11] * src[10] + tmp[5] * src[8]); + + // calculate determinant + det = src[0] * m16[0] + src[1] * m16[1] + src[2] * m16[2] + src[3] * m16[3]; + + // calculate matrix inverse + float invdet = 1 / det; + for (int j = 0; j < 16; ++j) + { + m16[j] *= invdet; + } + } + + return det; + } + + void matrix_t::RotationAxis(const vec_t& axis, float angle) + { + float length2 = axis.LengthSq(); + if (length2 < FLT_EPSILON) + { + SetToIdentity(); + return; + } + + vec_t n = axis * (1.f / sqrtf(length2)); + float s = sinf(angle); + float c = cosf(angle); + float k = 1.f - c; + + float xx = n.x * n.x * k + c; + float yy = n.y * n.y * k + c; + float zz = n.z * n.z * k + c; + float xy = n.x * n.y * k; + float yz = n.y * n.z * k; + float zx = n.z * n.x * k; + float xs = n.x * s; + float ys = n.y * s; + float zs = n.z * s; + + m[0][0] = xx; + m[0][1] = xy + zs; + m[0][2] = zx - ys; + m[0][3] = 0.f; + m[1][0] = xy - zs; + m[1][1] = yy; + m[1][2] = yz + xs; + m[1][3] = 0.f; + m[2][0] = zx + ys; + m[2][1] = yz - xs; + m[2][2] = zz; + m[2][3] = 0.f; + m[3][0] = 0.f; + m[3][1] = 0.f; + m[3][2] = 0.f; + m[3][3] = 1.f; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + enum MOVETYPE + { + MT_NONE, + MT_MOVE_X, + MT_MOVE_Y, + MT_MOVE_Z, + MT_MOVE_YZ, + MT_MOVE_ZX, + MT_MOVE_XY, + MT_MOVE_SCREEN, + MT_ROTATE_X, + MT_ROTATE_Y, + MT_ROTATE_Z, + MT_ROTATE_SCREEN, + MT_SCALE_X, + MT_SCALE_Y, + MT_SCALE_Z, + MT_SCALE_XYZ + }; + + static bool IsTranslateType(int type) + { + return type >= MT_MOVE_X && type <= MT_MOVE_SCREEN; + } + + static bool IsRotateType(int type) + { + return type >= MT_ROTATE_X && type <= MT_ROTATE_SCREEN; + } + + static bool IsScaleType(int type) + { + return type >= MT_SCALE_X && type <= MT_SCALE_XYZ; + } + + // Matches MT_MOVE_AB order + static const OPERATION TRANSLATE_PLANS[3] = { TRANSLATE_Y | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Z, TRANSLATE_X | TRANSLATE_Y }; + + Style::Style() + { + // default values + TranslationLineThickness = 3.0f; + TranslationLineArrowSize = 6.0f; + RotationLineThickness = 2.0f; + RotationOuterLineThickness = 3.0f; + ScaleLineThickness = 3.0f; + ScaleLineCircleSize = 6.0f; + HatchedAxisLineThickness = 6.0f; + CenterCircleSize = 6.0f; + + // initialize default colors + Colors[DIRECTION_X] = ImVec4(0.666f, 0.000f, 0.000f, 1.000f); + Colors[DIRECTION_Y] = ImVec4(0.000f, 0.666f, 0.000f, 1.000f); + Colors[DIRECTION_Z] = ImVec4(0.000f, 0.000f, 0.666f, 1.000f); + Colors[PLANE_X] = ImVec4(0.666f, 0.000f, 0.000f, 0.380f); + Colors[PLANE_Y] = ImVec4(0.000f, 0.666f, 0.000f, 0.380f); + Colors[PLANE_Z] = ImVec4(0.000f, 0.000f, 0.666f, 0.380f); + Colors[SELECTION] = ImVec4(1.000f, 0.500f, 0.062f, 0.541f); + Colors[INACTIVE] = ImVec4(0.600f, 0.600f, 0.600f, 0.600f); + Colors[TRANSLATION_LINE] = ImVec4(0.666f, 0.666f, 0.666f, 0.666f); + Colors[SCALE_LINE] = ImVec4(0.250f, 0.250f, 0.250f, 1.000f); + Colors[ROTATION_USING_BORDER] = ImVec4(1.000f, 0.500f, 0.062f, 1.000f); + Colors[ROTATION_USING_FILL] = ImVec4(1.000f, 0.500f, 0.062f, 0.500f); + Colors[HATCHED_AXIS_LINES] = ImVec4(0.000f, 0.000f, 0.000f, 0.500f); + Colors[TEXT] = ImVec4(1.000f, 1.000f, 1.000f, 1.000f); + Colors[TEXT_SHADOW] = ImVec4(0.000f, 0.000f, 0.000f, 1.000f); + } + + struct Context + { + Context() : mbUsing(false), mbEnable(true), mbUsingBounds(false) + { + } + + ImDrawList* mDrawList; + Style mStyle; + + MODE mMode; + matrix_t mViewMat; + matrix_t mProjectionMat; + matrix_t mModel; + matrix_t mModelLocal; // orthonormalized model + matrix_t mModelInverse; + matrix_t mModelSource; + matrix_t mModelSourceInverse; + matrix_t mMVP; + matrix_t mMVPLocal; // MVP with full model matrix whereas mMVP's model matrix might only be translation in case of World space edition + matrix_t mViewProjection; + + vec_t mModelScaleOrigin; + vec_t mCameraEye; + vec_t mCameraRight; + vec_t mCameraDir; + vec_t mCameraUp; + vec_t mRayOrigin; + vec_t mRayVector; + + float mRadiusSquareCenter; + ImVec2 mScreenSquareCenter; + ImVec2 mScreenSquareMin; + ImVec2 mScreenSquareMax; + + float mScreenFactor; + vec_t mRelativeOrigin; + + bool mbUsing; + bool mbEnable; + bool mbMouseOver; + bool mReversed; // reversed projection matrix + + // translation + vec_t mTranslationPlan; + vec_t mTranslationPlanOrigin; + vec_t mMatrixOrigin; + vec_t mTranslationLastDelta; + + // rotation + vec_t mRotationVectorSource; + float mRotationAngle; + float mRotationAngleOrigin; + //vec_t mWorldToLocalAxis; + + // scale + vec_t mScale; + vec_t mScaleValueOrigin; + vec_t mScaleLast; + float mSaveMousePosx; + + // save axis factor when using gizmo + bool mBelowAxisLimit[3]; + bool mBelowPlaneLimit[3]; + float mAxisFactor[3]; + + float mAxisLimit=0.0025f; + float mPlaneLimit=0.02f; + + // bounds stretching + vec_t mBoundsPivot; + vec_t mBoundsAnchor; + vec_t mBoundsPlan; + vec_t mBoundsLocalPivot; + int mBoundsBestAxis; + int mBoundsAxis[2]; + bool mbUsingBounds; + matrix_t mBoundsMatrix; + + // + int mCurrentOperation; + + float mX = 0.f; + float mY = 0.f; + float mWidth = 0.f; + float mHeight = 0.f; + float mXMax = 0.f; + float mYMax = 0.f; + float mDisplayRatio = 1.f; + + bool mIsOrthographic = false; + + int mActualID = -1; + int mEditingID = -1; + OPERATION mOperation = OPERATION(-1); + + bool mAllowAxisFlip = true; + float mGizmoSizeClipSpace = 0.1f; + }; + + static Context gContext; + + static const vec_t directionUnary[3] = { makeVect(1.f, 0.f, 0.f), makeVect(0.f, 1.f, 0.f), makeVect(0.f, 0.f, 1.f) }; + static const char* translationInfoMask[] = { "X : %5.3f", "Y : %5.3f", "Z : %5.3f", + "Y : %5.3f Z : %5.3f", "X : %5.3f Z : %5.3f", "X : %5.3f Y : %5.3f", + "X : %5.3f Y : %5.3f Z : %5.3f" }; + static const char* scaleInfoMask[] = { "X : %5.2f", "Y : %5.2f", "Z : %5.2f", "XYZ : %5.2f" }; + static const char* rotationInfoMask[] = { "X : %5.2f deg %5.2f rad", "Y : %5.2f deg %5.2f rad", "Z : %5.2f deg %5.2f rad", "Screen : %5.2f deg %5.2f rad" }; + static const int translationInfoIndex[] = { 0,0,0, 1,0,0, 2,0,0, 1,2,0, 0,2,0, 0,1,0, 0,1,2 }; + static const float quadMin = 0.5f; + static const float quadMax = 0.8f; + static const float quadUV[8] = { quadMin, quadMin, quadMin, quadMax, quadMax, quadMax, quadMax, quadMin }; + static const int halfCircleSegmentCount = 64; + static const float snapTension = 0.5f; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion); + static int GetRotateType(OPERATION op); + static int GetScaleType(OPERATION op); + + Style& GetStyle() + { + return gContext.mStyle; + } + + static ImU32 GetColorU32(int idx) + { + IM_ASSERT(idx < COLOR::COUNT); + return ImGui::ColorConvertFloat4ToU32(gContext.mStyle.Colors[idx]); + } + + static ImVec2 worldToPos(const vec_t& worldPos, const matrix_t& mat, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) + { + vec_t trans; + trans.TransformPoint(worldPos, mat); + trans *= 0.5f / trans.w; + trans += makeVect(0.5f, 0.5f); + trans.y = 1.f - trans.y; + trans.x *= size.x; + trans.y *= size.y; + trans.x += position.x; + trans.y += position.y; + return ImVec2(trans.x, trans.y); + } + + static void ComputeCameraRay(vec_t& rayOrigin, vec_t& rayDir, ImVec2 position = ImVec2(gContext.mX, gContext.mY), ImVec2 size = ImVec2(gContext.mWidth, gContext.mHeight)) + { + ImGuiIO& io = ImGui::GetIO(); + + matrix_t mViewProjInverse; + mViewProjInverse.Inverse(gContext.mViewMat * gContext.mProjectionMat); + + const float mox = ((io.MousePos.x - position.x) / size.x) * 2.f - 1.f; + const float moy = (1.f - ((io.MousePos.y - position.y) / size.y)) * 2.f - 1.f; + + const float zNear = gContext.mReversed ? (1.f - FLT_EPSILON) : 0.f; + const float zFar = gContext.mReversed ? 0.f : (1.f - FLT_EPSILON); + + rayOrigin.Transform(makeVect(mox, moy, zNear, 1.f), mViewProjInverse); + rayOrigin *= 1.f / rayOrigin.w; + vec_t rayEnd; + rayEnd.Transform(makeVect(mox, moy, zFar, 1.f), mViewProjInverse); + rayEnd *= 1.f / rayEnd.w; + rayDir = Normalized(rayEnd - rayOrigin); + } + + static float GetSegmentLengthClipSpace(const vec_t& start, const vec_t& end, const bool localCoordinates = false) + { + vec_t startOfSegment = start; + const matrix_t& mvp = localCoordinates ? gContext.mMVPLocal : gContext.mMVP; + startOfSegment.TransformPoint(mvp); + if (fabsf(startOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + startOfSegment *= 1.f / startOfSegment.w; + } + + vec_t endOfSegment = end; + endOfSegment.TransformPoint(mvp); + if (fabsf(endOfSegment.w) > FLT_EPSILON) // check for axis aligned with camera direction + { + endOfSegment *= 1.f / endOfSegment.w; + } + + vec_t clipSpaceAxis = endOfSegment - startOfSegment; + if (gContext.mDisplayRatio < 1.0) + clipSpaceAxis.x *= gContext.mDisplayRatio; + else + clipSpaceAxis.y /= gContext.mDisplayRatio; + float segmentLengthInClipSpace = sqrtf(clipSpaceAxis.x * clipSpaceAxis.x + clipSpaceAxis.y * clipSpaceAxis.y); + return segmentLengthInClipSpace; + } + + static float GetParallelogram(const vec_t& ptO, const vec_t& ptA, const vec_t& ptB) + { + vec_t pts[] = { ptO, ptA, ptB }; + for (unsigned int i = 0; i < 3; i++) + { + pts[i].TransformPoint(gContext.mMVP); + if (fabsf(pts[i].w) > FLT_EPSILON) // check for axis aligned with camera direction + { + pts[i] *= 1.f / pts[i].w; + } + } + vec_t segA = pts[1] - pts[0]; + vec_t segB = pts[2] - pts[0]; + segA.y /= gContext.mDisplayRatio; + segB.y /= gContext.mDisplayRatio; + vec_t segAOrtho = makeVect(-segA.y, segA.x); + segAOrtho.Normalize(); + float dt = segAOrtho.Dot3(segB); + float surface = sqrtf(segA.x * segA.x + segA.y * segA.y) * fabsf(dt); + return surface; + } + + inline vec_t PointOnSegment(const vec_t& point, const vec_t& vertPos1, const vec_t& vertPos2) + { + vec_t c = point - vertPos1; + vec_t V; + + V.Normalize(vertPos2 - vertPos1); + float d = (vertPos2 - vertPos1).Length(); + float t = V.Dot3(c); + + if (t < 0.f) + { + return vertPos1; + } + + if (t > d) + { + return vertPos2; + } + + return vertPos1 + V * t; + } + + static float IntersectRayPlane(const vec_t& rOrigin, const vec_t& rVector, const vec_t& plan) + { + const float numer = plan.Dot3(rOrigin) - plan.w; + const float denom = plan.Dot3(rVector); + + if (fabsf(denom) < FLT_EPSILON) // normal is orthogonal to vector, cant intersect + { + return -1.0f; + } + + return -(numer / denom); + } + + static float DistanceToPlane(const vec_t& point, const vec_t& plan) + { + return plan.Dot3(point) + plan.w; + } + + static bool IsInContextRect(ImVec2 p) + { + return IsWithin(p.x, gContext.mX, gContext.mXMax) && IsWithin(p.y, gContext.mY, gContext.mYMax); + } + + static bool IsHoveringWindow() + { + ImGuiContext& g = *ImGui::GetCurrentContext(); + ImGuiWindow* window = ImGui::FindWindowByName(gContext.mDrawList->_OwnerName); + if (g.HoveredWindow == window) // Mouse hovering drawlist window + return true; + if (g.HoveredWindow != NULL) // Any other window is hovered + return false; + if (ImGui::IsMouseHoveringRect(window->InnerRect.Min, window->InnerRect.Max, false)) // Hovering drawlist window rect, while no other window is hovered (for _NoInputs windows) + return true; + return false; + } + + void SetRect(float x, float y, float width, float height) + { + gContext.mX = x; + gContext.mY = y; + gContext.mWidth = width; + gContext.mHeight = height; + gContext.mXMax = gContext.mX + gContext.mWidth; + gContext.mYMax = gContext.mY + gContext.mXMax; + gContext.mDisplayRatio = width / height; + } + + void SetOrthographic(bool isOrthographic) + { + gContext.mIsOrthographic = isOrthographic; + } + + void SetDrawlist(ImDrawList* drawlist) + { + gContext.mDrawList = drawlist ? drawlist : ImGui::GetWindowDrawList(); + } + + void SetImGuiContext(ImGuiContext* ctx) + { + ImGui::SetCurrentContext(ctx); + } + + void BeginFrame() + { + const ImU32 flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus; + +#ifdef IMGUI_HAS_VIEWPORT + ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->Pos); +#else + ImGuiIO& io = ImGui::GetIO(); + ImGui::SetNextWindowSize(io.DisplaySize); + ImGui::SetNextWindowPos(ImVec2(0, 0)); +#endif + + ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); + ImGui::PushStyleColor(ImGuiCol_Border, 0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + + ImGui::Begin("gizmo", NULL, flags); + gContext.mDrawList = ImGui::GetWindowDrawList(); + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); + } + + bool IsUsing() + { + return (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) || gContext.mbUsingBounds; + } + + bool IsUsingAny() + { + return gContext.mbUsing || gContext.mbUsingBounds; + } + + bool IsOver() + { + return (Intersects(gContext.mOperation, TRANSLATE) && GetMoveType(gContext.mOperation, NULL) != MT_NONE) || + (Intersects(gContext.mOperation, ROTATE) && GetRotateType(gContext.mOperation) != MT_NONE) || + (Intersects(gContext.mOperation, SCALE) && GetScaleType(gContext.mOperation) != MT_NONE) || IsUsing(); + } + + bool IsOver(OPERATION op) + { + if(IsUsing()) + { + return true; + } + if(Intersects(op, SCALE) && GetScaleType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, ROTATE) && GetRotateType(op) != MT_NONE) + { + return true; + } + if(Intersects(op, TRANSLATE) && GetMoveType(op, NULL) != MT_NONE) + { + return true; + } + return false; + } + + void Enable(bool enable) + { + gContext.mbEnable = enable; + if (!enable) + { + gContext.mbUsing = false; + gContext.mbUsingBounds = false; + } + } + + static void ComputeContext(const float* view, const float* projection, float* matrix, MODE mode) + { + gContext.mMode = mode; + gContext.mViewMat = *(matrix_t*)view; + gContext.mProjectionMat = *(matrix_t*)projection; + gContext.mbMouseOver = IsHoveringWindow(); + + gContext.mModelLocal = *(matrix_t*)matrix; + gContext.mModelLocal.OrthoNormalize(); + + if (mode == LOCAL) + { + gContext.mModel = gContext.mModelLocal; + } + else + { + gContext.mModel.Translation(((matrix_t*)matrix)->v.position); + } + gContext.mModelSource = *(matrix_t*)matrix; + gContext.mModelScaleOrigin.Set(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + + gContext.mModelInverse.Inverse(gContext.mModel); + gContext.mModelSourceInverse.Inverse(gContext.mModelSource); + gContext.mViewProjection = gContext.mViewMat * gContext.mProjectionMat; + gContext.mMVP = gContext.mModel * gContext.mViewProjection; + gContext.mMVPLocal = gContext.mModelLocal * gContext.mViewProjection; + + matrix_t viewInverse; + viewInverse.Inverse(gContext.mViewMat); + gContext.mCameraDir = viewInverse.v.dir; + gContext.mCameraEye = viewInverse.v.position; + gContext.mCameraRight = viewInverse.v.right; + gContext.mCameraUp = viewInverse.v.up; + + // projection reverse + vec_t nearPos, farPos; + nearPos.Transform(makeVect(0, 0, 1.f, 1.f), gContext.mProjectionMat); + farPos.Transform(makeVect(0, 0, 2.f, 1.f), gContext.mProjectionMat); + + gContext.mReversed = (nearPos.z/nearPos.w) > (farPos.z / farPos.w); + + // compute scale from the size of camera right vector projected on screen at the matrix position + vec_t pointRight = viewInverse.v.right; + pointRight.TransformPoint(gContext.mViewProjection); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / (pointRight.x / pointRight.w - gContext.mMVP.v.position.x / gContext.mMVP.v.position.w); + + vec_t rightViewInverse = viewInverse.v.right; + rightViewInverse.TransformVector(gContext.mModelInverse); + float rightLength = GetSegmentLengthClipSpace(makeVect(0.f, 0.f), rightViewInverse); + gContext.mScreenFactor = gContext.mGizmoSizeClipSpace / rightLength; + + ImVec2 centerSSpace = worldToPos(makeVect(0.f, 0.f), gContext.mMVP); + gContext.mScreenSquareCenter = centerSSpace; + gContext.mScreenSquareMin = ImVec2(centerSSpace.x - 10.f, centerSSpace.y - 10.f); + gContext.mScreenSquareMax = ImVec2(centerSSpace.x + 10.f, centerSSpace.y + 10.f); + + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector); + } + + static void ComputeColors(ImU32* colors, int type, OPERATION operation) + { + if (gContext.mbEnable) + { + ImU32 selectionColor = GetColorU32(SELECTION); + + switch (operation) + { + case TRANSLATE: + colors[0] = (type == MT_MOVE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_MOVE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + colors[i + 4] = (type == (int)(MT_MOVE_YZ + i)) ? selectionColor : GetColorU32(PLANE_X + i); + colors[i + 4] = (type == MT_MOVE_SCREEN) ? selectionColor : colors[i + 4]; + } + break; + case ROTATE: + colors[0] = (type == MT_ROTATE_SCREEN) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_ROTATE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + } + break; + case SCALEU: + case SCALE: + colors[0] = (type == MT_SCALE_XYZ) ? selectionColor : IM_COL32_WHITE; + for (int i = 0; i < 3; i++) + { + colors[i + 1] = (type == (int)(MT_SCALE_X + i)) ? selectionColor : GetColorU32(DIRECTION_X + i); + } + break; + // note: this internal function is only called with three possible values for operation + default: + break; + } + } + else + { + ImU32 inactiveColor = GetColorU32(INACTIVE); + for (int i = 0; i < 7; i++) + { + colors[i] = inactiveColor; + } + } + } + + static void ComputeTripodAxisAndVisibility(const int axisIndex, vec_t& dirAxis, vec_t& dirPlaneX, vec_t& dirPlaneY, bool& belowAxisLimit, bool& belowPlaneLimit, const bool localCoordinates = false) + { + dirAxis = directionUnary[axisIndex]; + dirPlaneX = directionUnary[(axisIndex + 1) % 3]; + dirPlaneY = directionUnary[(axisIndex + 2) % 3]; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + // when using, use stored factors so the gizmo doesn't flip when we translate + belowAxisLimit = gContext.mBelowAxisLimit[axisIndex]; + belowPlaneLimit = gContext.mBelowPlaneLimit[axisIndex]; + + dirAxis *= gContext.mAxisFactor[axisIndex]; + dirPlaneX *= gContext.mAxisFactor[(axisIndex + 1) % 3]; + dirPlaneY *= gContext.mAxisFactor[(axisIndex + 2) % 3]; + } + else + { + // new method + float lenDir = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis, localCoordinates); + float lenDirMinus = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirAxis, localCoordinates); + + float lenDirPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneX, localCoordinates); + float lenDirMinusPlaneX = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneX, localCoordinates); + + float lenDirPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirPlaneY, localCoordinates); + float lenDirMinusPlaneY = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), -dirPlaneY, localCoordinates); + + // For readability + bool & allowFlip = gContext.mAllowAxisFlip; + float mulAxis = (allowFlip && lenDir < lenDirMinus&& fabsf(lenDir - lenDirMinus) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisX = (allowFlip && lenDirPlaneX < lenDirMinusPlaneX&& fabsf(lenDirPlaneX - lenDirMinusPlaneX) > FLT_EPSILON) ? -1.f : 1.f; + float mulAxisY = (allowFlip && lenDirPlaneY < lenDirMinusPlaneY&& fabsf(lenDirPlaneY - lenDirMinusPlaneY) > FLT_EPSILON) ? -1.f : 1.f; + dirAxis *= mulAxis; + dirPlaneX *= mulAxisX; + dirPlaneY *= mulAxisY; + + // for axis + float axisLengthInClipSpace = GetSegmentLengthClipSpace(makeVect(0.f, 0.f, 0.f), dirAxis * gContext.mScreenFactor, localCoordinates); + + float paraSurf = GetParallelogram(makeVect(0.f, 0.f, 0.f), dirPlaneX * gContext.mScreenFactor, dirPlaneY * gContext.mScreenFactor); + belowPlaneLimit = (paraSurf > gContext.mAxisLimit); + belowAxisLimit = (axisLengthInClipSpace > gContext.mPlaneLimit); + + // and store values + gContext.mAxisFactor[axisIndex] = mulAxis; + gContext.mAxisFactor[(axisIndex + 1) % 3] = mulAxisX; + gContext.mAxisFactor[(axisIndex + 2) % 3] = mulAxisY; + gContext.mBelowAxisLimit[axisIndex] = belowAxisLimit; + gContext.mBelowPlaneLimit[axisIndex] = belowPlaneLimit; + } + } + + static void ComputeSnap(float* value, float snap) + { + if (snap <= FLT_EPSILON) + { + return; + } + + float modulo = fmodf(*value, snap); + float moduloRatio = fabsf(modulo) / snap; + if (moduloRatio < snapTension) + { + *value -= modulo; + } + else if (moduloRatio > (1.f - snapTension)) + { + *value = *value - modulo + snap * ((*value < 0.f) ? -1.f : 1.f); + } + } + static void ComputeSnap(vec_t& value, const float* snap) + { + for (int i = 0; i < 3; i++) + { + ComputeSnap(&value[i], snap[i]); + } + } + + static float ComputeAngleOnPlan() + { + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = Normalized(gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position); + + vec_t perpendicularVector; + perpendicularVector.Cross(gContext.mRotationVectorSource, gContext.mTranslationPlan); + perpendicularVector.Normalize(); + float acosAngle = Clamp(Dot(localPos, gContext.mRotationVectorSource), -1.f, 1.f); + float angle = acosf(acosAngle); + angle *= (Dot(localPos, perpendicularVector) < 0.f) ? 1.f : -1.f; + return angle; + } + + static void DrawRotationGizmo(OPERATION op, int type) + { + if(!Intersects(op, ROTATE)) + { + return; + } + ImDrawList* drawList = gContext.mDrawList; + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, ROTATE); + + vec_t cameraToModelNormalized; + if (gContext.mIsOrthographic) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)&gContext.mViewMat); + cameraToModelNormalized = -viewInverse.v.dir; + } + else + { + cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + } + + cameraToModelNormalized.TransformVector(gContext.mModelInverse); + + gContext.mRadiusSquareCenter = screenRotateSize * gContext.mHeight; + + bool hasRSC = Intersects(op, ROTATE_SCREEN); + for (int axis = 0; axis < 3; axis++) + { + if(!Intersects(op, static_cast(ROTATE_Z >> axis))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_ROTATE_Z - axis); + const int circleMul = (hasRSC && !usingAxis ) ? 1 : 2; + + ImVec2* circlePos = (ImVec2*)alloca(sizeof(ImVec2) * (circleMul * halfCircleSegmentCount + 1)); + + float angleStart = atan2f(cameraToModelNormalized[(4 - axis) % 3], cameraToModelNormalized[(3 - axis) % 3]) + ZPI * 0.5f; + + for (int i = 0; i < circleMul * halfCircleSegmentCount + 1; i++) + { + float ng = angleStart + (float)circleMul * ZPI * ((float)i / (float)(circleMul * halfCircleSegmentCount)); + vec_t axisPos = makeVect(cosf(ng), sinf(ng), 0.f); + vec_t pos = makeVect(axisPos[axis], axisPos[(axis + 1) % 3], axisPos[(axis + 2) % 3]) * gContext.mScreenFactor * rotationDisplayFactor; + circlePos[i] = worldToPos(pos, gContext.mMVP); + } + if (!gContext.mbUsing || usingAxis) + { + drawList->AddPolyline(circlePos, circleMul* halfCircleSegmentCount + 1, colors[3 - axis], false, gContext.mStyle.RotationLineThickness); + } + + float radiusAxis = sqrtf((ImLengthSqr(worldToPos(gContext.mModel.v.position, gContext.mViewProjection) - circlePos[0]))); + if (radiusAxis > gContext.mRadiusSquareCenter) + { + gContext.mRadiusSquareCenter = radiusAxis; + } + } + if(hasRSC && (!gContext.mbUsing || type == MT_ROTATE_SCREEN)) + { + drawList->AddCircle(worldToPos(gContext.mModel.v.position, gContext.mViewProjection), gContext.mRadiusSquareCenter, colors[0], 64, gContext.mStyle.RotationOuterLineThickness); + } + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(type)) + { + ImVec2 circlePos[halfCircleSegmentCount + 1]; + + circlePos[0] = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + for (unsigned int i = 1; i < halfCircleSegmentCount + 1; i++) + { + float ng = gContext.mRotationAngle * ((float)(i - 1) / (float)(halfCircleSegmentCount - 1)); + matrix_t rotateVectorMatrix; + rotateVectorMatrix.RotationAxis(gContext.mTranslationPlan, ng); + vec_t pos; + pos.TransformPoint(gContext.mRotationVectorSource, rotateVectorMatrix); + pos *= gContext.mScreenFactor * rotationDisplayFactor; + circlePos[i] = worldToPos(pos + gContext.mModel.v.position, gContext.mViewProjection); + } + drawList->AddConvexPolyFilled(circlePos, halfCircleSegmentCount + 1, GetColorU32(ROTATION_USING_FILL)); + drawList->AddPolyline(circlePos, halfCircleSegmentCount + 1, GetColorU32(ROTATION_USING_BORDER), true, gContext.mStyle.RotationLineThickness); + + ImVec2 destinationPosOnScreen = circlePos[1]; + char tmps[512]; + ImFormatString(tmps, sizeof(tmps), rotationInfoMask[type - MT_ROTATE_X], (gContext.mRotationAngle / ZPI) * 180.f, gContext.mRotationAngle); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static void DrawHatchedAxis(const vec_t& axis) + { + if (gContext.mStyle.HatchedAxisLineThickness <= 0.0f) + { + return; + } + + for (int j = 1; j < 10; j++) + { + ImVec2 baseSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2) * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace2 = worldToPos(axis * 0.05f * (float)(j * 2 + 1) * gContext.mScreenFactor, gContext.mMVP); + gContext.mDrawList->AddLine(baseSSpace2, worldDirSSpace2, GetColorU32(HATCHED_AXIS_LINES), gContext.mStyle.HatchedAxisLineThickness); + } + } + + static void DrawScaleGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + + if(!Intersects(op, SCALE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, SCALE); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (int i = 0; i < 3; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); + if (!gContext.mbUsing || usingAxis) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVP); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + ImU32 scaleLineColor = GetColorU32(SCALE_LINE); + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, scaleLineColor, gContext.mStyle.ScaleLineThickness); + drawList->AddCircleFilled(worldDirSSpaceNoScale, gContext.mStyle.ScaleLineCircleSize, scaleLineColor); + } + + if (!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.ScaleLineThickness); + } + drawList->AddCircleFilled(worldDirSSpace, gContext.mStyle.ScaleLineCircleSize, colors[i + 1]); + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis * scaleDisplay[i]); + } + } + } + } + + // draw screen cirle + drawList->AddCircleFilled(gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type)) + { + //ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + + static void DrawScaleUniveralGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + + if (!Intersects(op, SCALEU)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, SCALEU); + + // draw + vec_t scaleDisplay = { 1.f, 1.f, 1.f, 1.f }; + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + scaleDisplay = gContext.mScale; + } + + for (int i = 0; i < 3; i++) + { + if (!Intersects(op, static_cast(SCALE_XU << i))) + { + continue; + } + const bool usingAxis = (gContext.mbUsing && type == MT_SCALE_X + i); + if (!gContext.mbUsing || usingAxis) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + //ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); + //ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale * scaleDisplay[i]) * gContext.mScreenFactor, gContext.mMVPLocal); + +#if 0 + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + drawList->AddLine(baseSSpace, worldDirSSpaceNoScale, IM_COL32(0x40, 0x40, 0x40, 0xFF), 3.f); + drawList->AddCircleFilled(worldDirSSpaceNoScale, 6.f, IM_COL32(0x40, 0x40, 0x40, 0xFF)); + } + /* + if (!hasTranslateOnAxis || gContext.mbUsing) + { + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], 3.f); + } + */ +#endif + drawList->AddCircleFilled(worldDirSSpace, 12.f, colors[i + 1]); + } + } + } + + // draw screen cirle + drawList->AddCircle(gContext.mScreenSquareCenter, 20.f, colors[0], 32, gContext.mStyle.CenterCircleSize); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(type)) + { + //ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + /*vec_t dif(destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y); + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + */ + char tmps[512]; + //vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_SCALE_X) * 3; + ImFormatString(tmps, sizeof(tmps), scaleInfoMask[type - MT_SCALE_X], scaleDisplay[translationInfoIndex[componentInfoIndex]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static void DrawTranslationGizmo(OPERATION op, int type) + { + ImDrawList* drawList = gContext.mDrawList; + if (!drawList) + { + return; + } + + if(!Intersects(op, TRANSLATE)) + { + return; + } + + // colors + ImU32 colors[7]; + ComputeColors(colors, type, TRANSLATE); + + const ImVec2 origin = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + + // draw + bool belowAxisLimit = false; + bool belowPlaneLimit = false; + for (int i = 0; i < 3; ++i) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + + if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_X + i)) + { + // draw axis + if (belowAxisLimit && Intersects(op, static_cast(TRANSLATE_X << i))) + { + ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos(dirAxis * gContext.mScreenFactor, gContext.mMVP); + + drawList->AddLine(baseSSpace, worldDirSSpace, colors[i + 1], gContext.mStyle.TranslationLineThickness); + + // Arrow head begin + ImVec2 dir(origin - worldDirSSpace); + + float d = sqrtf(ImLengthSqr(dir)); + dir /= d; // Normalize + dir *= gContext.mStyle.TranslationLineArrowSize; + + ImVec2 ortogonalDir(dir.y, -dir.x); // Perpendicular vector + ImVec2 a(worldDirSSpace + dir); + drawList->AddTriangleFilled(worldDirSSpace - dir, a + ortogonalDir, a - ortogonalDir, colors[i + 1]); + // Arrow head end + + if (gContext.mAxisFactor[i] < 0.f) + { + DrawHatchedAxis(dirAxis); + } + } + } + // draw plane + if (!gContext.mbUsing || (gContext.mbUsing && type == MT_MOVE_YZ + i)) + { + if (belowPlaneLimit && Contains(op, TRANSLATE_PLANS[i])) + { + ImVec2 screenQuadPts[4]; + for (int j = 0; j < 4; ++j) + { + vec_t cornerWorldPos = (dirPlaneX * quadUV[j * 2] + dirPlaneY * quadUV[j * 2 + 1]) * gContext.mScreenFactor; + screenQuadPts[j] = worldToPos(cornerWorldPos, gContext.mMVP); + } + drawList->AddPolyline(screenQuadPts, 4, GetColorU32(DIRECTION_X + i), true, 1.0f); + drawList->AddConvexPolyFilled(screenQuadPts, 4, colors[i + 4]); + } + } + } + + drawList->AddCircleFilled(gContext.mScreenSquareCenter, gContext.mStyle.CenterCircleSize, colors[0], 32); + + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(type)) + { + ImU32 translationLineColor = GetColorU32(TRANSLATION_LINE); + + ImVec2 sourcePosOnScreen = worldToPos(gContext.mMatrixOrigin, gContext.mViewProjection); + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + vec_t dif = { destinationPosOnScreen.x - sourcePosOnScreen.x, destinationPosOnScreen.y - sourcePosOnScreen.y, 0.f, 0.f }; + dif.Normalize(); + dif *= 5.f; + drawList->AddCircle(sourcePosOnScreen, 6.f, translationLineColor); + drawList->AddCircle(destinationPosOnScreen, 6.f, translationLineColor); + drawList->AddLine(ImVec2(sourcePosOnScreen.x + dif.x, sourcePosOnScreen.y + dif.y), ImVec2(destinationPosOnScreen.x - dif.x, destinationPosOnScreen.y - dif.y), translationLineColor, 2.f); + + char tmps[512]; + vec_t deltaInfo = gContext.mModel.v.position - gContext.mMatrixOrigin; + int componentInfoIndex = (type - MT_MOVE_X) * 3; + ImFormatString(tmps, sizeof(tmps), translationInfoMask[type - MT_MOVE_X], deltaInfo[translationInfoIndex[componentInfoIndex]], deltaInfo[translationInfoIndex[componentInfoIndex + 1]], deltaInfo[translationInfoIndex[componentInfoIndex + 2]]); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + } + + static bool CanActivate() + { + if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && !ImGui::IsAnyItemActive()) + { + return true; + } + return false; + } + + static void HandleAndDrawLocalBounds(const float* bounds, matrix_t* matrix, const float* snapValues, OPERATION operation) + { + ImGuiIO& io = ImGui::GetIO(); + ImDrawList* drawList = gContext.mDrawList; + + // compute best projection axis + vec_t axesWorldDirections[3]; + vec_t bestAxisWorldDirection = { 0.0f, 0.0f, 0.0f, 0.0f }; + int axes[3]; + unsigned int numAxes = 1; + axes[0] = gContext.mBoundsBestAxis; + int bestAxis = axes[0]; + if (!gContext.mbUsingBounds) + { + numAxes = 0; + float bestDot = 0.f; + for (int i = 0; i < 3; i++) + { + vec_t dirPlaneNormalWorld; + dirPlaneNormalWorld.TransformVector(directionUnary[i], gContext.mModelSource); + dirPlaneNormalWorld.Normalize(); + + float dt = fabsf(Dot(Normalized(gContext.mCameraEye - gContext.mModelSource.v.position), dirPlaneNormalWorld)); + if (dt >= bestDot) + { + bestDot = dt; + bestAxis = i; + bestAxisWorldDirection = dirPlaneNormalWorld; + } + + if (dt >= 0.1f) + { + axes[numAxes] = i; + axesWorldDirections[numAxes] = dirPlaneNormalWorld; + ++numAxes; + } + } + } + + if (numAxes == 0) + { + axes[0] = bestAxis; + axesWorldDirections[0] = bestAxisWorldDirection; + numAxes = 1; + } + + else if (bestAxis != axes[0]) + { + unsigned int bestIndex = 0; + for (unsigned int i = 0; i < numAxes; i++) + { + if (axes[i] == bestAxis) + { + bestIndex = i; + break; + } + } + int tempAxis = axes[0]; + axes[0] = axes[bestIndex]; + axes[bestIndex] = tempAxis; + vec_t tempDirection = axesWorldDirections[0]; + axesWorldDirections[0] = axesWorldDirections[bestIndex]; + axesWorldDirections[bestIndex] = tempDirection; + } + + for (unsigned int axisIndex = 0; axisIndex < numAxes; ++axisIndex) + { + bestAxis = axes[axisIndex]; + bestAxisWorldDirection = axesWorldDirections[axisIndex]; + + // corners + vec_t aabb[4]; + + int secondAxis = (bestAxis + 1) % 3; + int thirdAxis = (bestAxis + 2) % 3; + + for (int i = 0; i < 4; i++) + { + aabb[i][3] = aabb[i][bestAxis] = 0.f; + aabb[i][secondAxis] = bounds[secondAxis + 3 * (i >> 1)]; + aabb[i][thirdAxis] = bounds[thirdAxis + 3 * ((i >> 1) ^ (i & 1))]; + } + + // draw bounds + unsigned int anchorAlpha = gContext.mbEnable ? IM_COL32_BLACK : IM_COL32(0, 0, 0, 0x80); + + matrix_t boundsMVP = gContext.mModelSource * gContext.mViewProjection; + for (int i = 0; i < 4; i++) + { + ImVec2 worldBound1 = worldToPos(aabb[i], boundsMVP); + ImVec2 worldBound2 = worldToPos(aabb[(i + 1) % 4], boundsMVP); + if (!IsInContextRect(worldBound1) || !IsInContextRect(worldBound2)) + { + continue; + } + float boundDistance = sqrtf(ImLengthSqr(worldBound1 - worldBound2)); + int stepCount = (int)(boundDistance / 10.f); + stepCount = min(stepCount, 1000); + for (int j = 0; j < stepCount; j++) + { + float stepLength = 1.f / (float)stepCount; + float t1 = (float)j * stepLength; + float t2 = (float)j * stepLength + stepLength * 0.5f; + ImVec2 worldBoundSS1 = ImLerp(worldBound1, worldBound2, ImVec2(t1, t1)); + ImVec2 worldBoundSS2 = ImLerp(worldBound1, worldBound2, ImVec2(t2, t2)); + //drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0, 0, 0, 0) + anchorAlpha, 3.f); + drawList->AddLine(worldBoundSS1, worldBoundSS2, IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha, 2.f); + } + vec_t midPoint = (aabb[i] + aabb[(i + 1) % 4]) * 0.5f; + ImVec2 midBound = worldToPos(midPoint, boundsMVP); + static const float AnchorBigRadius = 8.f; + static const float AnchorSmallRadius = 6.f; + bool overBigAnchor = ImLengthSqr(worldBound1 - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + bool overSmallAnchor = ImLengthSqr(midBound - io.MousePos) <= (AnchorBigRadius * AnchorBigRadius); + + int type = MT_NONE; + vec_t gizmoHitProportion; + + if(Intersects(operation, TRANSLATE)) + { + type = GetMoveType(operation, &gizmoHitProportion); + } + if(Intersects(operation, ROTATE) && type == MT_NONE) + { + type = GetRotateType(operation); + } + if(Intersects(operation, SCALE) && type == MT_NONE) + { + type = GetScaleType(operation); + } + + if (type != MT_NONE) + { + overBigAnchor = false; + overSmallAnchor = false; + } + + ImU32 selectionColor = GetColorU32(SELECTION); + + unsigned int bigAnchorColor = overBigAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + unsigned int smallAnchorColor = overSmallAnchor ? selectionColor : (IM_COL32(0xAA, 0xAA, 0xAA, 0) + anchorAlpha); + + drawList->AddCircleFilled(worldBound1, AnchorBigRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(worldBound1, AnchorBigRadius - 1.2f, bigAnchorColor); + + drawList->AddCircleFilled(midBound, AnchorSmallRadius, IM_COL32_BLACK); + drawList->AddCircleFilled(midBound, AnchorSmallRadius - 1.2f, smallAnchorColor); + int oppositeIndex = (i + 2) % 4; + // big anchor on corners + if (!gContext.mbUsingBounds && gContext.mbEnable && overBigAnchor && CanActivate()) + { + gContext.mBoundsPivot.TransformPoint(aabb[(i + 2) % 4], gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(aabb[i], gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + gContext.mBoundsAxis[0] = secondAxis; + gContext.mBoundsAxis[1] = thirdAxis; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[secondAxis] = aabb[oppositeIndex][secondAxis]; + gContext.mBoundsLocalPivot[thirdAxis] = aabb[oppositeIndex][thirdAxis]; + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.mActualID; + gContext.mBoundsMatrix = gContext.mModelSource; + } + // small anchor on middle of segment + if (!gContext.mbUsingBounds && gContext.mbEnable && overSmallAnchor && CanActivate()) + { + vec_t midPointOpposite = (aabb[(i + 2) % 4] + aabb[(i + 3) % 4]) * 0.5f; + gContext.mBoundsPivot.TransformPoint(midPointOpposite, gContext.mModelSource); + gContext.mBoundsAnchor.TransformPoint(midPoint, gContext.mModelSource); + gContext.mBoundsPlan = BuildPlan(gContext.mBoundsAnchor, bestAxisWorldDirection); + gContext.mBoundsBestAxis = bestAxis; + int indices[] = { secondAxis , thirdAxis }; + gContext.mBoundsAxis[0] = indices[i % 2]; + gContext.mBoundsAxis[1] = -1; + + gContext.mBoundsLocalPivot.Set(0.f); + gContext.mBoundsLocalPivot[gContext.mBoundsAxis[0]] = aabb[oppositeIndex][indices[i % 2]];// bounds[gContext.mBoundsAxis[0]] * (((i + 1) & 2) ? 1.f : -1.f); + + gContext.mbUsingBounds = true; + gContext.mEditingID = gContext.mActualID; + gContext.mBoundsMatrix = gContext.mModelSource; + } + } + + if (gContext.mbUsingBounds && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID)) + { + matrix_t scale; + scale.SetToIdentity(); + + // compute projected mouse position on plan + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mBoundsPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute a reference and delta vectors base on mouse move + vec_t deltaVector = (newPos - gContext.mBoundsPivot).Abs(); + vec_t referenceVector = (gContext.mBoundsAnchor - gContext.mBoundsPivot).Abs(); + + // for 1 or 2 axes, compute a ratio that's used for scale and snap it based on resulting length + for (int i = 0; i < 2; i++) + { + int axisIndex1 = gContext.mBoundsAxis[i]; + if (axisIndex1 == -1) + { + continue; + } + + float ratioAxis = 1.f; + vec_t axisDir = gContext.mBoundsMatrix.component[axisIndex1].Abs(); + + float dtAxis = axisDir.Dot(referenceVector); + float boundSize = bounds[axisIndex1 + 3] - bounds[axisIndex1]; + if (dtAxis > FLT_EPSILON) + { + ratioAxis = axisDir.Dot(deltaVector) / dtAxis; + } + + if (snapValues) + { + float length = boundSize * ratioAxis; + ComputeSnap(&length, snapValues[axisIndex1]); + if (boundSize > FLT_EPSILON) + { + ratioAxis = length / boundSize; + } + } + scale.component[axisIndex1] *= ratioAxis; + } + + // transform matrix + matrix_t preScale, postScale; + preScale.Translation(-gContext.mBoundsLocalPivot); + postScale.Translation(gContext.mBoundsLocalPivot); + matrix_t res = preScale * scale * postScale * gContext.mBoundsMatrix; + *matrix = res; + + // info text + char tmps[512]; + ImVec2 destinationPosOnScreen = worldToPos(gContext.mModel.v.position, gContext.mViewProjection); + ImFormatString(tmps, sizeof(tmps), "X: %.2f Y: %.2f Z: %.2f" + , (bounds[3] - bounds[0]) * gContext.mBoundsMatrix.component[0].Length() * scale.component[0].Length() + , (bounds[4] - bounds[1]) * gContext.mBoundsMatrix.component[1].Length() * scale.component[1].Length() + , (bounds[5] - bounds[2]) * gContext.mBoundsMatrix.component[2].Length() * scale.component[2].Length() + ); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 15, destinationPosOnScreen.y + 15), GetColorU32(TEXT_SHADOW), tmps); + drawList->AddText(ImVec2(destinationPosOnScreen.x + 14, destinationPosOnScreen.y + 14), GetColorU32(TEXT), tmps); + } + + if (!io.MouseDown[0]) { + gContext.mbUsingBounds = false; + gContext.mEditingID = -1; + } + if (gContext.mbUsingBounds) + { + break; + } + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // + + static int GetScaleType(OPERATION op) + { + if (gContext.mbUsing) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, SCALE)) + { + type = MT_SCALE_XYZ; + } + + // compute + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(SCALE_X << i))) + { + continue; + } + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + dirAxis.TransformVector(gContext.mModelLocal); + dirPlaneX.TransformVector(gContext.mModelLocal); + dirPlaneY.TransformVector(gContext.mModelLocal); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModelLocal.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const float startOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.0f : 0.1f; + const float endOffset = Contains(op, static_cast(TRANSLATE_X << i)) ? 1.4f : 1.0f; + const ImVec2 posOnPlanScreen = worldToPos(posOnPlan, gContext.mViewProjection); + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * startOffset, gContext.mViewProjection); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModelLocal.v.position + dirAxis * gContext.mScreenFactor * endOffset, gContext.mViewProjection); + + vec_t closestPointOnAxis = PointOnSegment(makeVect(posOnPlanScreen), makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + + if ((closestPointOnAxis - makeVect(posOnPlanScreen)).Length() < 12.f) // pixel size + { + type = MT_SCALE_X + i; + } + } + + // universal + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Contains(op, SCALEU) && dist >= 17.0f && dist < 23.0f) + { + type = MT_SCALE_XYZ; + } + + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if (!Intersects(op, static_cast(SCALE_XU << i))) + { + continue; + } + + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit, true); + + // draw axis + if (belowAxisLimit) + { + bool hasTranslateOnAxis = Contains(op, static_cast(TRANSLATE_X << i)); + float markerScale = hasTranslateOnAxis ? 1.4f : 1.0f; + //ImVec2 baseSSpace = worldToPos(dirAxis * 0.1f * gContext.mScreenFactor, gContext.mMVPLocal); + //ImVec2 worldDirSSpaceNoScale = worldToPos(dirAxis * markerScale * gContext.mScreenFactor, gContext.mMVP); + ImVec2 worldDirSSpace = worldToPos((dirAxis * markerScale) * gContext.mScreenFactor, gContext.mMVPLocal); + + float distance = sqrtf(ImLengthSqr(worldDirSSpace - io.MousePos)); + if (distance < 12.f) + { + type = MT_SCALE_X + i; + } + } + } + return type; + } + + static int GetRotateType(OPERATION op) + { + if (gContext.mbUsing) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + vec_t deltaScreen = { io.MousePos.x - gContext.mScreenSquareCenter.x, io.MousePos.y - gContext.mScreenSquareCenter.y, 0.f, 0.f }; + float dist = deltaScreen.Length(); + if (Intersects(op, ROTATE_SCREEN) && dist >= (gContext.mRadiusSquareCenter - 4.0f) && dist < (gContext.mRadiusSquareCenter + 4.0f)) + { + type = MT_ROTATE_SCREEN; + } + + const vec_t planNormals[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir }; + + vec_t modelViewPos; + modelViewPos.TransformPoint(gContext.mModel.v.position, gContext.mViewMat); + + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + if(!Intersects(op, static_cast(ROTATE_X << i))) + { + continue; + } + // pickup plan + vec_t pickupPlan = BuildPlan(gContext.mModel.v.position, planNormals[i]); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, pickupPlan); + const vec_t intersectWorldPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t intersectViewPos; + intersectViewPos.TransformPoint(intersectWorldPos, gContext.mViewMat); + + if (ImAbs(modelViewPos.z) - ImAbs(intersectViewPos.z) < -FLT_EPSILON) + { + continue; + } + + const vec_t localPos = intersectWorldPos - gContext.mModel.v.position; + vec_t idealPosOnCircle = Normalized(localPos); + idealPosOnCircle.TransformVector(gContext.mModelInverse); + const ImVec2 idealPosOnCircleScreen = worldToPos(idealPosOnCircle * rotationDisplayFactor * gContext.mScreenFactor, gContext.mMVP); + + //gContext.mDrawList->AddCircle(idealPosOnCircleScreen, 5.f, IM_COL32_WHITE); + const ImVec2 distanceOnScreen = idealPosOnCircleScreen - io.MousePos; + + const float distance = makeVect(distanceOnScreen).Length(); + if (distance < 8.f) // pixel size + { + type = MT_ROTATE_X + i; + } + } + + return type; + } + + static int GetMoveType(OPERATION op, vec_t* gizmoHitProportion) + { + if(!Intersects(op, TRANSLATE) || gContext.mbUsing || !gContext.mbMouseOver) + { + return MT_NONE; + } + ImGuiIO& io = ImGui::GetIO(); + int type = MT_NONE; + + // screen + if (io.MousePos.x >= gContext.mScreenSquareMin.x && io.MousePos.x <= gContext.mScreenSquareMax.x && + io.MousePos.y >= gContext.mScreenSquareMin.y && io.MousePos.y <= gContext.mScreenSquareMax.y && + Contains(op, TRANSLATE)) + { + type = MT_MOVE_SCREEN; + } + + const vec_t screenCoord = makeVect(io.MousePos - ImVec2(gContext.mX, gContext.mY)); + + // compute + for (int i = 0; i < 3 && type == MT_NONE; i++) + { + vec_t dirPlaneX, dirPlaneY, dirAxis; + bool belowAxisLimit, belowPlaneLimit; + ComputeTripodAxisAndVisibility(i, dirAxis, dirPlaneX, dirPlaneY, belowAxisLimit, belowPlaneLimit); + dirAxis.TransformVector(gContext.mModel); + dirPlaneX.TransformVector(gContext.mModel); + dirPlaneY.TransformVector(gContext.mModel); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, BuildPlan(gContext.mModel.v.position, dirAxis)); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len; + + const ImVec2 axisStartOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor * 0.1f, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + const ImVec2 axisEndOnScreen = worldToPos(gContext.mModel.v.position + dirAxis * gContext.mScreenFactor, gContext.mViewProjection) - ImVec2(gContext.mX, gContext.mY); + + vec_t closestPointOnAxis = PointOnSegment(screenCoord, makeVect(axisStartOnScreen), makeVect(axisEndOnScreen)); + if ((closestPointOnAxis - screenCoord).Length() < 12.f && Intersects(op, static_cast(TRANSLATE_X << i))) // pixel size + { + type = MT_MOVE_X + i; + } + + const float dx = dirPlaneX.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + const float dy = dirPlaneY.Dot3((posOnPlan - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor)); + if (belowPlaneLimit && dx >= quadUV[0] && dx <= quadUV[4] && dy >= quadUV[1] && dy <= quadUV[3] && Contains(op, TRANSLATE_PLANS[i])) + { + type = MT_MOVE_YZ + i; + } + + if (gizmoHitProportion) + { + *gizmoHitProportion = makeVect(dx, dy, 0.f); + } + } + return type; + } + + static bool HandleTranslation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, TRANSLATE) || type != MT_NONE) + { + return false; + } + const ImGuiIO& io = ImGui::GetIO(); + const bool applyRotationLocaly = gContext.mMode == LOCAL || type == MT_MOVE_SCREEN; + bool modified = false; + + // move + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsTranslateType(gContext.mCurrentOperation)) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + const float signedLength = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + const float len = fabsf(signedLength); // near plan + const vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + + // compute delta + const vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModel.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_MOVE_X && gContext.mCurrentOperation <= MT_MOVE_Z) + { + const int axisIndex = gContext.mCurrentOperation - MT_MOVE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModel.m[axisIndex]; + const float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + } + + // snap + if (snap) + { + vec_t cumulativeDelta = gContext.mModel.v.position + delta - gContext.mMatrixOrigin; + if (applyRotationLocaly) + { + matrix_t modelSourceNormalized = gContext.mModelSource; + modelSourceNormalized.OrthoNormalize(); + matrix_t modelSourceNormalizedInverse; + modelSourceNormalizedInverse.Inverse(modelSourceNormalized); + cumulativeDelta.TransformVector(modelSourceNormalizedInverse); + ComputeSnap(cumulativeDelta, snap); + cumulativeDelta.TransformVector(modelSourceNormalized); + } + else + { + ComputeSnap(cumulativeDelta, snap); + } + delta = gContext.mMatrixOrigin + cumulativeDelta - gContext.mModel.v.position; + + } + + if (delta != gContext.mTranslationLastDelta) + { + modified = true; + } + gContext.mTranslationLastDelta = delta; + + // compute matrix & delta + matrix_t deltaMatrixTranslation; + deltaMatrixTranslation.Translation(delta); + if (deltaMatrix) + { + memcpy(deltaMatrix, deltaMatrixTranslation.m16, sizeof(float) * 16); + } + + const matrix_t res = gContext.mModelSource * deltaMatrixTranslation; + *(matrix_t*)matrix = res; + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + } + + type = gContext.mCurrentOperation; + } + else + { + // find new possible way to move + vec_t gizmoHitProportion; + type = GetMoveType(op, &gizmoHitProportion); + if (type != MT_NONE) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + vec_t movePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, + -gContext.mCameraDir }; + + vec_t cameraToModelNormalized = Normalized(gContext.mModel.v.position - gContext.mCameraEye); + for (unsigned int i = 0; i < 3; i++) + { + vec_t orthoVector = Cross(movePlanNormal[i], cameraToModelNormalized); + movePlanNormal[i].Cross(orthoVector); + movePlanNormal[i].Normalize(); + } + // pickup plan + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_MOVE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + } + } + return modified; + } + + static bool HandleScale(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if((!Intersects(op, SCALE) && !Intersects(op, SCALEU)) || type != MT_NONE || !gContext.mbMouseOver) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool modified = false; + + if (!gContext.mbUsing) + { + // find new possible way to scale + type = GetScaleType(op); + if (type != MT_NONE) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + const vec_t movePlanNormal[] = { gContext.mModel.v.up, gContext.mModel.v.dir, gContext.mModel.v.right, gContext.mModel.v.dir, gContext.mModel.v.up, gContext.mModel.v.right, -gContext.mCameraDir }; + // pickup plan + + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, movePlanNormal[type - MT_SCALE_X]); + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + gContext.mTranslationPlanOrigin = gContext.mRayOrigin + gContext.mRayVector * len; + gContext.mMatrixOrigin = gContext.mModel.v.position; + gContext.mScale.Set(1.f, 1.f, 1.f); + gContext.mRelativeOrigin = (gContext.mTranslationPlanOrigin - gContext.mModel.v.position) * (1.f / gContext.mScreenFactor); + gContext.mScaleValueOrigin = makeVect(gContext.mModelSource.v.right.Length(), gContext.mModelSource.v.up.Length(), gContext.mModelSource.v.dir.Length()); + gContext.mSaveMousePosx = io.MousePos.x; + } + } + // scale + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsScaleType(gContext.mCurrentOperation)) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t newPos = gContext.mRayOrigin + gContext.mRayVector * len; + vec_t newOrigin = newPos - gContext.mRelativeOrigin * gContext.mScreenFactor; + vec_t delta = newOrigin - gContext.mModelLocal.v.position; + + // 1 axis constraint + if (gContext.mCurrentOperation >= MT_SCALE_X && gContext.mCurrentOperation <= MT_SCALE_Z) + { + int axisIndex = gContext.mCurrentOperation - MT_SCALE_X; + const vec_t& axisValue = *(vec_t*)&gContext.mModelLocal.m[axisIndex]; + float lengthOnAxis = Dot(axisValue, delta); + delta = axisValue * lengthOnAxis; + + vec_t baseVector = gContext.mTranslationPlanOrigin - gContext.mModelLocal.v.position; + float ratio = Dot(axisValue, baseVector + delta) / Dot(axisValue, baseVector); + + gContext.mScale[axisIndex] = max(ratio, 0.001f); + } + else + { + float scaleDelta = (io.MousePos.x - gContext.mSaveMousePosx) * 0.01f; + gContext.mScale.Set(max(1.f + scaleDelta, 0.001f)); + } + + // snap + if (snap) + { + float scaleSnap[] = { snap[0], snap[0], snap[0] }; + ComputeSnap(gContext.mScale, scaleSnap); + } + + // no 0 allowed + for (int i = 0; i < 3; i++) + gContext.mScale[i] = max(gContext.mScale[i], 0.001f); + + if (gContext.mScaleLast != gContext.mScale) + { + modified = true; + } + gContext.mScaleLast = gContext.mScale; + + // compute matrix & delta + matrix_t deltaMatrixScale; + deltaMatrixScale.Scale(gContext.mScale * gContext.mScaleValueOrigin); + + matrix_t res = deltaMatrixScale * gContext.mModelLocal; + *(matrix_t*)matrix = res; + + if (deltaMatrix) + { + vec_t deltaScale = gContext.mScale * gContext.mScaleValueOrigin; + + vec_t originalScaleDivider; + originalScaleDivider.x = 1 / gContext.mModelScaleOrigin.x; + originalScaleDivider.y = 1 / gContext.mModelScaleOrigin.y; + originalScaleDivider.z = 1 / gContext.mModelScaleOrigin.z; + + deltaScale = deltaScale * originalScaleDivider; + + deltaMatrixScale.Scale(deltaScale); + memcpy(deltaMatrix, deltaMatrixScale.m16, sizeof(float) * 16); + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mScale.Set(1.f, 1.f, 1.f); + } + + type = gContext.mCurrentOperation; + } + return modified; + } + + static bool HandleRotation(float* matrix, float* deltaMatrix, OPERATION op, int& type, const float* snap) + { + if(!Intersects(op, ROTATE) || type != MT_NONE || !gContext.mbMouseOver) + { + return false; + } + ImGuiIO& io = ImGui::GetIO(); + bool applyRotationLocaly = gContext.mMode == LOCAL; + bool modified = false; + + if (!gContext.mbUsing) + { + type = GetRotateType(op); + + if (type != MT_NONE) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + } + + if (type == MT_ROTATE_SCREEN) + { + applyRotationLocaly = true; + } + + if (CanActivate() && type != MT_NONE) + { + gContext.mbUsing = true; + gContext.mEditingID = gContext.mActualID; + gContext.mCurrentOperation = type; + const vec_t rotatePlanNormal[] = { gContext.mModel.v.right, gContext.mModel.v.up, gContext.mModel.v.dir, -gContext.mCameraDir }; + // pickup plan + if (applyRotationLocaly) + { + gContext.mTranslationPlan = BuildPlan(gContext.mModel.v.position, rotatePlanNormal[type - MT_ROTATE_X]); + } + else + { + gContext.mTranslationPlan = BuildPlan(gContext.mModelSource.v.position, directionUnary[type - MT_ROTATE_X]); + } + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, gContext.mTranslationPlan); + vec_t localPos = gContext.mRayOrigin + gContext.mRayVector * len - gContext.mModel.v.position; + gContext.mRotationVectorSource = Normalized(localPos); + gContext.mRotationAngleOrigin = ComputeAngleOnPlan(); + } + } + + // rotation + if (gContext.mbUsing && (gContext.mActualID == -1 || gContext.mActualID == gContext.mEditingID) && IsRotateType(gContext.mCurrentOperation)) + { +#if IMGUI_VERSION_NUM >= 18723 + ImGui::SetNextFrameWantCaptureMouse(true); +#else + ImGui::CaptureMouseFromApp(); +#endif + gContext.mRotationAngle = ComputeAngleOnPlan(); + if (snap) + { + float snapInRadian = snap[0] * DEG2RAD; + ComputeSnap(&gContext.mRotationAngle, snapInRadian); + } + vec_t rotationAxisLocalSpace; + + rotationAxisLocalSpace.TransformVector(makeVect(gContext.mTranslationPlan.x, gContext.mTranslationPlan.y, gContext.mTranslationPlan.z, 0.f), gContext.mModelInverse); + rotationAxisLocalSpace.Normalize(); + + matrix_t deltaRotation; + deltaRotation.RotationAxis(rotationAxisLocalSpace, gContext.mRotationAngle - gContext.mRotationAngleOrigin); + if (gContext.mRotationAngle != gContext.mRotationAngleOrigin) + { + modified = true; + } + gContext.mRotationAngleOrigin = gContext.mRotationAngle; + + matrix_t scaleOrigin; + scaleOrigin.Scale(gContext.mModelScaleOrigin); + + if (applyRotationLocaly) + { + *(matrix_t*)matrix = scaleOrigin * deltaRotation * gContext.mModelLocal; + } + else + { + matrix_t res = gContext.mModelSource; + res.v.position.Set(0.f); + + *(matrix_t*)matrix = res * deltaRotation; + ((matrix_t*)matrix)->v.position = gContext.mModelSource.v.position; + } + + if (deltaMatrix) + { + *(matrix_t*)deltaMatrix = gContext.mModelInverse * deltaRotation * gContext.mModel; + } + + if (!io.MouseDown[0]) + { + gContext.mbUsing = false; + gContext.mEditingID = -1; + } + type = gContext.mCurrentOperation; + } + return modified; + } + + void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale) + { + matrix_t mat = *(matrix_t*)matrix; + + scale[0] = mat.v.right.Length(); + scale[1] = mat.v.up.Length(); + scale[2] = mat.v.dir.Length(); + + mat.OrthoNormalize(); + + rotation[0] = RAD2DEG * atan2f(mat.m[1][2], mat.m[2][2]); + rotation[1] = RAD2DEG * atan2f(-mat.m[0][2], sqrtf(mat.m[1][2] * mat.m[1][2] + mat.m[2][2] * mat.m[2][2])); + rotation[2] = RAD2DEG * atan2f(mat.m[0][1], mat.m[0][0]); + + translation[0] = mat.v.position.x; + translation[1] = mat.v.position.y; + translation[2] = mat.v.position.z; + } + + void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix) + { + matrix_t& mat = *(matrix_t*)matrix; + + matrix_t rot[3]; + for (int i = 0; i < 3; i++) + { + rot[i].RotationAxis(directionUnary[i], rotation[i] * DEG2RAD); + } + + mat = rot[0] * rot[1] * rot[2]; + + float validScale[3]; + for (int i = 0; i < 3; i++) + { + if (fabsf(scale[i]) < FLT_EPSILON) + { + validScale[i] = 0.001f; + } + else + { + validScale[i] = scale[i]; + } + } + mat.v.right *= validScale[0]; + mat.v.up *= validScale[1]; + mat.v.dir *= validScale[2]; + mat.v.position.Set(translation[0], translation[1], translation[2], 1.f); + } + + void SetID(int id) + { + gContext.mActualID = id; + } + + void AllowAxisFlip(bool value) + { + gContext.mAllowAxisFlip = value; + } + + void SetAxisLimit(float value) + { + gContext.mAxisLimit=value; + } + + void SetPlaneLimit(float value) + { + gContext.mPlaneLimit = value; + } + + bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix, const float* snap, const float* localBounds, const float* boundsSnap) + { + // Scale is always local or matrix will be skewed when applying world scale or oriented matrix + ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode); + + // set delta to identity + if (deltaMatrix) + { + ((matrix_t*)deltaMatrix)->SetToIdentity(); + } + + // behind camera + vec_t camSpacePosition; + camSpacePosition.TransformPoint(makeVect(0.f, 0.f, 0.f), gContext.mMVP); + if (!gContext.mIsOrthographic && camSpacePosition.z < 0.001f && !gContext.mbUsing) + { + return false; + } + + // -- + int type = MT_NONE; + bool manipulated = false; + if (gContext.mbEnable) + { + if (!gContext.mbUsingBounds) + { + manipulated = HandleTranslation(matrix, deltaMatrix, operation, type, snap) || + HandleScale(matrix, deltaMatrix, operation, type, snap) || + HandleRotation(matrix, deltaMatrix, operation, type, snap); + } + } + + if (localBounds && !gContext.mbUsing) + { + HandleAndDrawLocalBounds(localBounds, (matrix_t*)matrix, boundsSnap, operation); + } + + gContext.mOperation = operation; + if (!gContext.mbUsingBounds) + { + DrawRotationGizmo(operation, type); + DrawTranslationGizmo(operation, type); + DrawScaleGizmo(operation, type); + DrawScaleUniveralGizmo(operation, type); + } + return manipulated; + } + + void SetGizmoSizeClipSpace(float value) + { + gContext.mGizmoSizeClipSpace = value; + } + + /////////////////////////////////////////////////////////////////////////////////////////////////// + void ComputeFrustumPlanes(vec_t* frustum, const float* clip) + { + frustum[0].x = clip[3] - clip[0]; + frustum[0].y = clip[7] - clip[4]; + frustum[0].z = clip[11] - clip[8]; + frustum[0].w = clip[15] - clip[12]; + + frustum[1].x = clip[3] + clip[0]; + frustum[1].y = clip[7] + clip[4]; + frustum[1].z = clip[11] + clip[8]; + frustum[1].w = clip[15] + clip[12]; + + frustum[2].x = clip[3] + clip[1]; + frustum[2].y = clip[7] + clip[5]; + frustum[2].z = clip[11] + clip[9]; + frustum[2].w = clip[15] + clip[13]; + + frustum[3].x = clip[3] - clip[1]; + frustum[3].y = clip[7] - clip[5]; + frustum[3].z = clip[11] - clip[9]; + frustum[3].w = clip[15] - clip[13]; + + frustum[4].x = clip[3] - clip[2]; + frustum[4].y = clip[7] - clip[6]; + frustum[4].z = clip[11] - clip[10]; + frustum[4].w = clip[15] - clip[14]; + + frustum[5].x = clip[3] + clip[2]; + frustum[5].y = clip[7] + clip[6]; + frustum[5].z = clip[11] + clip[10]; + frustum[5].w = clip[15] + clip[14]; + + for (int i = 0; i < 6; i++) + { + frustum[i].Normalize(); + } + } + + void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount) + { + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + struct CubeFace + { + float z; + ImVec2 faceCoordsScreen[4]; + ImU32 color; + }; + CubeFace* faces = (CubeFace*)_malloca(sizeof(CubeFace) * matrixCount * 6); + + if (!faces) + { + return; + } + + vec_t frustum[6]; + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + ComputeFrustumPlanes(frustum, viewProjection.m16); + + int cubeFaceCount = 0; + for (int cube = 0; cube < matrixCount; cube++) + { + const float* matrix = &matrices[cube * 16]; + + matrix_t res = *(matrix_t*)matrix * *(matrix_t*)view * *(matrix_t*)projection; + + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + + const vec_t faceCoords[4] = { directionUnary[normalIndex] + directionUnary[perpXIndex] + directionUnary[perpYIndex], + directionUnary[normalIndex] + directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] - directionUnary[perpYIndex], + directionUnary[normalIndex] - directionUnary[perpXIndex] + directionUnary[perpYIndex], + }; + + // clipping + /* + bool skipFace = false; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + vec_t camSpacePosition; + camSpacePosition.TransformPoint(faceCoords[iCoord] * 0.5f * invert, res); + if (camSpacePosition.z < 0.001f) + { + skipFace = true; + break; + } + } + if (skipFace) + { + continue; + } + */ + vec_t centerPosition, centerPositionVP; + centerPosition.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, *(matrix_t*)matrix); + centerPositionVP.TransformPoint(directionUnary[normalIndex] * 0.5f * invert, res); + + bool inFrustum = true; + for (int iFrustum = 0; iFrustum < 6; iFrustum++) + { + float dist = DistanceToPlane(centerPosition, frustum[iFrustum]); + if (dist < 0.f) + { + inFrustum = false; + break; + } + } + + if (!inFrustum) + { + continue; + } + CubeFace& cubeFace = faces[cubeFaceCount]; + + // 3D->2D + //ImVec2 faceCoordsScreen[4]; + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + cubeFace.faceCoordsScreen[iCoord] = worldToPos(faceCoords[iCoord] * 0.5f * invert, res); + } + + ImU32 directionColor = GetColorU32(DIRECTION_X + normalIndex); + cubeFace.color = directionColor | IM_COL32(0x80, 0x80, 0x80, 0); + + cubeFace.z = centerPositionVP.z / centerPositionVP.w; + cubeFaceCount++; + } + } + qsort(faces, cubeFaceCount, sizeof(CubeFace), [](void const* _a, void const* _b) { + CubeFace* a = (CubeFace*)_a; + CubeFace* b = (CubeFace*)_b; + if (a->z < b->z) + { + return 1; + } + return -1; + }); + // draw face with lighter color + for (int iFace = 0; iFace < cubeFaceCount; iFace++) + { + const CubeFace& cubeFace = faces[iFace]; + gContext.mDrawList->AddConvexPolyFilled(cubeFace.faceCoordsScreen, 4, cubeFace.color); + } + + _freea(faces); + } + + void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize) + { + matrix_t viewProjection = *(matrix_t*)view * *(matrix_t*)projection; + vec_t frustum[6]; + ComputeFrustumPlanes(frustum, viewProjection.m16); + matrix_t res = *(matrix_t*)matrix * viewProjection; + + for (float f = -gridSize; f <= gridSize; f += 1.f) + { + for (int dir = 0; dir < 2; dir++) + { + vec_t ptA = makeVect(dir ? -gridSize : f, 0.f, dir ? f : -gridSize); + vec_t ptB = makeVect(dir ? gridSize : f, 0.f, dir ? f : gridSize); + bool visible = true; + for (int i = 0; i < 6; i++) + { + float dA = DistanceToPlane(ptA, frustum[i]); + float dB = DistanceToPlane(ptB, frustum[i]); + if (dA < 0.f && dB < 0.f) + { + visible = false; + break; + } + if (dA > 0.f && dB > 0.f) + { + continue; + } + if (dA < 0.f) + { + float len = fabsf(dA - dB); + float t = fabsf(dA) / len; + ptA.Lerp(ptB, t); + } + if (dB < 0.f) + { + float len = fabsf(dB - dA); + float t = fabsf(dB) / len; + ptB.Lerp(ptA, t); + } + } + if (visible) + { + ImU32 col = IM_COL32(0x80, 0x80, 0x80, 0xFF); + col = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? IM_COL32(0x90, 0x90, 0x90, 0xFF) : col; + col = (fabsf(f) < FLT_EPSILON) ? IM_COL32(0x40, 0x40, 0x40, 0xFF): col; + + float thickness = 1.f; + thickness = (fmodf(fabsf(f), 10.f) < FLT_EPSILON) ? 1.5f : thickness; + thickness = (fabsf(f) < FLT_EPSILON) ? 2.3f : thickness; + + gContext.mDrawList->AddLine(worldToPos(ptA, res), worldToPos(ptB, res), col, thickness); + } + } + } + } + + void ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor) + { + // Scale is always local or matrix will be skewed when applying world scale or oriented matrix + ComputeContext(view, projection, matrix, (operation & SCALE) ? LOCAL : mode); + ViewManipulate(view, length, position, size, backgroundColor); + } + + void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor) + { + static bool isDraging = false; + static bool isClicking = false; + static bool isInside = false; + static vec_t interpolationUp; + static vec_t interpolationDir; + static int interpolationFrames = 0; + const vec_t referenceUp = makeVect(0.f, 1.f, 0.f); + + matrix_t svgView, svgProjection; + svgView = gContext.mViewMat; + svgProjection = gContext.mProjectionMat; + + ImGuiIO& io = ImGui::GetIO(); + gContext.mDrawList->AddRectFilled(position, position + size, backgroundColor); + matrix_t viewInverse; + viewInverse.Inverse(*(matrix_t*)view); + + const vec_t camTarget = viewInverse.v.position - viewInverse.v.dir * length; + + // view/projection matrices + const float distance = 3.f; + matrix_t cubeProjection, cubeView; + float fov = acosf(distance / (sqrtf(distance * distance + 3.f))) * RAD2DEG; + Perspective(fov / sqrtf(2.f), size.x / size.y, 0.01f, 1000.f, cubeProjection.m16); + + vec_t dir = makeVect(viewInverse.m[2][0], viewInverse.m[2][1], viewInverse.m[2][2]); + vec_t up = makeVect(viewInverse.m[1][0], viewInverse.m[1][1], viewInverse.m[1][2]); + vec_t eye = dir * distance; + vec_t zero = makeVect(0.f, 0.f); + LookAt(&eye.x, &zero.x, &up.x, cubeView.m16); + + // set context + gContext.mViewMat = cubeView; + gContext.mProjectionMat = cubeProjection; + ComputeCameraRay(gContext.mRayOrigin, gContext.mRayVector, position, size); + + const matrix_t res = cubeView * cubeProjection; + + // panels + static const ImVec2 panelPosition[9] = { ImVec2(0.75f,0.75f), ImVec2(0.25f, 0.75f), ImVec2(0.f, 0.75f), + ImVec2(0.75f, 0.25f), ImVec2(0.25f, 0.25f), ImVec2(0.f, 0.25f), + ImVec2(0.75f, 0.f), ImVec2(0.25f, 0.f), ImVec2(0.f, 0.f) }; + + static const ImVec2 panelSize[9] = { ImVec2(0.25f,0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f), + ImVec2(0.25f, 0.5f), ImVec2(0.5f, 0.5f), ImVec2(0.25f, 0.5f), + ImVec2(0.25f, 0.25f), ImVec2(0.5f, 0.25f), ImVec2(0.25f, 0.25f) }; + + // tag faces + bool boxes[27]{}; + static int overBox = -1; + for (int iPass = 0; iPass < 2; iPass++) + { + for (int iFace = 0; iFace < 6; iFace++) + { + const int normalIndex = (iFace % 3); + const int perpXIndex = (normalIndex + 1) % 3; + const int perpYIndex = (normalIndex + 2) % 3; + const float invert = (iFace > 2) ? -1.f : 1.f; + const vec_t indexVectorX = directionUnary[perpXIndex] * invert; + const vec_t indexVectorY = directionUnary[perpYIndex] * invert; + const vec_t boxOrigin = directionUnary[normalIndex] * -invert - indexVectorX - indexVectorY; + + // plan local space + const vec_t n = directionUnary[normalIndex] * invert; + vec_t viewSpaceNormal = n; + vec_t viewSpacePoint = n * 0.5f; + viewSpaceNormal.TransformVector(cubeView); + viewSpaceNormal.Normalize(); + viewSpacePoint.TransformPoint(cubeView); + const vec_t viewSpaceFacePlan = BuildPlan(viewSpacePoint, viewSpaceNormal); + + // back face culling + if (viewSpaceFacePlan.w > 0.f) + { + continue; + } + + const vec_t facePlan = BuildPlan(n * 0.5f, n); + + const float len = IntersectRayPlane(gContext.mRayOrigin, gContext.mRayVector, facePlan); + vec_t posOnPlan = gContext.mRayOrigin + gContext.mRayVector * len - (n * 0.5f); + + float localx = Dot(directionUnary[perpXIndex], posOnPlan) * invert + 0.5f; + float localy = Dot(directionUnary[perpYIndex], posOnPlan) * invert + 0.5f; + + // panels + const vec_t dx = directionUnary[perpXIndex]; + const vec_t dy = directionUnary[perpYIndex]; + const vec_t origin = directionUnary[normalIndex] - dx - dy; + for (int iPanel = 0; iPanel < 9; iPanel++) + { + vec_t boxCoord = boxOrigin + indexVectorX * float(iPanel % 3) + indexVectorY * float(iPanel / 3) + makeVect(1.f, 1.f, 1.f); + const ImVec2 p = panelPosition[iPanel] * 2.f; + const ImVec2 s = panelSize[iPanel] * 2.f; + ImVec2 faceCoordsScreen[4]; + vec_t panelPos[4] = { dx * p.x + dy * p.y, + dx * p.x + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * (p.y + s.y), + dx * (p.x + s.x) + dy * p.y }; + + for (unsigned int iCoord = 0; iCoord < 4; iCoord++) + { + faceCoordsScreen[iCoord] = worldToPos((panelPos[iCoord] + origin) * 0.5f * invert, res, position, size); + } + + const ImVec2 panelCorners[2] = { panelPosition[iPanel], panelPosition[iPanel] + panelSize[iPanel] }; + bool insidePanel = localx > panelCorners[0].x && localx < panelCorners[1].x && localy > panelCorners[0].y && localy < panelCorners[1].y; + int boxCoordInt = int(boxCoord.x * 9.f + boxCoord.y * 3.f + boxCoord.z); + IM_ASSERT(boxCoordInt < 27); + boxes[boxCoordInt] |= insidePanel && (!isDraging) && gContext.mbMouseOver; + + // draw face with lighter color + if (iPass) + { + ImU32 directionColor = GetColorU32(DIRECTION_X + normalIndex); + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, (directionColor | IM_COL32(0x80, 0x80, 0x80, 0x80)) | (isInside ? IM_COL32(0x08, 0x08, 0x08, 0) : 0)); + if (boxes[boxCoordInt]) + { + gContext.mDrawList->AddConvexPolyFilled(faceCoordsScreen, 4, IM_COL32(0xF0, 0xA0, 0x60, 0x80)); + + if (io.MouseDown[0] && !isClicking && !isDraging && GImGui->ActiveId == 0) { + overBox = boxCoordInt; + isClicking = true; + isDraging = true; + } + } + } + } + } + } + if (interpolationFrames) + { + interpolationFrames--; + vec_t newDir = viewInverse.v.dir; + newDir.Lerp(interpolationDir, 0.2f); + newDir.Normalize(); + + vec_t newUp = viewInverse.v.up; + newUp.Lerp(interpolationUp, 0.3f); + newUp.Normalize(); + newUp = interpolationUp; + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &newUp.x, view); + } + isInside = gContext.mbMouseOver && ImRect(position, position + size).Contains(io.MousePos); + + if (io.MouseDown[0] && (fabsf(io.MouseDelta[0]) || fabsf(io.MouseDelta[1])) && isClicking) + { + isClicking = false; + } + + if (!io.MouseDown[0]) + { + if (isClicking) + { + // apply new view direction + int cx = overBox / 9; + int cy = (overBox - cx * 9) / 3; + int cz = overBox % 3; + interpolationDir = makeVect(1.f - (float)cx, 1.f - (float)cy, 1.f - (float)cz); + interpolationDir.Normalize(); + + if (fabsf(Dot(interpolationDir, referenceUp)) > 1.0f - 0.01f) + { + vec_t right = viewInverse.v.right; + if (fabsf(right.x) > fabsf(right.z)) + { + right.z = 0.f; + } + else + { + right.x = 0.f; + } + right.Normalize(); + interpolationUp = Cross(interpolationDir, right); + interpolationUp.Normalize(); + } + else + { + interpolationUp = referenceUp; + } + interpolationFrames = 40; + + } + isClicking = false; + isDraging = false; + } + + + if (isDraging) + { + matrix_t rx, ry, roll; + + rx.RotationAxis(referenceUp, -io.MouseDelta.x * 0.01f); + ry.RotationAxis(viewInverse.v.right, -io.MouseDelta.y * 0.01f); + + roll = rx * ry; + + vec_t newDir = viewInverse.v.dir; + newDir.TransformVector(roll); + newDir.Normalize(); + + // clamp + vec_t planDir = Cross(viewInverse.v.right, referenceUp); + planDir.y = 0.f; + planDir.Normalize(); + float dt = Dot(planDir, newDir); + if (dt < 0.0f) + { + newDir += planDir * dt; + newDir.Normalize(); + } + + vec_t newEye = camTarget + newDir * length; + LookAt(&newEye.x, &camTarget.x, &referenceUp.x, view); + } + + // restore view/projection because it was used to compute ray + ComputeContext(svgView.m16, svgProjection.m16, gContext.mModelSource.m16, gContext.mMode); + } +}; diff --git a/libs/imguizmo/ImGuizmo.h b/libs/imguizmo/ImGuizmo.h new file mode 100644 index 0000000..a755d4e --- /dev/null +++ b/libs/imguizmo/ImGuizmo.h @@ -0,0 +1,272 @@ +// https://github.com/CedricGuillemet/ImGuizmo +// v 1.89 WIP +// +// The MIT License(MIT) +// +// Copyright(c) 2021 Cedric Guillemet +// +// 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. +// +// ------------------------------------------------------------------------------------------- +// History : +// 2019/11/03 View gizmo +// 2016/09/11 Behind camera culling. Scaling Delta matrix not multiplied by source matrix scales. local/world rotation and translation fixed. Display message is incorrect (X: ... Y:...) in local mode. +// 2016/09/09 Hatched negative axis. Snapping. Documentation update. +// 2016/09/04 Axis switch and translation plan autohiding. Scale transform stability improved +// 2016/09/01 Mogwai changed to Manipulate. Draw debug cube. Fixed inverted scale. Mixing scale and translation/rotation gives bad results. +// 2016/08/31 First version +// +// ------------------------------------------------------------------------------------------- +// Future (no order): +// +// - Multi view +// - display rotation/translation/scale infos in local/world space and not only local +// - finish local/world matrix application +// - OPERATION as bitmask +// +// ------------------------------------------------------------------------------------------- +// Example +#if 0 +void EditTransform(const Camera& camera, matrix_t& matrix) +{ + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD); + if (ImGui::IsKeyPressed(90)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(69)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(82)) // r Key + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(matrix.m16, matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, matrix.m16); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + static bool useSnap(false); + if (ImGui::IsKeyPressed(83)) + useSnap = !useSnap; + ImGui::Checkbox("", &useSnap); + ImGui::SameLine(); + vec_t snap; + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + snap = config.mSnapTranslation; + ImGui::InputFloat3("Snap", &snap.x); + break; + case ImGuizmo::ROTATE: + snap = config.mSnapRotation; + ImGui::InputFloat("Angle Snap", &snap.x); + break; + case ImGuizmo::SCALE: + snap = config.mSnapScale; + ImGui::InputFloat("Scale Snap", &snap.x); + break; + } + ImGuiIO& io = ImGui::GetIO(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + ImGuizmo::Manipulate(camera.mView.m16, camera.mProjection.m16, mCurrentGizmoOperation, mCurrentGizmoMode, matrix.m16, NULL, useSnap ? &snap.x : NULL); +} +#endif +#pragma once + +#ifdef USE_IMGUI_API +#include "imconfig.h" +#endif +#ifndef IMGUI_API +#define IMGUI_API +#endif + +#ifndef IMGUIZMO_NAMESPACE +#define IMGUIZMO_NAMESPACE ImGuizmo +#endif + +namespace IMGUIZMO_NAMESPACE +{ + // call inside your own window and before Manipulate() in order to draw gizmo to that window. + // Or pass a specific ImDrawList to draw to (e.g. ImGui::GetForegroundDrawList()). + IMGUI_API void SetDrawlist(ImDrawList* drawlist = nullptr); + + // call BeginFrame right after ImGui_XXXX_NewFrame(); + IMGUI_API void BeginFrame(); + + // this is necessary because when imguizmo is compiled into a dll, and imgui into another + // globals are not shared between them. + // More details at https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam + // expose method to set imgui context + IMGUI_API void SetImGuiContext(ImGuiContext* ctx); + + // return true if mouse cursor is over any gizmo control (axis, plan or screen component) + IMGUI_API bool IsOver(); + + // return true if mouse IsOver or if the gizmo is in moving state + IMGUI_API bool IsUsing(); + + // return true if any gizmo is in moving state + IMGUI_API bool IsUsingAny(); + + // enable/disable the gizmo. Stay in the state until next call to Enable. + // gizmo is rendered with gray half transparent color when disabled + IMGUI_API void Enable(bool enable); + + // helper functions for manualy editing translation/rotation/scale with an input float + // translation, rotation and scale float points to 3 floats each + // Angles are in degrees (more suitable for human editing) + // example: + // float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + // ImGuizmo::DecomposeMatrixToComponents(gizmoMatrix.m16, matrixTranslation, matrixRotation, matrixScale); + // ImGui::InputFloat3("Tr", matrixTranslation, 3); + // ImGui::InputFloat3("Rt", matrixRotation, 3); + // ImGui::InputFloat3("Sc", matrixScale, 3); + // ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, gizmoMatrix.m16); + // + // These functions have some numerical stability issues for now. Use with caution. + IMGUI_API void DecomposeMatrixToComponents(const float* matrix, float* translation, float* rotation, float* scale); + IMGUI_API void RecomposeMatrixFromComponents(const float* translation, const float* rotation, const float* scale, float* matrix); + + IMGUI_API void SetRect(float x, float y, float width, float height); + // default is false + IMGUI_API void SetOrthographic(bool isOrthographic); + + // Render a cube with face color corresponding to face normal. Usefull for debug/tests + IMGUI_API void DrawCubes(const float* view, const float* projection, const float* matrices, int matrixCount); + IMGUI_API void DrawGrid(const float* view, const float* projection, const float* matrix, const float gridSize); + + // call it when you want a gizmo + // Needs view and projection matrices. + // matrix parameter is the source matrix (where will be gizmo be drawn) and might be transformed by the function. Return deltaMatrix is optional + // translation is applied in world space + enum OPERATION + { + TRANSLATE_X = (1u << 0), + TRANSLATE_Y = (1u << 1), + TRANSLATE_Z = (1u << 2), + ROTATE_X = (1u << 3), + ROTATE_Y = (1u << 4), + ROTATE_Z = (1u << 5), + ROTATE_SCREEN = (1u << 6), + SCALE_X = (1u << 7), + SCALE_Y = (1u << 8), + SCALE_Z = (1u << 9), + BOUNDS = (1u << 10), + SCALE_XU = (1u << 11), + SCALE_YU = (1u << 12), + SCALE_ZU = (1u << 13), + + TRANSLATE = TRANSLATE_X | TRANSLATE_Y | TRANSLATE_Z, + ROTATE = ROTATE_X | ROTATE_Y | ROTATE_Z | ROTATE_SCREEN, + SCALE = SCALE_X | SCALE_Y | SCALE_Z, + SCALEU = SCALE_XU | SCALE_YU | SCALE_ZU, // universal + UNIVERSAL = TRANSLATE | ROTATE | SCALEU + }; + + inline OPERATION operator|(OPERATION lhs, OPERATION rhs) + { + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + enum MODE + { + LOCAL, + WORLD + }; + + IMGUI_API bool Manipulate(const float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float* deltaMatrix = NULL, const float* snap = NULL, const float* localBounds = NULL, const float* boundsSnap = NULL); + // + // Please note that this cubeview is patented by Autodesk : https://patents.google.com/patent/US7782319B2/en + // It seems to be a defensive patent in the US. I don't think it will bring troubles using it as + // other software are using the same mechanics. But just in case, you are now warned! + // + IMGUI_API void ViewManipulate(float* view, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor); + + // use this version if you did not call Manipulate before and you are just using ViewManipulate + IMGUI_API void ViewManipulate(float* view, const float* projection, OPERATION operation, MODE mode, float* matrix, float length, ImVec2 position, ImVec2 size, ImU32 backgroundColor); + + IMGUI_API void SetID(int id); + + // return true if the cursor is over the operation's gizmo + IMGUI_API bool IsOver(OPERATION op); + IMGUI_API void SetGizmoSizeClipSpace(float value); + + // Allow axis to flip + // When true (default), the guizmo axis flip for better visibility + // When false, they always stay along the positive world/local axis + IMGUI_API void AllowAxisFlip(bool value); + + // Configure the limit where axis are hidden + IMGUI_API void SetAxisLimit(float value); + // Configure the limit where planes are hiden + IMGUI_API void SetPlaneLimit(float value); + + enum COLOR + { + DIRECTION_X, // directionColor[0] + DIRECTION_Y, // directionColor[1] + DIRECTION_Z, // directionColor[2] + PLANE_X, // planeColor[0] + PLANE_Y, // planeColor[1] + PLANE_Z, // planeColor[2] + SELECTION, // selectionColor + INACTIVE, // inactiveColor + TRANSLATION_LINE, // translationLineColor + SCALE_LINE, + ROTATION_USING_BORDER, + ROTATION_USING_FILL, + HATCHED_AXIS_LINES, + TEXT, + TEXT_SHADOW, + COUNT + }; + + struct Style + { + IMGUI_API Style(); + + float TranslationLineThickness; // Thickness of lines for translation gizmo + float TranslationLineArrowSize; // Size of arrow at the end of lines for translation gizmo + float RotationLineThickness; // Thickness of lines for rotation gizmo + float RotationOuterLineThickness; // Thickness of line surrounding the rotation gizmo + float ScaleLineThickness; // Thickness of lines for scale gizmo + float ScaleLineCircleSize; // Size of circle at the end of lines for scale gizmo + float HatchedAxisLineThickness; // Thickness of hatched axis lines + float CenterCircleSize; // Size of circle at the center of the translate/scale gizmo + + ImVec4 Colors[COLOR::COUNT]; + }; + + IMGUI_API Style& GetStyle(); +} diff --git a/libs/imguizmo/LICENSE b/libs/imguizmo/LICENSE new file mode 100644 index 0000000..dcbee45 --- /dev/null +++ b/libs/imguizmo/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Cedric Guillemet + +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. diff --git a/libs/implot/LICENSE b/libs/implot/LICENSE new file mode 100644 index 0000000..3995ef7 --- /dev/null +++ b/libs/implot/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Evan Pezent + +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. diff --git a/libs/implot/implot.cpp b/libs/implot/implot.cpp new file mode 100644 index 0000000..597b043 --- /dev/null +++ b/libs/implot/implot.cpp @@ -0,0 +1,5885 @@ +// MIT License + +// Copyright (c) 2023 Evan Pezent + +// 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. + +// ImPlot v0.17 + +/* + +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 a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all implot files. +You can read releases logs https://github.com/epezent/implot/releases for more details. + +- 2023/08/20 (0.17) - ImPlotFlags_NoChild was removed as child windows are no longer needed to capture scroll. You can safely remove this flag if you were using it. +- 2023/06/26 (0.15) - Various build fixes related to updates in Dear ImGui internals. +- 2022/11/25 (0.15) - Make PlotText honor ImPlotItemFlags_NoFit. +- 2022/06/19 (0.14) - The signature of ColormapScale has changed to accommodate a new ImPlotColormapScaleFlags parameter +- 2022/06/17 (0.14) - **IMPORTANT** All PlotX functions now take an ImPlotX_Flags `flags` parameter. Where applicable, it is located before the existing `offset` and `stride` parameters. + If you were providing offset and stride values, you will need to update your function call to include a `flags` value. If you fail to do this, you will likely see + unexpected results or crashes without a compiler warning since these three are all default args. We apologize for the inconvenience, but this was a necessary evil. + - PlotBarsH has been removed; use PlotBars + ImPlotBarsFlags_Horizontal instead + - PlotErrorBarsH has been removed; use PlotErrorBars + ImPlotErrorBarsFlags_Horizontal + - PlotHistogram/PlotHistogram2D signatures changed; `cumulative`, `density`, and `outliers` options now specified via ImPlotHistogramFlags + - PlotPieChart signature changed; `normalize` option now specified via ImPlotPieChartFlags + - PlotText signature changes; `vertical` option now specified via `ImPlotTextFlags_Vertical` + - `PlotVLines` and `PlotHLines` replaced with `PlotInfLines` (+ ImPlotInfLinesFlags_Horizontal ) + - arguments of ImPlotGetter have been reversed to be consistent with other API callbacks + - SetupAxisScale + ImPlotScale have replaced ImPlotAxisFlags_LogScale and ImPlotAxisFlags_Time flags + - ImPlotFormatters should now return an int indicating the size written + - the signature of ImPlotGetter has been reversed so that void* user_data is the last argument and consistent with other callbacks +- 2021/10/19 (0.13) - MAJOR API OVERHAUL! See #168 and #272 + - TRIVIAL RENAME: + - ImPlotLimits -> ImPlotRect + - ImPlotYAxis_ -> ImAxis_ + - SetPlotYAxis -> SetAxis + - BeginDragDropTarget -> BeginDragDropTargetPlot + - BeginDragDropSource -> BeginDragDropSourcePlot + - ImPlotFlags_NoMousePos -> ImPlotFlags_NoMouseText + - SetNextPlotLimits -> SetNextAxesLimits + - SetMouseTextLocation -> SetupMouseText + - SIGNATURE MODIFIED: + - PixelsToPlot/PlotToPixels -> added optional X-Axis arg + - GetPlotMousePos -> added optional X-Axis arg + - GetPlotLimits -> added optional X-Axis arg + - GetPlotSelection -> added optional X-Axis arg + - DragLineX/Y/DragPoint -> now takes int id; removed labels (render with Annotation/Tag instead) + - REPLACED: + - IsPlotXAxisHovered/IsPlotXYAxisHovered -> IsAxisHovered(ImAxis) + - BeginDragDropTargetX/BeginDragDropTargetY -> BeginDragDropTargetAxis(ImAxis) + - BeginDragDropSourceX/BeginDragDropSourceY -> BeginDragDropSourceAxis(ImAxis) + - ImPlotCol_XAxis, ImPlotCol_YAxis1, etc. -> ImPlotCol_AxisText (push/pop this around SetupAxis to style individual axes) + - ImPlotCol_XAxisGrid, ImPlotCol_Y1AxisGrid -> ImPlotCol_AxisGrid (push/pop this around SetupAxis to style individual axes) + - SetNextPlotLimitsX/Y -> SetNextAxisLimits(ImAxis) + - LinkNextPlotLimits -> SetNextAxisLinks(ImAxis) + - FitNextPlotAxes -> SetNextAxisToFit(ImAxis)/SetNextAxesToFit + - SetLegendLocation -> SetupLegend + - ImPlotFlags_NoHighlight -> ImPlotLegendFlags_NoHighlight + - ImPlotOrientation -> ImPlotLegendFlags_Horizontal + - Annotate -> Annotation + - REMOVED: + - GetPlotQuery, SetPlotQuery, IsPlotQueried -> use DragRect + - SetNextPlotTicksX, SetNextPlotTicksY -> use SetupAxisTicks + - SetNextPlotFormatX, SetNextPlotFormatY -> use SetupAxisFormat + - AnnotateClamped -> use Annotation(bool clamp = true) + - OBSOLETED: + - BeginPlot (original signature) -> use simplified signature + Setup API +- 2021/07/30 (0.12) - The offset argument of `PlotXG` functions was been removed. Implement offsetting in your getter callback instead. +- 2021/03/08 (0.9) - SetColormap and PushColormap(ImVec4*) were removed. Use AddColormap for custom colormap support. LerpColormap was changed to SampleColormap. + ShowColormapScale was changed to ColormapScale and requires additional arguments. +- 2021/03/07 (0.9) - The signature of ShowColormapScale was modified to accept a ImVec2 size. +- 2021/02/28 (0.9) - BeginLegendDragDropSource was changed to BeginDragDropSourceItem with a number of other drag and drop improvements. +- 2021/01/18 (0.9) - The default behavior for opening context menus was change from double right-click to single right-click. ImPlotInputMap and related functions were moved + to implot_internal.h due to its immaturity. +- 2020/10/16 (0.8) - ImPlotStyleVar_InfoPadding was changed to ImPlotStyleVar_MousePosPadding +- 2020/09/10 (0.8) - The single array versions of PlotLine, PlotScatter, PlotStems, and PlotShaded were given additional arguments for x-scale and x0. +- 2020/09/07 (0.8) - Plotting functions which accept a custom getter function pointer have been post-fixed with a G (e.g. PlotLineG) +- 2020/09/06 (0.7) - Several flags under ImPlotFlags and ImPlotAxisFlags were inverted (e.g. ImPlotFlags_Legend -> ImPlotFlags_NoLegend) so that the default flagset + is simply 0. This more closely matches ImGui's style and makes it easier to enable non-default but commonly used flags (e.g. ImPlotAxisFlags_Time). +- 2020/08/28 (0.5) - ImPlotMarker_ can no longer be combined with bitwise OR, |. This features caused unecessary slow-down, and almost no one used it. +- 2020/08/25 (0.5) - ImPlotAxisFlags_Scientific was removed. Logarithmic axes automatically uses scientific notation. +- 2020/08/17 (0.5) - PlotText was changed so that text is centered horizontally and vertically about the desired point. +- 2020/08/16 (0.5) - An ImPlotContext must be explicitly created and destroyed now with `CreateContext` and `DestroyContext`. Previously, the context was statically initialized in this source file. +- 2020/06/13 (0.4) - The flags `ImPlotAxisFlag_Adaptive` and `ImPlotFlags_Cull` were removed. Both are now done internally by default. +- 2020/06/03 (0.3) - The signature and behavior of PlotPieChart was changed so that data with sum less than 1 can optionally be normalized. The label format can now be specified as well. +- 2020/06/01 (0.3) - SetPalette was changed to `SetColormap` for consistency with other plotting libraries. `RestorePalette` was removed. Use `SetColormap(ImPlotColormap_Default)`. +- 2020/05/31 (0.3) - Plot functions taking custom ImVec2* getters were removed. Use the ImPlotPoint* getter versions instead. +- 2020/05/29 (0.3) - The signature of ImPlotLimits::Contains was changed to take two doubles instead of ImVec2 +- 2020/05/16 (0.2) - All plotting functions were reverted to being prefixed with "Plot" to maintain a consistent VerbNoun style. `Plot` was split into `PlotLine` + and `PlotScatter` (however, `PlotLine` can still be used to plot scatter points as `Plot` did before.). `Bar` is not `PlotBars`, to indicate + that multiple bars will be plotted. +- 2020/05/13 (0.2) - `ImMarker` was change to `ImPlotMarker` and `ImAxisFlags` was changed to `ImPlotAxisFlags`. +- 2020/05/11 (0.2) - `ImPlotFlags_Selection` was changed to `ImPlotFlags_BoxSelect` +- 2020/05/11 (0.2) - The namespace ImGui:: was replaced with ImPlot::. As a result, the following additional changes were made: + - Functions that were prefixed or decorated with the word "Plot" have been truncated. E.g., `ImGui::PlotBars` is now just `ImPlot::Bar`. + It should be fairly obvious what was what. + - Some functions have been given names that would have otherwise collided with the ImGui namespace. This has been done to maintain a consistent + style with ImGui. E.g., 'ImGui::PushPlotStyleVar` is now 'ImPlot::PushStyleVar'. +- 2020/05/10 (0.2) - The following function/struct names were changes: + - ImPlotRange -> ImPlotLimits + - GetPlotRange() -> GetPlotLimits() + - SetNextPlotRange -> SetNextPlotLimits + - SetNextPlotRangeX -> SetNextPlotLimitsX + - SetNextPlotRangeY -> SetNextPlotLimitsY +- 2020/05/10 (0.2) - Plot queries are pixel based by default. Query rects that maintain relative plot position have been removed. This was done to support multi-y-axis. + +*/ + +#define IMGUI_DEFINE_MATH_OPERATORS +#include "implot.h" +#include "implot_internal.h" + +#include + +// Support for pre-1.82 versions. Users on 1.82+ can use 0 (default) flags to mean "all corners" but in order to support older versions we are more explicit. +#if (IMGUI_VERSION_NUM < 18102) && !defined(ImDrawFlags_RoundCornersAll) +#define ImDrawFlags_RoundCornersAll ImDrawCornerFlags_All +#endif + +// Support for pre-1.89.7 versions. +#if (IMGUI_VERSION_NUM < 18966) +#define ImGuiButtonFlags_AllowOverlap ImGuiButtonFlags_AllowItemOverlap +#endif + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#endif + +// Clang/GCC warnings with -Weverything +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#endif + +// Global plot context +#ifndef GImPlot +ImPlotContext* GImPlot = nullptr; +#endif + +//----------------------------------------------------------------------------- +// Struct Implementations +//----------------------------------------------------------------------------- + +ImPlotInputMap::ImPlotInputMap() { + ImPlot::MapInputDefault(this); +} + +ImPlotStyle::ImPlotStyle() { + + LineWeight = 1; + Marker = ImPlotMarker_None; + MarkerSize = 4; + MarkerWeight = 1; + FillAlpha = 1; + ErrorBarSize = 5; + ErrorBarWeight = 1.5f; + DigitalBitHeight = 8; + DigitalBitGap = 4; + + PlotBorderSize = 1; + MinorAlpha = 0.25f; + MajorTickLen = ImVec2(10,10); + MinorTickLen = ImVec2(5,5); + MajorTickSize = ImVec2(1,1); + MinorTickSize = ImVec2(1,1); + MajorGridSize = ImVec2(1,1); + MinorGridSize = ImVec2(1,1); + PlotPadding = ImVec2(10,10); + LabelPadding = ImVec2(5,5); + LegendPadding = ImVec2(10,10); + LegendInnerPadding = ImVec2(5,5); + LegendSpacing = ImVec2(5,0); + MousePosPadding = ImVec2(10,10); + AnnotationPadding = ImVec2(2,2); + FitPadding = ImVec2(0,0); + PlotDefaultSize = ImVec2(400,300); + PlotMinSize = ImVec2(200,150); + + ImPlot::StyleColorsAuto(this); + + Colormap = ImPlotColormap_Deep; + + UseLocalTime = false; + Use24HourClock = false; + UseISO8601 = false; +} + +//----------------------------------------------------------------------------- +// Style +//----------------------------------------------------------------------------- + +namespace ImPlot { + +const char* GetStyleColorName(ImPlotCol col) { + static const char* col_names[ImPlotCol_COUNT] = { + "Line", + "Fill", + "MarkerOutline", + "MarkerFill", + "ErrorBar", + "FrameBg", + "PlotBg", + "PlotBorder", + "LegendBg", + "LegendBorder", + "LegendText", + "TitleText", + "InlayText", + "AxisText", + "AxisGrid", + "AxisTick", + "AxisBg", + "AxisBgHovered", + "AxisBgActive", + "Selection", + "Crosshairs" + }; + return col_names[col]; +} + +const char* GetMarkerName(ImPlotMarker marker) { + switch (marker) { + case ImPlotMarker_None: return "None"; + case ImPlotMarker_Circle: return "Circle"; + case ImPlotMarker_Square: return "Square"; + case ImPlotMarker_Diamond: return "Diamond"; + case ImPlotMarker_Up: return "Up"; + case ImPlotMarker_Down: return "Down"; + case ImPlotMarker_Left: return "Left"; + case ImPlotMarker_Right: return "Right"; + case ImPlotMarker_Cross: return "Cross"; + case ImPlotMarker_Plus: return "Plus"; + case ImPlotMarker_Asterisk: return "Asterisk"; + default: return ""; + } +} + +ImVec4 GetAutoColor(ImPlotCol idx) { + ImVec4 col(0,0,0,1); + switch(idx) { + case ImPlotCol_Line: return col; // these are plot dependent! + case ImPlotCol_Fill: return col; // these are plot dependent! + case ImPlotCol_MarkerOutline: return col; // these are plot dependent! + case ImPlotCol_MarkerFill: return col; // these are plot dependent! + case ImPlotCol_ErrorBar: return ImGui::GetStyleColorVec4(ImGuiCol_Text); + case ImPlotCol_FrameBg: return ImGui::GetStyleColorVec4(ImGuiCol_FrameBg); + case ImPlotCol_PlotBg: return ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + case ImPlotCol_PlotBorder: return ImGui::GetStyleColorVec4(ImGuiCol_Border); + case ImPlotCol_LegendBg: return ImGui::GetStyleColorVec4(ImGuiCol_PopupBg); + case ImPlotCol_LegendBorder: return GetStyleColorVec4(ImPlotCol_PlotBorder); + case ImPlotCol_LegendText: return GetStyleColorVec4(ImPlotCol_InlayText); + case ImPlotCol_TitleText: return ImGui::GetStyleColorVec4(ImGuiCol_Text); + case ImPlotCol_InlayText: return ImGui::GetStyleColorVec4(ImGuiCol_Text); + case ImPlotCol_AxisText: return ImGui::GetStyleColorVec4(ImGuiCol_Text); + case ImPlotCol_AxisGrid: return GetStyleColorVec4(ImPlotCol_AxisText) * ImVec4(1,1,1,0.25f); + case ImPlotCol_AxisTick: return GetStyleColorVec4(ImPlotCol_AxisGrid); + case ImPlotCol_AxisBg: return ImVec4(0,0,0,0); + case ImPlotCol_AxisBgHovered: return ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + case ImPlotCol_AxisBgActive: return ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive); + case ImPlotCol_Selection: return ImVec4(1,1,0,1); + case ImPlotCol_Crosshairs: return GetStyleColorVec4(ImPlotCol_PlotBorder); + default: return col; + } +} + +struct ImPlotStyleVarInfo { + ImGuiDataType Type; + ImU32 Count; + ImU32 Offset; + void* GetVarPtr(ImPlotStyle* style) const { return (void*)((unsigned char*)style + Offset); } +}; + +static const ImPlotStyleVarInfo GPlotStyleVarInfo[] = +{ + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, LineWeight) }, // ImPlotStyleVar_LineWeight + { ImGuiDataType_S32, 1, (ImU32)offsetof(ImPlotStyle, Marker) }, // ImPlotStyleVar_Marker + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, MarkerSize) }, // ImPlotStyleVar_MarkerSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, MarkerWeight) }, // ImPlotStyleVar_MarkerWeight + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, FillAlpha) }, // ImPlotStyleVar_FillAlpha + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, ErrorBarSize) }, // ImPlotStyleVar_ErrorBarSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, ErrorBarWeight) }, // ImPlotStyleVar_ErrorBarWeight + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, DigitalBitHeight) }, // ImPlotStyleVar_DigitalBitHeight + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, DigitalBitGap) }, // ImPlotStyleVar_DigitalBitGap + + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, PlotBorderSize) }, // ImPlotStyleVar_PlotBorderSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, MinorAlpha) }, // ImPlotStyleVar_MinorAlpha + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MajorTickLen) }, // ImPlotStyleVar_MajorTickLen + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MinorTickLen) }, // ImPlotStyleVar_MinorTickLen + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MajorTickSize) }, // ImPlotStyleVar_MajorTickSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MinorTickSize) }, // ImPlotStyleVar_MinorTickSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MajorGridSize) }, // ImPlotStyleVar_MajorGridSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MinorGridSize) }, // ImPlotStyleVar_MinorGridSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, PlotPadding) }, // ImPlotStyleVar_PlotPadding + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, LabelPadding) }, // ImPlotStyleVar_LabelPaddine + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, LegendPadding) }, // ImPlotStyleVar_LegendPadding + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, LegendInnerPadding) }, // ImPlotStyleVar_LegendInnerPadding + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, LegendSpacing) }, // ImPlotStyleVar_LegendSpacing + + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MousePosPadding) }, // ImPlotStyleVar_MousePosPadding + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, AnnotationPadding) }, // ImPlotStyleVar_AnnotationPadding + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, FitPadding) }, // ImPlotStyleVar_FitPadding + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, PlotDefaultSize) }, // ImPlotStyleVar_PlotDefaultSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, PlotMinSize) } // ImPlotStyleVar_PlotMinSize +}; + +static const ImPlotStyleVarInfo* GetPlotStyleVarInfo(ImPlotStyleVar idx) { + IM_ASSERT(idx >= 0 && idx < ImPlotStyleVar_COUNT); + IM_ASSERT(IM_ARRAYSIZE(GPlotStyleVarInfo) == ImPlotStyleVar_COUNT); + return &GPlotStyleVarInfo[idx]; +} + +//----------------------------------------------------------------------------- +// Generic Helpers +//----------------------------------------------------------------------------- + +void AddTextVertical(ImDrawList *DrawList, ImVec2 pos, ImU32 col, const char *text_begin, const char* text_end) { + // the code below is based loosely on ImFont::RenderText + if (!text_end) + text_end = text_begin + strlen(text_begin); + ImGuiContext& g = *GImGui; + ImFont* font = g.Font; + // Align to be pixel perfect + pos.x = ImFloor(pos.x); + pos.y = ImFloor(pos.y); + const float scale = g.FontSize / font->FontSize; + const char* s = text_begin; + int chars_exp = (int)(text_end - s); + int chars_rnd = 0; + const int vtx_count_max = chars_exp * 4; + const int idx_count_max = chars_exp * 6; + DrawList->PrimReserve(idx_count_max, vtx_count_max); + while (s < text_end) { + 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; + } + const ImFontGlyph * glyph = font->FindGlyph((ImWchar)c); + if (glyph == nullptr) { + continue; + } + DrawList->PrimQuadUV(pos + ImVec2(glyph->Y0, -glyph->X0) * scale, pos + ImVec2(glyph->Y0, -glyph->X1) * scale, + pos + ImVec2(glyph->Y1, -glyph->X1) * scale, pos + ImVec2(glyph->Y1, -glyph->X0) * scale, + ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V0), + ImVec2(glyph->U1, glyph->V1), ImVec2(glyph->U0, glyph->V1), + col); + pos.y -= glyph->AdvanceX * scale; + chars_rnd++; + } + // Give back unused vertices + int chars_skp = chars_exp-chars_rnd; + DrawList->PrimUnreserve(chars_skp*6, chars_skp*4); +} + +void AddTextCentered(ImDrawList* DrawList, ImVec2 top_center, ImU32 col, const char* text_begin, const char* text_end) { + float txt_ht = ImGui::GetTextLineHeight(); + const char* title_end = ImGui::FindRenderedTextEnd(text_begin, text_end); + ImVec2 text_size; + float y = 0; + while (const char* tmp = (const char*)memchr(text_begin, '\n', title_end-text_begin)) { + text_size = ImGui::CalcTextSize(text_begin,tmp,true); + DrawList->AddText(ImVec2(top_center.x - text_size.x * 0.5f, top_center.y+y),col,text_begin,tmp); + text_begin = tmp + 1; + y += txt_ht; + } + text_size = ImGui::CalcTextSize(text_begin,title_end,true); + DrawList->AddText(ImVec2(top_center.x - text_size.x * 0.5f, top_center.y+y),col,text_begin,title_end); +} + +double NiceNum(double x, bool round) { + double f; + double nf; + int expv = (int)floor(ImLog10(x)); + f = x / ImPow(10.0, (double)expv); + if (round) + if (f < 1.5) + nf = 1; + else if (f < 3) + nf = 2; + else if (f < 7) + nf = 5; + else + nf = 10; + else if (f <= 1) + nf = 1; + else if (f <= 2) + nf = 2; + else if (f <= 5) + nf = 5; + else + nf = 10; + return nf * ImPow(10.0, expv); +} + +//----------------------------------------------------------------------------- +// Context Utils +//----------------------------------------------------------------------------- + +void SetImGuiContext(ImGuiContext* ctx) { + ImGui::SetCurrentContext(ctx); +} + +ImPlotContext* CreateContext() { + ImPlotContext* ctx = IM_NEW(ImPlotContext)(); + Initialize(ctx); + if (GImPlot == nullptr) + SetCurrentContext(ctx); + return ctx; +} + +void DestroyContext(ImPlotContext* ctx) { + if (ctx == nullptr) + ctx = GImPlot; + if (GImPlot == ctx) + SetCurrentContext(nullptr); + IM_DELETE(ctx); +} + +ImPlotContext* GetCurrentContext() { + return GImPlot; +} + +void SetCurrentContext(ImPlotContext* ctx) { + GImPlot = ctx; +} + +#define IMPLOT_APPEND_CMAP(name, qual) ctx->ColormapData.Append(#name, name, sizeof(name)/sizeof(ImU32), qual) +#define IM_RGB(r,g,b) IM_COL32(r,g,b,255) + +void Initialize(ImPlotContext* ctx) { + ResetCtxForNextPlot(ctx); + ResetCtxForNextAlignedPlots(ctx); + ResetCtxForNextSubplot(ctx); + + const ImU32 Deep[] = {4289753676, 4283598045, 4285048917, 4283584196, 4289950337, 4284512403, 4291005402, 4287401100, 4285839820, 4291671396 }; + const ImU32 Dark[] = {4280031972, 4290281015, 4283084621, 4288892568, 4278222847, 4281597951, 4280833702, 4290740727, 4288256409 }; + const ImU32 Pastel[] = {4289639675, 4293119411, 4291161036, 4293184478, 4289124862, 4291624959, 4290631909, 4293712637, 4294111986 }; + const ImU32 Paired[] = {4293119554, 4290017311, 4287291314, 4281114675, 4288256763, 4280031971, 4285513725, 4278222847, 4292260554, 4288298346, 4288282623, 4280834481}; + const ImU32 Viridis[] = {4283695428, 4285867080, 4287054913, 4287455029, 4287526954, 4287402273, 4286883874, 4285579076, 4283552122, 4280737725, 4280674301 }; + const ImU32 Plasma[] = {4287039501, 4288480321, 4289200234, 4288941455, 4287638193, 4286072780, 4284638433, 4283139314, 4281771772, 4280667900, 4280416752 }; + const ImU32 Hot[] = {4278190144, 4278190208, 4278190271, 4278190335, 4278206719, 4278223103, 4278239231, 4278255615, 4283826175, 4289396735, 4294967295 }; + const ImU32 Cool[] = {4294967040, 4294960666, 4294954035, 4294947661, 4294941030, 4294934656, 4294928025, 4294921651, 4294915020, 4294908646, 4294902015 }; + const ImU32 Pink[] = {4278190154, 4282532475, 4284308894, 4285690554, 4286879686, 4287870160, 4288794330, 4289651940, 4291685869, 4293392118, 4294967295 }; + const ImU32 Jet[] = {4289331200, 4294901760, 4294923520, 4294945280, 4294967040, 4289396565, 4283826090, 4278255615, 4278233855, 4278212095, 4278190335 }; + const ImU32 Twilight[] = {IM_RGB(226,217,226),IM_RGB(166,191,202),IM_RGB(109,144,192),IM_RGB(95,88,176),IM_RGB(83,30,124),IM_RGB(47,20,54),IM_RGB(100,25,75),IM_RGB(159,60,80),IM_RGB(192,117,94),IM_RGB(208,179,158),IM_RGB(226,217,226)}; + const ImU32 RdBu[] = {IM_RGB(103,0,31),IM_RGB(178,24,43),IM_RGB(214,96,77),IM_RGB(244,165,130),IM_RGB(253,219,199),IM_RGB(247,247,247),IM_RGB(209,229,240),IM_RGB(146,197,222),IM_RGB(67,147,195),IM_RGB(33,102,172),IM_RGB(5,48,97)}; + const ImU32 BrBG[] = {IM_RGB(84,48,5),IM_RGB(140,81,10),IM_RGB(191,129,45),IM_RGB(223,194,125),IM_RGB(246,232,195),IM_RGB(245,245,245),IM_RGB(199,234,229),IM_RGB(128,205,193),IM_RGB(53,151,143),IM_RGB(1,102,94),IM_RGB(0,60,48)}; + const ImU32 PiYG[] = {IM_RGB(142,1,82),IM_RGB(197,27,125),IM_RGB(222,119,174),IM_RGB(241,182,218),IM_RGB(253,224,239),IM_RGB(247,247,247),IM_RGB(230,245,208),IM_RGB(184,225,134),IM_RGB(127,188,65),IM_RGB(77,146,33),IM_RGB(39,100,25)}; + const ImU32 Spectral[] = {IM_RGB(158,1,66),IM_RGB(213,62,79),IM_RGB(244,109,67),IM_RGB(253,174,97),IM_RGB(254,224,139),IM_RGB(255,255,191),IM_RGB(230,245,152),IM_RGB(171,221,164),IM_RGB(102,194,165),IM_RGB(50,136,189),IM_RGB(94,79,162)}; + const ImU32 Greys[] = {IM_COL32_WHITE, IM_COL32_BLACK }; + + IMPLOT_APPEND_CMAP(Deep, true); + IMPLOT_APPEND_CMAP(Dark, true); + IMPLOT_APPEND_CMAP(Pastel, true); + IMPLOT_APPEND_CMAP(Paired, true); + IMPLOT_APPEND_CMAP(Viridis, false); + IMPLOT_APPEND_CMAP(Plasma, false); + IMPLOT_APPEND_CMAP(Hot, false); + IMPLOT_APPEND_CMAP(Cool, false); + IMPLOT_APPEND_CMAP(Pink, false); + IMPLOT_APPEND_CMAP(Jet, false); + IMPLOT_APPEND_CMAP(Twilight, false); + IMPLOT_APPEND_CMAP(RdBu, false); + IMPLOT_APPEND_CMAP(BrBG, false); + IMPLOT_APPEND_CMAP(PiYG, false); + IMPLOT_APPEND_CMAP(Spectral, false); + IMPLOT_APPEND_CMAP(Greys, false); +} + +void ResetCtxForNextPlot(ImPlotContext* ctx) { + // reset the next plot/item data + ctx->NextPlotData.Reset(); + ctx->NextItemData.Reset(); + // reset labels + ctx->Annotations.Reset(); + ctx->Tags.Reset(); + // reset extents/fit + ctx->OpenContextThisFrame = false; + // reset digital plot items count + ctx->DigitalPlotItemCnt = 0; + ctx->DigitalPlotOffset = 0; + // nullify plot + ctx->CurrentPlot = nullptr; + ctx->CurrentItem = nullptr; + ctx->PreviousItem = nullptr; +} + +void ResetCtxForNextAlignedPlots(ImPlotContext* ctx) { + ctx->CurrentAlignmentH = nullptr; + ctx->CurrentAlignmentV = nullptr; +} + +void ResetCtxForNextSubplot(ImPlotContext* ctx) { + ctx->CurrentSubplot = nullptr; + ctx->CurrentAlignmentH = nullptr; + ctx->CurrentAlignmentV = nullptr; +} + +//----------------------------------------------------------------------------- +// Plot Utils +//----------------------------------------------------------------------------- + +ImPlotPlot* GetPlot(const char* title) { + ImGuiWindow* Window = GImGui->CurrentWindow; + const ImGuiID ID = Window->GetID(title); + return GImPlot->Plots.GetByKey(ID); +} + +ImPlotPlot* GetCurrentPlot() { + return GImPlot->CurrentPlot; +} + +void BustPlotCache() { + ImPlotContext& gp = *GImPlot; + gp.Plots.Clear(); + gp.Subplots.Clear(); +} + +//----------------------------------------------------------------------------- +// Legend Utils +//----------------------------------------------------------------------------- + +ImVec2 GetLocationPos(const ImRect& outer_rect, const ImVec2& inner_size, ImPlotLocation loc, const ImVec2& pad) { + ImVec2 pos; + if (ImHasFlag(loc, ImPlotLocation_West) && !ImHasFlag(loc, ImPlotLocation_East)) + pos.x = outer_rect.Min.x + pad.x; + else if (!ImHasFlag(loc, ImPlotLocation_West) && ImHasFlag(loc, ImPlotLocation_East)) + pos.x = outer_rect.Max.x - pad.x - inner_size.x; + else + pos.x = outer_rect.GetCenter().x - inner_size.x * 0.5f; + // legend reference point y + if (ImHasFlag(loc, ImPlotLocation_North) && !ImHasFlag(loc, ImPlotLocation_South)) + pos.y = outer_rect.Min.y + pad.y; + else if (!ImHasFlag(loc, ImPlotLocation_North) && ImHasFlag(loc, ImPlotLocation_South)) + pos.y = outer_rect.Max.y - pad.y - inner_size.y; + else + pos.y = outer_rect.GetCenter().y - inner_size.y * 0.5f; + pos.x = IM_ROUND(pos.x); + pos.y = IM_ROUND(pos.y); + return pos; +} + +ImVec2 CalcLegendSize(ImPlotItemGroup& items, const ImVec2& pad, const ImVec2& spacing, bool vertical) { + // vars + const int nItems = items.GetLegendCount(); + const float txt_ht = ImGui::GetTextLineHeight(); + const float icon_size = txt_ht; + // get label max width + float max_label_width = 0; + float sum_label_width = 0; + for (int i = 0; i < nItems; ++i) { + const char* label = items.GetLegendLabel(i); + const float label_width = ImGui::CalcTextSize(label, nullptr, true).x; + max_label_width = label_width > max_label_width ? label_width : max_label_width; + sum_label_width += label_width; + } + // calc legend size + const ImVec2 legend_size = vertical ? + ImVec2(pad.x * 2 + icon_size + max_label_width, pad.y * 2 + nItems * txt_ht + (nItems - 1) * spacing.y) : + ImVec2(pad.x * 2 + icon_size * nItems + sum_label_width + (nItems - 1) * spacing.x, pad.y * 2 + txt_ht); + return legend_size; +} + +bool ClampLegendRect(ImRect& legend_rect, const ImRect& outer_rect, const ImVec2& pad) { + bool clamped = false; + ImRect outer_rect_pad(outer_rect.Min + pad, outer_rect.Max - pad); + if (legend_rect.Min.x < outer_rect_pad.Min.x) { + legend_rect.Min.x = outer_rect_pad.Min.x; + clamped = true; + } + if (legend_rect.Min.y < outer_rect_pad.Min.y) { + legend_rect.Min.y = outer_rect_pad.Min.y; + clamped = true; + } + if (legend_rect.Max.x > outer_rect_pad.Max.x) { + legend_rect.Max.x = outer_rect_pad.Max.x; + clamped = true; + } + if (legend_rect.Max.y > outer_rect_pad.Max.y) { + legend_rect.Max.y = outer_rect_pad.Max.y; + clamped = true; + } + return clamped; +} + +int LegendSortingComp(const void* _a, const void* _b) { + ImPlotItemGroup* items = GImPlot->SortItems; + const int a = *(const int*)_a; + const int b = *(const int*)_b; + const char* label_a = items->GetLegendLabel(a); + const char* label_b = items->GetLegendLabel(b); + return strcmp(label_a,label_b); +} + +bool ShowLegendEntries(ImPlotItemGroup& items, const ImRect& legend_bb, bool hovered, const ImVec2& pad, const ImVec2& spacing, bool vertical, ImDrawList& DrawList) { + // vars + const float txt_ht = ImGui::GetTextLineHeight(); + const float icon_size = txt_ht; + const float icon_shrink = 2; + ImU32 col_txt = GetStyleColorU32(ImPlotCol_LegendText); + ImU32 col_txt_dis = ImAlphaU32(col_txt, 0.25f); + // render each legend item + float sum_label_width = 0; + bool any_item_hovered = false; + + const int num_items = items.GetLegendCount(); + if (num_items < 1) + return hovered; + // build render order + ImPlotContext& gp = *GImPlot; + ImVector& indices = gp.TempInt1; + indices.resize(num_items); + for (int i = 0; i < num_items; ++i) + indices[i] = i; + if (ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_Sort) && num_items > 1) { + gp.SortItems = &items; + qsort(indices.Data, num_items, sizeof(int), LegendSortingComp); + } + // render + for (int i = 0; i < num_items; ++i) { + const int idx = indices[i]; + ImPlotItem* item = items.GetLegendItem(idx); + const char* label = items.GetLegendLabel(idx); + const float label_width = ImGui::CalcTextSize(label, nullptr, true).x; + const ImVec2 top_left = vertical ? + legend_bb.Min + pad + ImVec2(0, i * (txt_ht + spacing.y)) : + legend_bb.Min + pad + ImVec2(i * (icon_size + spacing.x) + sum_label_width, 0); + sum_label_width += label_width; + ImRect icon_bb; + icon_bb.Min = top_left + ImVec2(icon_shrink,icon_shrink); + icon_bb.Max = top_left + ImVec2(icon_size - icon_shrink, icon_size - icon_shrink); + ImRect label_bb; + label_bb.Min = top_left; + label_bb.Max = top_left + ImVec2(label_width + icon_size, icon_size); + ImU32 col_txt_hl; + ImU32 col_item = ImAlphaU32(item->Color,1); + + ImRect button_bb(icon_bb.Min, label_bb.Max); + + ImGui::KeepAliveID(item->ID); + + bool item_hov = false; + bool item_hld = false; + bool item_clk = ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_NoButtons) + ? false + : ImGui::ButtonBehavior(button_bb, item->ID, &item_hov, &item_hld); + + if (item_clk) + item->Show = !item->Show; + + + const bool can_hover = (item_hov) + && (!ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_NoHighlightItem) + || !ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_NoHighlightAxis)); + + if (can_hover) { + item->LegendHoverRect.Min = icon_bb.Min; + item->LegendHoverRect.Max = label_bb.Max; + item->LegendHovered = true; + col_txt_hl = ImMixU32(col_txt, col_item, 64); + any_item_hovered = true; + } + else { + col_txt_hl = ImGui::GetColorU32(col_txt); + } + ImU32 col_icon; + if (item_hld) + col_icon = item->Show ? ImAlphaU32(col_item,0.5f) : ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.5f); + else if (item_hov) + col_icon = item->Show ? ImAlphaU32(col_item,0.75f) : ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.75f); + else + col_icon = item->Show ? col_item : col_txt_dis; + + DrawList.AddRectFilled(icon_bb.Min, icon_bb.Max, col_icon); + const char* text_display_end = ImGui::FindRenderedTextEnd(label, nullptr); + if (label != text_display_end) + DrawList.AddText(top_left + ImVec2(icon_size, 0), item->Show ? col_txt_hl : col_txt_dis, label, text_display_end); + } + return hovered && !any_item_hovered; +} + +//----------------------------------------------------------------------------- +// Locators +//----------------------------------------------------------------------------- + +static const float TICK_FILL_X = 0.8f; +static const float TICK_FILL_Y = 1.0f; + +void Locator_Default(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) { + if (range.Min == range.Max) + return; + const int nMinor = 10; + const int nMajor = ImMax(2, (int)IM_ROUND(pixels / (vertical ? 300.0f : 400.0f))); + const double nice_range = NiceNum(range.Size() * 0.99, false); + const double interval = NiceNum(nice_range / (nMajor - 1), true); + const double graphmin = floor(range.Min / interval) * interval; + const double graphmax = ceil(range.Max / interval) * interval; + bool first_major_set = false; + int first_major_idx = 0; + const int idx0 = ticker.TickCount(); // ticker may have user custom ticks + ImVec2 total_size(0,0); + for (double major = graphmin; major < graphmax + 0.5 * interval; major += interval) { + // is this zero? combat zero formatting issues + if (major-interval < 0 && major+interval > 0) + major = 0; + if (range.Contains(major)) { + if (!first_major_set) { + first_major_idx = ticker.TickCount(); + first_major_set = true; + } + total_size += ticker.AddTick(major, true, 0, true, formatter, formatter_data).LabelSize; + } + for (int i = 1; i < nMinor; ++i) { + double minor = major + i * interval / nMinor; + if (range.Contains(minor)) { + total_size += ticker.AddTick(minor, false, 0, true, formatter, formatter_data).LabelSize; + } + } + } + // prune if necessary + if ((!vertical && total_size.x > pixels*TICK_FILL_X) || (vertical && total_size.y > pixels*TICK_FILL_Y)) { + for (int i = first_major_idx-1; i >= idx0; i -= 2) + ticker.Ticks[i].ShowLabel = false; + for (int i = first_major_idx+1; i < ticker.TickCount(); i += 2) + ticker.Ticks[i].ShowLabel = false; + } +} + +bool CalcLogarithmicExponents(const ImPlotRange& range, float pix, bool vertical, int& exp_min, int& exp_max, int& exp_step) { + if (range.Min * range.Max > 0) { + const int nMajor = vertical ? ImMax(2, (int)IM_ROUND(pix * 0.02f)) : ImMax(2, (int)IM_ROUND(pix * 0.01f)); // TODO: magic numbers + double log_min = ImLog10(ImAbs(range.Min)); + double log_max = ImLog10(ImAbs(range.Max)); + double log_a = ImMin(log_min,log_max); + double log_b = ImMax(log_min,log_max); + exp_step = ImMax(1,(int)(log_b - log_a) / nMajor); + exp_min = (int)log_a; + exp_max = (int)log_b; + if (exp_step != 1) { + while(exp_step % 3 != 0) exp_step++; // make step size multiple of three + while(exp_min % exp_step != 0) exp_min--; // decrease exp_min until exp_min + N * exp_step will be 0 + } + return true; + } + return false; +} + +void AddTicksLogarithmic(const ImPlotRange& range, int exp_min, int exp_max, int exp_step, ImPlotTicker& ticker, ImPlotFormatter formatter, void* data) { + const double sign = ImSign(range.Max); + for (int e = exp_min - exp_step; e < (exp_max + exp_step); e += exp_step) { + double major1 = sign*ImPow(10, (double)(e)); + double major2 = sign*ImPow(10, (double)(e + 1)); + double interval = (major2 - major1) / 9; + if (major1 >= (range.Min - DBL_EPSILON) && major1 <= (range.Max + DBL_EPSILON)) + ticker.AddTick(major1, true, 0, true, formatter, data); + for (int j = 0; j < exp_step; ++j) { + major1 = sign*ImPow(10, (double)(e+j)); + major2 = sign*ImPow(10, (double)(e+j+1)); + interval = (major2 - major1) / 9; + for (int i = 1; i < (9 + (int)(j < (exp_step - 1))); ++i) { + double minor = major1 + i * interval; + if (minor >= (range.Min - DBL_EPSILON) && minor <= (range.Max + DBL_EPSILON)) + ticker.AddTick(minor, false, 0, false, formatter, data); + } + } + } +} + +void Locator_Log10(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) { + int exp_min, exp_max, exp_step; + if (CalcLogarithmicExponents(range, pixels, vertical, exp_min, exp_max, exp_step)) + AddTicksLogarithmic(range, exp_min, exp_max, exp_step, ticker, formatter, formatter_data); +} + +float CalcSymLogPixel(double plt, const ImPlotRange& range, float pixels) { + double scaleToPixels = pixels / range.Size(); + double scaleMin = TransformForward_SymLog(range.Min,nullptr); + double scaleMax = TransformForward_SymLog(range.Max,nullptr); + double s = TransformForward_SymLog(plt, nullptr); + double t = (s - scaleMin) / (scaleMax - scaleMin); + plt = range.Min + range.Size() * t; + + return (float)(0 + scaleToPixels * (plt - range.Min)); +} + +void Locator_SymLog(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) { + if (range.Min >= -1 && range.Max <= 1) { + Locator_Default(ticker, range, pixels, vertical, formatter, formatter_data); + } + else if (range.Min * range.Max < 0) { // cross zero + const float pix_min = 0; + const float pix_max = pixels; + const float pix_p1 = CalcSymLogPixel(1, range, pixels); + const float pix_n1 = CalcSymLogPixel(-1, range, pixels); + int exp_min_p, exp_max_p, exp_step_p; + int exp_min_n, exp_max_n, exp_step_n; + CalcLogarithmicExponents(ImPlotRange(1,range.Max), ImAbs(pix_max-pix_p1),vertical,exp_min_p,exp_max_p,exp_step_p); + CalcLogarithmicExponents(ImPlotRange(range.Min,-1),ImAbs(pix_n1-pix_min),vertical,exp_min_n,exp_max_n,exp_step_n); + int exp_step = ImMax(exp_step_n, exp_step_p); + ticker.AddTick(0,true,0,true,formatter,formatter_data); + AddTicksLogarithmic(ImPlotRange(1,range.Max), exp_min_p,exp_max_p,exp_step,ticker,formatter,formatter_data); + AddTicksLogarithmic(ImPlotRange(range.Min,-1),exp_min_n,exp_max_n,exp_step,ticker,formatter,formatter_data); + } + else { + Locator_Log10(ticker, range, pixels, vertical, formatter, formatter_data); + } +} + +void AddTicksCustom(const double* values, const char* const labels[], int n, ImPlotTicker& ticker, ImPlotFormatter formatter, void* data) { + for (int i = 0; i < n; ++i) { + if (labels != nullptr) + ticker.AddTick(values[i], false, 0, true, labels[i]); + else + ticker.AddTick(values[i], false, 0, true, formatter, data); + } +} + +//----------------------------------------------------------------------------- +// Time Ticks and Utils +//----------------------------------------------------------------------------- + +// this may not be thread safe? +static const double TimeUnitSpans[ImPlotTimeUnit_COUNT] = { + 0.000001, + 0.001, + 1, + 60, + 3600, + 86400, + 2629800, + 31557600 +}; + +inline ImPlotTimeUnit GetUnitForRange(double range) { + static double cutoffs[ImPlotTimeUnit_COUNT] = {0.001, 1, 60, 3600, 86400, 2629800, 31557600, IMPLOT_MAX_TIME}; + for (int i = 0; i < ImPlotTimeUnit_COUNT; ++i) { + if (range <= cutoffs[i]) + return (ImPlotTimeUnit)i; + } + return ImPlotTimeUnit_Yr; +} + +inline int LowerBoundStep(int max_divs, const int* divs, const int* step, int size) { + if (max_divs < divs[0]) + return 0; + for (int i = 1; i < size; ++i) { + if (max_divs < divs[i]) + return step[i-1]; + } + return step[size-1]; +} + +inline int GetTimeStep(int max_divs, ImPlotTimeUnit unit) { + if (unit == ImPlotTimeUnit_Ms || unit == ImPlotTimeUnit_Us) { + static const int step[] = {500,250,200,100,50,25,20,10,5,2,1}; + static const int divs[] = {2,4,5,10,20,40,50,100,200,500,1000}; + return LowerBoundStep(max_divs, divs, step, 11); + } + if (unit == ImPlotTimeUnit_S || unit == ImPlotTimeUnit_Min) { + static const int step[] = {30,15,10,5,1}; + static const int divs[] = {2,4,6,12,60}; + return LowerBoundStep(max_divs, divs, step, 5); + } + else if (unit == ImPlotTimeUnit_Hr) { + static const int step[] = {12,6,3,2,1}; + static const int divs[] = {2,4,8,12,24}; + return LowerBoundStep(max_divs, divs, step, 5); + } + else if (unit == ImPlotTimeUnit_Day) { + static const int step[] = {14,7,2,1}; + static const int divs[] = {2,4,14,28}; + return LowerBoundStep(max_divs, divs, step, 4); + } + else if (unit == ImPlotTimeUnit_Mo) { + static const int step[] = {6,3,2,1}; + static const int divs[] = {2,4,6,12}; + return LowerBoundStep(max_divs, divs, step, 4); + } + return 0; +} + +ImPlotTime MkGmtTime(struct tm *ptm) { + ImPlotTime t; +#ifdef _WIN32 + t.S = _mkgmtime(ptm); +#else + t.S = timegm(ptm); +#endif + if (t.S < 0) + t.S = 0; + return t; +} + +tm* GetGmtTime(const ImPlotTime& t, tm* ptm) +{ +#ifdef _WIN32 + if (gmtime_s(ptm, &t.S) == 0) + return ptm; + else + return nullptr; +#else + return gmtime_r(&t.S, ptm); +#endif +} + +ImPlotTime MkLocTime(struct tm *ptm) { + ImPlotTime t; + t.S = mktime(ptm); + if (t.S < 0) + t.S = 0; + return t; +} + +tm* GetLocTime(const ImPlotTime& t, tm* ptm) { +#ifdef _WIN32 + if (localtime_s(ptm, &t.S) == 0) + return ptm; + else + return nullptr; +#else + return localtime_r(&t.S, ptm); +#endif +} + +inline ImPlotTime MkTime(struct tm *ptm) { + if (GetStyle().UseLocalTime) + return MkLocTime(ptm); + else + return MkGmtTime(ptm); +} + +inline tm* GetTime(const ImPlotTime& t, tm* ptm) { + if (GetStyle().UseLocalTime) + return GetLocTime(t,ptm); + else + return GetGmtTime(t,ptm); +} + +ImPlotTime MakeTime(int year, int month, int day, int hour, int min, int sec, int us) { + tm& Tm = GImPlot->Tm; + + int yr = year - 1900; + if (yr < 0) + yr = 0; + + sec = sec + us / 1000000; + us = us % 1000000; + + Tm.tm_sec = sec; + Tm.tm_min = min; + Tm.tm_hour = hour; + Tm.tm_mday = day; + Tm.tm_mon = month; + Tm.tm_year = yr; + + ImPlotTime t = MkTime(&Tm); + + t.Us = us; + return t; +} + +int GetYear(const ImPlotTime& t) { + tm& Tm = GImPlot->Tm; + GetTime(t, &Tm); + return Tm.tm_year + 1900; +} + +ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count) { + tm& Tm = GImPlot->Tm; + ImPlotTime t_out = t; + switch(unit) { + case ImPlotTimeUnit_Us: t_out.Us += count; break; + case ImPlotTimeUnit_Ms: t_out.Us += count * 1000; break; + case ImPlotTimeUnit_S: t_out.S += count; break; + case ImPlotTimeUnit_Min: t_out.S += count * 60; break; + case ImPlotTimeUnit_Hr: t_out.S += count * 3600; break; + case ImPlotTimeUnit_Day: t_out.S += count * 86400; break; + case ImPlotTimeUnit_Mo: for (int i = 0; i < abs(count); ++i) { + GetTime(t_out, &Tm); + if (count > 0) + t_out.S += 86400 * GetDaysInMonth(Tm.tm_year + 1900, Tm.tm_mon); + else if (count < 0) + t_out.S -= 86400 * GetDaysInMonth(Tm.tm_year + 1900 - (Tm.tm_mon == 0 ? 1 : 0), Tm.tm_mon == 0 ? 11 : Tm.tm_mon - 1); // NOT WORKING + } + break; + case ImPlotTimeUnit_Yr: for (int i = 0; i < abs(count); ++i) { + if (count > 0) + t_out.S += 86400 * (365 + (int)IsLeapYear(GetYear(t_out))); + else if (count < 0) + t_out.S -= 86400 * (365 + (int)IsLeapYear(GetYear(t_out) - 1)); + // this is incorrect if leap year and we are past Feb 28 + } + break; + default: break; + } + t_out.RollOver(); + return t_out; +} + +ImPlotTime FloorTime(const ImPlotTime& t, ImPlotTimeUnit unit) { + ImPlotContext& gp = *GImPlot; + GetTime(t, &gp.Tm); + switch (unit) { + case ImPlotTimeUnit_S: return ImPlotTime(t.S, 0); + case ImPlotTimeUnit_Ms: return ImPlotTime(t.S, (t.Us / 1000) * 1000); + case ImPlotTimeUnit_Us: return t; + case ImPlotTimeUnit_Yr: gp.Tm.tm_mon = 0; // fall-through + case ImPlotTimeUnit_Mo: gp.Tm.tm_mday = 1; // fall-through + case ImPlotTimeUnit_Day: gp.Tm.tm_hour = 0; // fall-through + case ImPlotTimeUnit_Hr: gp.Tm.tm_min = 0; // fall-through + case ImPlotTimeUnit_Min: gp.Tm.tm_sec = 0; break; + default: return t; + } + return MkTime(&gp.Tm); +} + +ImPlotTime CeilTime(const ImPlotTime& t, ImPlotTimeUnit unit) { + return AddTime(FloorTime(t, unit), unit, 1); +} + +ImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit) { + ImPlotTime t1 = FloorTime(t, unit); + ImPlotTime t2 = AddTime(t1,unit,1); + if (t1.S == t2.S) + return t.Us - t1.Us < t2.Us - t.Us ? t1 : t2; + return t.S - t1.S < t2.S - t.S ? t1 : t2; +} + +ImPlotTime CombineDateTime(const ImPlotTime& date_part, const ImPlotTime& tod_part) { + ImPlotContext& gp = *GImPlot; + tm& Tm = gp.Tm; + GetTime(date_part, &gp.Tm); + int y = Tm.tm_year; + int m = Tm.tm_mon; + int d = Tm.tm_mday; + GetTime(tod_part, &gp.Tm); + Tm.tm_year = y; + Tm.tm_mon = m; + Tm.tm_mday = d; + ImPlotTime t = MkTime(&Tm); + t.Us = tod_part.Us; + return t; +} + +// TODO: allow users to define these +static const char* MONTH_NAMES[] = {"January","February","March","April","May","June","July","August","September","October","November","December"}; +static const char* WD_ABRVS[] = {"Su","Mo","Tu","We","Th","Fr","Sa"}; +static const char* MONTH_ABRVS[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; + +int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt, bool use_24_hr_clk) { + tm& Tm = GImPlot->Tm; + GetTime(t, &Tm); + const int us = t.Us % 1000; + const int ms = t.Us / 1000; + const int sec = Tm.tm_sec; + const int min = Tm.tm_min; + if (use_24_hr_clk) { + const int hr = Tm.tm_hour; + switch(fmt) { + case ImPlotTimeFmt_Us: return ImFormatString(buffer, size, ".%03d %03d", ms, us); + case ImPlotTimeFmt_SUs: return ImFormatString(buffer, size, ":%02d.%03d %03d", sec, ms, us); + case ImPlotTimeFmt_SMs: return ImFormatString(buffer, size, ":%02d.%03d", sec, ms); + case ImPlotTimeFmt_S: return ImFormatString(buffer, size, ":%02d", sec); + case ImPlotTimeFmt_MinSMs: return ImFormatString(buffer, size, ":%02d:%02d.%03d", min, sec, ms); + case ImPlotTimeFmt_HrMinSMs: return ImFormatString(buffer, size, "%02d:%02d:%02d.%03d", hr, min, sec, ms); + case ImPlotTimeFmt_HrMinS: return ImFormatString(buffer, size, "%02d:%02d:%02d", hr, min, sec); + case ImPlotTimeFmt_HrMin: return ImFormatString(buffer, size, "%02d:%02d", hr, min); + case ImPlotTimeFmt_Hr: return ImFormatString(buffer, size, "%02d:00", hr); + default: return 0; + } + } + else { + const char* ap = Tm.tm_hour < 12 ? "am" : "pm"; + const int hr = (Tm.tm_hour == 0 || Tm.tm_hour == 12) ? 12 : Tm.tm_hour % 12; + switch(fmt) { + case ImPlotTimeFmt_Us: return ImFormatString(buffer, size, ".%03d %03d", ms, us); + case ImPlotTimeFmt_SUs: return ImFormatString(buffer, size, ":%02d.%03d %03d", sec, ms, us); + case ImPlotTimeFmt_SMs: return ImFormatString(buffer, size, ":%02d.%03d", sec, ms); + case ImPlotTimeFmt_S: return ImFormatString(buffer, size, ":%02d", sec); + case ImPlotTimeFmt_MinSMs: return ImFormatString(buffer, size, ":%02d:%02d.%03d", min, sec, ms); + case ImPlotTimeFmt_HrMinSMs: return ImFormatString(buffer, size, "%d:%02d:%02d.%03d%s", hr, min, sec, ms, ap); + case ImPlotTimeFmt_HrMinS: return ImFormatString(buffer, size, "%d:%02d:%02d%s", hr, min, sec, ap); + case ImPlotTimeFmt_HrMin: return ImFormatString(buffer, size, "%d:%02d%s", hr, min, ap); + case ImPlotTimeFmt_Hr: return ImFormatString(buffer, size, "%d%s", hr, ap); + default: return 0; + } + } +} + +int FormatDate(const ImPlotTime& t, char* buffer, int size, ImPlotDateFmt fmt, bool use_iso_8601) { + tm& Tm = GImPlot->Tm; + GetTime(t, &Tm); + const int day = Tm.tm_mday; + const int mon = Tm.tm_mon + 1; + const int year = Tm.tm_year + 1900; + const int yr = year % 100; + if (use_iso_8601) { + switch (fmt) { + case ImPlotDateFmt_DayMo: return ImFormatString(buffer, size, "--%02d-%02d", mon, day); + case ImPlotDateFmt_DayMoYr: return ImFormatString(buffer, size, "%d-%02d-%02d", year, mon, day); + case ImPlotDateFmt_MoYr: return ImFormatString(buffer, size, "%d-%02d", year, mon); + case ImPlotDateFmt_Mo: return ImFormatString(buffer, size, "--%02d", mon); + case ImPlotDateFmt_Yr: return ImFormatString(buffer, size, "%d", year); + default: return 0; + } + } + else { + switch (fmt) { + case ImPlotDateFmt_DayMo: return ImFormatString(buffer, size, "%d/%d", mon, day); + case ImPlotDateFmt_DayMoYr: return ImFormatString(buffer, size, "%d/%d/%02d", mon, day, yr); + case ImPlotDateFmt_MoYr: return ImFormatString(buffer, size, "%s %d", MONTH_ABRVS[Tm.tm_mon], year); + case ImPlotDateFmt_Mo: return ImFormatString(buffer, size, "%s", MONTH_ABRVS[Tm.tm_mon]); + case ImPlotDateFmt_Yr: return ImFormatString(buffer, size, "%d", year); + default: return 0; + } + } + } + +int FormatDateTime(const ImPlotTime& t, char* buffer, int size, ImPlotDateTimeSpec fmt) { + int written = 0; + if (fmt.Date != ImPlotDateFmt_None) + written += FormatDate(t, buffer, size, fmt.Date, fmt.UseISO8601); + if (fmt.Time != ImPlotTimeFmt_None) { + if (fmt.Date != ImPlotDateFmt_None) + buffer[written++] = ' '; + written += FormatTime(t, &buffer[written], size - written, fmt.Time, fmt.Use24HourClock); + } + return written; +} + +inline float GetDateTimeWidth(ImPlotDateTimeSpec fmt) { + static const ImPlotTime t_max_width = MakeTime(2888, 12, 22, 12, 58, 58, 888888); // best guess at time that maximizes pixel width + char buffer[32]; + FormatDateTime(t_max_width, buffer, 32, fmt); + return ImGui::CalcTextSize(buffer).x; +} + +inline bool TimeLabelSame(const char* l1, const char* l2) { + size_t len1 = strlen(l1); + size_t len2 = strlen(l2); + size_t n = len1 < len2 ? len1 : len2; + return strcmp(l1 + len1 - n, l2 + len2 - n) == 0; +} + +static const ImPlotDateTimeSpec TimeFormatLevel0[ImPlotTimeUnit_COUNT] = { + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_Us), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_SMs), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_S), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMin), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_Hr), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMo, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_Mo, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_Yr, ImPlotTimeFmt_None) +}; + +static const ImPlotDateTimeSpec TimeFormatLevel1[ImPlotTimeUnit_COUNT] = { + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMin), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMinS), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMin), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMin), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_Yr, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_Yr, ImPlotTimeFmt_None) +}; + +static const ImPlotDateTimeSpec TimeFormatLevel1First[ImPlotTimeUnit_COUNT] = { + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMinS), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMinS), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMin), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMin), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_Yr, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_Yr, ImPlotTimeFmt_None) +}; + +static const ImPlotDateTimeSpec TimeFormatMouseCursor[ImPlotTimeUnit_COUNT] = { + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_Us), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_SUs), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_SMs), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMinS), + ImPlotDateTimeSpec(ImPlotDateFmt_None, ImPlotTimeFmt_HrMin), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMo, ImPlotTimeFmt_Hr), + ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None), + ImPlotDateTimeSpec(ImPlotDateFmt_MoYr, ImPlotTimeFmt_None) +}; + +inline ImPlotDateTimeSpec GetDateTimeFmt(const ImPlotDateTimeSpec* ctx, ImPlotTimeUnit idx) { + ImPlotStyle& style = GetStyle(); + ImPlotDateTimeSpec fmt = ctx[idx]; + fmt.UseISO8601 = style.UseISO8601; + fmt.Use24HourClock = style.Use24HourClock; + return fmt; +} + +void Locator_Time(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) { + IM_ASSERT_USER_ERROR(vertical == false, "Cannot locate Time ticks on vertical axis!"); + (void)vertical; + // get units for level 0 and level 1 labels + const ImPlotTimeUnit unit0 = GetUnitForRange(range.Size() / (pixels / 100)); // level = 0 (top) + const ImPlotTimeUnit unit1 = ImClamp(unit0 + 1, 0, ImPlotTimeUnit_COUNT-1); // level = 1 (bottom) + // get time format specs + const ImPlotDateTimeSpec fmt0 = GetDateTimeFmt(TimeFormatLevel0, unit0); + const ImPlotDateTimeSpec fmt1 = GetDateTimeFmt(TimeFormatLevel1, unit1); + const ImPlotDateTimeSpec fmtf = GetDateTimeFmt(TimeFormatLevel1First, unit1); + // min max times + const ImPlotTime t_min = ImPlotTime::FromDouble(range.Min); + const ImPlotTime t_max = ImPlotTime::FromDouble(range.Max); + // maximum allowable density of labels + const float max_density = 0.5f; + // book keeping + int last_major_offset = -1; + // formatter data + Formatter_Time_Data ftd; + ftd.UserFormatter = formatter; + ftd.UserFormatterData = formatter_data; + if (unit0 != ImPlotTimeUnit_Yr) { + // pixels per major (level 1) division + const float pix_per_major_div = pixels / (float)(range.Size() / TimeUnitSpans[unit1]); + // nominal pixels taken up by labels + const float fmt0_width = GetDateTimeWidth(fmt0); + const float fmt1_width = GetDateTimeWidth(fmt1); + const float fmtf_width = GetDateTimeWidth(fmtf); + // the maximum number of minor (level 0) labels that can fit between major (level 1) divisions + const int minor_per_major = (int)(max_density * pix_per_major_div / fmt0_width); + // the minor step size (level 0) + const int step = GetTimeStep(minor_per_major, unit0); + // generate ticks + ImPlotTime t1 = FloorTime(ImPlotTime::FromDouble(range.Min), unit1); + while (t1 < t_max) { + // get next major + const ImPlotTime t2 = AddTime(t1, unit1, 1); + // add major tick + if (t1 >= t_min && t1 <= t_max) { + // minor level 0 tick + ftd.Time = t1; ftd.Spec = fmt0; + ticker.AddTick(t1.ToDouble(), true, 0, true, Formatter_Time, &ftd); + // major level 1 tick + ftd.Time = t1; ftd.Spec = last_major_offset < 0 ? fmtf : fmt1; + ImPlotTick& tick_maj = ticker.AddTick(t1.ToDouble(), true, 1, true, Formatter_Time, &ftd); + const char* this_major = ticker.GetText(tick_maj); + if (last_major_offset >= 0 && TimeLabelSame(ticker.TextBuffer.Buf.Data + last_major_offset, this_major)) + tick_maj.ShowLabel = false; + last_major_offset = tick_maj.TextOffset; + } + // add minor ticks up until next major + if (minor_per_major > 1 && (t_min <= t2 && t1 <= t_max)) { + ImPlotTime t12 = AddTime(t1, unit0, step); + while (t12 < t2) { + float px_to_t2 = (float)((t2 - t12).ToDouble()/range.Size()) * pixels; + if (t12 >= t_min && t12 <= t_max) { + ftd.Time = t12; ftd.Spec = fmt0; + ticker.AddTick(t12.ToDouble(), false, 0, px_to_t2 >= fmt0_width, Formatter_Time, &ftd); + if (last_major_offset < 0 && px_to_t2 >= fmt0_width && px_to_t2 >= (fmt1_width + fmtf_width) / 2) { + ftd.Time = t12; ftd.Spec = fmtf; + ImPlotTick& tick_maj = ticker.AddTick(t12.ToDouble(), true, 1, true, Formatter_Time, &ftd); + last_major_offset = tick_maj.TextOffset; + } + } + t12 = AddTime(t12, unit0, step); + } + } + t1 = t2; + } + } + else { + const ImPlotDateTimeSpec fmty = GetDateTimeFmt(TimeFormatLevel0, ImPlotTimeUnit_Yr); + const float label_width = GetDateTimeWidth(fmty); + const int max_labels = (int)(max_density * pixels / label_width); + const int year_min = GetYear(t_min); + const int year_max = GetYear(CeilTime(t_max, ImPlotTimeUnit_Yr)); + const double nice_range = NiceNum((year_max - year_min)*0.99,false); + const double interval = NiceNum(nice_range / (max_labels - 1), true); + const int graphmin = (int)(floor(year_min / interval) * interval); + const int graphmax = (int)(ceil(year_max / interval) * interval); + const int step = (int)interval <= 0 ? 1 : (int)interval; + + for (int y = graphmin; y < graphmax; y += step) { + ImPlotTime t = MakeTime(y); + if (t >= t_min && t <= t_max) { + ftd.Time = t; ftd.Spec = fmty; + ticker.AddTick(t.ToDouble(), true, 0, true, Formatter_Time, &ftd); + } + } + } +} + +//----------------------------------------------------------------------------- +// Context Menu +//----------------------------------------------------------------------------- + +template +bool DragFloat(const char*, F*, float, F, F) { + return false; +} + +template <> +bool DragFloat(const char* label, double* v, float v_speed, double v_min, double v_max) { + return ImGui::DragScalar(label, ImGuiDataType_Double, v, v_speed, &v_min, &v_max, "%.3g", 1); +} + +template <> +bool DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max) { + return ImGui::DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, "%.3g", 1); +} + +inline void BeginDisabledControls(bool cond) { + if (cond) { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.25f); + } +} + +inline void EndDisabledControls(bool cond) { + if (cond) { + ImGui::PopItemFlag(); + ImGui::PopStyleVar(); + } +} + +void ShowAxisContextMenu(ImPlotAxis& axis, ImPlotAxis* equal_axis, bool /*time_allowed*/) { + + ImGui::PushItemWidth(75); + bool always_locked = axis.IsRangeLocked() || axis.IsAutoFitting(); + bool label = axis.HasLabel(); + bool grid = axis.HasGridLines(); + bool ticks = axis.HasTickMarks(); + bool labels = axis.HasTickLabels(); + double drag_speed = (axis.Range.Size() <= DBL_EPSILON) ? DBL_EPSILON * 1.0e+13 : 0.01 * axis.Range.Size(); // recover from almost equal axis limits. + + if (axis.Scale == ImPlotScale_Time) { + ImPlotTime tmin = ImPlotTime::FromDouble(axis.Range.Min); + ImPlotTime tmax = ImPlotTime::FromDouble(axis.Range.Max); + + BeginDisabledControls(always_locked); + ImGui::CheckboxFlags("##LockMin", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMin); + EndDisabledControls(always_locked); + ImGui::SameLine(); + BeginDisabledControls(axis.IsLockedMin() || always_locked); + if (ImGui::BeginMenu("Min Time")) { + if (ShowTimePicker("mintime", &tmin)) { + if (tmin >= tmax) + tmax = AddTime(tmin, ImPlotTimeUnit_S, 1); + axis.SetRange(tmin.ToDouble(),tmax.ToDouble()); + } + ImGui::Separator(); + if (ShowDatePicker("mindate",&axis.PickerLevel,&axis.PickerTimeMin,&tmin,&tmax)) { + tmin = CombineDateTime(axis.PickerTimeMin, tmin); + if (tmin >= tmax) + tmax = AddTime(tmin, ImPlotTimeUnit_S, 1); + axis.SetRange(tmin.ToDouble(), tmax.ToDouble()); + } + ImGui::EndMenu(); + } + EndDisabledControls(axis.IsLockedMin() || always_locked); + + BeginDisabledControls(always_locked); + ImGui::CheckboxFlags("##LockMax", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMax); + EndDisabledControls(always_locked); + ImGui::SameLine(); + BeginDisabledControls(axis.IsLockedMax() || always_locked); + if (ImGui::BeginMenu("Max Time")) { + if (ShowTimePicker("maxtime", &tmax)) { + if (tmax <= tmin) + tmin = AddTime(tmax, ImPlotTimeUnit_S, -1); + axis.SetRange(tmin.ToDouble(),tmax.ToDouble()); + } + ImGui::Separator(); + if (ShowDatePicker("maxdate",&axis.PickerLevel,&axis.PickerTimeMax,&tmin,&tmax)) { + tmax = CombineDateTime(axis.PickerTimeMax, tmax); + if (tmax <= tmin) + tmin = AddTime(tmax, ImPlotTimeUnit_S, -1); + axis.SetRange(tmin.ToDouble(), tmax.ToDouble()); + } + ImGui::EndMenu(); + } + EndDisabledControls(axis.IsLockedMax() || always_locked); + } + else { + BeginDisabledControls(always_locked); + ImGui::CheckboxFlags("##LockMin", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMin); + EndDisabledControls(always_locked); + ImGui::SameLine(); + BeginDisabledControls(axis.IsLockedMin() || always_locked); + double temp_min = axis.Range.Min; + if (DragFloat("Min", &temp_min, (float)drag_speed, -HUGE_VAL, axis.Range.Max - DBL_EPSILON)) { + axis.SetMin(temp_min,true); + if (equal_axis != nullptr) + equal_axis->SetAspect(axis.GetAspect()); + } + EndDisabledControls(axis.IsLockedMin() || always_locked); + + BeginDisabledControls(always_locked); + ImGui::CheckboxFlags("##LockMax", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMax); + EndDisabledControls(always_locked); + ImGui::SameLine(); + BeginDisabledControls(axis.IsLockedMax() || always_locked); + double temp_max = axis.Range.Max; + if (DragFloat("Max", &temp_max, (float)drag_speed, axis.Range.Min + DBL_EPSILON, HUGE_VAL)) { + axis.SetMax(temp_max,true); + if (equal_axis != nullptr) + equal_axis->SetAspect(axis.GetAspect()); + } + EndDisabledControls(axis.IsLockedMax() || always_locked); + } + + ImGui::Separator(); + + ImGui::CheckboxFlags("Auto-Fit",(unsigned int*)&axis.Flags, ImPlotAxisFlags_AutoFit); + // TODO + // BeginDisabledControls(axis.IsTime() && time_allowed); + // ImGui::CheckboxFlags("Log Scale",(unsigned int*)&axis.Flags, ImPlotAxisFlags_LogScale); + // EndDisabledControls(axis.IsTime() && time_allowed); + // if (time_allowed) { + // BeginDisabledControls(axis.IsLog() || axis.IsSymLog()); + // ImGui::CheckboxFlags("Time",(unsigned int*)&axis.Flags, ImPlotAxisFlags_Time); + // EndDisabledControls(axis.IsLog() || axis.IsSymLog()); + // } + ImGui::Separator(); + ImGui::CheckboxFlags("Invert",(unsigned int*)&axis.Flags, ImPlotAxisFlags_Invert); + ImGui::CheckboxFlags("Opposite",(unsigned int*)&axis.Flags, ImPlotAxisFlags_Opposite); + ImGui::Separator(); + BeginDisabledControls(axis.LabelOffset == -1); + if (ImGui::Checkbox("Label", &label)) + ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoLabel); + EndDisabledControls(axis.LabelOffset == -1); + if (ImGui::Checkbox("Grid Lines", &grid)) + ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoGridLines); + if (ImGui::Checkbox("Tick Marks", &ticks)) + ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoTickMarks); + if (ImGui::Checkbox("Tick Labels", &labels)) + ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoTickLabels); + +} + +bool ShowLegendContextMenu(ImPlotLegend& legend, bool visible) { + const float s = ImGui::GetFrameHeight(); + bool ret = false; + if (ImGui::Checkbox("Show",&visible)) + ret = true; + if (legend.CanGoInside) + ImGui::CheckboxFlags("Outside",(unsigned int*)&legend.Flags, ImPlotLegendFlags_Outside); + if (ImGui::RadioButton("H", ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal))) + legend.Flags |= ImPlotLegendFlags_Horizontal; + ImGui::SameLine(); + if (ImGui::RadioButton("V", !ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal))) + legend.Flags &= ~ImPlotLegendFlags_Horizontal; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2,2)); + if (ImGui::Button("NW",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_NorthWest; } ImGui::SameLine(); + if (ImGui::Button("N", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_North; } ImGui::SameLine(); + if (ImGui::Button("NE",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_NorthEast; } + if (ImGui::Button("W", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_West; } ImGui::SameLine(); + if (ImGui::InvisibleButton("C", ImVec2(1.5f*s,s))) { } ImGui::SameLine(); + if (ImGui::Button("E", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_East; } + if (ImGui::Button("SW",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_SouthWest; } ImGui::SameLine(); + if (ImGui::Button("S", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_South; } ImGui::SameLine(); + if (ImGui::Button("SE",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_SouthEast; } + ImGui::PopStyleVar(); + return ret; +} + +void ShowSubplotsContextMenu(ImPlotSubplot& subplot) { + if ((ImGui::BeginMenu("Linking"))) { + if (ImGui::MenuItem("Link Rows",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkRows))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkRows); + if (ImGui::MenuItem("Link Cols",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkCols))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkCols); + if (ImGui::MenuItem("Link All X",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllX))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllX); + if (ImGui::MenuItem("Link All Y",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllY))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllY); + ImGui::EndMenu(); + } + if ((ImGui::BeginMenu("Settings"))) { + BeginDisabledControls(!subplot.HasTitle); + if (ImGui::MenuItem("Title",nullptr,subplot.HasTitle && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoTitle))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_NoTitle); + EndDisabledControls(!subplot.HasTitle); + if (ImGui::MenuItem("Resizable",nullptr,!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoResize))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_NoResize); + if (ImGui::MenuItem("Align",nullptr,!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoAlign))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_NoAlign); + if (ImGui::MenuItem("Share Items",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems))) + ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems); + ImGui::EndMenu(); + } +} + +void ShowPlotContextMenu(ImPlotPlot& plot) { + ImPlotContext& gp = *GImPlot; + const bool owns_legend = gp.CurrentItems == &plot.Items; + const bool equal = ImHasFlag(plot.Flags, ImPlotFlags_Equal); + + char buf[16] = {}; + + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (!x_axis.Enabled || !x_axis.HasMenus()) + continue; + ImGui::PushID(i); + ImFormatString(buf, sizeof(buf) - 1, i == 0 ? "X-Axis" : "X-Axis %d", i + 1); + if (ImGui::BeginMenu(x_axis.HasLabel() ? plot.GetAxisLabel(x_axis) : buf)) { + ShowAxisContextMenu(x_axis, equal ? x_axis.OrthoAxis : nullptr, false); + ImGui::EndMenu(); + } + ImGui::PopID(); + } + + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (!y_axis.Enabled || !y_axis.HasMenus()) + continue; + ImGui::PushID(i); + ImFormatString(buf, sizeof(buf) - 1, i == 0 ? "Y-Axis" : "Y-Axis %d", i + 1); + if (ImGui::BeginMenu(y_axis.HasLabel() ? plot.GetAxisLabel(y_axis) : buf)) { + ShowAxisContextMenu(y_axis, equal ? y_axis.OrthoAxis : nullptr, false); + ImGui::EndMenu(); + } + ImGui::PopID(); + } + + ImGui::Separator(); + if (!ImHasFlag(gp.CurrentItems->Legend.Flags, ImPlotLegendFlags_NoMenus)) { + if ((ImGui::BeginMenu("Legend"))) { + if (owns_legend) { + if (ShowLegendContextMenu(plot.Items.Legend, !ImHasFlag(plot.Flags, ImPlotFlags_NoLegend))) + ImFlipFlag(plot.Flags, ImPlotFlags_NoLegend); + } + else if (gp.CurrentSubplot != nullptr) { + if (ShowLegendContextMenu(gp.CurrentSubplot->Items.Legend, !ImHasFlag(gp.CurrentSubplot->Flags, ImPlotSubplotFlags_NoLegend))) + ImFlipFlag(gp.CurrentSubplot->Flags, ImPlotSubplotFlags_NoLegend); + } + ImGui::EndMenu(); + } + } + if ((ImGui::BeginMenu("Settings"))) { + if (ImGui::MenuItem("Equal", nullptr, ImHasFlag(plot.Flags, ImPlotFlags_Equal))) + ImFlipFlag(plot.Flags, ImPlotFlags_Equal); + if (ImGui::MenuItem("Box Select",nullptr,!ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect))) + ImFlipFlag(plot.Flags, ImPlotFlags_NoBoxSelect); + BeginDisabledControls(plot.TitleOffset == -1); + if (ImGui::MenuItem("Title",nullptr,plot.HasTitle())) + ImFlipFlag(plot.Flags, ImPlotFlags_NoTitle); + EndDisabledControls(plot.TitleOffset == -1); + if (ImGui::MenuItem("Mouse Position",nullptr,!ImHasFlag(plot.Flags, ImPlotFlags_NoMouseText))) + ImFlipFlag(plot.Flags, ImPlotFlags_NoMouseText); + if (ImGui::MenuItem("Crosshairs",nullptr,ImHasFlag(plot.Flags, ImPlotFlags_Crosshairs))) + ImFlipFlag(plot.Flags, ImPlotFlags_Crosshairs); + ImGui::EndMenu(); + } + if (gp.CurrentSubplot != nullptr && !ImHasFlag(gp.CurrentSubplot->Flags, ImPlotSubplotFlags_NoMenus)) { + ImGui::Separator(); + if ((ImGui::BeginMenu("Subplots"))) { + ShowSubplotsContextMenu(*gp.CurrentSubplot); + ImGui::EndMenu(); + } + } +} + +//----------------------------------------------------------------------------- +// Axis Utils +//----------------------------------------------------------------------------- + +static inline int AxisPrecision(const ImPlotAxis& axis) { + const double range = axis.Ticker.TickCount() > 1 ? (axis.Ticker.Ticks[1].PlotPos - axis.Ticker.Ticks[0].PlotPos) : axis.Range.Size(); + return Precision(range); +} + +static inline double RoundAxisValue(const ImPlotAxis& axis, double value) { + return RoundTo(value, AxisPrecision(axis)); +} + +void LabelAxisValue(const ImPlotAxis& axis, double value, char* buff, int size, bool round) { + ImPlotContext& gp = *GImPlot; + // TODO: We shouldn't explicitly check that the axis is Time here. Ideally, + // Formatter_Time would handle the formatting for us, but the code below + // needs additional arguments which are not currently available in ImPlotFormatter + if (axis.Locator == Locator_Time) { + ImPlotTimeUnit unit = axis.Vertical + ? GetUnitForRange(axis.Range.Size() / (gp.CurrentPlot->PlotRect.GetHeight() / 100)) // TODO: magic value! + : GetUnitForRange(axis.Range.Size() / (gp.CurrentPlot->PlotRect.GetWidth() / 100)); // TODO: magic value! + FormatDateTime(ImPlotTime::FromDouble(value), buff, size, GetDateTimeFmt(TimeFormatMouseCursor, unit)); + } + else { + if (round) + value = RoundAxisValue(axis, value); + axis.Formatter(value, buff, size, axis.FormatterData); + } +} + +void UpdateAxisColors(ImPlotAxis& axis) { + const ImVec4 col_grid = GetStyleColorVec4(ImPlotCol_AxisGrid); + axis.ColorMaj = ImGui::GetColorU32(col_grid); + axis.ColorMin = ImGui::GetColorU32(col_grid*ImVec4(1,1,1,GImPlot->Style.MinorAlpha)); + axis.ColorTick = GetStyleColorU32(ImPlotCol_AxisTick); + axis.ColorTxt = GetStyleColorU32(ImPlotCol_AxisText); + axis.ColorBg = GetStyleColorU32(ImPlotCol_AxisBg); + axis.ColorHov = GetStyleColorU32(ImPlotCol_AxisBgHovered); + axis.ColorAct = GetStyleColorU32(ImPlotCol_AxisBgActive); + // axis.ColorHiLi = IM_COL32_BLACK_TRANS; +} + +void PadAndDatumAxesX(ImPlotPlot& plot, float& pad_T, float& pad_B, ImPlotAlignmentData* align) { + + ImPlotContext& gp = *GImPlot; + + const float T = ImGui::GetTextLineHeight(); + const float P = gp.Style.LabelPadding.y; + const float K = gp.Style.MinorTickLen.x; + + int count_T = 0; + int count_B = 0; + float last_T = plot.AxesRect.Min.y; + float last_B = plot.AxesRect.Max.y; + + for (int i = IMPLOT_NUM_X_AXES; i-- > 0;) { // FYI: can iterate forward + ImPlotAxis& axis = plot.XAxis(i); + if (!axis.Enabled) + continue; + const bool label = axis.HasLabel(); + const bool ticks = axis.HasTickLabels(); + const bool opp = axis.IsOpposite(); + const bool time = axis.Scale == ImPlotScale_Time; + if (opp) { + if (count_T++ > 0) + pad_T += K + P; + if (label) + pad_T += T + P; + if (ticks) + pad_T += ImMax(T, axis.Ticker.MaxSize.y) + P + (time ? T + P : 0); + axis.Datum1 = plot.CanvasRect.Min.y + pad_T; + axis.Datum2 = last_T; + last_T = axis.Datum1; + } + else { + if (count_B++ > 0) + pad_B += K + P; + if (label) + pad_B += T + P; + if (ticks) + pad_B += ImMax(T, axis.Ticker.MaxSize.y) + P + (time ? T + P : 0); + axis.Datum1 = plot.CanvasRect.Max.y - pad_B; + axis.Datum2 = last_B; + last_B = axis.Datum1; + } + } + + if (align) { + count_T = count_B = 0; + float delta_T, delta_B; + align->Update(pad_T,pad_B,delta_T,delta_B); + for (int i = IMPLOT_NUM_X_AXES; i-- > 0;) { + ImPlotAxis& axis = plot.XAxis(i); + if (!axis.Enabled) + continue; + if (axis.IsOpposite()) { + axis.Datum1 += delta_T; + axis.Datum2 += count_T++ > 1 ? delta_T : 0; + } + else { + axis.Datum1 -= delta_B; + axis.Datum2 -= count_B++ > 1 ? delta_B : 0; + } + } + } +} + +void PadAndDatumAxesY(ImPlotPlot& plot, float& pad_L, float& pad_R, ImPlotAlignmentData* align) { + + // [ pad_L ] [ pad_R ] + // .................CanvasRect................ + // :TPWPK.PTPWP _____PlotRect____ PWPTP.KPWPT: + // :A # |- A # |- -| # A -| # A: + // :X | X | | X | x: + // :I # |- I # |- -| # I -| # I: + // :S | S | | S | S: + // :3 # |- 0 # |-_______________-| # 1 -| # 2: + // :.........................................: + // + // T = text height + // P = label padding + // K = minor tick length + // W = label width + + ImPlotContext& gp = *GImPlot; + + const float T = ImGui::GetTextLineHeight(); + const float P = gp.Style.LabelPadding.x; + const float K = gp.Style.MinorTickLen.y; + + int count_L = 0; + int count_R = 0; + float last_L = plot.AxesRect.Min.x; + float last_R = plot.AxesRect.Max.x; + + for (int i = IMPLOT_NUM_Y_AXES; i-- > 0;) { // FYI: can iterate forward + ImPlotAxis& axis = plot.YAxis(i); + if (!axis.Enabled) + continue; + const bool label = axis.HasLabel(); + const bool ticks = axis.HasTickLabels(); + const bool opp = axis.IsOpposite(); + if (opp) { + if (count_R++ > 0) + pad_R += K + P; + if (label) + pad_R += T + P; + if (ticks) + pad_R += axis.Ticker.MaxSize.x + P; + axis.Datum1 = plot.CanvasRect.Max.x - pad_R; + axis.Datum2 = last_R; + last_R = axis.Datum1; + } + else { + if (count_L++ > 0) + pad_L += K + P; + if (label) + pad_L += T + P; + if (ticks) + pad_L += axis.Ticker.MaxSize.x + P; + axis.Datum1 = plot.CanvasRect.Min.x + pad_L; + axis.Datum2 = last_L; + last_L = axis.Datum1; + } + } + + plot.PlotRect.Min.x = plot.CanvasRect.Min.x + pad_L; + plot.PlotRect.Max.x = plot.CanvasRect.Max.x - pad_R; + + if (align) { + count_L = count_R = 0; + float delta_L, delta_R; + align->Update(pad_L,pad_R,delta_L,delta_R); + for (int i = IMPLOT_NUM_Y_AXES; i-- > 0;) { + ImPlotAxis& axis = plot.YAxis(i); + if (!axis.Enabled) + continue; + if (axis.IsOpposite()) { + axis.Datum1 -= delta_R; + axis.Datum2 -= count_R++ > 1 ? delta_R : 0; + } + else { + axis.Datum1 += delta_L; + axis.Datum2 += count_L++ > 1 ? delta_L : 0; + } + } + } +} + +//----------------------------------------------------------------------------- +// RENDERING +//----------------------------------------------------------------------------- + +static inline void RenderGridLinesX(ImDrawList& DrawList, const ImPlotTicker& ticker, const ImRect& rect, ImU32 col_maj, ImU32 col_min, float size_maj, float size_min) { + const float density = ticker.TickCount() / rect.GetWidth(); + ImVec4 col_min4 = ImGui::ColorConvertU32ToFloat4(col_min); + col_min4.w *= ImClamp(ImRemap(density, 0.1f, 0.2f, 1.0f, 0.0f), 0.0f, 1.0f); + col_min = ImGui::ColorConvertFloat4ToU32(col_min4); + for (int t = 0; t < ticker.TickCount(); t++) { + const ImPlotTick& xt = ticker.Ticks[t]; + if (xt.PixelPos < rect.Min.x || xt.PixelPos > rect.Max.x) + continue; + if (xt.Level == 0) { + if (xt.Major) + DrawList.AddLine(ImVec2(xt.PixelPos, rect.Min.y), ImVec2(xt.PixelPos, rect.Max.y), col_maj, size_maj); + else if (density < 0.2f) + DrawList.AddLine(ImVec2(xt.PixelPos, rect.Min.y), ImVec2(xt.PixelPos, rect.Max.y), col_min, size_min); + } + } +} + +static inline void RenderGridLinesY(ImDrawList& DrawList, const ImPlotTicker& ticker, const ImRect& rect, ImU32 col_maj, ImU32 col_min, float size_maj, float size_min) { + const float density = ticker.TickCount() / rect.GetHeight(); + ImVec4 col_min4 = ImGui::ColorConvertU32ToFloat4(col_min); + col_min4.w *= ImClamp(ImRemap(density, 0.1f, 0.2f, 1.0f, 0.0f), 0.0f, 1.0f); + col_min = ImGui::ColorConvertFloat4ToU32(col_min4); + for (int t = 0; t < ticker.TickCount(); t++) { + const ImPlotTick& yt = ticker.Ticks[t]; + if (yt.PixelPos < rect.Min.y || yt.PixelPos > rect.Max.y) + continue; + if (yt.Major) + DrawList.AddLine(ImVec2(rect.Min.x, yt.PixelPos), ImVec2(rect.Max.x, yt.PixelPos), col_maj, size_maj); + else if (density < 0.2f) + DrawList.AddLine(ImVec2(rect.Min.x, yt.PixelPos), ImVec2(rect.Max.x, yt.PixelPos), col_min, size_min); + } +} + +static inline void RenderSelectionRect(ImDrawList& DrawList, const ImVec2& p_min, const ImVec2& p_max, const ImVec4& col) { + const ImU32 col_bg = ImGui::GetColorU32(col * ImVec4(1,1,1,0.25f)); + const ImU32 col_bd = ImGui::GetColorU32(col); + DrawList.AddRectFilled(p_min, p_max, col_bg); + DrawList.AddRect(p_min, p_max, col_bd); +} + +//----------------------------------------------------------------------------- +// Input Handling +//----------------------------------------------------------------------------- + +static const float MOUSE_CURSOR_DRAG_THRESHOLD = 5.0f; +static const float BOX_SELECT_DRAG_THRESHOLD = 4.0f; + +bool UpdateInput(ImPlotPlot& plot) { + + bool changed = false; + + ImPlotContext& gp = *GImPlot; + ImGuiIO& IO = ImGui::GetIO(); + + // BUTTON STATE ----------------------------------------------------------- + + const ImGuiButtonFlags plot_button_flags = ImGuiButtonFlags_AllowOverlap + | ImGuiButtonFlags_PressedOnClick + | ImGuiButtonFlags_PressedOnDoubleClick + | ImGuiButtonFlags_MouseButtonLeft + | ImGuiButtonFlags_MouseButtonRight + | ImGuiButtonFlags_MouseButtonMiddle; + const ImGuiButtonFlags axis_button_flags = ImGuiButtonFlags_FlattenChildren + | plot_button_flags; + + const bool plot_clicked = ImGui::ButtonBehavior(plot.PlotRect,plot.ID,&plot.Hovered,&plot.Held,plot_button_flags); +#if (IMGUI_VERSION_NUM < 18966) + ImGui::SetItemAllowOverlap(); // Handled by ButtonBehavior() +#endif + + if (plot_clicked) { + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect) && IO.MouseClicked[gp.InputMap.Select] && ImHasFlag(IO.KeyMods, gp.InputMap.SelectMod)) { + plot.Selecting = true; + plot.SelectStart = IO.MousePos; + plot.SelectRect = ImRect(0,0,0,0); + } + if (IO.MouseDoubleClicked[gp.InputMap.Fit]) { + plot.FitThisFrame = true; + for (int i = 0; i < ImAxis_COUNT; ++i) + plot.Axes[i].FitThisFrame = true; + } + } + + const bool can_pan = IO.MouseDown[gp.InputMap.Pan] && ImHasFlag(IO.KeyMods, gp.InputMap.PanMod); + + plot.Held = plot.Held && can_pan; + + bool x_click[IMPLOT_NUM_X_AXES] = {false}; + bool x_held[IMPLOT_NUM_X_AXES] = {false}; + bool x_hov[IMPLOT_NUM_X_AXES] = {false}; + + bool y_click[IMPLOT_NUM_Y_AXES] = {false}; + bool y_held[IMPLOT_NUM_Y_AXES] = {false}; + bool y_hov[IMPLOT_NUM_Y_AXES] = {false}; + + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) { + ImPlotAxis& xax = plot.XAxis(i); + if (xax.Enabled) { + ImGui::KeepAliveID(xax.ID); + x_click[i] = ImGui::ButtonBehavior(xax.HoverRect,xax.ID,&xax.Hovered,&xax.Held,axis_button_flags); + if (x_click[i] && IO.MouseDoubleClicked[gp.InputMap.Fit]) + plot.FitThisFrame = xax.FitThisFrame = true; + xax.Held = xax.Held && can_pan; + x_hov[i] = xax.Hovered || plot.Hovered; + x_held[i] = xax.Held || plot.Held; + } + } + + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) { + ImPlotAxis& yax = plot.YAxis(i); + if (yax.Enabled) { + ImGui::KeepAliveID(yax.ID); + y_click[i] = ImGui::ButtonBehavior(yax.HoverRect,yax.ID,&yax.Hovered,&yax.Held,axis_button_flags); + if (y_click[i] && IO.MouseDoubleClicked[gp.InputMap.Fit]) + plot.FitThisFrame = yax.FitThisFrame = true; + yax.Held = yax.Held && can_pan; + y_hov[i] = yax.Hovered || plot.Hovered; + y_held[i] = yax.Held || plot.Held; + } + } + + // cancel due to DND activity + if (GImGui->DragDropActive || (IO.KeyMods == gp.InputMap.OverrideMod && gp.InputMap.OverrideMod != 0)) + return false; + + // STATE ------------------------------------------------------------------- + + const bool axis_equal = ImHasFlag(plot.Flags, ImPlotFlags_Equal); + + const bool any_x_hov = plot.Hovered || AnyAxesHovered(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES); + const bool any_x_held = plot.Held || AnyAxesHeld(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES); + const bool any_y_hov = plot.Hovered || AnyAxesHovered(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES); + const bool any_y_held = plot.Held || AnyAxesHeld(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES); + const bool any_hov = any_x_hov || any_y_hov; + const bool any_held = any_x_held || any_y_held; + + const ImVec2 select_drag = ImGui::GetMouseDragDelta(gp.InputMap.Select); + const ImVec2 pan_drag = ImGui::GetMouseDragDelta(gp.InputMap.Pan); + const float select_drag_sq = ImLengthSqr(select_drag); + const float pan_drag_sq = ImLengthSqr(pan_drag); + const bool selecting = plot.Selecting && select_drag_sq > MOUSE_CURSOR_DRAG_THRESHOLD; + const bool panning = any_held && pan_drag_sq > MOUSE_CURSOR_DRAG_THRESHOLD; + + // CONTEXT MENU ----------------------------------------------------------- + + if (IO.MouseReleased[gp.InputMap.Menu] && !plot.ContextLocked) + gp.OpenContextThisFrame = true; + + if (selecting || panning) + plot.ContextLocked = true; + else if (!(IO.MouseDown[gp.InputMap.Menu] || IO.MouseReleased[gp.InputMap.Menu])) + plot.ContextLocked = false; + + // DRAG INPUT ------------------------------------------------------------- + + if (any_held && !plot.Selecting) { + int drag_direction = 0; + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (x_held[i] && !x_axis.IsInputLocked()) { + drag_direction |= (1 << 1); + bool increasing = x_axis.IsInverted() ? IO.MouseDelta.x > 0 : IO.MouseDelta.x < 0; + if (IO.MouseDelta.x != 0 && !x_axis.IsPanLocked(increasing)) { + const double plot_l = x_axis.PixelsToPlot(plot.PlotRect.Min.x - IO.MouseDelta.x); + const double plot_r = x_axis.PixelsToPlot(plot.PlotRect.Max.x - IO.MouseDelta.x); + x_axis.SetMin(x_axis.IsInverted() ? plot_r : plot_l); + x_axis.SetMax(x_axis.IsInverted() ? plot_l : plot_r); + if (axis_equal && x_axis.OrthoAxis != nullptr) + x_axis.OrthoAxis->SetAspect(x_axis.GetAspect()); + changed = true; + } + } + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (y_held[i] && !y_axis.IsInputLocked()) { + drag_direction |= (1 << 2); + bool increasing = y_axis.IsInverted() ? IO.MouseDelta.y < 0 : IO.MouseDelta.y > 0; + if (IO.MouseDelta.y != 0 && !y_axis.IsPanLocked(increasing)) { + const double plot_t = y_axis.PixelsToPlot(plot.PlotRect.Min.y - IO.MouseDelta.y); + const double plot_b = y_axis.PixelsToPlot(plot.PlotRect.Max.y - IO.MouseDelta.y); + y_axis.SetMin(y_axis.IsInverted() ? plot_t : plot_b); + y_axis.SetMax(y_axis.IsInverted() ? plot_b : plot_t); + if (axis_equal && y_axis.OrthoAxis != nullptr) + y_axis.OrthoAxis->SetAspect(y_axis.GetAspect()); + changed = true; + } + } + } + if (IO.MouseDragMaxDistanceSqr[gp.InputMap.Pan] > MOUSE_CURSOR_DRAG_THRESHOLD) { + switch (drag_direction) { + case 0 : ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed); break; + case (1 << 1) : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); break; + case (1 << 2) : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); break; + default : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); break; + } + } + } + + // SCROLL INPUT ----------------------------------------------------------- + + if (any_hov && ImHasFlag(IO.KeyMods, gp.InputMap.ZoomMod)) { + + float zoom_rate = gp.InputMap.ZoomRate; + if (IO.MouseWheel == 0.0f) + zoom_rate = 0; + else if (IO.MouseWheel > 0) + zoom_rate = (-zoom_rate) / (1.0f + (2.0f * zoom_rate)); + ImVec2 rect_size = plot.PlotRect.GetSize(); + float tx = ImRemap(IO.MousePos.x, plot.PlotRect.Min.x, plot.PlotRect.Max.x, 0.0f, 1.0f); + float ty = ImRemap(IO.MousePos.y, plot.PlotRect.Min.y, plot.PlotRect.Max.y, 0.0f, 1.0f); + + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& x_axis = plot.XAxis(i); + const bool equal_zoom = axis_equal && x_axis.OrthoAxis != nullptr; + const bool equal_locked = (equal_zoom != false) && x_axis.OrthoAxis->IsInputLocked(); + if (x_hov[i] && !x_axis.IsInputLocked() && !equal_locked) { + ImGui::SetKeyOwner(ImGuiKey_MouseWheelY, plot.ID); + if (zoom_rate != 0.0f) { + float correction = (plot.Hovered && equal_zoom) ? 0.5f : 1.0f; + const double plot_l = x_axis.PixelsToPlot(plot.PlotRect.Min.x - rect_size.x * tx * zoom_rate * correction); + const double plot_r = x_axis.PixelsToPlot(plot.PlotRect.Max.x + rect_size.x * (1 - tx) * zoom_rate * correction); + x_axis.SetMin(x_axis.IsInverted() ? plot_r : plot_l); + x_axis.SetMax(x_axis.IsInverted() ? plot_l : plot_r); + if (axis_equal && x_axis.OrthoAxis != nullptr) + x_axis.OrthoAxis->SetAspect(x_axis.GetAspect()); + changed = true; + } + } + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& y_axis = plot.YAxis(i); + const bool equal_zoom = axis_equal && y_axis.OrthoAxis != nullptr; + const bool equal_locked = equal_zoom && y_axis.OrthoAxis->IsInputLocked(); + if (y_hov[i] && !y_axis.IsInputLocked() && !equal_locked) { + ImGui::SetKeyOwner(ImGuiKey_MouseWheelY, plot.ID); + if (zoom_rate != 0.0f) { + float correction = (plot.Hovered && equal_zoom) ? 0.5f : 1.0f; + const double plot_t = y_axis.PixelsToPlot(plot.PlotRect.Min.y - rect_size.y * ty * zoom_rate * correction); + const double plot_b = y_axis.PixelsToPlot(plot.PlotRect.Max.y + rect_size.y * (1 - ty) * zoom_rate * correction); + y_axis.SetMin(y_axis.IsInverted() ? plot_t : plot_b); + y_axis.SetMax(y_axis.IsInverted() ? plot_b : plot_t); + if (axis_equal && y_axis.OrthoAxis != nullptr) + y_axis.OrthoAxis->SetAspect(y_axis.GetAspect()); + changed = true; + } + } + } + } + + // BOX-SELECTION ---------------------------------------------------------- + + if (plot.Selecting) { + const ImVec2 d = plot.SelectStart - IO.MousePos; + const bool x_can_change = !ImHasFlag(IO.KeyMods,gp.InputMap.SelectHorzMod) && ImFabs(d.x) > 2; + const bool y_can_change = !ImHasFlag(IO.KeyMods,gp.InputMap.SelectVertMod) && ImFabs(d.y) > 2; + // confirm + if (IO.MouseReleased[gp.InputMap.Select]) { + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (!x_axis.IsInputLocked() && x_can_change) { + const double p1 = x_axis.PixelsToPlot(plot.SelectStart.x); + const double p2 = x_axis.PixelsToPlot(IO.MousePos.x); + x_axis.SetMin(ImMin(p1, p2)); + x_axis.SetMax(ImMax(p1, p2)); + changed = true; + } + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (!y_axis.IsInputLocked() && y_can_change) { + const double p1 = y_axis.PixelsToPlot(plot.SelectStart.y); + const double p2 = y_axis.PixelsToPlot(IO.MousePos.y); + y_axis.SetMin(ImMin(p1, p2)); + y_axis.SetMax(ImMax(p1, p2)); + changed = true; + } + } + if (x_can_change || y_can_change || (ImHasFlag(IO.KeyMods,gp.InputMap.SelectHorzMod) && ImHasFlag(IO.KeyMods,gp.InputMap.SelectVertMod))) + gp.OpenContextThisFrame = false; + plot.Selected = plot.Selecting = false; + } + // cancel + else if (IO.MouseReleased[gp.InputMap.SelectCancel]) { + plot.Selected = plot.Selecting = false; + gp.OpenContextThisFrame = false; + } + else if (ImLengthSqr(d) > BOX_SELECT_DRAG_THRESHOLD) { + // bad selection + if (plot.IsInputLocked()) { + ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed); + gp.OpenContextThisFrame = false; + plot.Selected = false; + } + else { + // TODO: Handle only min or max locked cases + const bool full_width = ImHasFlag(IO.KeyMods, gp.InputMap.SelectHorzMod) || AllAxesInputLocked(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES); + const bool full_height = ImHasFlag(IO.KeyMods, gp.InputMap.SelectVertMod) || AllAxesInputLocked(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES); + plot.SelectRect.Min.x = full_width ? plot.PlotRect.Min.x : ImMin(plot.SelectStart.x, IO.MousePos.x); + plot.SelectRect.Max.x = full_width ? plot.PlotRect.Max.x : ImMax(plot.SelectStart.x, IO.MousePos.x); + plot.SelectRect.Min.y = full_height ? plot.PlotRect.Min.y : ImMin(plot.SelectStart.y, IO.MousePos.y); + plot.SelectRect.Max.y = full_height ? plot.PlotRect.Max.y : ImMax(plot.SelectStart.y, IO.MousePos.y); + plot.SelectRect.Min -= plot.PlotRect.Min; + plot.SelectRect.Max -= plot.PlotRect.Min; + plot.Selected = true; + } + } + else { + plot.Selected = false; + } + } + return changed; +} + +//----------------------------------------------------------------------------- +// Next Plot Data (Legacy) +//----------------------------------------------------------------------------- + +void ApplyNextPlotData(ImAxis idx) { + ImPlotContext& gp = *GImPlot; + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + if (!axis.Enabled) + return; + double* npd_lmin = gp.NextPlotData.LinkedMin[idx]; + double* npd_lmax = gp.NextPlotData.LinkedMax[idx]; + bool npd_rngh = gp.NextPlotData.HasRange[idx]; + ImPlotCond npd_rngc = gp.NextPlotData.RangeCond[idx]; + ImPlotRange npd_rngv = gp.NextPlotData.Range[idx]; + axis.LinkedMin = npd_lmin; + axis.LinkedMax = npd_lmax; + axis.PullLinks(); + if (npd_rngh) { + if (!plot.Initialized || npd_rngc == ImPlotCond_Always) + axis.SetRange(npd_rngv); + } + axis.HasRange = npd_rngh; + axis.RangeCond = npd_rngc; +} + +//----------------------------------------------------------------------------- +// Setup +//----------------------------------------------------------------------------- + +void SetupAxis(ImAxis idx, const char* label, ImPlotAxisFlags flags) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + // get plot and axis + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + // set ID + axis.ID = plot.ID + idx + 1; + // check and set flags + if (plot.JustCreated || flags != axis.PreviousFlags) + axis.Flags = flags; + axis.PreviousFlags = flags; + // enable axis + axis.Enabled = true; + // set label + plot.SetAxisLabel(axis,label); + // cache colors + UpdateAxisColors(axis); +} + +void SetupAxisLimits(ImAxis idx, double min_lim, double max_lim, ImPlotCond cond) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); // get plot and axis + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + if (!plot.Initialized || cond == ImPlotCond_Always) + axis.SetRange(min_lim, max_lim); + axis.HasRange = true; + axis.RangeCond = cond; +} + +void SetupAxisFormat(ImAxis idx, const char* fmt) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.HasFormatSpec = fmt != nullptr; + if (fmt != nullptr) + ImStrncpy(axis.FormatSpec,fmt,sizeof(axis.FormatSpec)); +} + +void SetupAxisLinks(ImAxis idx, double* min_lnk, double* max_lnk) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.LinkedMin = min_lnk; + axis.LinkedMax = max_lnk; + axis.PullLinks(); +} + +void SetupAxisFormat(ImAxis idx, ImPlotFormatter formatter, void* data) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.Formatter = formatter; + axis.FormatterData = data; +} + +void SetupAxisTicks(ImAxis idx, const double* values, int n_ticks, const char* const labels[], bool show_default) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.ShowDefaultTicks = show_default; + AddTicksCustom(values, + labels, + n_ticks, + axis.Ticker, + axis.Formatter ? axis.Formatter : Formatter_Default, + (axis.Formatter && axis.FormatterData) ? axis.FormatterData : axis.HasFormatSpec ? axis.FormatSpec : (void*)IMPLOT_LABEL_FORMAT); +} + +void SetupAxisTicks(ImAxis idx, double v_min, double v_max, int n_ticks, const char* const labels[], bool show_default) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + n_ticks = n_ticks < 2 ? 2 : n_ticks; + FillRange(gp.TempDouble1, n_ticks, v_min, v_max); + SetupAxisTicks(idx, gp.TempDouble1.Data, n_ticks, labels, show_default); +} + +void SetupAxisScale(ImAxis idx, ImPlotScale scale) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.Scale = scale; + switch (scale) + { + case ImPlotScale_Time: + axis.TransformForward = nullptr; + axis.TransformInverse = nullptr; + axis.TransformData = nullptr; + axis.Locator = Locator_Time; + axis.ConstraintRange = ImPlotRange(IMPLOT_MIN_TIME, IMPLOT_MAX_TIME); + axis.Ticker.Levels = 2; + break; + case ImPlotScale_Log10: + axis.TransformForward = TransformForward_Log10; + axis.TransformInverse = TransformInverse_Log10; + axis.TransformData = nullptr; + axis.Locator = Locator_Log10; + axis.ConstraintRange = ImPlotRange(DBL_MIN, INFINITY); + break; + case ImPlotScale_SymLog: + axis.TransformForward = TransformForward_SymLog; + axis.TransformInverse = TransformInverse_SymLog; + axis.TransformData = nullptr; + axis.Locator = Locator_SymLog; + axis.ConstraintRange = ImPlotRange(-INFINITY, INFINITY); + break; + default: + axis.TransformForward = nullptr; + axis.TransformInverse = nullptr; + axis.TransformData = nullptr; + axis.Locator = nullptr; + axis.ConstraintRange = ImPlotRange(-INFINITY, INFINITY); + break; + } +} + +void SetupAxisScale(ImAxis idx, ImPlotTransform fwd, ImPlotTransform inv, void* data) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.Scale = IMPLOT_AUTO; + axis.TransformForward = fwd; + axis.TransformInverse = inv; + axis.TransformData = data; +} + +void SetupAxisLimitsConstraints(ImAxis idx, double v_min, double v_max) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.ConstraintRange.Min = v_min; + axis.ConstraintRange.Max = v_max; +} + +void SetupAxisZoomConstraints(ImAxis idx, double z_min, double z_max) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& axis = plot.Axes[idx]; + IM_ASSERT_USER_ERROR(axis.Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + axis.ConstraintZoom.Min = z_min; + axis.ConstraintZoom.Max = z_max; +} + +void SetupAxes(const char* x_label, const char* y_label, ImPlotAxisFlags x_flags, ImPlotAxisFlags y_flags) { + SetupAxis(ImAxis_X1, x_label, x_flags); + SetupAxis(ImAxis_Y1, y_label, y_flags); +} + +void SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, ImPlotCond cond) { + SetupAxisLimits(ImAxis_X1, x_min, x_max, cond); + SetupAxisLimits(ImAxis_Y1, y_min, y_max, cond); +} + +void SetupLegend(ImPlotLocation location, ImPlotLegendFlags flags) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR((gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked) || (gp.CurrentSubplot != nullptr && gp.CurrentPlot == nullptr), + "Setup needs to be called after BeginPlot or BeginSubplots and before any setup locking functions (e.g. PlotX)!"); + if (gp.CurrentItems) { + ImPlotLegend& legend = gp.CurrentItems->Legend; + // check and set location + if (location != legend.PreviousLocation) + legend.Location = location; + legend.PreviousLocation = location; + // check and set flags + if (flags != legend.PreviousFlags) + legend.Flags = flags; + legend.PreviousFlags = flags; + } +} + +void SetupMouseText(ImPlotLocation location, ImPlotMouseTextFlags flags) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked, + "Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!"); + gp.CurrentPlot->MouseTextLocation = location; + gp.CurrentPlot->MouseTextFlags = flags; +} + +//----------------------------------------------------------------------------- +// SetNext +//----------------------------------------------------------------------------- + +void SetNextAxisLimits(ImAxis axis, double v_min, double v_max, ImPlotCond cond) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "SetNextAxisLimits() needs to be called before BeginPlot()!"); + IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. + gp.NextPlotData.HasRange[axis] = true; + gp.NextPlotData.RangeCond[axis] = cond; + gp.NextPlotData.Range[axis].Min = v_min; + gp.NextPlotData.Range[axis].Max = v_max; +} + +void SetNextAxisLinks(ImAxis axis, double* link_min, double* link_max) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "SetNextAxisLinks() needs to be called before BeginPlot()!"); + gp.NextPlotData.LinkedMin[axis] = link_min; + gp.NextPlotData.LinkedMax[axis] = link_max; +} + +void SetNextAxisToFit(ImAxis axis) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "SetNextAxisToFit() needs to be called before BeginPlot()!"); + gp.NextPlotData.Fit[axis] = true; +} + +void SetNextAxesLimits(double x_min, double x_max, double y_min, double y_max, ImPlotCond cond) { + SetNextAxisLimits(ImAxis_X1, x_min, x_max, cond); + SetNextAxisLimits(ImAxis_Y1, y_min, y_max, cond); +} + +void SetNextAxesToFit() { + for (int i = 0; i < ImAxis_COUNT; ++i) + SetNextAxisToFit(i); +} + +//----------------------------------------------------------------------------- +// BeginPlot +//----------------------------------------------------------------------------- + +bool BeginPlot(const char* title_id, const ImVec2& size, ImPlotFlags flags) { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "Mismatched BeginPlot()/EndPlot()!"); + + // FRONT MATTER ----------------------------------------------------------- + + if (gp.CurrentSubplot != nullptr) + ImGui::PushID(gp.CurrentSubplot->CurrentIdx); + + // get globals + ImGuiContext &G = *GImGui; + ImGuiWindow* Window = G.CurrentWindow; + + // skip if needed + if (Window->SkipItems && !gp.CurrentSubplot) { + ResetCtxForNextPlot(GImPlot); + return false; + } + + // ID and age (TODO: keep track of plot age in frames) + const ImGuiID ID = Window->GetID(title_id); + const bool just_created = gp.Plots.GetByKey(ID) == nullptr; + gp.CurrentPlot = gp.Plots.GetOrAddByKey(ID); + + ImPlotPlot &plot = *gp.CurrentPlot; + plot.ID = ID; + plot.Items.ID = ID - 1; + plot.JustCreated = just_created; + plot.SetupLocked = false; + + // check flags + if (plot.JustCreated) + plot.Flags = flags; + else if (flags != plot.PreviousFlags) + plot.Flags = flags; + plot.PreviousFlags = flags; + + // setup default axes + if (plot.JustCreated) { + SetupAxis(ImAxis_X1); + SetupAxis(ImAxis_Y1); + } + + // reset axes + for (int i = 0; i < ImAxis_COUNT; ++i) { + plot.Axes[i].Reset(); + UpdateAxisColors(plot.Axes[i]); + } + // ensure first axes enabled + plot.Axes[ImAxis_X1].Enabled = true; + plot.Axes[ImAxis_Y1].Enabled = true; + // set initial axes + plot.CurrentX = ImAxis_X1; + plot.CurrentY = ImAxis_Y1; + + // process next plot data (legacy) + for (int i = 0; i < ImAxis_COUNT; ++i) + ApplyNextPlotData(i); + + // clear text buffers + plot.ClearTextBuffer(); + plot.SetTitle(title_id); + + // set frame size + ImVec2 frame_size; + if (gp.CurrentSubplot != nullptr) + frame_size = gp.CurrentSubplot->CellSize; + else + frame_size = ImGui::CalcItemSize(size, gp.Style.PlotDefaultSize.x, gp.Style.PlotDefaultSize.y); + + if (frame_size.x < gp.Style.PlotMinSize.x && (size.x < 0.0f || gp.CurrentSubplot != nullptr)) + frame_size.x = gp.Style.PlotMinSize.x; + if (frame_size.y < gp.Style.PlotMinSize.y && (size.y < 0.0f || gp.CurrentSubplot != nullptr)) + frame_size.y = gp.Style.PlotMinSize.y; + + plot.FrameRect = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size); + ImGui::ItemSize(plot.FrameRect); + if (!ImGui::ItemAdd(plot.FrameRect, plot.ID, &plot.FrameRect) && !gp.CurrentSubplot) { + ResetCtxForNextPlot(GImPlot); + return false; + } + + // setup items (or dont) + if (gp.CurrentItems == nullptr) + gp.CurrentItems = &plot.Items; + + return true; +} + +//----------------------------------------------------------------------------- +// SetupFinish +//----------------------------------------------------------------------------- + +void SetupFinish() { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "SetupFinish needs to be called after BeginPlot!"); + + ImGuiContext& G = *GImGui; + ImDrawList& DrawList = *G.CurrentWindow->DrawList; + const ImGuiStyle& Style = G.Style; + + ImPlotPlot &plot = *gp.CurrentPlot; + + // lock setup + plot.SetupLocked = true; + + // finalize axes and set default formatter/locator + for (int i = 0; i < ImAxis_COUNT; ++i) { + ImPlotAxis& axis = plot.Axes[i]; + if (axis.Enabled) { + axis.Constrain(); + if (!plot.Initialized && axis.CanInitFit()) + plot.FitThisFrame = axis.FitThisFrame = true; + } + if (axis.Formatter == nullptr) { + axis.Formatter = Formatter_Default; + if (axis.HasFormatSpec) + axis.FormatterData = axis.FormatSpec; + else + axis.FormatterData = (void*)IMPLOT_LABEL_FORMAT; + } + if (axis.Locator == nullptr) { + axis.Locator = Locator_Default; + } + } + + // setup nullptr orthogonal axes + const bool axis_equal = ImHasFlag(plot.Flags, ImPlotFlags_Equal); + for (int ix = ImAxis_X1, iy = ImAxis_Y1; ix < ImAxis_Y1 || iy < ImAxis_COUNT; ++ix, ++iy) { + ImPlotAxis& x_axis = plot.Axes[ix]; + ImPlotAxis& y_axis = plot.Axes[iy]; + if (x_axis.Enabled && y_axis.Enabled) { + if (x_axis.OrthoAxis == nullptr) + x_axis.OrthoAxis = &y_axis; + if (y_axis.OrthoAxis == nullptr) + y_axis.OrthoAxis = &x_axis; + } + else if (x_axis.Enabled) + { + if (x_axis.OrthoAxis == nullptr && !axis_equal) + x_axis.OrthoAxis = &plot.Axes[ImAxis_Y1]; + } + else if (y_axis.Enabled) { + if (y_axis.OrthoAxis == nullptr && !axis_equal) + y_axis.OrthoAxis = &plot.Axes[ImAxis_X1]; + } + } + + // canvas/axes bb + plot.CanvasRect = ImRect(plot.FrameRect.Min + gp.Style.PlotPadding, plot.FrameRect.Max - gp.Style.PlotPadding); + plot.AxesRect = plot.FrameRect; + + // outside legend adjustments + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) && plot.Items.GetLegendCount() > 0 && ImHasFlag(plot.Items.Legend.Flags, ImPlotLegendFlags_Outside)) { + ImPlotLegend& legend = plot.Items.Legend; + const bool horz = ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal); + const ImVec2 legend_size = CalcLegendSize(plot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !horz); + const bool west = ImHasFlag(legend.Location, ImPlotLocation_West) && !ImHasFlag(legend.Location, ImPlotLocation_East); + const bool east = ImHasFlag(legend.Location, ImPlotLocation_East) && !ImHasFlag(legend.Location, ImPlotLocation_West); + const bool north = ImHasFlag(legend.Location, ImPlotLocation_North) && !ImHasFlag(legend.Location, ImPlotLocation_South); + const bool south = ImHasFlag(legend.Location, ImPlotLocation_South) && !ImHasFlag(legend.Location, ImPlotLocation_North); + if ((west && !horz) || (west && horz && !north && !south)) { + plot.CanvasRect.Min.x += (legend_size.x + gp.Style.LegendPadding.x); + plot.AxesRect.Min.x += (legend_size.x + gp.Style.PlotPadding.x); + } + if ((east && !horz) || (east && horz && !north && !south)) { + plot.CanvasRect.Max.x -= (legend_size.x + gp.Style.LegendPadding.x); + plot.AxesRect.Max.x -= (legend_size.x + gp.Style.PlotPadding.x); + } + if ((north && horz) || (north && !horz && !west && !east)) { + plot.CanvasRect.Min.y += (legend_size.y + gp.Style.LegendPadding.y); + plot.AxesRect.Min.y += (legend_size.y + gp.Style.PlotPadding.y); + } + if ((south && horz) || (south && !horz && !west && !east)) { + plot.CanvasRect.Max.y -= (legend_size.y + gp.Style.LegendPadding.y); + plot.AxesRect.Max.y -= (legend_size.y + gp.Style.PlotPadding.y); + } + } + + // plot bb + float pad_top = 0, pad_bot = 0, pad_left = 0, pad_right = 0; + + // (0) calc top padding form title + ImVec2 title_size(0.0f, 0.0f); + if (plot.HasTitle()) + title_size = ImGui::CalcTextSize(plot.GetTitle(), nullptr, true); + if (title_size.x > 0) { + pad_top += title_size.y + gp.Style.LabelPadding.y; + plot.AxesRect.Min.y += gp.Style.PlotPadding.y + pad_top; + } + + // (1) calc addition top padding and bot padding + PadAndDatumAxesX(plot,pad_top,pad_bot,gp.CurrentAlignmentH); + + const float plot_height = plot.CanvasRect.GetHeight() - pad_top - pad_bot; + + // (2) get y tick labels (needed for left/right pad) + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& axis = plot.YAxis(i); + if (axis.WillRender() && axis.ShowDefaultTicks && plot_height > 0) { + axis.Locator(axis.Ticker, axis.Range, plot_height, true, axis.Formatter, axis.FormatterData); + } + } + + // (3) calc left/right pad + PadAndDatumAxesY(plot,pad_left,pad_right,gp.CurrentAlignmentV); + + const float plot_width = plot.CanvasRect.GetWidth() - pad_left - pad_right; + + // (4) get x ticks + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& axis = plot.XAxis(i); + if (axis.WillRender() && axis.ShowDefaultTicks && plot_width > 0) { + axis.Locator(axis.Ticker, axis.Range, plot_width, false, axis.Formatter, axis.FormatterData); + } + } + + // (5) calc plot bb + plot.PlotRect = ImRect(plot.CanvasRect.Min + ImVec2(pad_left, pad_top), plot.CanvasRect.Max - ImVec2(pad_right, pad_bot)); + + // HOVER------------------------------------------------------------ + + // axes hover rect, pixel ranges + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) { + ImPlotAxis& xax = plot.XAxis(i); + xax.HoverRect = ImRect(ImVec2(plot.PlotRect.Min.x, ImMin(xax.Datum1,xax.Datum2)), + ImVec2(plot.PlotRect.Max.x, ImMax(xax.Datum1,xax.Datum2))); + xax.PixelMin = xax.IsInverted() ? plot.PlotRect.Max.x : plot.PlotRect.Min.x; + xax.PixelMax = xax.IsInverted() ? plot.PlotRect.Min.x : plot.PlotRect.Max.x; + xax.UpdateTransformCache(); + } + + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) { + ImPlotAxis& yax = plot.YAxis(i); + yax.HoverRect = ImRect(ImVec2(ImMin(yax.Datum1,yax.Datum2),plot.PlotRect.Min.y), + ImVec2(ImMax(yax.Datum1,yax.Datum2),plot.PlotRect.Max.y)); + yax.PixelMin = yax.IsInverted() ? plot.PlotRect.Min.y : plot.PlotRect.Max.y; + yax.PixelMax = yax.IsInverted() ? plot.PlotRect.Max.y : plot.PlotRect.Min.y; + yax.UpdateTransformCache(); + } + // Equal axis constraint. Must happen after we set Pixels + // constrain equal axes for primary x and y if not approximately equal + // constrains x to y since x pixel size depends on y labels width, and causes feedback loops in opposite case + if (axis_equal) { + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (x_axis.OrthoAxis == nullptr) + continue; + double xar = x_axis.GetAspect(); + double yar = x_axis.OrthoAxis->GetAspect(); + // edge case: user has set x range this frame, so fit y to x so that we honor their request for x range + // NB: because of feedback across several frames, the user's x request may not be perfectly honored + if (x_axis.HasRange) + x_axis.OrthoAxis->SetAspect(xar); + else if (!ImAlmostEqual(xar,yar) && !x_axis.OrthoAxis->IsInputLocked()) + x_axis.SetAspect(yar); + } + } + + // INPUT ------------------------------------------------------------------ + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoInputs)) + UpdateInput(plot); + + // fit from FitNextPlotAxes or auto fit + for (int i = 0; i < ImAxis_COUNT; ++i) { + if (gp.NextPlotData.Fit[i] || plot.Axes[i].IsAutoFitting()) { + plot.FitThisFrame = true; + plot.Axes[i].FitThisFrame = true; + } + } + + // RENDER ----------------------------------------------------------------- + + const float txt_height = ImGui::GetTextLineHeight(); + + // render frame + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoFrame)) + ImGui::RenderFrame(plot.FrameRect.Min, plot.FrameRect.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, Style.FrameRounding); + + // grid bg + DrawList.AddRectFilled(plot.PlotRect.Min, plot.PlotRect.Max, GetStyleColorU32(ImPlotCol_PlotBg)); + + // transform ticks + for (int i = 0; i < ImAxis_COUNT; i++) { + ImPlotAxis& axis = plot.Axes[i]; + if (axis.WillRender()) { + for (int t = 0; t < axis.Ticker.TickCount(); t++) { + ImPlotTick& tk = axis.Ticker.Ticks[t]; + tk.PixelPos = IM_ROUND(axis.PlotToPixels(tk.PlotPos)); + } + } + } + + // render grid (background) + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (x_axis.Enabled && x_axis.HasGridLines() && !x_axis.IsForeground()) + RenderGridLinesX(DrawList, x_axis.Ticker, plot.PlotRect, x_axis.ColorMaj, x_axis.ColorMin, gp.Style.MajorGridSize.x, gp.Style.MinorGridSize.x); + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (y_axis.Enabled && y_axis.HasGridLines() && !y_axis.IsForeground()) + RenderGridLinesY(DrawList, y_axis.Ticker, plot.PlotRect, y_axis.ColorMaj, y_axis.ColorMin, gp.Style.MajorGridSize.y, gp.Style.MinorGridSize.y); + } + + // render x axis button, label, tick labels + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& ax = plot.XAxis(i); + if (!ax.Enabled) + continue; + if ((ax.Hovered || ax.Held) && !plot.Held && !ImHasFlag(ax.Flags, ImPlotAxisFlags_NoHighlight)) + DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.Held ? ax.ColorAct : ax.ColorHov); + else if (ax.ColorHiLi != IM_COL32_BLACK_TRANS) { + DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorHiLi); + ax.ColorHiLi = IM_COL32_BLACK_TRANS; + } + else if (ax.ColorBg != IM_COL32_BLACK_TRANS) { + DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorBg); + } + const ImPlotTicker& tkr = ax.Ticker; + const bool opp = ax.IsOpposite(); + if (ax.HasLabel()) { + const char* label = plot.GetAxisLabel(ax); + const ImVec2 label_size = ImGui::CalcTextSize(label); + const float label_offset = (ax.HasTickLabels() ? tkr.MaxSize.y + gp.Style.LabelPadding.y : 0.0f) + + (tkr.Levels - 1) * (txt_height + gp.Style.LabelPadding.y) + + gp.Style.LabelPadding.y; + const ImVec2 label_pos(plot.PlotRect.GetCenter().x - label_size.x * 0.5f, + opp ? ax.Datum1 - label_offset - label_size.y : ax.Datum1 + label_offset); + DrawList.AddText(label_pos, ax.ColorTxt, label); + } + if (ax.HasTickLabels()) { + for (int j = 0; j < tkr.TickCount(); ++j) { + const ImPlotTick& tk = tkr.Ticks[j]; + const float datum = ax.Datum1 + (opp ? (-gp.Style.LabelPadding.y -txt_height -tk.Level * (txt_height + gp.Style.LabelPadding.y)) + : gp.Style.LabelPadding.y + tk.Level * (txt_height + gp.Style.LabelPadding.y)); + if (tk.ShowLabel && tk.PixelPos >= plot.PlotRect.Min.x - 1 && tk.PixelPos <= plot.PlotRect.Max.x + 1) { + ImVec2 start(tk.PixelPos - 0.5f * tk.LabelSize.x, datum); + DrawList.AddText(start, ax.ColorTxt, tkr.GetText(j)); + } + } + } + } + + // render y axis button, label, tick labels + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& ax = plot.YAxis(i); + if (!ax.Enabled) + continue; + if ((ax.Hovered || ax.Held) && !plot.Held && !ImHasFlag(ax.Flags, ImPlotAxisFlags_NoHighlight)) + DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.Held ? ax.ColorAct : ax.ColorHov); + else if (ax.ColorHiLi != IM_COL32_BLACK_TRANS) { + DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorHiLi); + ax.ColorHiLi = IM_COL32_BLACK_TRANS; + } + else if (ax.ColorBg != IM_COL32_BLACK_TRANS) { + DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorBg); + } + const ImPlotTicker& tkr = ax.Ticker; + const bool opp = ax.IsOpposite(); + if (ax.HasLabel()) { + const char* label = plot.GetAxisLabel(ax); + const ImVec2 label_size = CalcTextSizeVertical(label); + const float label_offset = (ax.HasTickLabels() ? tkr.MaxSize.x + gp.Style.LabelPadding.x : 0.0f) + + gp.Style.LabelPadding.x; + const ImVec2 label_pos(opp ? ax.Datum1 + label_offset : ax.Datum1 - label_offset - label_size.x, + plot.PlotRect.GetCenter().y + label_size.y * 0.5f); + AddTextVertical(&DrawList, label_pos, ax.ColorTxt, label); + } + if (ax.HasTickLabels()) { + for (int j = 0; j < tkr.TickCount(); ++j) { + const ImPlotTick& tk = tkr.Ticks[j]; + const float datum = ax.Datum1 + (opp ? gp.Style.LabelPadding.x : (-gp.Style.LabelPadding.x - tk.LabelSize.x)); + if (tk.ShowLabel && tk.PixelPos >= plot.PlotRect.Min.y - 1 && tk.PixelPos <= plot.PlotRect.Max.y + 1) { + ImVec2 start(datum, tk.PixelPos - 0.5f * tk.LabelSize.y); + DrawList.AddText(start, ax.ColorTxt, tkr.GetText(j)); + } + } + } + } + + + // clear legend (TODO: put elsewhere) + plot.Items.Legend.Reset(); + // push ID to set item hashes (NB: !!!THIS PROBABLY NEEDS TO BE IN BEGIN PLOT!!!!) + ImGui::PushOverrideID(gp.CurrentItems->ID); +} + +//----------------------------------------------------------------------------- +// EndPlot() +//----------------------------------------------------------------------------- + +void EndPlot() { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "Mismatched BeginPlot()/EndPlot()!"); + + SetupLock(); + + ImGuiContext &G = *GImGui; + ImPlotPlot &plot = *gp.CurrentPlot; + ImGuiWindow * Window = G.CurrentWindow; + ImDrawList & DrawList = *Window->DrawList; + const ImGuiIO & IO = ImGui::GetIO(); + + // FINAL RENDER ----------------------------------------------------------- + + const bool render_border = gp.Style.PlotBorderSize > 0 && GetStyleColorVec4(ImPlotCol_PlotBorder).w > 0; + const bool any_x_held = plot.Held || AnyAxesHeld(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES); + const bool any_y_held = plot.Held || AnyAxesHeld(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES); + + ImGui::PushClipRect(plot.FrameRect.Min, plot.FrameRect.Max, true); + + // render grid (foreground) + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (x_axis.Enabled && x_axis.HasGridLines() && x_axis.IsForeground()) + RenderGridLinesX(DrawList, x_axis.Ticker, plot.PlotRect, x_axis.ColorMaj, x_axis.ColorMin, gp.Style.MajorGridSize.x, gp.Style.MinorGridSize.x); + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (y_axis.Enabled && y_axis.HasGridLines() && y_axis.IsForeground()) + RenderGridLinesY(DrawList, y_axis.Ticker, plot.PlotRect, y_axis.ColorMaj, y_axis.ColorMin, gp.Style.MajorGridSize.y, gp.Style.MinorGridSize.y); + } + + + // render title + if (plot.HasTitle()) { + ImU32 col = GetStyleColorU32(ImPlotCol_TitleText); + AddTextCentered(&DrawList,ImVec2(plot.PlotRect.GetCenter().x, plot.CanvasRect.Min.y),col,plot.GetTitle()); + } + + // render x ticks + int count_B = 0, count_T = 0; + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + const ImPlotAxis& ax = plot.XAxis(i); + if (!ax.Enabled) + continue; + const ImPlotTicker& tkr = ax.Ticker; + const bool opp = ax.IsOpposite(); + const bool aux = ((opp && count_T > 0)||(!opp && count_B > 0)); + if (ax.HasTickMarks()) { + const float direction = opp ? 1.0f : -1.0f; + for (int j = 0; j < tkr.TickCount(); ++j) { + const ImPlotTick& tk = tkr.Ticks[j]; + if (tk.Level != 0 || tk.PixelPos < plot.PlotRect.Min.x || tk.PixelPos > plot.PlotRect.Max.x) + continue; + const ImVec2 start(tk.PixelPos, ax.Datum1); + const float len = (!aux && tk.Major) ? gp.Style.MajorTickLen.x : gp.Style.MinorTickLen.x; + const float thk = (!aux && tk.Major) ? gp.Style.MajorTickSize.x : gp.Style.MinorTickSize.x; + DrawList.AddLine(start, start + ImVec2(0,direction*len), ax.ColorTick, thk); + } + if (aux || !render_border) + DrawList.AddLine(ImVec2(plot.PlotRect.Min.x,ax.Datum1), ImVec2(plot.PlotRect.Max.x,ax.Datum1), ax.ColorTick, gp.Style.MinorTickSize.x); + } + count_B += !opp; + count_T += opp; + } + + // render y ticks + int count_L = 0, count_R = 0; + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + const ImPlotAxis& ax = plot.YAxis(i); + if (!ax.Enabled) + continue; + const ImPlotTicker& tkr = ax.Ticker; + const bool opp = ax.IsOpposite(); + const bool aux = ((opp && count_R > 0)||(!opp && count_L > 0)); + if (ax.HasTickMarks()) { + const float direction = opp ? -1.0f : 1.0f; + for (int j = 0; j < tkr.TickCount(); ++j) { + const ImPlotTick& tk = tkr.Ticks[j]; + if (tk.Level != 0 || tk.PixelPos < plot.PlotRect.Min.y || tk.PixelPos > plot.PlotRect.Max.y) + continue; + const ImVec2 start(ax.Datum1, tk.PixelPos); + const float len = (!aux && tk.Major) ? gp.Style.MajorTickLen.y : gp.Style.MinorTickLen.y; + const float thk = (!aux && tk.Major) ? gp.Style.MajorTickSize.y : gp.Style.MinorTickSize.y; + DrawList.AddLine(start, start + ImVec2(direction*len,0), ax.ColorTick, thk); + } + if (aux || !render_border) + DrawList.AddLine(ImVec2(ax.Datum1, plot.PlotRect.Min.y), ImVec2(ax.Datum1, plot.PlotRect.Max.y), ax.ColorTick, gp.Style.MinorTickSize.y); + } + count_L += !opp; + count_R += opp; + } + ImGui::PopClipRect(); + + // render annotations + PushPlotClipRect(); + for (int i = 0; i < gp.Annotations.Size; ++i) { + const char* txt = gp.Annotations.GetText(i); + ImPlotAnnotation& an = gp.Annotations.Annotations[i]; + const ImVec2 txt_size = ImGui::CalcTextSize(txt); + const ImVec2 size = txt_size + gp.Style.AnnotationPadding * 2; + ImVec2 pos = an.Pos; + if (an.Offset.x == 0) + pos.x -= size.x / 2; + else if (an.Offset.x > 0) + pos.x += an.Offset.x; + else + pos.x -= size.x - an.Offset.x; + if (an.Offset.y == 0) + pos.y -= size.y / 2; + else if (an.Offset.y > 0) + pos.y += an.Offset.y; + else + pos.y -= size.y - an.Offset.y; + if (an.Clamp) + pos = ClampLabelPos(pos, size, plot.PlotRect.Min, plot.PlotRect.Max); + ImRect rect(pos,pos+size); + if (an.Offset.x != 0 || an.Offset.y != 0) { + ImVec2 corners[4] = {rect.GetTL(), rect.GetTR(), rect.GetBR(), rect.GetBL()}; + int min_corner = 0; + float min_len = FLT_MAX; + for (int c = 0; c < 4; ++c) { + float len = ImLengthSqr(an.Pos - corners[c]); + if (len < min_len) { + min_corner = c; + min_len = len; + } + } + DrawList.AddLine(an.Pos, corners[min_corner], an.ColorBg); + } + DrawList.AddRectFilled(rect.Min, rect.Max, an.ColorBg); + DrawList.AddText(pos + gp.Style.AnnotationPadding, an.ColorFg, txt); + } + + // render selection + if (plot.Selected) + RenderSelectionRect(DrawList, plot.SelectRect.Min + plot.PlotRect.Min, plot.SelectRect.Max + plot.PlotRect.Min, GetStyleColorVec4(ImPlotCol_Selection)); + + // render crosshairs + if (ImHasFlag(plot.Flags, ImPlotFlags_Crosshairs) && plot.Hovered && !(any_x_held || any_y_held) && !plot.Selecting && !plot.Items.Legend.Hovered) { + ImGui::SetMouseCursor(ImGuiMouseCursor_None); + ImVec2 xy = IO.MousePos; + ImVec2 h1(plot.PlotRect.Min.x, xy.y); + ImVec2 h2(xy.x - 5, xy.y); + ImVec2 h3(xy.x + 5, xy.y); + ImVec2 h4(plot.PlotRect.Max.x, xy.y); + ImVec2 v1(xy.x, plot.PlotRect.Min.y); + ImVec2 v2(xy.x, xy.y - 5); + ImVec2 v3(xy.x, xy.y + 5); + ImVec2 v4(xy.x, plot.PlotRect.Max.y); + ImU32 col = GetStyleColorU32(ImPlotCol_Crosshairs); + DrawList.AddLine(h1, h2, col); + DrawList.AddLine(h3, h4, col); + DrawList.AddLine(v1, v2, col); + DrawList.AddLine(v3, v4, col); + } + + // render mouse pos + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMouseText) && (plot.Hovered || ImHasFlag(plot.MouseTextFlags, ImPlotMouseTextFlags_ShowAlways))) { + + const bool no_aux = ImHasFlag(plot.MouseTextFlags, ImPlotMouseTextFlags_NoAuxAxes); + const bool no_fmt = ImHasFlag(plot.MouseTextFlags, ImPlotMouseTextFlags_NoFormat); + + ImGuiTextBuffer& builder = gp.MousePosStringBuilder; + builder.Buf.shrink(0); + char buff[IMPLOT_LABEL_MAX_SIZE]; + + const int num_x = no_aux ? 1 : IMPLOT_NUM_X_AXES; + for (int i = 0; i < num_x; ++i) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (!x_axis.Enabled) + continue; + if (i > 0) + builder.append(", ("); + double v = x_axis.PixelsToPlot(IO.MousePos.x); + if (no_fmt) + Formatter_Default(v,buff,IMPLOT_LABEL_MAX_SIZE,(void*)IMPLOT_LABEL_FORMAT); + else + LabelAxisValue(x_axis,v,buff,IMPLOT_LABEL_MAX_SIZE,true); + builder.append(buff); + if (i > 0) + builder.append(")"); + } + builder.append(", "); + const int num_y = no_aux ? 1 : IMPLOT_NUM_Y_AXES; + for (int i = 0; i < num_y; ++i) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (!y_axis.Enabled) + continue; + if (i > 0) + builder.append(", ("); + double v = y_axis.PixelsToPlot(IO.MousePos.y); + if (no_fmt) + Formatter_Default(v,buff,IMPLOT_LABEL_MAX_SIZE,(void*)IMPLOT_LABEL_FORMAT); + else + LabelAxisValue(y_axis,v,buff,IMPLOT_LABEL_MAX_SIZE,true); + builder.append(buff); + if (i > 0) + builder.append(")"); + } + + if (!builder.empty()) { + const ImVec2 size = ImGui::CalcTextSize(builder.c_str()); + const ImVec2 pos = GetLocationPos(plot.PlotRect, size, plot.MouseTextLocation, gp.Style.MousePosPadding); + DrawList.AddText(pos, GetStyleColorU32(ImPlotCol_InlayText), builder.c_str()); + } + } + PopPlotClipRect(); + + // axis side switch + if (!plot.Held) { + ImVec2 mouse_pos = ImGui::GetIO().MousePos; + ImRect trigger_rect = plot.PlotRect; + trigger_rect.Expand(-10); + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (ImHasFlag(x_axis.Flags, ImPlotAxisFlags_NoSideSwitch)) + continue; + if (x_axis.Held && plot.PlotRect.Contains(mouse_pos)) { + const bool opp = ImHasFlag(x_axis.Flags, ImPlotAxisFlags_Opposite); + if (!opp) { + ImRect rect(plot.PlotRect.Min.x - 5, plot.PlotRect.Min.y - 5, + plot.PlotRect.Max.x + 5, plot.PlotRect.Min.y + 5); + if (mouse_pos.y < plot.PlotRect.Max.y - 10) + DrawList.AddRectFilled(rect.Min, rect.Max, x_axis.ColorHov); + if (rect.Contains(mouse_pos)) + x_axis.Flags |= ImPlotAxisFlags_Opposite; + } + else { + ImRect rect(plot.PlotRect.Min.x - 5, plot.PlotRect.Max.y - 5, + plot.PlotRect.Max.x + 5, plot.PlotRect.Max.y + 5); + if (mouse_pos.y > plot.PlotRect.Min.y + 10) + DrawList.AddRectFilled(rect.Min, rect.Max, x_axis.ColorHov); + if (rect.Contains(mouse_pos)) + x_axis.Flags &= ~ImPlotAxisFlags_Opposite; + } + } + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (ImHasFlag(y_axis.Flags, ImPlotAxisFlags_NoSideSwitch)) + continue; + if (y_axis.Held && plot.PlotRect.Contains(mouse_pos)) { + const bool opp = ImHasFlag(y_axis.Flags, ImPlotAxisFlags_Opposite); + if (!opp) { + ImRect rect(plot.PlotRect.Max.x - 5, plot.PlotRect.Min.y - 5, + plot.PlotRect.Max.x + 5, plot.PlotRect.Max.y + 5); + if (mouse_pos.x > plot.PlotRect.Min.x + 10) + DrawList.AddRectFilled(rect.Min, rect.Max, y_axis.ColorHov); + if (rect.Contains(mouse_pos)) + y_axis.Flags |= ImPlotAxisFlags_Opposite; + } + else { + ImRect rect(plot.PlotRect.Min.x - 5, plot.PlotRect.Min.y - 5, + plot.PlotRect.Min.x + 5, plot.PlotRect.Max.y + 5); + if (mouse_pos.x < plot.PlotRect.Max.x - 10) + DrawList.AddRectFilled(rect.Min, rect.Max, y_axis.ColorHov); + if (rect.Contains(mouse_pos)) + y_axis.Flags &= ~ImPlotAxisFlags_Opposite; + } + } + } + } + + // reset legend hovers + plot.Items.Legend.Hovered = false; + for (int i = 0; i < plot.Items.GetItemCount(); ++i) + plot.Items.GetItemByIndex(i)->LegendHovered = false; + // render legend + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) && plot.Items.GetLegendCount() > 0) { + ImPlotLegend& legend = plot.Items.Legend; + const bool legend_out = ImHasFlag(legend.Flags, ImPlotLegendFlags_Outside); + const bool legend_horz = ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal); + const ImVec2 legend_size = CalcLegendSize(plot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz); + const ImVec2 legend_pos = GetLocationPos(legend_out ? plot.FrameRect : plot.PlotRect, + legend_size, + legend.Location, + legend_out ? gp.Style.PlotPadding : gp.Style.LegendPadding); + legend.Rect = ImRect(legend_pos, legend_pos + legend_size); + legend.RectClamped = legend.Rect; + const bool legend_scrollable = ClampLegendRect(legend.RectClamped, + legend_out ? plot.FrameRect : plot.PlotRect, + legend_out ? gp.Style.PlotPadding : gp.Style.LegendPadding + ); + const ImGuiButtonFlags legend_button_flags = ImGuiButtonFlags_AllowOverlap + | ImGuiButtonFlags_PressedOnClick + | ImGuiButtonFlags_PressedOnDoubleClick + | ImGuiButtonFlags_MouseButtonLeft + | ImGuiButtonFlags_MouseButtonRight + | ImGuiButtonFlags_MouseButtonMiddle + | ImGuiButtonFlags_FlattenChildren; + ImGui::KeepAliveID(plot.Items.ID); + ImGui::ButtonBehavior(legend.RectClamped, plot.Items.ID, &legend.Hovered, &legend.Held, legend_button_flags); + legend.Hovered = legend.Hovered || (ImGui::IsWindowHovered() && legend.RectClamped.Contains(IO.MousePos)); + + if (legend_scrollable) { + if (legend.Hovered) { + ImGui::SetKeyOwner(ImGuiKey_MouseWheelY, plot.Items.ID); + if (IO.MouseWheel != 0.0f) { + ImVec2 max_step = legend.Rect.GetSize() * 0.67f; + float font_size = ImGui::GetCurrentWindow()->CalcFontSize(); + float scroll_step = ImFloor(ImMin(2 * font_size, max_step.x)); + legend.Scroll.x += scroll_step * IO.MouseWheel; + legend.Scroll.y += scroll_step * IO.MouseWheel; + } + } + const ImVec2 min_scroll_offset = legend.RectClamped.GetSize() - legend.Rect.GetSize(); + legend.Scroll.x = ImClamp(legend.Scroll.x, min_scroll_offset.x, 0.0f); + legend.Scroll.y = ImClamp(legend.Scroll.y, min_scroll_offset.y, 0.0f); + const ImVec2 scroll_offset = legend_horz ? ImVec2(legend.Scroll.x, 0) : ImVec2(0, legend.Scroll.y); + ImVec2 legend_offset = legend.RectClamped.Min - legend.Rect.Min + scroll_offset; + legend.Rect.Min += legend_offset; + legend.Rect.Max += legend_offset; + } else { + legend.Scroll = ImVec2(0,0); + } + + const ImU32 col_bg = GetStyleColorU32(ImPlotCol_LegendBg); + const ImU32 col_bd = GetStyleColorU32(ImPlotCol_LegendBorder); + ImGui::PushClipRect(legend.RectClamped.Min, legend.RectClamped.Max, true); + DrawList.AddRectFilled(legend.RectClamped.Min, legend.RectClamped.Max, col_bg); + bool legend_contextable = ShowLegendEntries(plot.Items, legend.Rect, legend.Hovered, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz, DrawList) + && !ImHasFlag(legend.Flags, ImPlotLegendFlags_NoMenus); + DrawList.AddRect(legend.RectClamped.Min, legend.RectClamped.Max, col_bd); + ImGui::PopClipRect(); + + // main ctx menu + if (gp.OpenContextThisFrame && legend_contextable && !ImHasFlag(plot.Flags, ImPlotFlags_NoMenus)) + ImGui::OpenPopup("##LegendContext"); + + if (ImGui::BeginPopup("##LegendContext")) { + ImGui::Text("Legend"); ImGui::Separator(); + if (ShowLegendContextMenu(legend, !ImHasFlag(plot.Flags, ImPlotFlags_NoLegend))) + ImFlipFlag(plot.Flags, ImPlotFlags_NoLegend); + ImGui::EndPopup(); + } + } + else { + plot.Items.Legend.Rect = ImRect(); + } + + // render border + if (render_border) + DrawList.AddRect(plot.PlotRect.Min, plot.PlotRect.Max, GetStyleColorU32(ImPlotCol_PlotBorder), 0, ImDrawFlags_RoundCornersAll, gp.Style.PlotBorderSize); + + // render tags + for (int i = 0; i < gp.Tags.Size; ++i) { + ImPlotTag& tag = gp.Tags.Tags[i]; + ImPlotAxis& axis = plot.Axes[tag.Axis]; + if (!axis.Enabled || !axis.Range.Contains(tag.Value)) + continue; + const char* txt = gp.Tags.GetText(i); + ImVec2 text_size = ImGui::CalcTextSize(txt); + ImVec2 size = text_size + gp.Style.AnnotationPadding * 2; + ImVec2 pos; + axis.Ticker.OverrideSizeLate(size); + float pix = IM_ROUND(axis.PlotToPixels(tag.Value)); + if (axis.Vertical) { + if (axis.IsOpposite()) { + pos = ImVec2(axis.Datum1 + gp.Style.LabelPadding.x, pix - size.y * 0.5f); + DrawList.AddTriangleFilled(ImVec2(axis.Datum1,pix), pos, pos + ImVec2(0,size.y), tag.ColorBg); + } + else { + pos = ImVec2(axis.Datum1 - size.x - gp.Style.LabelPadding.x, pix - size.y * 0.5f); + DrawList.AddTriangleFilled(pos + ImVec2(size.x,0), ImVec2(axis.Datum1,pix), pos+size, tag.ColorBg); + } + } + else { + if (axis.IsOpposite()) { + pos = ImVec2(pix - size.x * 0.5f, axis.Datum1 - size.y - gp.Style.LabelPadding.y ); + DrawList.AddTriangleFilled(pos + ImVec2(0,size.y), pos + size, ImVec2(pix,axis.Datum1), tag.ColorBg); + } + else { + pos = ImVec2(pix - size.x * 0.5f, axis.Datum1 + gp.Style.LabelPadding.y); + DrawList.AddTriangleFilled(pos, ImVec2(pix,axis.Datum1), pos + ImVec2(size.x, 0), tag.ColorBg); + } + } + DrawList.AddRectFilled(pos,pos+size,tag.ColorBg); + DrawList.AddText(pos+gp.Style.AnnotationPadding,tag.ColorFg,txt); + } + + // FIT DATA -------------------------------------------------------------- + const bool axis_equal = ImHasFlag(plot.Flags, ImPlotFlags_Equal); + if (plot.FitThisFrame) { + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& x_axis = plot.XAxis(i); + if (x_axis.FitThisFrame) { + x_axis.ApplyFit(gp.Style.FitPadding.x); + if (axis_equal && x_axis.OrthoAxis != nullptr) { + double aspect = x_axis.GetAspect(); + ImPlotAxis& y_axis = *x_axis.OrthoAxis; + if (y_axis.FitThisFrame) { + y_axis.ApplyFit(gp.Style.FitPadding.y); + y_axis.FitThisFrame = false; + aspect = ImMax(aspect, y_axis.GetAspect()); + } + x_axis.SetAspect(aspect); + y_axis.SetAspect(aspect); + } + } + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& y_axis = plot.YAxis(i); + if (y_axis.FitThisFrame) { + y_axis.ApplyFit(gp.Style.FitPadding.y); + if (axis_equal && y_axis.OrthoAxis != nullptr) { + double aspect = y_axis.GetAspect(); + ImPlotAxis& x_axis = *y_axis.OrthoAxis; + if (x_axis.FitThisFrame) { + x_axis.ApplyFit(gp.Style.FitPadding.x); + x_axis.FitThisFrame = false; + aspect = ImMax(x_axis.GetAspect(), aspect); + } + x_axis.SetAspect(aspect); + y_axis.SetAspect(aspect); + } + } + } + plot.FitThisFrame = false; + } + + // CONTEXT MENUS ----------------------------------------------------------- + + ImGui::PushOverrideID(plot.ID); + + const bool can_ctx = gp.OpenContextThisFrame && + !ImHasFlag(plot.Flags, ImPlotFlags_NoMenus) && + !plot.Items.Legend.Hovered; + + + + // main ctx menu + if (can_ctx && plot.Hovered) + ImGui::OpenPopup("##PlotContext"); + if (ImGui::BeginPopup("##PlotContext")) { + ShowPlotContextMenu(plot); + ImGui::EndPopup(); + } + + // axes ctx menus + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) { + ImGui::PushID(i); + ImPlotAxis& x_axis = plot.XAxis(i); + if (can_ctx && x_axis.Hovered && x_axis.HasMenus()) + ImGui::OpenPopup("##XContext"); + if (ImGui::BeginPopup("##XContext")) { + ImGui::Text(x_axis.HasLabel() ? plot.GetAxisLabel(x_axis) : i == 0 ? "X-Axis" : "X-Axis %d", i + 1); + ImGui::Separator(); + ShowAxisContextMenu(x_axis, axis_equal ? x_axis.OrthoAxis : nullptr, true); + ImGui::EndPopup(); + } + ImGui::PopID(); + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) { + ImGui::PushID(i); + ImPlotAxis& y_axis = plot.YAxis(i); + if (can_ctx && y_axis.Hovered && y_axis.HasMenus()) + ImGui::OpenPopup("##YContext"); + if (ImGui::BeginPopup("##YContext")) { + ImGui::Text(y_axis.HasLabel() ? plot.GetAxisLabel(y_axis) : i == 0 ? "Y-Axis" : "Y-Axis %d", i + 1); + ImGui::Separator(); + ShowAxisContextMenu(y_axis, axis_equal ? y_axis.OrthoAxis : nullptr, false); + ImGui::EndPopup(); + } + ImGui::PopID(); + } + ImGui::PopID(); + + // LINKED AXES ------------------------------------------------------------ + + for (int i = 0; i < ImAxis_COUNT; ++i) + plot.Axes[i].PushLinks(); + + + // CLEANUP ---------------------------------------------------------------- + + // remove items + if (gp.CurrentItems == &plot.Items) + gp.CurrentItems = nullptr; + // reset the plot items for the next frame + for (int i = 0; i < plot.Items.GetItemCount(); ++i) { + plot.Items.GetItemByIndex(i)->SeenThisFrame = false; + } + + // mark the plot as initialized, i.e. having made it through one frame completely + plot.Initialized = true; + // Pop ImGui::PushID at the end of BeginPlot + ImGui::PopID(); + // Reset context for next plot + ResetCtxForNextPlot(GImPlot); + + // setup next subplot + if (gp.CurrentSubplot != nullptr) { + ImGui::PopID(); + SubplotNextCell(); + } +} + +//----------------------------------------------------------------------------- +// BEGIN/END SUBPLOT +//----------------------------------------------------------------------------- + +static const float SUBPLOT_BORDER_SIZE = 1.0f; +static const float SUBPLOT_SPLITTER_HALF_THICKNESS = 4.0f; +static const float SUBPLOT_SPLITTER_FEEDBACK_TIMER = 0.06f; + +void SubplotSetCell(int row, int col) { + ImPlotContext& gp = *GImPlot; + ImPlotSubplot& subplot = *gp.CurrentSubplot; + if (row >= subplot.Rows || col >= subplot.Cols) + return; + float xoff = 0; + float yoff = 0; + for (int c = 0; c < col; ++c) + xoff += subplot.ColRatios[c]; + for (int r = 0; r < row; ++r) + yoff += subplot.RowRatios[r]; + const ImVec2 grid_size = subplot.GridRect.GetSize(); + ImVec2 cpos = subplot.GridRect.Min + ImVec2(xoff*grid_size.x,yoff*grid_size.y); + cpos.x = IM_ROUND(cpos.x); + cpos.y = IM_ROUND(cpos.y); + ImGui::GetCurrentWindow()->DC.CursorPos = cpos; + // set cell size + subplot.CellSize.x = IM_ROUND(subplot.GridRect.GetWidth() * subplot.ColRatios[col]); + subplot.CellSize.y = IM_ROUND(subplot.GridRect.GetHeight() * subplot.RowRatios[row]); + // setup links + const bool lx = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllX); + const bool ly = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllY); + const bool lr = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkRows); + const bool lc = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkCols); + + SetNextAxisLinks(ImAxis_X1, lx ? &subplot.ColLinkData[0].Min : lc ? &subplot.ColLinkData[col].Min : nullptr, + lx ? &subplot.ColLinkData[0].Max : lc ? &subplot.ColLinkData[col].Max : nullptr); + SetNextAxisLinks(ImAxis_Y1, ly ? &subplot.RowLinkData[0].Min : lr ? &subplot.RowLinkData[row].Min : nullptr, + ly ? &subplot.RowLinkData[0].Max : lr ? &subplot.RowLinkData[row].Max : nullptr); + // setup alignment + if (!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoAlign)) { + gp.CurrentAlignmentH = &subplot.RowAlignmentData[row]; + gp.CurrentAlignmentV = &subplot.ColAlignmentData[col]; + } + // set idx + if (ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ColMajor)) + subplot.CurrentIdx = col * subplot.Rows + row; + else + subplot.CurrentIdx = row * subplot.Cols + col; +} + +void SubplotSetCell(int idx) { + ImPlotContext& gp = *GImPlot; + ImPlotSubplot& subplot = *gp.CurrentSubplot; + if (idx >= subplot.Rows * subplot.Cols) + return; + int row = 0, col = 0; + if (ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ColMajor)) { + row = idx % subplot.Rows; + col = idx / subplot.Rows; + } + else { + row = idx / subplot.Cols; + col = idx % subplot.Cols; + } + return SubplotSetCell(row, col); +} + +void SubplotNextCell() { + ImPlotContext& gp = *GImPlot; + ImPlotSubplot& subplot = *gp.CurrentSubplot; + SubplotSetCell(++subplot.CurrentIdx); +} + +bool BeginSubplots(const char* title, int rows, int cols, const ImVec2& size, ImPlotSubplotFlags flags, float* row_sizes, float* col_sizes) { + IM_ASSERT_USER_ERROR(rows > 0 && cols > 0, "Invalid sizing arguments!"); + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentSubplot == nullptr, "Mismatched BeginSubplots()/EndSubplots()!"); + ImGuiContext &G = *GImGui; + ImGuiWindow * Window = G.CurrentWindow; + if (Window->SkipItems) + return false; + const ImGuiID ID = Window->GetID(title); + bool just_created = gp.Subplots.GetByKey(ID) == nullptr; + gp.CurrentSubplot = gp.Subplots.GetOrAddByKey(ID); + ImPlotSubplot& subplot = *gp.CurrentSubplot; + subplot.ID = ID; + subplot.Items.ID = ID - 1; + subplot.HasTitle = ImGui::FindRenderedTextEnd(title, nullptr) != title; + // push ID + ImGui::PushID(ID); + + if (just_created) + subplot.Flags = flags; + else if (flags != subplot.PreviousFlags) + subplot.Flags = flags; + subplot.PreviousFlags = flags; + + // check for change in rows and cols + if (subplot.Rows != rows || subplot.Cols != cols) { + subplot.RowAlignmentData.resize(rows); + subplot.RowLinkData.resize(rows); + subplot.RowRatios.resize(rows); + for (int r = 0; r < rows; ++r) { + subplot.RowAlignmentData[r].Reset(); + subplot.RowLinkData[r] = ImPlotRange(0,1); + subplot.RowRatios[r] = 1.0f / rows; + } + subplot.ColAlignmentData.resize(cols); + subplot.ColLinkData.resize(cols); + subplot.ColRatios.resize(cols); + for (int c = 0; c < cols; ++c) { + subplot.ColAlignmentData[c].Reset(); + subplot.ColLinkData[c] = ImPlotRange(0,1); + subplot.ColRatios[c] = 1.0f / cols; + } + } + // check incoming size requests + float row_sum = 0, col_sum = 0; + if (row_sizes != nullptr) { + row_sum = ImSum(row_sizes, rows); + for (int r = 0; r < rows; ++r) + subplot.RowRatios[r] = row_sizes[r] / row_sum; + } + if (col_sizes != nullptr) { + col_sum = ImSum(col_sizes, cols); + for (int c = 0; c < cols; ++c) + subplot.ColRatios[c] = col_sizes[c] / col_sum; + } + subplot.Rows = rows; + subplot.Cols = cols; + + // calc plot frame sizes + ImVec2 title_size(0.0f, 0.0f); + if (!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoTitle)) + title_size = ImGui::CalcTextSize(title, nullptr, true); + const float pad_top = title_size.x > 0.0f ? title_size.y + gp.Style.LabelPadding.y : 0; + const ImVec2 half_pad = gp.Style.PlotPadding/2; + const ImVec2 frame_size = ImGui::CalcItemSize(size, gp.Style.PlotDefaultSize.x, gp.Style.PlotDefaultSize.y); + subplot.FrameRect = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size); + subplot.GridRect.Min = subplot.FrameRect.Min + half_pad + ImVec2(0,pad_top); + subplot.GridRect.Max = subplot.FrameRect.Max - half_pad; + subplot.FrameHovered = subplot.FrameRect.Contains(ImGui::GetMousePos()) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows|ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); + + // outside legend adjustments (TODO: make function) + const bool share_items = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems); + if (share_items) + gp.CurrentItems = &subplot.Items; + if (share_items && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoLegend) && subplot.Items.GetLegendCount() > 0) { + ImPlotLegend& legend = subplot.Items.Legend; + const bool horz = ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal); + const ImVec2 legend_size = CalcLegendSize(subplot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !horz); + const bool west = ImHasFlag(legend.Location, ImPlotLocation_West) && !ImHasFlag(legend.Location, ImPlotLocation_East); + const bool east = ImHasFlag(legend.Location, ImPlotLocation_East) && !ImHasFlag(legend.Location, ImPlotLocation_West); + const bool north = ImHasFlag(legend.Location, ImPlotLocation_North) && !ImHasFlag(legend.Location, ImPlotLocation_South); + const bool south = ImHasFlag(legend.Location, ImPlotLocation_South) && !ImHasFlag(legend.Location, ImPlotLocation_North); + if ((west && !horz) || (west && horz && !north && !south)) + subplot.GridRect.Min.x += (legend_size.x + gp.Style.LegendPadding.x); + if ((east && !horz) || (east && horz && !north && !south)) + subplot.GridRect.Max.x -= (legend_size.x + gp.Style.LegendPadding.x); + if ((north && horz) || (north && !horz && !west && !east)) + subplot.GridRect.Min.y += (legend_size.y + gp.Style.LegendPadding.y); + if ((south && horz) || (south && !horz && !west && !east)) + subplot.GridRect.Max.y -= (legend_size.y + gp.Style.LegendPadding.y); + } + + // render single background frame + ImGui::RenderFrame(subplot.FrameRect.Min, subplot.FrameRect.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, ImGui::GetStyle().FrameRounding); + // render title + if (title_size.x > 0.0f && !ImHasFlag(subplot.Flags, ImPlotFlags_NoTitle)) { + const ImU32 col = GetStyleColorU32(ImPlotCol_TitleText); + AddTextCentered(ImGui::GetWindowDrawList(),ImVec2(subplot.GridRect.GetCenter().x, subplot.GridRect.Min.y - pad_top + half_pad.y),col,title); + } + + // render splitters + if (!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoResize)) { + ImDrawList& DrawList = *ImGui::GetWindowDrawList(); + const ImU32 hov_col = ImGui::ColorConvertFloat4ToU32(GImGui->Style.Colors[ImGuiCol_SeparatorHovered]); + const ImU32 act_col = ImGui::ColorConvertFloat4ToU32(GImGui->Style.Colors[ImGuiCol_SeparatorActive]); + float xpos = subplot.GridRect.Min.x; + float ypos = subplot.GridRect.Min.y; + int separator = 1; + // bool pass = false; + for (int r = 0; r < subplot.Rows-1; ++r) { + ypos += subplot.RowRatios[r] * subplot.GridRect.GetHeight(); + const ImGuiID sep_id = subplot.ID + separator; + ImGui::KeepAliveID(sep_id); + const ImRect sep_bb = ImRect(subplot.GridRect.Min.x, ypos-SUBPLOT_SPLITTER_HALF_THICKNESS, subplot.GridRect.Max.x, ypos+SUBPLOT_SPLITTER_HALF_THICKNESS); + bool sep_hov = false, sep_hld = false; + const bool sep_clk = ImGui::ButtonBehavior(sep_bb, sep_id, &sep_hov, &sep_hld, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick); + if ((sep_hov && G.HoveredIdTimer > SUBPLOT_SPLITTER_FEEDBACK_TIMER) || sep_hld) { + if (sep_clk && ImGui::IsMouseDoubleClicked(0)) { + float p = (subplot.RowRatios[r] + subplot.RowRatios[r+1])/2; + subplot.RowRatios[r] = subplot.RowRatios[r+1] = p; + } + if (sep_clk) { + subplot.TempSizes[0] = subplot.RowRatios[r]; + subplot.TempSizes[1] = subplot.RowRatios[r+1]; + } + if (sep_hld) { + float dp = ImGui::GetMouseDragDelta(0).y / subplot.GridRect.GetHeight(); + if (subplot.TempSizes[0] + dp > 0.1f && subplot.TempSizes[1] - dp > 0.1f) { + subplot.RowRatios[r] = subplot.TempSizes[0] + dp; + subplot.RowRatios[r+1] = subplot.TempSizes[1] - dp; + } + } + DrawList.AddLine(ImVec2(IM_ROUND(subplot.GridRect.Min.x),IM_ROUND(ypos)), + ImVec2(IM_ROUND(subplot.GridRect.Max.x),IM_ROUND(ypos)), + sep_hld ? act_col : hov_col, SUBPLOT_BORDER_SIZE); + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); + } + separator++; + } + for (int c = 0; c < subplot.Cols-1; ++c) { + xpos += subplot.ColRatios[c] * subplot.GridRect.GetWidth(); + const ImGuiID sep_id = subplot.ID + separator; + ImGui::KeepAliveID(sep_id); + const ImRect sep_bb = ImRect(xpos-SUBPLOT_SPLITTER_HALF_THICKNESS, subplot.GridRect.Min.y, xpos+SUBPLOT_SPLITTER_HALF_THICKNESS, subplot.GridRect.Max.y); + bool sep_hov = false, sep_hld = false; + const bool sep_clk = ImGui::ButtonBehavior(sep_bb, sep_id, &sep_hov, &sep_hld, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick); + if ((sep_hov && G.HoveredIdTimer > SUBPLOT_SPLITTER_FEEDBACK_TIMER) || sep_hld) { + if (sep_clk && ImGui::IsMouseDoubleClicked(0)) { + float p = (subplot.ColRatios[c] + subplot.ColRatios[c+1])/2; + subplot.ColRatios[c] = subplot.ColRatios[c+1] = p; + } + if (sep_clk) { + subplot.TempSizes[0] = subplot.ColRatios[c]; + subplot.TempSizes[1] = subplot.ColRatios[c+1]; + } + if (sep_hld) { + float dp = ImGui::GetMouseDragDelta(0).x / subplot.GridRect.GetWidth(); + if (subplot.TempSizes[0] + dp > 0.1f && subplot.TempSizes[1] - dp > 0.1f) { + subplot.ColRatios[c] = subplot.TempSizes[0] + dp; + subplot.ColRatios[c+1] = subplot.TempSizes[1] - dp; + } + } + DrawList.AddLine(ImVec2(IM_ROUND(xpos),IM_ROUND(subplot.GridRect.Min.y)), + ImVec2(IM_ROUND(xpos),IM_ROUND(subplot.GridRect.Max.y)), + sep_hld ? act_col : hov_col, SUBPLOT_BORDER_SIZE); + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + } + separator++; + } + } + + // set outgoing sizes + if (row_sizes != nullptr) { + for (int r = 0; r < rows; ++r) + row_sizes[r] = subplot.RowRatios[r] * row_sum; + } + if (col_sizes != nullptr) { + for (int c = 0; c < cols; ++c) + col_sizes[c] = subplot.ColRatios[c] * col_sum; + } + + // push styling + PushStyleColor(ImPlotCol_FrameBg, IM_COL32_BLACK_TRANS); + PushStyleVar(ImPlotStyleVar_PlotPadding, half_pad); + PushStyleVar(ImPlotStyleVar_PlotMinSize, ImVec2(0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize,0); + + // set initial cursor pos + Window->DC.CursorPos = subplot.GridRect.Min; + // begin alignments + for (int r = 0; r < subplot.Rows; ++r) + subplot.RowAlignmentData[r].Begin(); + for (int c = 0; c < subplot.Cols; ++c) + subplot.ColAlignmentData[c].Begin(); + // clear legend data + subplot.Items.Legend.Reset(); + // Setup first subplot + SubplotSetCell(0,0); + return true; +} + +void EndSubplots() { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentSubplot != nullptr, "Mismatched BeginSubplots()/EndSubplots()!"); + ImPlotSubplot& subplot = *gp.CurrentSubplot; + const ImGuiIO& IO = ImGui::GetIO(); + // set alignments + for (int r = 0; r < subplot.Rows; ++r) + subplot.RowAlignmentData[r].End(); + for (int c = 0; c < subplot.Cols; ++c) + subplot.ColAlignmentData[c].End(); + // pop styling + PopStyleColor(); + PopStyleVar(); + PopStyleVar(); + ImGui::PopStyleVar(); + // legend + subplot.Items.Legend.Hovered = false; + for (int i = 0; i < subplot.Items.GetItemCount(); ++i) + subplot.Items.GetItemByIndex(i)->LegendHovered = false; + // render legend + const bool share_items = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems); + ImDrawList& DrawList = *ImGui::GetWindowDrawList(); + if (share_items && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoLegend) && subplot.Items.GetLegendCount() > 0) { + ImPlotLegend& legend = subplot.Items.Legend; + const bool legend_horz = ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal); + const ImVec2 legend_size = CalcLegendSize(subplot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz); + const ImVec2 legend_pos = GetLocationPos(subplot.FrameRect, legend_size, legend.Location, gp.Style.PlotPadding); + legend.Rect = ImRect(legend_pos, legend_pos + legend_size); + legend.RectClamped = legend.Rect; + const bool legend_scrollable = ClampLegendRect(legend.RectClamped,subplot.FrameRect, gp.Style.PlotPadding); + const ImGuiButtonFlags legend_button_flags = ImGuiButtonFlags_AllowOverlap + | ImGuiButtonFlags_PressedOnClick + | ImGuiButtonFlags_PressedOnDoubleClick + | ImGuiButtonFlags_MouseButtonLeft + | ImGuiButtonFlags_MouseButtonRight + | ImGuiButtonFlags_MouseButtonMiddle + | ImGuiButtonFlags_FlattenChildren; + ImGui::KeepAliveID(subplot.Items.ID); + ImGui::ButtonBehavior(legend.RectClamped, subplot.Items.ID, &legend.Hovered, &legend.Held, legend_button_flags); + legend.Hovered = legend.Hovered || (subplot.FrameHovered && legend.RectClamped.Contains(ImGui::GetIO().MousePos)); + + if (legend_scrollable) { + if (legend.Hovered) { + ImGui::SetKeyOwner(ImGuiKey_MouseWheelY, subplot.Items.ID); + if (IO.MouseWheel != 0.0f) { + ImVec2 max_step = legend.Rect.GetSize() * 0.67f; + float font_size = ImGui::GetCurrentWindow()->CalcFontSize(); + float scroll_step = ImFloor(ImMin(2 * font_size, max_step.x)); + legend.Scroll.x += scroll_step * IO.MouseWheel; + legend.Scroll.y += scroll_step * IO.MouseWheel; + } + } + const ImVec2 min_scroll_offset = legend.RectClamped.GetSize() - legend.Rect.GetSize(); + legend.Scroll.x = ImClamp(legend.Scroll.x, min_scroll_offset.x, 0.0f); + legend.Scroll.y = ImClamp(legend.Scroll.y, min_scroll_offset.y, 0.0f); + const ImVec2 scroll_offset = legend_horz ? ImVec2(legend.Scroll.x, 0) : ImVec2(0, legend.Scroll.y); + ImVec2 legend_offset = legend.RectClamped.Min - legend.Rect.Min + scroll_offset; + legend.Rect.Min += legend_offset; + legend.Rect.Max += legend_offset; + } else { + legend.Scroll = ImVec2(0,0); + } + + const ImU32 col_bg = GetStyleColorU32(ImPlotCol_LegendBg); + const ImU32 col_bd = GetStyleColorU32(ImPlotCol_LegendBorder); + ImGui::PushClipRect(legend.RectClamped.Min, legend.RectClamped.Max, true); + DrawList.AddRectFilled(legend.RectClamped.Min, legend.RectClamped.Max, col_bg); + bool legend_contextable = ShowLegendEntries(subplot.Items, legend.Rect, legend.Hovered, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz, DrawList) + && !ImHasFlag(legend.Flags, ImPlotLegendFlags_NoMenus); + DrawList.AddRect(legend.RectClamped.Min, legend.RectClamped.Max, col_bd); + ImGui::PopClipRect(); + + if (legend_contextable && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoMenus) && ImGui::GetIO().MouseReleased[gp.InputMap.Menu]) + ImGui::OpenPopup("##LegendContext"); + if (ImGui::BeginPopup("##LegendContext")) { + ImGui::Text("Legend"); ImGui::Separator(); + if (ShowLegendContextMenu(legend, !ImHasFlag(subplot.Flags, ImPlotFlags_NoLegend))) + ImFlipFlag(subplot.Flags, ImPlotFlags_NoLegend); + ImGui::EndPopup(); + } + } + else { + subplot.Items.Legend.Rect = ImRect(); + } + // remove items + if (gp.CurrentItems == &subplot.Items) + gp.CurrentItems = nullptr; + // reset the plot items for the next frame (TODO: put this elswhere) + for (int i = 0; i < subplot.Items.GetItemCount(); ++i) { + subplot.Items.GetItemByIndex(i)->SeenThisFrame = false; + } + // pop id + ImGui::PopID(); + // set DC back correctly + GImGui->CurrentWindow->DC.CursorPos = subplot.FrameRect.Min; + ImGui::Dummy(subplot.FrameRect.GetSize()); + ResetCtxForNextSubplot(GImPlot); + +} + +//----------------------------------------------------------------------------- +// [SECTION] Plot Utils +//----------------------------------------------------------------------------- + +void SetAxis(ImAxis axis) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "SetAxis() needs to be called between BeginPlot() and EndPlot()!"); + IM_ASSERT_USER_ERROR(axis >= ImAxis_X1 && axis < ImAxis_COUNT, "Axis index out of bounds!"); + IM_ASSERT_USER_ERROR(gp.CurrentPlot->Axes[axis].Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + SetupLock(); + if (axis < ImAxis_Y1) + gp.CurrentPlot->CurrentX = axis; + else + gp.CurrentPlot->CurrentY = axis; +} + +void SetAxes(ImAxis x_idx, ImAxis y_idx) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "SetAxes() needs to be called between BeginPlot() and EndPlot()!"); + IM_ASSERT_USER_ERROR(x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1, "X-Axis index out of bounds!"); + IM_ASSERT_USER_ERROR(y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT, "Y-Axis index out of bounds!"); + IM_ASSERT_USER_ERROR(gp.CurrentPlot->Axes[x_idx].Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + IM_ASSERT_USER_ERROR(gp.CurrentPlot->Axes[y_idx].Enabled, "Axis is not enabled! Did you forget to call SetupAxis()?"); + SetupLock(); + gp.CurrentPlot->CurrentX = x_idx; + gp.CurrentPlot->CurrentY = y_idx; +} + +ImPlotPoint PixelsToPlot(float x, float y, ImAxis x_idx, ImAxis y_idx) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PixelsToPlot() needs to be called between BeginPlot() and EndPlot()!"); + IM_ASSERT_USER_ERROR(x_idx == IMPLOT_AUTO || (x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1), "X-Axis index out of bounds!"); + IM_ASSERT_USER_ERROR(y_idx == IMPLOT_AUTO || (y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT), "Y-Axis index out of bounds!"); + SetupLock(); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& x_axis = x_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentX] : plot.Axes[x_idx]; + ImPlotAxis& y_axis = y_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentY] : plot.Axes[y_idx]; + return ImPlotPoint( x_axis.PixelsToPlot(x), y_axis.PixelsToPlot(y) ); +} + +ImPlotPoint PixelsToPlot(const ImVec2& pix, ImAxis x_idx, ImAxis y_idx) { + return PixelsToPlot(pix.x, pix.y, x_idx, y_idx); +} + +ImVec2 PlotToPixels(double x, double y, ImAxis x_idx, ImAxis y_idx) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PlotToPixels() needs to be called between BeginPlot() and EndPlot()!"); + IM_ASSERT_USER_ERROR(x_idx == IMPLOT_AUTO || (x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1), "X-Axis index out of bounds!"); + IM_ASSERT_USER_ERROR(y_idx == IMPLOT_AUTO || (y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT), "Y-Axis index out of bounds!"); + SetupLock(); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& x_axis = x_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentX] : plot.Axes[x_idx]; + ImPlotAxis& y_axis = y_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentY] : plot.Axes[y_idx]; + return ImVec2( x_axis.PlotToPixels(x), y_axis.PlotToPixels(y) ); +} + +ImVec2 PlotToPixels(const ImPlotPoint& plt, ImAxis x_idx, ImAxis y_idx) { + return PlotToPixels(plt.x, plt.y, x_idx, y_idx); +} + +ImVec2 GetPlotPos() { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotPos() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + return gp.CurrentPlot->PlotRect.Min; +} + +ImVec2 GetPlotSize() { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotSize() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + return gp.CurrentPlot->PlotRect.GetSize(); +} + +ImPlotPoint GetPlotMousePos(ImAxis x_idx, ImAxis y_idx) { + IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, "GetPlotMousePos() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + return PixelsToPlot(ImGui::GetMousePos(), x_idx, y_idx); +} + +ImPlotRect GetPlotLimits(ImAxis x_idx, ImAxis y_idx) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotLimits() needs to be called between BeginPlot() and EndPlot()!"); + IM_ASSERT_USER_ERROR(x_idx == IMPLOT_AUTO || (x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1), "X-Axis index out of bounds!"); + IM_ASSERT_USER_ERROR(y_idx == IMPLOT_AUTO || (y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT), "Y-Axis index out of bounds!"); + SetupLock(); + ImPlotPlot& plot = *gp.CurrentPlot; + ImPlotAxis& x_axis = x_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentX] : plot.Axes[x_idx]; + ImPlotAxis& y_axis = y_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentY] : plot.Axes[y_idx]; + ImPlotRect limits; + limits.X = x_axis.Range; + limits.Y = y_axis.Range; + return limits; +} + +bool IsPlotHovered() { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "IsPlotHovered() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + return gp.CurrentPlot->Hovered; +} + +bool IsAxisHovered(ImAxis axis) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "IsPlotXAxisHovered() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + return gp.CurrentPlot->Axes[axis].Hovered; +} + +bool IsSubplotsHovered() { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentSubplot != nullptr, "IsSubplotsHovered() needs to be called between BeginSubplots() and EndSubplots()!"); + return gp.CurrentSubplot->FrameHovered; +} + +bool IsPlotSelected() { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "IsPlotSelected() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + return gp.CurrentPlot->Selected; +} + +ImPlotRect GetPlotSelection(ImAxis x_idx, ImAxis y_idx) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "GetPlotSelection() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + ImPlotPlot& plot = *gp.CurrentPlot; + if (!plot.Selected) + return ImPlotRect(0,0,0,0); + ImPlotPoint p1 = PixelsToPlot(plot.SelectRect.Min + plot.PlotRect.Min, x_idx, y_idx); + ImPlotPoint p2 = PixelsToPlot(plot.SelectRect.Max + plot.PlotRect.Min, x_idx, y_idx); + ImPlotRect result; + result.X.Min = ImMin(p1.x, p2.x); + result.X.Max = ImMax(p1.x, p2.x); + result.Y.Min = ImMin(p1.y, p2.y); + result.Y.Max = ImMax(p1.y, p2.y); + return result; +} + +void CancelPlotSelection() { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "CancelPlotSelection() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + ImPlotPlot& plot = *gp.CurrentPlot; + if (plot.Selected) + plot.Selected = plot.Selecting = false; +} + +void HideNextItem(bool hidden, ImPlotCond cond) { + ImPlotContext& gp = *GImPlot; + gp.NextItemData.HasHidden = true; + gp.NextItemData.Hidden = hidden; + gp.NextItemData.HiddenCond = cond; +} + +//----------------------------------------------------------------------------- +// [SECTION] Plot Tools +//----------------------------------------------------------------------------- + +void Annotation(double x, double y, const ImVec4& col, const ImVec2& offset, bool clamp, bool round) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "Annotation() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + char x_buff[IMPLOT_LABEL_MAX_SIZE]; + char y_buff[IMPLOT_LABEL_MAX_SIZE]; + ImPlotAxis& x_axis = gp.CurrentPlot->Axes[gp.CurrentPlot->CurrentX]; + ImPlotAxis& y_axis = gp.CurrentPlot->Axes[gp.CurrentPlot->CurrentY]; + LabelAxisValue(x_axis, x, x_buff, sizeof(x_buff), round); + LabelAxisValue(y_axis, y, y_buff, sizeof(y_buff), round); + Annotation(x,y,col,offset,clamp,"%s, %s",x_buff,y_buff); +} + +void AnnotationV(double x, double y, const ImVec4& col, const ImVec2& offset, bool clamp, const char* fmt, va_list args) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "Annotation() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + ImVec2 pos = PlotToPixels(x,y,IMPLOT_AUTO,IMPLOT_AUTO); + ImU32 bg = ImGui::GetColorU32(col); + ImU32 fg = col.w == 0 ? GetStyleColorU32(ImPlotCol_InlayText) : CalcTextColor(col); + gp.Annotations.AppendV(pos, offset, bg, fg, clamp, fmt, args); +} + +void Annotation(double x, double y, const ImVec4& col, const ImVec2& offset, bool clamp, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + AnnotationV(x,y,col,offset,clamp,fmt,args); + va_end(args); +} + +void TagV(ImAxis axis, double v, const ImVec4& col, const char* fmt, va_list args) { + ImPlotContext& gp = *GImPlot; + SetupLock(); + ImU32 bg = ImGui::GetColorU32(col); + ImU32 fg = col.w == 0 ? GetStyleColorU32(ImPlotCol_AxisText) : CalcTextColor(col); + gp.Tags.AppendV(axis,v,bg,fg,fmt,args); +} + +void Tag(ImAxis axis, double v, const ImVec4& col, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + TagV(axis,v,col,fmt,args); + va_end(args); +} + +void Tag(ImAxis axis, double v, const ImVec4& color, bool round) { + ImPlotContext& gp = *GImPlot; + SetupLock(); + char buff[IMPLOT_LABEL_MAX_SIZE]; + ImPlotAxis& ax = gp.CurrentPlot->Axes[axis]; + LabelAxisValue(ax, v, buff, sizeof(buff), round); + Tag(axis,v,color,"%s",buff); +} + +IMPLOT_API void TagX(double x, const ImVec4& color, bool round) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagX() needs to be called between BeginPlot() and EndPlot()!"); + Tag(gp.CurrentPlot->CurrentX, x, color, round); +} + +IMPLOT_API void TagX(double x, const ImVec4& color, const char* fmt, ...) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagX() needs to be called between BeginPlot() and EndPlot()!"); + va_list args; + va_start(args, fmt); + TagV(gp.CurrentPlot->CurrentX,x,color,fmt,args); + va_end(args); +} + +IMPLOT_API void TagXV(double x, const ImVec4& color, const char* fmt, va_list args) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagX() needs to be called between BeginPlot() and EndPlot()!"); + TagV(gp.CurrentPlot->CurrentX, x, color, fmt, args); +} + +IMPLOT_API void TagY(double y, const ImVec4& color, bool round) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagY() needs to be called between BeginPlot() and EndPlot()!"); + Tag(gp.CurrentPlot->CurrentY, y, color, round); +} + +IMPLOT_API void TagY(double y, const ImVec4& color, const char* fmt, ...) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagY() needs to be called between BeginPlot() and EndPlot()!"); + va_list args; + va_start(args, fmt); + TagV(gp.CurrentPlot->CurrentY,y,color,fmt,args); + va_end(args); +} + +IMPLOT_API void TagYV(double y, const ImVec4& color, const char* fmt, va_list args) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "TagY() needs to be called between BeginPlot() and EndPlot()!"); + TagV(gp.CurrentPlot->CurrentY, y, color, fmt, args); +} + +static const float DRAG_GRAB_HALF_SIZE = 4.0f; + +bool DragPoint(int n_id, double* x, double* y, const ImVec4& col, float radius, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) { + ImGui::PushID("#IMPLOT_DRAG_POINT"); + IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, "DragPoint() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + + if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) { + FitPoint(ImPlotPoint(*x,*y)); + } + + const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs); + const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors); + const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed); + const float grab_half_size = ImMax(DRAG_GRAB_HALF_SIZE, radius); + const ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col; + const ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color); + + ImVec2 pos = PlotToPixels(*x,*y,IMPLOT_AUTO,IMPLOT_AUTO); + const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id); + ImRect rect(pos.x-grab_half_size,pos.y-grab_half_size,pos.x+grab_half_size,pos.y+grab_half_size); + bool hovered = false, held = false; + + ImGui::KeepAliveID(id); + if (input) { + bool clicked = ImGui::ButtonBehavior(rect,id,&hovered,&held); + if (out_clicked) *out_clicked = clicked; + if (out_hovered) *out_hovered = hovered; + if (out_held) *out_held = held; + } + + bool modified = false; + if (held && ImGui::IsMouseDragging(0)) { + *x = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x; + *y = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y; + modified = true; + } + + PushPlotClipRect(); + ImDrawList& DrawList = *GetPlotDrawList(); + if ((hovered || held) && show_curs) + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + if (modified && no_delay) + pos = PlotToPixels(*x,*y,IMPLOT_AUTO,IMPLOT_AUTO); + DrawList.AddCircleFilled(pos, radius, col32); + PopPlotClipRect(); + + ImGui::PopID(); + return modified; +} + +bool DragLineX(int n_id, double* value, const ImVec4& col, float thickness, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) { + // ImGui::PushID("#IMPLOT_DRAG_LINE_X"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "DragLineX() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + + if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) { + FitPointX(*value); + } + + const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs); + const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors); + const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed); + const float grab_half_size = ImMax(DRAG_GRAB_HALF_SIZE, thickness/2); + float yt = gp.CurrentPlot->PlotRect.Min.y; + float yb = gp.CurrentPlot->PlotRect.Max.y; + float x = IM_ROUND(PlotToPixels(*value,0,IMPLOT_AUTO,IMPLOT_AUTO).x); + const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id); + ImRect rect(x-grab_half_size,yt,x+grab_half_size,yb); + bool hovered = false, held = false; + + ImGui::KeepAliveID(id); + if (input) { + bool clicked = ImGui::ButtonBehavior(rect,id,&hovered,&held); + if (out_clicked) *out_clicked = clicked; + if (out_hovered) *out_hovered = hovered; + if (out_held) *out_held = held; + } + + if ((hovered || held) && show_curs) + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + + float len = gp.Style.MajorTickLen.x; + ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col; + ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color); + + bool modified = false; + if (held && ImGui::IsMouseDragging(0)) { + *value = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x; + modified = true; + } + + PushPlotClipRect(); + ImDrawList& DrawList = *GetPlotDrawList(); + if (modified && no_delay) + x = IM_ROUND(PlotToPixels(*value,0,IMPLOT_AUTO,IMPLOT_AUTO).x); + DrawList.AddLine(ImVec2(x,yt), ImVec2(x,yb), col32, thickness); + DrawList.AddLine(ImVec2(x,yt), ImVec2(x,yt+len), col32, 3*thickness); + DrawList.AddLine(ImVec2(x,yb), ImVec2(x,yb-len), col32, 3*thickness); + PopPlotClipRect(); + + // ImGui::PopID(); + return modified; +} + +bool DragLineY(int n_id, double* value, const ImVec4& col, float thickness, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) { + ImGui::PushID("#IMPLOT_DRAG_LINE_Y"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "DragLineY() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + + if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) { + FitPointY(*value); + } + + const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs); + const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors); + const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed); + const float grab_half_size = ImMax(DRAG_GRAB_HALF_SIZE, thickness/2); + float xl = gp.CurrentPlot->PlotRect.Min.x; + float xr = gp.CurrentPlot->PlotRect.Max.x; + float y = IM_ROUND(PlotToPixels(0, *value,IMPLOT_AUTO,IMPLOT_AUTO).y); + + const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id); + ImRect rect(xl,y-grab_half_size,xr,y+grab_half_size); + bool hovered = false, held = false; + + ImGui::KeepAliveID(id); + if (input) { + bool clicked = ImGui::ButtonBehavior(rect,id,&hovered,&held); + if (out_clicked) *out_clicked = clicked; + if (out_hovered) *out_hovered = hovered; + if (out_held) *out_held = held; + } + + if ((hovered || held) && show_curs) + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); + + float len = gp.Style.MajorTickLen.y; + ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col; + ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color); + + bool modified = false; + if (held && ImGui::IsMouseDragging(0)) { + *value = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y; + modified = true; + } + + PushPlotClipRect(); + ImDrawList& DrawList = *GetPlotDrawList(); + if (modified && no_delay) + y = IM_ROUND(PlotToPixels(0, *value,IMPLOT_AUTO,IMPLOT_AUTO).y); + DrawList.AddLine(ImVec2(xl,y), ImVec2(xr,y), col32, thickness); + DrawList.AddLine(ImVec2(xl,y), ImVec2(xl+len,y), col32, 3*thickness); + DrawList.AddLine(ImVec2(xr,y), ImVec2(xr-len,y), col32, 3*thickness); + PopPlotClipRect(); + + ImGui::PopID(); + return modified; +} + +bool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_max, const ImVec4& col, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) { + ImGui::PushID("#IMPLOT_DRAG_RECT"); + IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, "DragRect() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + + if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) { + FitPoint(ImPlotPoint(*x_min,*y_min)); + FitPoint(ImPlotPoint(*x_max,*y_max)); + } + + const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs); + const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors); + const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed); + bool h[] = {true,false,true,false}; + double* x[] = {x_min,x_max,x_max,x_min}; + double* y[] = {y_min,y_min,y_max,y_max}; + ImVec2 p[4]; + for (int i = 0; i < 4; ++i) + p[i] = PlotToPixels(*x[i],*y[i],IMPLOT_AUTO,IMPLOT_AUTO); + ImVec2 pc = PlotToPixels((*x_min+*x_max)/2,(*y_min+*y_max)/2,IMPLOT_AUTO,IMPLOT_AUTO); + ImRect rect(ImMin(p[0],p[2]),ImMax(p[0],p[2])); + ImRect rect_grab = rect; rect_grab.Expand(DRAG_GRAB_HALF_SIZE); + + ImGuiMouseCursor cur[4]; + if (show_curs) { + cur[0] = (rect.Min.x == p[0].x && rect.Min.y == p[0].y) || (rect.Max.x == p[0].x && rect.Max.y == p[0].y) ? ImGuiMouseCursor_ResizeNWSE : ImGuiMouseCursor_ResizeNESW; + cur[1] = cur[0] == ImGuiMouseCursor_ResizeNWSE ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; + cur[2] = cur[1] == ImGuiMouseCursor_ResizeNWSE ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; + cur[3] = cur[2] == ImGuiMouseCursor_ResizeNWSE ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; + } + + ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col; + ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color); + color.w *= 0.25f; + ImU32 col32_a = ImGui::ColorConvertFloat4ToU32(color); + const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id); + + bool modified = false; + bool clicked = false, hovered = false, held = false; + ImRect b_rect(pc.x-DRAG_GRAB_HALF_SIZE,pc.y-DRAG_GRAB_HALF_SIZE,pc.x+DRAG_GRAB_HALF_SIZE,pc.y+DRAG_GRAB_HALF_SIZE); + + ImGui::KeepAliveID(id); + if (input) { + // middle point + clicked = ImGui::ButtonBehavior(b_rect,id,&hovered,&held); + if (out_clicked) *out_clicked = clicked; + if (out_hovered) *out_hovered = hovered; + if (out_held) *out_held = held; + } + + if ((hovered || held) && show_curs) + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); + if (held && ImGui::IsMouseDragging(0)) { + for (int i = 0; i < 4; ++i) { + ImPlotPoint pp = PixelsToPlot(p[i] + ImGui::GetIO().MouseDelta,IMPLOT_AUTO,IMPLOT_AUTO); + *y[i] = pp.y; + *x[i] = pp.x; + } + modified = true; + } + + for (int i = 0; i < 4; ++i) { + // points + b_rect = ImRect(p[i].x-DRAG_GRAB_HALF_SIZE,p[i].y-DRAG_GRAB_HALF_SIZE,p[i].x+DRAG_GRAB_HALF_SIZE,p[i].y+DRAG_GRAB_HALF_SIZE); + ImGuiID p_id = id + i + 1; + ImGui::KeepAliveID(p_id); + if (input) { + clicked = ImGui::ButtonBehavior(b_rect,p_id,&hovered,&held); + if (out_clicked) *out_clicked = *out_clicked || clicked; + if (out_hovered) *out_hovered = *out_hovered || hovered; + if (out_held) *out_held = *out_held || held; + } + if ((hovered || held) && show_curs) + ImGui::SetMouseCursor(cur[i]); + + if (held && ImGui::IsMouseDragging(0)) { + *x[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x; + *y[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y; + modified = true; + } + + // edges + ImVec2 e_min = ImMin(p[i],p[(i+1)%4]); + ImVec2 e_max = ImMax(p[i],p[(i+1)%4]); + b_rect = h[i] ? ImRect(e_min.x + DRAG_GRAB_HALF_SIZE, e_min.y - DRAG_GRAB_HALF_SIZE, e_max.x - DRAG_GRAB_HALF_SIZE, e_max.y + DRAG_GRAB_HALF_SIZE) + : ImRect(e_min.x - DRAG_GRAB_HALF_SIZE, e_min.y + DRAG_GRAB_HALF_SIZE, e_max.x + DRAG_GRAB_HALF_SIZE, e_max.y - DRAG_GRAB_HALF_SIZE); + ImGuiID e_id = id + i + 5; + ImGui::KeepAliveID(e_id); + if (input) { + clicked = ImGui::ButtonBehavior(b_rect,e_id,&hovered,&held); + if (out_clicked) *out_clicked = *out_clicked || clicked; + if (out_hovered) *out_hovered = *out_hovered || hovered; + if (out_held) *out_held = *out_held || held; + } + if ((hovered || held) && show_curs) + h[i] ? ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS) : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + if (held && ImGui::IsMouseDragging(0)) { + if (h[i]) + *y[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y; + else + *x[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x; + modified = true; + } + if (hovered && ImGui::IsMouseDoubleClicked(0)) + { + ImPlotRect b = GetPlotLimits(IMPLOT_AUTO,IMPLOT_AUTO); + if (h[i]) + *y[i] = ((y[i] == y_min && *y_min < *y_max) || (y[i] == y_max && *y_max < *y_min)) ? b.Y.Min : b.Y.Max; + else + *x[i] = ((x[i] == x_min && *x_min < *x_max) || (x[i] == x_max && *x_max < *x_min)) ? b.X.Min : b.X.Max; + modified = true; + } + } + + const bool mouse_inside = rect_grab.Contains(ImGui::GetMousePos()); + const bool mouse_clicked = ImGui::IsMouseClicked(0); + const bool mouse_down = ImGui::IsMouseDown(0); + if (input && mouse_inside) { + if (out_clicked) *out_clicked = *out_clicked || mouse_clicked; + if (out_hovered) *out_hovered = true; + if (out_held) *out_held = *out_held || mouse_down; + } + + PushPlotClipRect(); + ImDrawList& DrawList = *GetPlotDrawList(); + if (modified && no_delay) { + for (int i = 0; i < 4; ++i) + p[i] = PlotToPixels(*x[i],*y[i],IMPLOT_AUTO,IMPLOT_AUTO); + pc = PlotToPixels((*x_min+*x_max)/2,(*y_min+*y_max)/2,IMPLOT_AUTO,IMPLOT_AUTO); + rect = ImRect(ImMin(p[0],p[2]),ImMax(p[0],p[2])); + } + DrawList.AddRectFilled(rect.Min, rect.Max, col32_a); + DrawList.AddRect(rect.Min, rect.Max, col32); + if (input && (modified || mouse_inside)) { + DrawList.AddCircleFilled(pc,DRAG_GRAB_HALF_SIZE,col32); + for (int i = 0; i < 4; ++i) + DrawList.AddCircleFilled(p[i],DRAG_GRAB_HALF_SIZE,col32); + } + PopPlotClipRect(); + ImGui::PopID(); + return modified; +} + +bool DragRect(int id, ImPlotRect* bounds, const ImVec4& col, ImPlotDragToolFlags flags, bool* out_clicked, bool* out_hovered, bool* out_held) { + return DragRect(id, &bounds->X.Min, &bounds->Y.Min,&bounds->X.Max, &bounds->Y.Max, col, flags, out_clicked, out_hovered, out_held); +} + +//----------------------------------------------------------------------------- +// [SECTION] Legend Utils and Tools +//----------------------------------------------------------------------------- + +bool IsLegendEntryHovered(const char* label_id) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, "IsPlotItemHighlight() needs to be called within an itemized context!"); + SetupLock(); + ImGuiID id = ImGui::GetIDWithSeed(label_id, nullptr, gp.CurrentItems->ID); + ImPlotItem* item = gp.CurrentItems->GetItem(id); + return item && item->LegendHovered; +} + +bool BeginLegendPopup(const char* label_id, ImGuiMouseButton mouse_button) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, "BeginLegendPopup() needs to be called within an itemized context!"); + SetupLock(); + ImGuiWindow* window = GImGui->CurrentWindow; + if (window->SkipItems) + return false; + ImGuiID id = ImGui::GetIDWithSeed(label_id, nullptr, gp.CurrentItems->ID); + if (ImGui::IsMouseReleased(mouse_button)) { + ImPlotItem* item = gp.CurrentItems->GetItem(id); + if (item && item->LegendHovered) + ImGui::OpenPopupEx(id); + } + return ImGui::BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings); +} + +void EndLegendPopup() { + SetupLock(); + ImGui::EndPopup(); +} + +void ShowAltLegend(const char* title_id, bool vertical, const ImVec2 size, bool interactable) { + ImPlotContext& gp = *GImPlot; + ImGuiContext &G = *GImGui; + ImGuiWindow * Window = G.CurrentWindow; + if (Window->SkipItems) + return; + ImDrawList &DrawList = *Window->DrawList; + ImPlotPlot* plot = GetPlot(title_id); + ImVec2 legend_size; + ImVec2 default_size = gp.Style.LegendPadding * 2; + if (plot != nullptr) { + legend_size = CalcLegendSize(plot->Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, vertical); + default_size = legend_size + gp.Style.LegendPadding * 2; + } + ImVec2 frame_size = ImGui::CalcItemSize(size, default_size.x, default_size.y); + ImRect bb_frame = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size); + ImGui::ItemSize(bb_frame); + if (!ImGui::ItemAdd(bb_frame, 0, &bb_frame)) + return; + ImGui::RenderFrame(bb_frame.Min, bb_frame.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, G.Style.FrameRounding); + DrawList.PushClipRect(bb_frame.Min, bb_frame.Max, true); + if (plot != nullptr) { + const ImVec2 legend_pos = GetLocationPos(bb_frame, legend_size, 0, gp.Style.LegendPadding); + const ImRect legend_bb(legend_pos, legend_pos + legend_size); + interactable = interactable && bb_frame.Contains(ImGui::GetIO().MousePos); + // render legend box + ImU32 col_bg = GetStyleColorU32(ImPlotCol_LegendBg); + ImU32 col_bd = GetStyleColorU32(ImPlotCol_LegendBorder); + DrawList.AddRectFilled(legend_bb.Min, legend_bb.Max, col_bg); + DrawList.AddRect(legend_bb.Min, legend_bb.Max, col_bd); + // render entries + ShowLegendEntries(plot->Items, legend_bb, interactable, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, vertical, DrawList); + } + DrawList.PopClipRect(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Drag and Drop Utils +//----------------------------------------------------------------------------- + +bool BeginDragDropTargetPlot() { + SetupLock(); + ImPlotContext& gp = *GImPlot; + ImRect rect = gp.CurrentPlot->PlotRect; + return ImGui::BeginDragDropTargetCustom(rect, gp.CurrentPlot->ID); +} + +bool BeginDragDropTargetAxis(ImAxis axis) { + SetupLock(); + ImPlotPlot& plot = *GImPlot->CurrentPlot; + ImPlotAxis& ax = plot.Axes[axis]; + ImRect rect = ax.HoverRect; + rect.Expand(-3.5f); + return ImGui::BeginDragDropTargetCustom(rect, ax.ID); +} + +bool BeginDragDropTargetLegend() { + SetupLock(); + ImPlotItemGroup& items = *GImPlot->CurrentItems; + ImRect rect = items.Legend.RectClamped; + return ImGui::BeginDragDropTargetCustom(rect, items.ID); +} + +void EndDragDropTarget() { + SetupLock(); + ImGui::EndDragDropTarget(); +} + +bool BeginDragDropSourcePlot(ImGuiDragDropFlags flags) { + SetupLock(); + ImPlotContext& gp = *GImPlot; + ImPlotPlot* plot = gp.CurrentPlot; + if (GImGui->IO.KeyMods == gp.InputMap.OverrideMod || GImGui->DragDropPayload.SourceId == plot->ID) + return ImGui::ItemAdd(plot->PlotRect, plot->ID) && ImGui::BeginDragDropSource(flags); + return false; +} + +bool BeginDragDropSourceAxis(ImAxis idx, ImGuiDragDropFlags flags) { + SetupLock(); + ImPlotContext& gp = *GImPlot; + ImPlotAxis& axis = gp.CurrentPlot->Axes[idx]; + if (GImGui->IO.KeyMods == gp.InputMap.OverrideMod || GImGui->DragDropPayload.SourceId == axis.ID) + return ImGui::ItemAdd(axis.HoverRect, axis.ID) && ImGui::BeginDragDropSource(flags); + return false; +} + +bool BeginDragDropSourceItem(const char* label_id, ImGuiDragDropFlags flags) { + SetupLock(); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, "BeginDragDropSourceItem() needs to be called within an itemized context!"); + ImGuiID item_id = ImGui::GetIDWithSeed(label_id, nullptr, gp.CurrentItems->ID); + ImPlotItem* item = gp.CurrentItems->GetItem(item_id); + if (item != nullptr) { + return ImGui::ItemAdd(item->LegendHoverRect, item->ID) && ImGui::BeginDragDropSource(flags); + } + return false; +} + +void EndDragDropSource() { + SetupLock(); + ImGui::EndDragDropSource(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Aligned Plots +//----------------------------------------------------------------------------- + +bool BeginAlignedPlots(const char* group_id, bool vertical) { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentAlignmentH == nullptr && gp.CurrentAlignmentV == nullptr, "Mismatched BeginAlignedPlots()/EndAlignedPlots()!"); + ImGuiContext &G = *GImGui; + ImGuiWindow * Window = G.CurrentWindow; + if (Window->SkipItems) + return false; + const ImGuiID ID = Window->GetID(group_id); + ImPlotAlignmentData* alignment = gp.AlignmentData.GetOrAddByKey(ID); + if (vertical) + gp.CurrentAlignmentV = alignment; + else + gp.CurrentAlignmentH = alignment; + if (alignment->Vertical != vertical) + alignment->Reset(); + alignment->Vertical = vertical; + alignment->Begin(); + return true; +} + +void EndAlignedPlots() { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentAlignmentH != nullptr || gp.CurrentAlignmentV != nullptr, "Mismatched BeginAlignedPlots()/EndAlignedPlots()!"); + ImPlotAlignmentData* alignment = gp.CurrentAlignmentH != nullptr ? gp.CurrentAlignmentH : (gp.CurrentAlignmentV != nullptr ? gp.CurrentAlignmentV : nullptr); + if (alignment) + alignment->End(); + ResetCtxForNextAlignedPlots(GImPlot); +} + +//----------------------------------------------------------------------------- +// [SECTION] Plot and Item Styling +//----------------------------------------------------------------------------- + +ImPlotStyle& GetStyle() { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + return gp.Style; +} + +void PushStyleColor(ImPlotCol idx, ImU32 col) { + ImPlotContext& gp = *GImPlot; + ImGuiColorMod backup; + backup.Col = (ImGuiCol)idx; + backup.BackupValue = gp.Style.Colors[idx]; + gp.ColorModifiers.push_back(backup); + gp.Style.Colors[idx] = ImGui::ColorConvertU32ToFloat4(col); +} + +void PushStyleColor(ImPlotCol idx, const ImVec4& col) { + ImPlotContext& gp = *GImPlot; + ImGuiColorMod backup; + backup.Col = (ImGuiCol)idx; + backup.BackupValue = gp.Style.Colors[idx]; + gp.ColorModifiers.push_back(backup); + gp.Style.Colors[idx] = col; +} + +void PopStyleColor(int count) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(count <= gp.ColorModifiers.Size, "You can't pop more modifiers than have been pushed!"); + while (count > 0) + { + ImGuiColorMod& backup = gp.ColorModifiers.back(); + gp.Style.Colors[backup.Col] = backup.BackupValue; + gp.ColorModifiers.pop_back(); + count--; + } +} + +void PushStyleVar(ImPlotStyleVar idx, float val) { + ImPlotContext& gp = *GImPlot; + const ImPlotStyleVarInfo* var_info = GetPlotStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) { + float* pvar = (float*)var_info->GetVarPtr(&gp.Style); + gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0 && "Called PushStyleVar() float variant but variable is not a float!"); +} + +void PushStyleVar(ImPlotStyleVar idx, int val) { + ImPlotContext& gp = *GImPlot; + const ImPlotStyleVarInfo* var_info = GetPlotStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_S32 && var_info->Count == 1) { + int* pvar = (int*)var_info->GetVarPtr(&gp.Style); + gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar)); + *pvar = val; + return; + } + else if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) { + float* pvar = (float*)var_info->GetVarPtr(&gp.Style); + gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar)); + *pvar = (float)val; + return; + } + IM_ASSERT(0 && "Called PushStyleVar() int variant but variable is not a int!"); +} + +void PushStyleVar(ImPlotStyleVar idx, const ImVec2& val) +{ + ImPlotContext& gp = *GImPlot; + const ImPlotStyleVarInfo* var_info = GetPlotStyleVarInfo(idx); + if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2) + { + ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&gp.Style); + gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar)); + *pvar = val; + return; + } + IM_ASSERT(0 && "Called PushStyleVar() ImVec2 variant but variable is not a ImVec2!"); +} + +void PopStyleVar(int count) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(count <= gp.StyleModifiers.Size, "You can't pop more modifiers than have been pushed!"); + while (count > 0) { + ImGuiStyleMod& backup = gp.StyleModifiers.back(); + const ImPlotStyleVarInfo* info = GetPlotStyleVarInfo(backup.VarIdx); + void* data = info->GetVarPtr(&gp.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]; + } + else if (info->Type == ImGuiDataType_S32 && info->Count == 1) { + ((int*)data)[0] = backup.BackupInt[0]; + } + gp.StyleModifiers.pop_back(); + count--; + } +} + +//------------------------------------------------------------------------------ +// [Section] Colormaps +//------------------------------------------------------------------------------ + +ImPlotColormap AddColormap(const char* name, const ImVec4* colormap, int size, bool qual) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(size > 1, "The colormap size must be greater than 1!"); + IM_ASSERT_USER_ERROR(gp.ColormapData.GetIndex(name) == -1, "The colormap name has already been used!"); + ImVector buffer; + buffer.resize(size); + for (int i = 0; i < size; ++i) + buffer[i] = ImGui::ColorConvertFloat4ToU32(colormap[i]); + return gp.ColormapData.Append(name, buffer.Data, size, qual); +} + +ImPlotColormap AddColormap(const char* name, const ImU32* colormap, int size, bool qual) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(size > 1, "The colormap size must be greater than 1!"); + IM_ASSERT_USER_ERROR(gp.ColormapData.GetIndex(name) == -1, "The colormap name has already be used!"); + return gp.ColormapData.Append(name, colormap, size, qual); +} + +int GetColormapCount() { + ImPlotContext& gp = *GImPlot; + return gp.ColormapData.Count; +} + +const char* GetColormapName(ImPlotColormap colormap) { + ImPlotContext& gp = *GImPlot; + return gp.ColormapData.GetName(colormap); +} + +ImPlotColormap GetColormapIndex(const char* name) { + ImPlotContext& gp = *GImPlot; + return gp.ColormapData.GetIndex(name); +} + +void PushColormap(ImPlotColormap colormap) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(colormap >= 0 && colormap < gp.ColormapData.Count, "The colormap index is invalid!"); + gp.ColormapModifiers.push_back(gp.Style.Colormap); + gp.Style.Colormap = colormap; +} + +void PushColormap(const char* name) { + ImPlotContext& gp = *GImPlot; + ImPlotColormap idx = gp.ColormapData.GetIndex(name); + IM_ASSERT_USER_ERROR(idx != -1, "The colormap name is invalid!"); + PushColormap(idx); +} + +void PopColormap(int count) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(count <= gp.ColormapModifiers.Size, "You can't pop more modifiers than have been pushed!"); + while (count > 0) { + const ImPlotColormap& backup = gp.ColormapModifiers.back(); + gp.Style.Colormap = backup; + gp.ColormapModifiers.pop_back(); + count--; + } +} + +ImU32 NextColormapColorU32() { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, "NextColormapColor() needs to be called between BeginPlot() and EndPlot()!"); + int idx = gp.CurrentItems->ColormapIdx % gp.ColormapData.GetKeyCount(gp.Style.Colormap); + ImU32 col = gp.ColormapData.GetKeyColor(gp.Style.Colormap, idx); + gp.CurrentItems->ColormapIdx++; + return col; +} + +ImVec4 NextColormapColor() { + return ImGui::ColorConvertU32ToFloat4(NextColormapColorU32()); +} + +int GetColormapSize(ImPlotColormap cmap) { + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + return gp.ColormapData.GetKeyCount(cmap); +} + +ImU32 GetColormapColorU32(int idx, ImPlotColormap cmap) { + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + idx = idx % gp.ColormapData.GetKeyCount(cmap); + return gp.ColormapData.GetKeyColor(cmap, idx); +} + +ImVec4 GetColormapColor(int idx, ImPlotColormap cmap) { + return ImGui::ColorConvertU32ToFloat4(GetColormapColorU32(idx,cmap)); +} + +ImU32 SampleColormapU32(float t, ImPlotColormap cmap) { + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + return gp.ColormapData.LerpTable(cmap, t); +} + +ImVec4 SampleColormap(float t, ImPlotColormap cmap) { + return ImGui::ColorConvertU32ToFloat4(SampleColormapU32(t,cmap)); +} + +void RenderColorBar(const ImU32* colors, int size, ImDrawList& DrawList, const ImRect& bounds, bool vert, bool reversed, bool continuous) { + const int n = continuous ? size - 1 : size; + ImU32 col1, col2; + if (vert) { + const float step = bounds.GetHeight() / n; + ImRect rect(bounds.Min.x, bounds.Min.y, bounds.Max.x, bounds.Min.y + step); + for (int i = 0; i < n; ++i) { + if (reversed) { + col1 = colors[size-i-1]; + col2 = continuous ? colors[size-i-2] : col1; + } + else { + col1 = colors[i]; + col2 = continuous ? colors[i+1] : col1; + } + DrawList.AddRectFilledMultiColor(rect.Min, rect.Max, col1, col1, col2, col2); + rect.TranslateY(step); + } + } + else { + const float step = bounds.GetWidth() / n; + ImRect rect(bounds.Min.x, bounds.Min.y, bounds.Min.x + step, bounds.Max.y); + for (int i = 0; i < n; ++i) { + if (reversed) { + col1 = colors[size-i-1]; + col2 = continuous ? colors[size-i-2] : col1; + } + else { + col1 = colors[i]; + col2 = continuous ? colors[i+1] : col1; + } + DrawList.AddRectFilledMultiColor(rect.Min, rect.Max, col1, col2, col2, col1); + rect.TranslateX(step); + } + } +} + +void ColormapScale(const char* label, double scale_min, double scale_max, const ImVec2& size, const char* format, ImPlotColormapScaleFlags flags, ImPlotColormap cmap) { + ImGuiContext &G = *GImGui; + ImGuiWindow * Window = G.CurrentWindow; + if (Window->SkipItems) + return; + + const ImGuiID ID = Window->GetID(label); + ImVec2 label_size(0,0); + if (!ImHasFlag(flags, ImPlotColormapScaleFlags_NoLabel)) { + label_size = ImGui::CalcTextSize(label,nullptr,true); + } + + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + + ImVec2 frame_size = ImGui::CalcItemSize(size, 0, gp.Style.PlotDefaultSize.y); + if (frame_size.y < gp.Style.PlotMinSize.y && size.y < 0.0f) + frame_size.y = gp.Style.PlotMinSize.y; + + ImPlotRange range(ImMin(scale_min,scale_max), ImMax(scale_min,scale_max)); + gp.CTicker.Reset(); + Locator_Default(gp.CTicker, range, frame_size.y, true, Formatter_Default, (void*)format); + + const bool rend_label = label_size.x > 0; + const float txt_off = gp.Style.LabelPadding.x; + const float pad = txt_off + gp.CTicker.MaxSize.x + (rend_label ? txt_off + label_size.y : 0); + float bar_w = 20; + if (frame_size.x == 0) + frame_size.x = bar_w + pad + 2 * gp.Style.PlotPadding.x; + else { + bar_w = frame_size.x - (pad + 2 * gp.Style.PlotPadding.x); + if (bar_w < gp.Style.MajorTickLen.y) + bar_w = gp.Style.MajorTickLen.y; + } + + ImDrawList &DrawList = *Window->DrawList; + ImRect bb_frame = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size); + ImGui::ItemSize(bb_frame); + if (!ImGui::ItemAdd(bb_frame, ID, &bb_frame)) + return; + + ImGui::RenderFrame(bb_frame.Min, bb_frame.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, G.Style.FrameRounding); + + const bool opposite = ImHasFlag(flags, ImPlotColormapScaleFlags_Opposite); + const bool inverted = ImHasFlag(flags, ImPlotColormapScaleFlags_Invert); + const bool reversed = scale_min > scale_max; + + float bb_grad_shift = opposite ? pad : 0; + ImRect bb_grad(bb_frame.Min + gp.Style.PlotPadding + ImVec2(bb_grad_shift, 0), + bb_frame.Min + ImVec2(bar_w + gp.Style.PlotPadding.x + bb_grad_shift, + frame_size.y - gp.Style.PlotPadding.y)); + + ImGui::PushClipRect(bb_frame.Min, bb_frame.Max, true); + const ImU32 col_text = ImGui::GetColorU32(ImGuiCol_Text); + + const bool invert_scale = inverted ? (reversed ? false : true) : (reversed ? true : false); + const float y_min = invert_scale ? bb_grad.Max.y : bb_grad.Min.y; + const float y_max = invert_scale ? bb_grad.Min.y : bb_grad.Max.y; + + RenderColorBar(gp.ColormapData.GetKeys(cmap), gp.ColormapData.GetKeyCount(cmap), DrawList, bb_grad, true, !inverted, !gp.ColormapData.IsQual(cmap)); + for (int i = 0; i < gp.CTicker.TickCount(); ++i) { + const double y_pos_plt = gp.CTicker.Ticks[i].PlotPos; + const float y_pos = ImRemap((float)y_pos_plt, (float)range.Max, (float)range.Min, y_min, y_max); + const float tick_width = gp.CTicker.Ticks[i].Major ? gp.Style.MajorTickLen.y : gp.Style.MinorTickLen.y; + const float tick_thick = gp.CTicker.Ticks[i].Major ? gp.Style.MajorTickSize.y : gp.Style.MinorTickSize.y; + const float tick_t = (float)((y_pos_plt - scale_min) / (scale_max - scale_min)); + const ImU32 tick_col = CalcTextColor(gp.ColormapData.LerpTable(cmap,tick_t)); + if (y_pos < bb_grad.Max.y - 2 && y_pos > bb_grad.Min.y + 2) { + DrawList.AddLine(opposite ? ImVec2(bb_grad.Min.x+1, y_pos) : ImVec2(bb_grad.Max.x-1, y_pos), + opposite ? ImVec2(bb_grad.Min.x + tick_width, y_pos) : ImVec2(bb_grad.Max.x - tick_width, y_pos), + tick_col, + tick_thick); + } + const float txt_x = opposite ? bb_grad.Min.x - txt_off - gp.CTicker.Ticks[i].LabelSize.x : bb_grad.Max.x + txt_off; + const float txt_y = y_pos - gp.CTicker.Ticks[i].LabelSize.y * 0.5f; + DrawList.AddText(ImVec2(txt_x, txt_y), col_text, gp.CTicker.GetText(i)); + } + + if (rend_label) { + const float pos_x = opposite ? bb_frame.Min.x + gp.Style.PlotPadding.x : bb_grad.Max.x + 2 * txt_off + gp.CTicker.MaxSize.x; + const float pos_y = bb_grad.GetCenter().y + label_size.x * 0.5f; + const char* label_end = ImGui::FindRenderedTextEnd(label); + AddTextVertical(&DrawList,ImVec2(pos_x,pos_y),col_text,label,label_end); + } + DrawList.AddRect(bb_grad.Min, bb_grad.Max, GetStyleColorU32(ImPlotCol_PlotBorder)); + ImGui::PopClipRect(); +} + +bool ColormapSlider(const char* label, float* t, ImVec4* out, const char* format, ImPlotColormap cmap) { + *t = ImClamp(*t,0.0f,1.0f); + ImGuiContext &G = *GImGui; + ImGuiWindow * Window = G.CurrentWindow; + if (Window->SkipItems) + return false; + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + const ImU32* keys = gp.ColormapData.GetKeys(cmap); + const int count = gp.ColormapData.GetKeyCount(cmap); + const bool qual = gp.ColormapData.IsQual(cmap); + const ImVec2 pos = ImGui::GetCurrentWindow()->DC.CursorPos; + const float w = ImGui::CalcItemWidth(); + const float h = ImGui::GetFrameHeight(); + const ImRect rect = ImRect(pos.x,pos.y,pos.x+w,pos.y+h); + RenderColorBar(keys,count,*ImGui::GetWindowDrawList(),rect,false,false,!qual); + const ImU32 grab = CalcTextColor(gp.ColormapData.LerpTable(cmap,*t)); + // const ImU32 text = CalcTextColor(gp.ColormapData.LerpTable(cmap,0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBg,IM_COL32_BLACK_TRANS); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive,IM_COL32_BLACK_TRANS); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered,ImVec4(1,1,1,0.1f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab,grab); + ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, grab); + ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize,2); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0); + const bool changed = ImGui::SliderFloat(label,t,0,1,format); + ImGui::PopStyleColor(5); + ImGui::PopStyleVar(2); + if (out != nullptr) + *out = ImGui::ColorConvertU32ToFloat4(gp.ColormapData.LerpTable(cmap,*t)); + return changed; +} + +bool ColormapButton(const char* label, const ImVec2& size_arg, ImPlotColormap cmap) { + ImGuiContext &G = *GImGui; + const ImGuiStyle& style = G.Style; + ImGuiWindow * Window = G.CurrentWindow; + if (Window->SkipItems) + return false; + ImPlotContext& gp = *GImPlot; + cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap; + IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, "Invalid colormap index!"); + const ImU32* keys = gp.ColormapData.GetKeys(cmap); + const int count = gp.ColormapData.GetKeyCount(cmap); + const bool qual = gp.ColormapData.IsQual(cmap); + const ImVec2 pos = ImGui::GetCurrentWindow()->DC.CursorPos; + const ImVec2 label_size = ImGui::CalcTextSize(label, nullptr, true); + ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); + const ImRect rect = ImRect(pos.x,pos.y,pos.x+size.x,pos.y+size.y); + RenderColorBar(keys,count,*ImGui::GetWindowDrawList(),rect,false,false,!qual); + const ImU32 text = CalcTextColor(gp.ColormapData.LerpTable(cmap,G.Style.ButtonTextAlign.x)); + ImGui::PushStyleColor(ImGuiCol_Button,IM_COL32_BLACK_TRANS); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered,ImVec4(1,1,1,0.1f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive,ImVec4(1,1,1,0.2f)); + ImGui::PushStyleColor(ImGuiCol_Text,text); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0); + const bool pressed = ImGui::Button(label,size); + ImGui::PopStyleColor(4); + ImGui::PopStyleVar(1); + return pressed; +} + +//----------------------------------------------------------------------------- +// [Section] Miscellaneous +//----------------------------------------------------------------------------- + +ImPlotInputMap& GetInputMap() { + IM_ASSERT_USER_ERROR(GImPlot != nullptr, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); + ImPlotContext& gp = *GImPlot; + return gp.InputMap; +} + +void MapInputDefault(ImPlotInputMap* dst) { + ImPlotInputMap& map = dst ? *dst : GetInputMap(); + map.Pan = ImGuiMouseButton_Left; + map.PanMod = ImGuiMod_None; + map.Fit = ImGuiMouseButton_Left; + map.Menu = ImGuiMouseButton_Right; + map.Select = ImGuiMouseButton_Right; + map.SelectMod = ImGuiMod_None; + map.SelectCancel = ImGuiMouseButton_Left; + map.SelectHorzMod = ImGuiMod_Alt; + map.SelectVertMod = ImGuiMod_Shift; + map.OverrideMod = ImGuiMod_Ctrl; + map.ZoomMod = ImGuiMod_None; + map.ZoomRate = 0.1f; +} + +void MapInputReverse(ImPlotInputMap* dst) { + ImPlotInputMap& map = dst ? *dst : GetInputMap(); + map.Pan = ImGuiMouseButton_Right; + map.PanMod = ImGuiMod_None; + map.Fit = ImGuiMouseButton_Left; + map.Menu = ImGuiMouseButton_Right; + map.Select = ImGuiMouseButton_Left; + map.SelectMod = ImGuiMod_None; + map.SelectCancel = ImGuiMouseButton_Right; + map.SelectHorzMod = ImGuiMod_Alt; + map.SelectVertMod = ImGuiMod_Shift; + map.OverrideMod = ImGuiMod_Ctrl; + map.ZoomMod = ImGuiMod_None; + map.ZoomRate = 0.1f; +} + +//----------------------------------------------------------------------------- +// [Section] Miscellaneous +//----------------------------------------------------------------------------- + +void ItemIcon(const ImVec4& col) { + ItemIcon(ImGui::ColorConvertFloat4ToU32(col)); +} + +void ItemIcon(ImU32 col) { + const float txt_size = ImGui::GetTextLineHeight(); + ImVec2 size(txt_size-4,txt_size); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + ImVec2 pos = window->DC.CursorPos; + ImGui::GetWindowDrawList()->AddRectFilled(pos + ImVec2(0,2), pos + size - ImVec2(0,2), col); + ImGui::Dummy(size); +} + +void ColormapIcon(ImPlotColormap cmap) { + ImPlotContext& gp = *GImPlot; + const float txt_size = ImGui::GetTextLineHeight(); + ImVec2 size(txt_size-4,txt_size); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + ImVec2 pos = window->DC.CursorPos; + ImRect rect(pos+ImVec2(0,2),pos+size-ImVec2(0,2)); + ImDrawList& DrawList = *ImGui::GetWindowDrawList(); + RenderColorBar(gp.ColormapData.GetKeys(cmap),gp.ColormapData.GetKeyCount(cmap),DrawList,rect,false,false,!gp.ColormapData.IsQual(cmap)); + ImGui::Dummy(size); +} + +ImDrawList* GetPlotDrawList() { + return ImGui::GetWindowDrawList(); +} + +void PushPlotClipRect(float expand) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PushPlotClipRect() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + ImRect rect = gp.CurrentPlot->PlotRect; + rect.Expand(expand); + ImGui::PushClipRect(rect.Min, rect.Max, true); +} + +void PopPlotClipRect() { + SetupLock(); + ImGui::PopClipRect(); +} + +static void HelpMarker(const char* desc) { + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +bool ShowStyleSelector(const char* label) +{ + static int style_idx = -1; + if (ImGui::Combo(label, &style_idx, "Auto\0Classic\0Dark\0Light\0")) + { + switch (style_idx) + { + case 0: StyleColorsAuto(); break; + case 1: StyleColorsClassic(); break; + case 2: StyleColorsDark(); break; + case 3: StyleColorsLight(); break; + } + return true; + } + return false; +} + +bool ShowColormapSelector(const char* label) { + ImPlotContext& gp = *GImPlot; + bool set = false; + if (ImGui::BeginCombo(label, gp.ColormapData.GetName(gp.Style.Colormap))) { + for (int i = 0; i < gp.ColormapData.Count; ++i) { + const char* name = gp.ColormapData.GetName(i); + if (ImGui::Selectable(name, gp.Style.Colormap == i)) { + gp.Style.Colormap = i; + ImPlot::BustItemCache(); + set = true; + } + } + ImGui::EndCombo(); + } + return set; +} + +bool ShowInputMapSelector(const char* label) { + static int map_idx = -1; + if (ImGui::Combo(label, &map_idx, "Default\0Reversed\0")) + { + switch (map_idx) + { + case 0: MapInputDefault(); break; + case 1: MapInputReverse(); break; + } + return true; + } + return false; +} + + +void ShowStyleEditor(ImPlotStyle* ref) { + ImPlotContext& gp = *GImPlot; + ImPlotStyle& style = GetStyle(); + static ImPlotStyle ref_saved_style; + // Default to using internal storage as reference + static bool init = true; + if (init && ref == nullptr) + ref_saved_style = style; + init = false; + if (ref == nullptr) + ref = &ref_saved_style; + + if (ImPlot::ShowStyleSelector("Colors##Selector")) + ref_saved_style = style; + + // Save/Revert button + if (ImGui::Button("Save Ref")) + *ref = ref_saved_style = style; + ImGui::SameLine(); + if (ImGui::Button("Revert Ref")) + style = *ref; + ImGui::SameLine(); + HelpMarker("Save/Revert in local non-persistent storage. Default Colors definition are not affected. " + "Use \"Export\" below to save them somewhere."); + if (ImGui::BeginTabBar("##StyleEditor")) { + if (ImGui::BeginTabItem("Variables")) { + ImGui::Text("Item Styling"); + ImGui::SliderFloat("LineWeight", &style.LineWeight, 0.0f, 5.0f, "%.1f"); + ImGui::SliderFloat("MarkerSize", &style.MarkerSize, 2.0f, 10.0f, "%.1f"); + ImGui::SliderFloat("MarkerWeight", &style.MarkerWeight, 0.0f, 5.0f, "%.1f"); + ImGui::SliderFloat("FillAlpha", &style.FillAlpha, 0.0f, 1.0f, "%.2f"); + ImGui::SliderFloat("ErrorBarSize", &style.ErrorBarSize, 0.0f, 10.0f, "%.1f"); + ImGui::SliderFloat("ErrorBarWeight", &style.ErrorBarWeight, 0.0f, 5.0f, "%.1f"); + ImGui::SliderFloat("DigitalBitHeight", &style.DigitalBitHeight, 0.0f, 20.0f, "%.1f"); + ImGui::SliderFloat("DigitalBitGap", &style.DigitalBitGap, 0.0f, 20.0f, "%.1f"); + ImGui::Text("Plot Styling"); + ImGui::SliderFloat("PlotBorderSize", &style.PlotBorderSize, 0.0f, 2.0f, "%.0f"); + ImGui::SliderFloat("MinorAlpha", &style.MinorAlpha, 0.0f, 1.0f, "%.2f"); + ImGui::SliderFloat2("MajorTickLen", (float*)&style.MajorTickLen, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("MinorTickLen", (float*)&style.MinorTickLen, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("MajorTickSize", (float*)&style.MajorTickSize, 0.0f, 2.0f, "%.1f"); + ImGui::SliderFloat2("MinorTickSize", (float*)&style.MinorTickSize, 0.0f, 2.0f, "%.1f"); + ImGui::SliderFloat2("MajorGridSize", (float*)&style.MajorGridSize, 0.0f, 2.0f, "%.1f"); + ImGui::SliderFloat2("MinorGridSize", (float*)&style.MinorGridSize, 0.0f, 2.0f, "%.1f"); + ImGui::SliderFloat2("PlotDefaultSize", (float*)&style.PlotDefaultSize, 0.0f, 1000, "%.0f"); + ImGui::SliderFloat2("PlotMinSize", (float*)&style.PlotMinSize, 0.0f, 300, "%.0f"); + ImGui::Text("Plot Padding"); + ImGui::SliderFloat2("PlotPadding", (float*)&style.PlotPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("LabelPadding", (float*)&style.LabelPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("LegendPadding", (float*)&style.LegendPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("LegendInnerPadding", (float*)&style.LegendInnerPadding, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat2("LegendSpacing", (float*)&style.LegendSpacing, 0.0f, 5.0f, "%.0f"); + ImGui::SliderFloat2("MousePosPadding", (float*)&style.MousePosPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("AnnotationPadding", (float*)&style.AnnotationPadding, 0.0f, 5.0f, "%.0f"); + ImGui::SliderFloat2("FitPadding", (float*)&style.FitPadding, 0, 0.2f, "%.2f"); + + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Colors")) { + static int output_dest = 0; + static bool output_only_modified = false; + + if (ImGui::Button("Export", ImVec2(75,0))) { + if (output_dest == 0) + ImGui::LogToClipboard(); + else + ImGui::LogToTTY(); + ImGui::LogText("ImVec4* colors = ImPlot::GetStyle().Colors;\n"); + for (int i = 0; i < ImPlotCol_COUNT; i++) { + const ImVec4& col = style.Colors[i]; + const char* name = ImPlot::GetStyleColorName(i); + if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) { + if (IsColorAuto(i)) + ImGui::LogText("colors[ImPlotCol_%s]%*s= IMPLOT_AUTO_COL;\n",name,14 - (int)strlen(name), ""); + else + ImGui::LogText("colors[ImPlotCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);\n", + name, 14 - (int)strlen(name), "", col.x, col.y, col.z, col.w); + } + } + ImGui::LogFinish(); + } + ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); + ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified); + + static ImGuiTextFilter filter; + filter.Draw("Filter colors", ImGui::GetFontSize() * 16); + + static ImGuiColorEditFlags alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; + if (ImGui::RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } ImGui::SameLine(); + if (ImGui::RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_AlphaPreview)) { alpha_flags = ImGuiColorEditFlags_AlphaPreview; } ImGui::SameLine(); + if (ImGui::RadioButton("Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; } ImGui::SameLine(); + HelpMarker( + "In the color list:\n" + "Left-click on colored square to open color picker,\n" + "Right-click to open edit options menu."); + ImGui::Separator(); + ImGui::PushItemWidth(-160); + for (int i = 0; i < ImPlotCol_COUNT; i++) { + const char* name = ImPlot::GetStyleColorName(i); + if (!filter.PassFilter(name)) + continue; + ImGui::PushID(i); + ImVec4 temp = GetStyleColorVec4(i); + const bool is_auto = IsColorAuto(i); + if (!is_auto) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.25f); + if (ImGui::Button("Auto")) { + if (is_auto) + style.Colors[i] = temp; + else + style.Colors[i] = IMPLOT_AUTO_COL; + BustItemCache(); + } + if (!is_auto) + ImGui::PopStyleVar(); + ImGui::SameLine(); + if (ImGui::ColorEdit4(name, &temp.x, ImGuiColorEditFlags_NoInputs | alpha_flags)) { + style.Colors[i] = temp; + BustItemCache(); + } + if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) { + ImGui::SameLine(175); if (ImGui::Button("Save")) { ref->Colors[i] = style.Colors[i]; } + ImGui::SameLine(); if (ImGui::Button("Revert")) { + style.Colors[i] = ref->Colors[i]; + BustItemCache(); + } + } + ImGui::PopID(); + } + ImGui::PopItemWidth(); + ImGui::Separator(); + ImGui::Text("Colors that are set to Auto (i.e. IMPLOT_AUTO_COL) will\n" + "be automatically deduced from your ImGui style or the\n" + "current ImPlot Colormap. If you want to style individual\n" + "plot items, use Push/PopStyleColor around its function."); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Colormaps")) { + static int output_dest = 0; + if (ImGui::Button("Export", ImVec2(75,0))) { + if (output_dest == 0) + ImGui::LogToClipboard(); + else + ImGui::LogToTTY(); + int size = GetColormapSize(); + const char* name = GetColormapName(gp.Style.Colormap); + ImGui::LogText("static const ImU32 %s_Data[%d] = {\n", name, size); + for (int i = 0; i < size; ++i) { + ImU32 col = GetColormapColorU32(i,gp.Style.Colormap); + ImGui::LogText(" %u%s\n", col, i == size - 1 ? "" : ","); + } + ImGui::LogText("};\nImPlotColormap %s = ImPlot::AddColormap(\"%s\", %s_Data, %d);", name, name, name, size); + ImGui::LogFinish(); + } + ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); + ImGui::SameLine(); + static bool edit = false; + ImGui::Checkbox("Edit Mode",&edit); + + // built-in/added + ImGui::Separator(); + for (int i = 0; i < gp.ColormapData.Count; ++i) { + ImGui::PushID(i); + int size = gp.ColormapData.GetKeyCount(i); + bool selected = i == gp.Style.Colormap; + + const char* name = GetColormapName(i); + if (!selected) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.25f); + if (ImGui::Button(name, ImVec2(100,0))) { + gp.Style.Colormap = i; + BustItemCache(); + } + if (!selected) + ImGui::PopStyleVar(); + ImGui::SameLine(); + ImGui::BeginGroup(); + if (edit) { + for (int c = 0; c < size; ++c) { + ImGui::PushID(c); + ImVec4 col4 = ImGui::ColorConvertU32ToFloat4(gp.ColormapData.GetKeyColor(i,c)); + if (ImGui::ColorEdit4("",&col4.x,ImGuiColorEditFlags_NoInputs)) { + ImU32 col32 = ImGui::ColorConvertFloat4ToU32(col4); + gp.ColormapData.SetKeyColor(i,c,col32); + BustItemCache(); + } + if ((c + 1) % 12 != 0 && c != size -1) + ImGui::SameLine(); + ImGui::PopID(); + } + } + else { + if (ImPlot::ColormapButton("##",ImVec2(-1,0),i)) + edit = true; + } + ImGui::EndGroup(); + ImGui::PopID(); + } + + + static ImVector custom; + if (custom.Size == 0) { + custom.push_back(ImVec4(1,0,0,1)); + custom.push_back(ImVec4(0,1,0,1)); + custom.push_back(ImVec4(0,0,1,1)); + } + ImGui::Separator(); + ImGui::BeginGroup(); + static char name[16] = "MyColormap"; + + + if (ImGui::Button("+", ImVec2((100 - ImGui::GetStyle().ItemSpacing.x)/2,0))) + custom.push_back(ImVec4(0,0,0,1)); + ImGui::SameLine(); + if (ImGui::Button("-", ImVec2((100 - ImGui::GetStyle().ItemSpacing.x)/2,0)) && custom.Size > 2) + custom.pop_back(); + ImGui::SetNextItemWidth(100); + ImGui::InputText("##Name",name,16,ImGuiInputTextFlags_CharsNoBlank); + static bool qual = true; + ImGui::Checkbox("Qualitative",&qual); + if (ImGui::Button("Add", ImVec2(100, 0)) && gp.ColormapData.GetIndex(name)==-1) + AddColormap(name,custom.Data,custom.Size,qual); + + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::BeginGroup(); + for (int c = 0; c < custom.Size; ++c) { + ImGui::PushID(c); + if (ImGui::ColorEdit4("##Col1", &custom[c].x, ImGuiColorEditFlags_NoInputs)) { + + } + if ((c + 1) % 12 != 0) + ImGui::SameLine(); + ImGui::PopID(); + } + ImGui::EndGroup(); + + + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } +} + +void ShowUserGuide() { + ImGui::BulletText("Left-click drag within the plot area to pan X and Y axes."); + ImGui::Indent(); + ImGui::BulletText("Left-click drag on axis labels to pan an individual axis."); + ImGui::Unindent(); + ImGui::BulletText("Scroll in the plot area to zoom both X any Y axes."); + ImGui::Indent(); + ImGui::BulletText("Scroll on axis labels to zoom an individual axis."); + ImGui::Unindent(); + ImGui::BulletText("Right-click drag to box select data."); + ImGui::Indent(); + ImGui::BulletText("Hold Alt to expand box selection horizontally."); + ImGui::BulletText("Hold Shift to expand box selection vertically."); + ImGui::BulletText("Left-click while box selecting to cancel the selection."); + ImGui::Unindent(); + ImGui::BulletText("Double left-click to fit all visible data."); + ImGui::Indent(); + ImGui::BulletText("Double left-click axis labels to fit the individual axis."); + ImGui::Unindent(); + ImGui::BulletText("Right-click open the full plot context menu."); + ImGui::Indent(); + ImGui::BulletText("Right-click axis labels to open an individual axis context menu."); + ImGui::Unindent(); + ImGui::BulletText("Click legend label icons to show/hide plot items."); +} + +void ShowTicksMetrics(const ImPlotTicker& ticker) { + ImGui::BulletText("Size: %d", ticker.TickCount()); + ImGui::BulletText("MaxSize: [%f,%f]", ticker.MaxSize.x, ticker.MaxSize.y); +} + +void ShowAxisMetrics(const ImPlotPlot& plot, const ImPlotAxis& axis) { + ImGui::BulletText("Label: %s", axis.LabelOffset == -1 ? "[none]" : plot.GetAxisLabel(axis)); + ImGui::BulletText("Flags: 0x%08X", axis.Flags); + ImGui::BulletText("Range: [%f,%f]",axis.Range.Min, axis.Range.Max); + ImGui::BulletText("Pixels: %f", axis.PixelSize()); + ImGui::BulletText("Aspect: %f", axis.GetAspect()); + ImGui::BulletText(axis.OrthoAxis == nullptr ? "OrtherAxis: NULL" : "OrthoAxis: 0x%08X", axis.OrthoAxis->ID); + ImGui::BulletText("LinkedMin: %p", (void*)axis.LinkedMin); + ImGui::BulletText("LinkedMax: %p", (void*)axis.LinkedMax); + ImGui::BulletText("HasRange: %s", axis.HasRange ? "true" : "false"); + ImGui::BulletText("Hovered: %s", axis.Hovered ? "true" : "false"); + ImGui::BulletText("Held: %s", axis.Held ? "true" : "false"); + + if (ImGui::TreeNode("Transform")) { + ImGui::BulletText("PixelMin: %f", axis.PixelMin); + ImGui::BulletText("PixelMax: %f", axis.PixelMax); + ImGui::BulletText("ScaleToPixel: %f", axis.ScaleToPixel); + ImGui::BulletText("ScaleMax: %f", axis.ScaleMax); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Ticks")) { + ShowTicksMetrics(axis.Ticker); + ImGui::TreePop(); + } +} + +void ShowMetricsWindow(bool* p_popen) { + + static bool show_plot_rects = false; + static bool show_axes_rects = false; + static bool show_axis_rects = false; + static bool show_canvas_rects = false; + static bool show_frame_rects = false; + static bool show_subplot_frame_rects = false; + static bool show_subplot_grid_rects = false; + static bool show_legend_rects = false; + + ImDrawList& fg = *ImGui::GetForegroundDrawList(); + + ImPlotContext& gp = *GImPlot; + // ImGuiContext& g = *GImGui; + ImGuiIO& io = ImGui::GetIO(); + ImGui::Begin("ImPlot Metrics", p_popen); + ImGui::Text("ImPlot " IMPLOT_VERSION); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::Text("Mouse Position: [%.0f,%.0f]", io.MousePos.x, io.MousePos.y); + ImGui::Separator(); + if (ImGui::TreeNode("Tools")) { + if (ImGui::Button("Bust Plot Cache")) + BustPlotCache(); + ImGui::SameLine(); + if (ImGui::Button("Bust Item Cache")) + BustItemCache(); + ImGui::Checkbox("Show Frame Rects", &show_frame_rects); + ImGui::Checkbox("Show Canvas Rects",&show_canvas_rects); + ImGui::Checkbox("Show Plot Rects", &show_plot_rects); + ImGui::Checkbox("Show Axes Rects", &show_axes_rects); + ImGui::Checkbox("Show Axis Rects", &show_axis_rects); + ImGui::Checkbox("Show Subplot Frame Rects", &show_subplot_frame_rects); + ImGui::Checkbox("Show Subplot Grid Rects", &show_subplot_grid_rects); + ImGui::Checkbox("Show Legend Rects", &show_legend_rects); + ImGui::TreePop(); + } + const int n_plots = gp.Plots.GetBufSize(); + const int n_subplots = gp.Subplots.GetBufSize(); + // render rects + for (int p = 0; p < n_plots; ++p) { + ImPlotPlot* plot = gp.Plots.GetByIndex(p); + if (show_frame_rects) + fg.AddRect(plot->FrameRect.Min, plot->FrameRect.Max, IM_COL32(255,0,255,255)); + if (show_canvas_rects) + fg.AddRect(plot->CanvasRect.Min, plot->CanvasRect.Max, IM_COL32(0,255,255,255)); + if (show_plot_rects) + fg.AddRect(plot->PlotRect.Min, plot->PlotRect.Max, IM_COL32(255,255,0,255)); + if (show_axes_rects) + fg.AddRect(plot->AxesRect.Min, plot->AxesRect.Max, IM_COL32(0,255,128,255)); + if (show_axis_rects) { + for (int i = 0; i < ImAxis_COUNT; ++i) { + if (plot->Axes[i].Enabled) + fg.AddRect(plot->Axes[i].HoverRect.Min, plot->Axes[i].HoverRect.Max, IM_COL32(0,255,0,255)); + } + } + if (show_legend_rects && plot->Items.GetLegendCount() > 0) { + fg.AddRect(plot->Items.Legend.Rect.Min, plot->Items.Legend.Rect.Max, IM_COL32(255,192,0,255)); + fg.AddRect(plot->Items.Legend.RectClamped.Min, plot->Items.Legend.RectClamped.Max, IM_COL32(255,128,0,255)); + } + } + for (int p = 0; p < n_subplots; ++p) { + ImPlotSubplot* subplot = gp.Subplots.GetByIndex(p); + if (show_subplot_frame_rects) + fg.AddRect(subplot->FrameRect.Min, subplot->FrameRect.Max, IM_COL32(255,0,0,255)); + if (show_subplot_grid_rects) + fg.AddRect(subplot->GridRect.Min, subplot->GridRect.Max, IM_COL32(0,0,255,255)); + if (show_legend_rects && subplot->Items.GetLegendCount() > 0) { + fg.AddRect(subplot->Items.Legend.Rect.Min, subplot->Items.Legend.Rect.Max, IM_COL32(255,192,0,255)); + fg.AddRect(subplot->Items.Legend.RectClamped.Min, subplot->Items.Legend.RectClamped.Max, IM_COL32(255,128,0,255)); + } + } + if (ImGui::TreeNode("Plots","Plots (%d)", n_plots)) { + for (int p = 0; p < n_plots; ++p) { + // plot + ImPlotPlot& plot = *gp.Plots.GetByIndex(p); + ImGui::PushID(p); + if (ImGui::TreeNode("Plot", "Plot [0x%08X]", plot.ID)) { + int n_items = plot.Items.GetItemCount(); + if (ImGui::TreeNode("Items", "Items (%d)", n_items)) { + for (int i = 0; i < n_items; ++i) { + ImPlotItem* item = plot.Items.GetItemByIndex(i); + ImGui::PushID(i); + if (ImGui::TreeNode("Item", "Item [0x%08X]", item->ID)) { + ImGui::Bullet(); ImGui::Checkbox("Show", &item->Show); + ImGui::Bullet(); + ImVec4 temp = ImGui::ColorConvertU32ToFloat4(item->Color); + if (ImGui::ColorEdit4("Color",&temp.x, ImGuiColorEditFlags_NoInputs)) + item->Color = ImGui::ColorConvertFloat4ToU32(temp); + + ImGui::BulletText("NameOffset: %d",item->NameOffset); + ImGui::BulletText("Name: %s", item->NameOffset != -1 ? plot.Items.Legend.Labels.Buf.Data + item->NameOffset : "N/A"); + ImGui::BulletText("Hovered: %s",item->LegendHovered ? "true" : "false"); + ImGui::TreePop(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + char buff[16]; + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) { + ImFormatString(buff,16,"X-Axis %d", i+1); + if (plot.XAxis(i).Enabled && ImGui::TreeNode(buff, "X-Axis %d [0x%08X]", i+1, plot.XAxis(i).ID)) { + ShowAxisMetrics(plot, plot.XAxis(i)); + ImGui::TreePop(); + } + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) { + ImFormatString(buff,16,"Y-Axis %d", i+1); + if (plot.YAxis(i).Enabled && ImGui::TreeNode(buff, "Y-Axis %d [0x%08X]", i+1, plot.YAxis(i).ID)) { + ShowAxisMetrics(plot, plot.YAxis(i)); + ImGui::TreePop(); + } + } + ImGui::BulletText("Title: %s", plot.HasTitle() ? plot.GetTitle() : "none"); + ImGui::BulletText("Flags: 0x%08X", plot.Flags); + ImGui::BulletText("Initialized: %s", plot.Initialized ? "true" : "false"); + ImGui::BulletText("Selecting: %s", plot.Selecting ? "true" : "false"); + ImGui::BulletText("Selected: %s", plot.Selected ? "true" : "false"); + ImGui::BulletText("Hovered: %s", plot.Hovered ? "true" : "false"); + ImGui::BulletText("Held: %s", plot.Held ? "true" : "false"); + ImGui::BulletText("LegendHovered: %s", plot.Items.Legend.Hovered ? "true" : "false"); + ImGui::BulletText("ContextLocked: %s", plot.ContextLocked ? "true" : "false"); + ImGui::TreePop(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Subplots","Subplots (%d)", n_subplots)) { + for (int p = 0; p < n_subplots; ++p) { + // plot + ImPlotSubplot& plot = *gp.Subplots.GetByIndex(p); + ImGui::PushID(p); + if (ImGui::TreeNode("Subplot", "Subplot [0x%08X]", plot.ID)) { + int n_items = plot.Items.GetItemCount(); + if (ImGui::TreeNode("Items", "Items (%d)", n_items)) { + for (int i = 0; i < n_items; ++i) { + ImPlotItem* item = plot.Items.GetItemByIndex(i); + ImGui::PushID(i); + if (ImGui::TreeNode("Item", "Item [0x%08X]", item->ID)) { + ImGui::Bullet(); ImGui::Checkbox("Show", &item->Show); + ImGui::Bullet(); + ImVec4 temp = ImGui::ColorConvertU32ToFloat4(item->Color); + if (ImGui::ColorEdit4("Color",&temp.x, ImGuiColorEditFlags_NoInputs)) + item->Color = ImGui::ColorConvertFloat4ToU32(temp); + + ImGui::BulletText("NameOffset: %d",item->NameOffset); + ImGui::BulletText("Name: %s", item->NameOffset != -1 ? plot.Items.Legend.Labels.Buf.Data + item->NameOffset : "N/A"); + ImGui::BulletText("Hovered: %s",item->LegendHovered ? "true" : "false"); + ImGui::TreePop(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + ImGui::BulletText("Flags: 0x%08X", plot.Flags); + ImGui::BulletText("FrameHovered: %s", plot.FrameHovered ? "true" : "false"); + ImGui::BulletText("LegendHovered: %s", plot.Items.Legend.Hovered ? "true" : "false"); + ImGui::TreePop(); + } + ImGui::PopID(); + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Colormaps")) { + ImGui::BulletText("Colormaps: %d", gp.ColormapData.Count); + ImGui::BulletText("Memory: %d bytes", gp.ColormapData.Tables.Size * 4); + if (ImGui::TreeNode("Data")) { + for (int m = 0; m < gp.ColormapData.Count; ++m) { + if (ImGui::TreeNode(gp.ColormapData.GetName(m))) { + int count = gp.ColormapData.GetKeyCount(m); + int size = gp.ColormapData.GetTableSize(m); + bool qual = gp.ColormapData.IsQual(m); + ImGui::BulletText("Qualitative: %s", qual ? "true" : "false"); + ImGui::BulletText("Key Count: %d", count); + ImGui::BulletText("Table Size: %d", size); + ImGui::Indent(); + + static float t = 0.5; + ImVec4 samp; + float wid = 32 * 10 - ImGui::GetFrameHeight() - ImGui::GetStyle().ItemSpacing.x; + ImGui::SetNextItemWidth(wid); + ImPlot::ColormapSlider("##Sample",&t,&samp,"%.3f",m); + ImGui::SameLine(); + ImGui::ColorButton("Sampler",samp); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + for (int c = 0; c < size; ++c) { + ImVec4 col = ImGui::ColorConvertU32ToFloat4(gp.ColormapData.GetTableColor(m,c)); + ImGui::PushID(m*1000+c); + ImGui::ColorButton("",col,0,ImVec2(10,10)); + ImGui::PopID(); + if ((c + 1) % 32 != 0 && c != size - 1) + ImGui::SameLine(); + } + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + ImGui::Unindent(); + ImGui::TreePop(); + } + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + ImGui::End(); +} + +bool ShowDatePicker(const char* id, int* level, ImPlotTime* t, const ImPlotTime* t1, const ImPlotTime* t2) { + + ImGui::PushID(id); + ImGui::BeginGroup(); + + ImGuiStyle& style = ImGui::GetStyle(); + ImVec4 col_txt = style.Colors[ImGuiCol_Text]; + ImVec4 col_dis = style.Colors[ImGuiCol_TextDisabled]; + ImVec4 col_btn = style.Colors[ImGuiCol_Button]; + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + + const float ht = ImGui::GetFrameHeight(); + ImVec2 cell_size(ht*1.25f,ht); + char buff[32]; + bool clk = false; + tm& Tm = GImPlot->Tm; + + const int min_yr = 1970; + const int max_yr = 2999; + + // t1 parts + int t1_mo = 0; int t1_md = 0; int t1_yr = 0; + if (t1 != nullptr) { + GetTime(*t1,&Tm); + t1_mo = Tm.tm_mon; + t1_md = Tm.tm_mday; + t1_yr = Tm.tm_year + 1900; + } + + // t2 parts + int t2_mo = 0; int t2_md = 0; int t2_yr = 0; + if (t2 != nullptr) { + GetTime(*t2,&Tm); + t2_mo = Tm.tm_mon; + t2_md = Tm.tm_mday; + t2_yr = Tm.tm_year + 1900; + } + + // day widget + if (*level == 0) { + *t = FloorTime(*t, ImPlotTimeUnit_Day); + GetTime(*t, &Tm); + const int this_year = Tm.tm_year + 1900; + const int last_year = this_year - 1; + const int next_year = this_year + 1; + const int this_mon = Tm.tm_mon; + const int last_mon = this_mon == 0 ? 11 : this_mon - 1; + const int next_mon = this_mon == 11 ? 0 : this_mon + 1; + const int days_this_mo = GetDaysInMonth(this_year, this_mon); + const int days_last_mo = GetDaysInMonth(this_mon == 0 ? last_year : this_year, last_mon); + ImPlotTime t_first_mo = FloorTime(*t,ImPlotTimeUnit_Mo); + GetTime(t_first_mo,&Tm); + const int first_wd = Tm.tm_wday; + // month year + ImFormatString(buff, 32, "%s %d", MONTH_NAMES[this_mon], this_year); + if (ImGui::Button(buff)) + *level = 1; + ImGui::SameLine(5*cell_size.x); + BeginDisabledControls(this_year <= min_yr && this_mon == 0); + if (ImGui::ArrowButtonEx("##Up",ImGuiDir_Up,cell_size)) + *t = AddTime(*t, ImPlotTimeUnit_Mo, -1); + EndDisabledControls(this_year <= min_yr && this_mon == 0); + ImGui::SameLine(); + BeginDisabledControls(this_year >= max_yr && this_mon == 11); + if (ImGui::ArrowButtonEx("##Down",ImGuiDir_Down,cell_size)) + *t = AddTime(*t, ImPlotTimeUnit_Mo, 1); + EndDisabledControls(this_year >= max_yr && this_mon == 11); + // render weekday abbreviations + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + for (int i = 0; i < 7; ++i) { + ImGui::Button(WD_ABRVS[i],cell_size); + if (i != 6) { ImGui::SameLine(); } + } + ImGui::PopItemFlag(); + // 0 = last mo, 1 = this mo, 2 = next mo + int mo = first_wd > 0 ? 0 : 1; + int day = mo == 1 ? 1 : days_last_mo - first_wd + 1; + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 7; ++j) { + if (mo == 0 && day > days_last_mo) { + mo = 1; + day = 1; + } + else if (mo == 1 && day > days_this_mo) { + mo = 2; + day = 1; + } + const int now_yr = (mo == 0 && this_mon == 0) ? last_year : ((mo == 2 && this_mon == 11) ? next_year : this_year); + const int now_mo = mo == 0 ? last_mon : (mo == 1 ? this_mon : next_mon); + const int now_md = day; + + const bool off_mo = mo == 0 || mo == 2; + const bool t1_or_t2 = (t1 != nullptr && t1_mo == now_mo && t1_yr == now_yr && t1_md == now_md) || + (t2 != nullptr && t2_mo == now_mo && t2_yr == now_yr && t2_md == now_md); + + if (off_mo) + ImGui::PushStyleColor(ImGuiCol_Text, col_dis); + if (t1_or_t2) { + ImGui::PushStyleColor(ImGuiCol_Button, col_btn); + ImGui::PushStyleColor(ImGuiCol_Text, col_txt); + } + ImGui::PushID(i*7+j); + ImFormatString(buff,32,"%d",day); + if (now_yr == min_yr-1 || now_yr == max_yr+1) { + ImGui::Dummy(cell_size); + } + else if (ImGui::Button(buff,cell_size) && !clk) { + *t = MakeTime(now_yr, now_mo, now_md); + clk = true; + } + ImGui::PopID(); + if (t1_or_t2) + ImGui::PopStyleColor(2); + if (off_mo) + ImGui::PopStyleColor(); + if (j != 6) + ImGui::SameLine(); + day++; + } + } + } + // month widget + else if (*level == 1) { + *t = FloorTime(*t, ImPlotTimeUnit_Mo); + GetTime(*t, &Tm); + int this_yr = Tm.tm_year + 1900; + ImFormatString(buff, 32, "%d", this_yr); + if (ImGui::Button(buff)) + *level = 2; + BeginDisabledControls(this_yr <= min_yr); + ImGui::SameLine(5*cell_size.x); + if (ImGui::ArrowButtonEx("##Up",ImGuiDir_Up,cell_size)) + *t = AddTime(*t, ImPlotTimeUnit_Yr, -1); + EndDisabledControls(this_yr <= min_yr); + ImGui::SameLine(); + BeginDisabledControls(this_yr >= max_yr); + if (ImGui::ArrowButtonEx("##Down",ImGuiDir_Down,cell_size)) + *t = AddTime(*t, ImPlotTimeUnit_Yr, 1); + EndDisabledControls(this_yr >= max_yr); + // ImGui::Dummy(cell_size); + cell_size.x *= 7.0f/4.0f; + cell_size.y *= 7.0f/3.0f; + int mo = 0; + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 4; ++j) { + const bool t1_or_t2 = (t1 != nullptr && t1_yr == this_yr && t1_mo == mo) || + (t2 != nullptr && t2_yr == this_yr && t2_mo == mo); + if (t1_or_t2) + ImGui::PushStyleColor(ImGuiCol_Button, col_btn); + if (ImGui::Button(MONTH_ABRVS[mo],cell_size) && !clk) { + *t = MakeTime(this_yr, mo); + *level = 0; + } + if (t1_or_t2) + ImGui::PopStyleColor(); + if (j != 3) + ImGui::SameLine(); + mo++; + } + } + } + else if (*level == 2) { + *t = FloorTime(*t, ImPlotTimeUnit_Yr); + int this_yr = GetYear(*t); + int yr = this_yr - this_yr % 20; + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImFormatString(buff,32,"%d-%d",yr,yr+19); + ImGui::Button(buff); + ImGui::PopItemFlag(); + ImGui::SameLine(5*cell_size.x); + BeginDisabledControls(yr <= min_yr); + if (ImGui::ArrowButtonEx("##Up",ImGuiDir_Up,cell_size)) + *t = MakeTime(yr-20); + EndDisabledControls(yr <= min_yr); + ImGui::SameLine(); + BeginDisabledControls(yr + 20 >= max_yr); + if (ImGui::ArrowButtonEx("##Down",ImGuiDir_Down,cell_size)) + *t = MakeTime(yr+20); + EndDisabledControls(yr+ 20 >= max_yr); + // ImGui::Dummy(cell_size); + cell_size.x *= 7.0f/4.0f; + cell_size.y *= 7.0f/5.0f; + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 4; ++j) { + const bool t1_or_t2 = (t1 != nullptr && t1_yr == yr) || (t2 != nullptr && t2_yr == yr); + if (t1_or_t2) + ImGui::PushStyleColor(ImGuiCol_Button, col_btn); + ImFormatString(buff,32,"%d",yr); + if (yr<1970||yr>3000) { + ImGui::Dummy(cell_size); + } + else if (ImGui::Button(buff,cell_size)) { + *t = MakeTime(yr); + *level = 1; + } + if (t1_or_t2) + ImGui::PopStyleColor(); + if (j != 3) + ImGui::SameLine(); + yr++; + } + } + } + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + ImGui::EndGroup(); + ImGui::PopID(); + return clk; +} + +bool ShowTimePicker(const char* id, ImPlotTime* t) { + ImPlotContext& gp = *GImPlot; + ImGui::PushID(id); + tm& Tm = gp.Tm; + GetTime(*t,&Tm); + + static const char* nums[] = { "00","01","02","03","04","05","06","07","08","09", + "10","11","12","13","14","15","16","17","18","19", + "20","21","22","23","24","25","26","27","28","29", + "30","31","32","33","34","35","36","37","38","39", + "40","41","42","43","44","45","46","47","48","49", + "50","51","52","53","54","55","56","57","58","59"}; + + static const char* am_pm[] = {"am","pm"}; + + bool hour24 = gp.Style.Use24HourClock; + + int hr = hour24 ? Tm.tm_hour : ((Tm.tm_hour == 0 || Tm.tm_hour == 12) ? 12 : Tm.tm_hour % 12); + int min = Tm.tm_min; + int sec = Tm.tm_sec; + int ap = Tm.tm_hour < 12 ? 0 : 1; + + bool changed = false; + + ImVec2 spacing = ImGui::GetStyle().ItemSpacing; + spacing.x = 0; + float width = ImGui::CalcTextSize("888").x; + float height = ImGui::GetFrameHeight(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, spacing); + ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize,2.0f); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered)); + + ImGui::SetNextItemWidth(width); + if (ImGui::BeginCombo("##hr",nums[hr],ImGuiComboFlags_NoArrowButton)) { + const int ia = hour24 ? 0 : 1; + const int ib = hour24 ? 24 : 13; + for (int i = ia; i < ib; ++i) { + if (ImGui::Selectable(nums[i],i==hr)) { + hr = i; + changed = true; + } + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + ImGui::Text(":"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(width); + if (ImGui::BeginCombo("##min",nums[min],ImGuiComboFlags_NoArrowButton)) { + for (int i = 0; i < 60; ++i) { + if (ImGui::Selectable(nums[i],i==min)) { + min = i; + changed = true; + } + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + ImGui::Text(":"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(width); + if (ImGui::BeginCombo("##sec",nums[sec],ImGuiComboFlags_NoArrowButton)) { + for (int i = 0; i < 60; ++i) { + if (ImGui::Selectable(nums[i],i==sec)) { + sec = i; + changed = true; + } + } + ImGui::EndCombo(); + } + if (!hour24) { + ImGui::SameLine(); + if (ImGui::Button(am_pm[ap],ImVec2(0,height))) { + ap = 1 - ap; + changed = true; + } + } + + ImGui::PopStyleColor(3); + ImGui::PopStyleVar(2); + ImGui::PopID(); + + if (changed) { + if (!hour24) + hr = hr % 12 + ap * 12; + Tm.tm_hour = hr; + Tm.tm_min = min; + Tm.tm_sec = sec; + *t = MkTime(&Tm); + } + + return changed; +} + +void StyleColorsAuto(ImPlotStyle* dst) { + ImPlotStyle* style = dst ? dst : &ImPlot::GetStyle(); + ImVec4* colors = style->Colors; + + style->MinorAlpha = 0.25f; + + colors[ImPlotCol_Line] = IMPLOT_AUTO_COL; + colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_ErrorBar] = IMPLOT_AUTO_COL; + colors[ImPlotCol_FrameBg] = IMPLOT_AUTO_COL; + colors[ImPlotCol_PlotBg] = IMPLOT_AUTO_COL; + colors[ImPlotCol_PlotBorder] = IMPLOT_AUTO_COL; + colors[ImPlotCol_LegendBg] = IMPLOT_AUTO_COL; + colors[ImPlotCol_LegendBorder] = IMPLOT_AUTO_COL; + colors[ImPlotCol_LegendText] = IMPLOT_AUTO_COL; + colors[ImPlotCol_TitleText] = IMPLOT_AUTO_COL; + colors[ImPlotCol_InlayText] = IMPLOT_AUTO_COL; + colors[ImPlotCol_PlotBorder] = IMPLOT_AUTO_COL; + colors[ImPlotCol_AxisText] = IMPLOT_AUTO_COL; + colors[ImPlotCol_AxisGrid] = IMPLOT_AUTO_COL; + colors[ImPlotCol_AxisTick] = IMPLOT_AUTO_COL; + colors[ImPlotCol_AxisBg] = IMPLOT_AUTO_COL; + colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL; + colors[ImPlotCol_AxisBgActive] = IMPLOT_AUTO_COL; + colors[ImPlotCol_Selection] = IMPLOT_AUTO_COL; + colors[ImPlotCol_Crosshairs] = IMPLOT_AUTO_COL; +} + +void StyleColorsClassic(ImPlotStyle* dst) { + ImPlotStyle* style = dst ? dst : &ImPlot::GetStyle(); + ImVec4* colors = style->Colors; + + style->MinorAlpha = 0.5f; + + colors[ImPlotCol_Line] = IMPLOT_AUTO_COL; + colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_ErrorBar] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImPlotCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f); + colors[ImPlotCol_PlotBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.35f); + colors[ImPlotCol_PlotBorder] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f); + colors[ImPlotCol_LegendBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f); + colors[ImPlotCol_LegendBorder] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f); + colors[ImPlotCol_LegendText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImPlotCol_TitleText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImPlotCol_InlayText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImPlotCol_AxisText] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImPlotCol_AxisGrid] = ImVec4(0.90f, 0.90f, 0.90f, 0.25f); + colors[ImPlotCol_AxisTick] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBg] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBgActive] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_Selection] = ImVec4(0.97f, 0.97f, 0.39f, 1.00f); + colors[ImPlotCol_Crosshairs] = ImVec4(0.50f, 0.50f, 0.50f, 0.75f); +} + +void StyleColorsDark(ImPlotStyle* dst) { + ImPlotStyle* style = dst ? dst : &ImPlot::GetStyle(); + ImVec4* colors = style->Colors; + + style->MinorAlpha = 0.25f; + + colors[ImPlotCol_Line] = IMPLOT_AUTO_COL; + colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_ErrorBar] = IMPLOT_AUTO_COL; + colors[ImPlotCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f); + colors[ImPlotCol_PlotBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.50f); + colors[ImPlotCol_PlotBorder] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); + colors[ImPlotCol_LegendBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); + colors[ImPlotCol_LegendBorder] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); + colors[ImPlotCol_LegendText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_TitleText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_InlayText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_AxisText] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_AxisGrid] = ImVec4(1.00f, 1.00f, 1.00f, 0.25f); + colors[ImPlotCol_AxisTick] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBg] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBgActive] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_Selection] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImPlotCol_Crosshairs] = ImVec4(1.00f, 1.00f, 1.00f, 0.50f); +} + +void StyleColorsLight(ImPlotStyle* dst) { + ImPlotStyle* style = dst ? dst : &ImPlot::GetStyle(); + ImVec4* colors = style->Colors; + + style->MinorAlpha = 1.0f; + + colors[ImPlotCol_Line] = IMPLOT_AUTO_COL; + colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_ErrorBar] = IMPLOT_AUTO_COL; + colors[ImPlotCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_PlotBg] = ImVec4(0.42f, 0.57f, 1.00f, 0.13f); + colors[ImPlotCol_PlotBorder] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImPlotCol_LegendBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.98f); + colors[ImPlotCol_LegendBorder] = ImVec4(0.82f, 0.82f, 0.82f, 0.80f); + colors[ImPlotCol_LegendText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_TitleText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_InlayText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_AxisText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_AxisGrid] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_AxisTick] = ImVec4(0.00f, 0.00f, 0.00f, 0.25f); + colors[ImPlotCol_AxisBg] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_AxisBgActive] = IMPLOT_AUTO_COL; // TODO + colors[ImPlotCol_Selection] = ImVec4(0.82f, 0.64f, 0.03f, 1.00f); + colors[ImPlotCol_Crosshairs] = ImVec4(0.00f, 0.00f, 0.00f, 0.50f); +} + +//----------------------------------------------------------------------------- +// [SECTION] Obsolete Functions/Types +//----------------------------------------------------------------------------- + +#ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS + +bool BeginPlot(const char* title, const char* x_label, const char* y1_label, const ImVec2& size, + ImPlotFlags flags, ImPlotAxisFlags x_flags, ImPlotAxisFlags y1_flags, ImPlotAxisFlags y2_flags, ImPlotAxisFlags y3_flags, + const char* y2_label, const char* y3_label) +{ + if (!BeginPlot(title, size, flags)) + return false; + SetupAxis(ImAxis_X1, x_label, x_flags); + SetupAxis(ImAxis_Y1, y1_label, y1_flags); + if (ImHasFlag(flags, ImPlotFlags_YAxis2)) + SetupAxis(ImAxis_Y2, y2_label, y2_flags); + if (ImHasFlag(flags, ImPlotFlags_YAxis3)) + SetupAxis(ImAxis_Y3, y3_label, y3_flags); + return true; +} + +#endif + +} // namespace ImPlot diff --git a/libs/implot/implot.h b/libs/implot/implot.h new file mode 100644 index 0000000..3054331 --- /dev/null +++ b/libs/implot/implot.h @@ -0,0 +1,1297 @@ +// MIT License + +// Copyright (c) 2023 Evan Pezent + +// 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. + +// ImPlot v0.17 + +// Table of Contents: +// +// [SECTION] Macros and Defines +// [SECTION] Enums and Types +// [SECTION] Callbacks +// [SECTION] Contexts +// [SECTION] Begin/End Plot +// [SECTION] Begin/End Subplot +// [SECTION] Setup +// [SECTION] SetNext +// [SECTION] Plot Items +// [SECTION] Plot Tools +// [SECTION] Plot Utils +// [SECTION] Legend Utils +// [SECTION] Drag and Drop +// [SECTION] Styling +// [SECTION] Colormaps +// [SECTION] Input Mapping +// [SECTION] Miscellaneous +// [SECTION] Demo +// [SECTION] Obsolete API + +#pragma once +#include "imgui.h" + +//----------------------------------------------------------------------------- +// [SECTION] Macros and Defines +//----------------------------------------------------------------------------- + +// Define attributes of all API symbols declarations (e.g. for DLL under Windows) +// Using ImPlot via a shared library is not recommended, because we don't guarantee +// backward nor forward ABI compatibility and also function call overhead. If you +// do use ImPlot as a DLL, be sure to call SetImGuiContext (see Miscellanous section). +#ifndef IMPLOT_API +#define IMPLOT_API +#endif + +// ImPlot version string. +#define IMPLOT_VERSION "0.17" +// Indicates variable should deduced automatically. +#define IMPLOT_AUTO -1 +// Special color used to indicate that a color should be deduced automatically. +#define IMPLOT_AUTO_COL ImVec4(0,0,0,-1) +// Macro for templated plotting functions; keeps header clean. +#define IMPLOT_TMP template IMPLOT_API + +//----------------------------------------------------------------------------- +// [SECTION] Enums and Types +//----------------------------------------------------------------------------- + +// Forward declarations +struct ImPlotContext; // ImPlot context (opaque struct, see implot_internal.h) + +// Enums/Flags +typedef int ImAxis; // -> enum ImAxis_ +typedef int ImPlotFlags; // -> enum ImPlotFlags_ +typedef int ImPlotAxisFlags; // -> enum ImPlotAxisFlags_ +typedef int ImPlotSubplotFlags; // -> enum ImPlotSubplotFlags_ +typedef int ImPlotLegendFlags; // -> enum ImPlotLegendFlags_ +typedef int ImPlotMouseTextFlags; // -> enum ImPlotMouseTextFlags_ +typedef int ImPlotDragToolFlags; // -> ImPlotDragToolFlags_ +typedef int ImPlotColormapScaleFlags; // -> ImPlotColormapScaleFlags_ + +typedef int ImPlotItemFlags; // -> ImPlotItemFlags_ +typedef int ImPlotLineFlags; // -> ImPlotLineFlags_ +typedef int ImPlotScatterFlags; // -> ImPlotScatterFlags +typedef int ImPlotStairsFlags; // -> ImPlotStairsFlags_ +typedef int ImPlotShadedFlags; // -> ImPlotShadedFlags_ +typedef int ImPlotBarsFlags; // -> ImPlotBarsFlags_ +typedef int ImPlotBarGroupsFlags; // -> ImPlotBarGroupsFlags_ +typedef int ImPlotErrorBarsFlags; // -> ImPlotErrorBarsFlags_ +typedef int ImPlotStemsFlags; // -> ImPlotStemsFlags_ +typedef int ImPlotInfLinesFlags; // -> ImPlotInfLinesFlags_ +typedef int ImPlotPieChartFlags; // -> ImPlotPieChartFlags_ +typedef int ImPlotHeatmapFlags; // -> ImPlotHeatmapFlags_ +typedef int ImPlotHistogramFlags; // -> ImPlotHistogramFlags_ +typedef int ImPlotDigitalFlags; // -> ImPlotDigitalFlags_ +typedef int ImPlotImageFlags; // -> ImPlotImageFlags_ +typedef int ImPlotTextFlags; // -> ImPlotTextFlags_ +typedef int ImPlotDummyFlags; // -> ImPlotDummyFlags_ + +typedef int ImPlotCond; // -> enum ImPlotCond_ +typedef int ImPlotCol; // -> enum ImPlotCol_ +typedef int ImPlotStyleVar; // -> enum ImPlotStyleVar_ +typedef int ImPlotScale; // -> enum ImPlotScale_ +typedef int ImPlotMarker; // -> enum ImPlotMarker_ +typedef int ImPlotColormap; // -> enum ImPlotColormap_ +typedef int ImPlotLocation; // -> enum ImPlotLocation_ +typedef int ImPlotBin; // -> enum ImPlotBin_ + +// Axis indices. The values assigned may change; NEVER hardcode these. +enum ImAxis_ { + // horizontal axes + ImAxis_X1 = 0, // enabled by default + ImAxis_X2, // disabled by default + ImAxis_X3, // disabled by default + // vertical axes + ImAxis_Y1, // enabled by default + ImAxis_Y2, // disabled by default + ImAxis_Y3, // disabled by default + // bookeeping + ImAxis_COUNT +}; + +// Options for plots (see BeginPlot). +enum ImPlotFlags_ { + ImPlotFlags_None = 0, // default + ImPlotFlags_NoTitle = 1 << 0, // the plot title will not be displayed (titles are also hidden if preceeded by double hashes, e.g. "##MyPlot") + ImPlotFlags_NoLegend = 1 << 1, // the legend will not be displayed + ImPlotFlags_NoMouseText = 1 << 2, // the mouse position, in plot coordinates, will not be displayed inside of the plot + ImPlotFlags_NoInputs = 1 << 3, // the user will not be able to interact with the plot + ImPlotFlags_NoMenus = 1 << 4, // the user will not be able to open context menus + ImPlotFlags_NoBoxSelect = 1 << 5, // the user will not be able to box-select + ImPlotFlags_NoFrame = 1 << 6, // the ImGui frame will not be rendered + ImPlotFlags_Equal = 1 << 7, // x and y axes pairs will be constrained to have the same units/pixel + ImPlotFlags_Crosshairs = 1 << 8, // the default mouse cursor will be replaced with a crosshair when hovered + ImPlotFlags_CanvasOnly = ImPlotFlags_NoTitle | ImPlotFlags_NoLegend | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText +}; + +// Options for plot axes (see SetupAxis). +enum ImPlotAxisFlags_ { + ImPlotAxisFlags_None = 0, // default + ImPlotAxisFlags_NoLabel = 1 << 0, // the axis label will not be displayed (axis labels are also hidden if the supplied string name is nullptr) + ImPlotAxisFlags_NoGridLines = 1 << 1, // no grid lines will be displayed + ImPlotAxisFlags_NoTickMarks = 1 << 2, // no tick marks will be displayed + ImPlotAxisFlags_NoTickLabels = 1 << 3, // no text labels will be displayed + ImPlotAxisFlags_NoInitialFit = 1 << 4, // axis will not be initially fit to data extents on the first rendered frame + ImPlotAxisFlags_NoMenus = 1 << 5, // the user will not be able to open context menus with right-click + ImPlotAxisFlags_NoSideSwitch = 1 << 6, // the user will not be able to switch the axis side by dragging it + ImPlotAxisFlags_NoHighlight = 1 << 7, // the axis will not have its background highlighted when hovered or held + ImPlotAxisFlags_Opposite = 1 << 8, // axis ticks and labels will be rendered on the conventionally opposite side (i.e, right or top) + ImPlotAxisFlags_Foreground = 1 << 9, // grid lines will be displayed in the foreground (i.e. on top of data) instead of the background + ImPlotAxisFlags_Invert = 1 << 10, // the axis will be inverted + ImPlotAxisFlags_AutoFit = 1 << 11, // axis will be auto-fitting to data extents + ImPlotAxisFlags_RangeFit = 1 << 12, // axis will only fit points if the point is in the visible range of the **orthogonal** axis + ImPlotAxisFlags_PanStretch = 1 << 13, // panning in a locked or constrained state will cause the axis to stretch if possible + ImPlotAxisFlags_LockMin = 1 << 14, // the axis minimum value will be locked when panning/zooming + ImPlotAxisFlags_LockMax = 1 << 15, // the axis maximum value will be locked when panning/zooming + ImPlotAxisFlags_Lock = ImPlotAxisFlags_LockMin | ImPlotAxisFlags_LockMax, + ImPlotAxisFlags_NoDecorations = ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoTickMarks | ImPlotAxisFlags_NoTickLabels, + ImPlotAxisFlags_AuxDefault = ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_Opposite +}; + +// Options for subplots (see BeginSubplot) +enum ImPlotSubplotFlags_ { + ImPlotSubplotFlags_None = 0, // default + ImPlotSubplotFlags_NoTitle = 1 << 0, // the subplot title will not be displayed (titles are also hidden if preceeded by double hashes, e.g. "##MySubplot") + ImPlotSubplotFlags_NoLegend = 1 << 1, // the legend will not be displayed (only applicable if ImPlotSubplotFlags_ShareItems is enabled) + ImPlotSubplotFlags_NoMenus = 1 << 2, // the user will not be able to open context menus with right-click + ImPlotSubplotFlags_NoResize = 1 << 3, // resize splitters between subplot cells will be not be provided + ImPlotSubplotFlags_NoAlign = 1 << 4, // subplot edges will not be aligned vertically or horizontally + ImPlotSubplotFlags_ShareItems = 1 << 5, // items across all subplots will be shared and rendered into a single legend entry + ImPlotSubplotFlags_LinkRows = 1 << 6, // link the y-axis limits of all plots in each row (does not apply to auxiliary axes) + ImPlotSubplotFlags_LinkCols = 1 << 7, // link the x-axis limits of all plots in each column (does not apply to auxiliary axes) + ImPlotSubplotFlags_LinkAllX = 1 << 8, // link the x-axis limits in every plot in the subplot (does not apply to auxiliary axes) + ImPlotSubplotFlags_LinkAllY = 1 << 9, // link the y-axis limits in every plot in the subplot (does not apply to auxiliary axes) + ImPlotSubplotFlags_ColMajor = 1 << 10 // subplots are added in column major order instead of the default row major order +}; + +// Options for legends (see SetupLegend) +enum ImPlotLegendFlags_ { + ImPlotLegendFlags_None = 0, // default + ImPlotLegendFlags_NoButtons = 1 << 0, // legend icons will not function as hide/show buttons + ImPlotLegendFlags_NoHighlightItem = 1 << 1, // plot items will not be highlighted when their legend entry is hovered + ImPlotLegendFlags_NoHighlightAxis = 1 << 2, // axes will not be highlighted when legend entries are hovered (only relevant if x/y-axis count > 1) + ImPlotLegendFlags_NoMenus = 1 << 3, // the user will not be able to open context menus with right-click + ImPlotLegendFlags_Outside = 1 << 4, // legend will be rendered outside of the plot area + ImPlotLegendFlags_Horizontal = 1 << 5, // legend entries will be displayed horizontally + ImPlotLegendFlags_Sort = 1 << 6, // legend entries will be displayed in alphabetical order +}; + +// Options for mouse hover text (see SetupMouseText) +enum ImPlotMouseTextFlags_ { + ImPlotMouseTextFlags_None = 0, // default + ImPlotMouseTextFlags_NoAuxAxes = 1 << 0, // only show the mouse position for primary axes + ImPlotMouseTextFlags_NoFormat = 1 << 1, // axes label formatters won't be used to render text + ImPlotMouseTextFlags_ShowAlways = 1 << 2, // always display mouse position even if plot not hovered +}; + +// Options for DragPoint, DragLine, DragRect +enum ImPlotDragToolFlags_ { + ImPlotDragToolFlags_None = 0, // default + ImPlotDragToolFlags_NoCursors = 1 << 0, // drag tools won't change cursor icons when hovered or held + ImPlotDragToolFlags_NoFit = 1 << 1, // the drag tool won't be considered for plot fits + ImPlotDragToolFlags_NoInputs = 1 << 2, // lock the tool from user inputs + ImPlotDragToolFlags_Delayed = 1 << 3, // tool rendering will be delayed one frame; useful when applying position-constraints +}; + +// Flags for ColormapScale +enum ImPlotColormapScaleFlags_ { + ImPlotColormapScaleFlags_None = 0, // default + ImPlotColormapScaleFlags_NoLabel = 1 << 0, // the colormap axis label will not be displayed + ImPlotColormapScaleFlags_Opposite = 1 << 1, // render the colormap label and tick labels on the opposite side + ImPlotColormapScaleFlags_Invert = 1 << 2, // invert the colormap bar and axis scale (this only affects rendering; if you only want to reverse the scale mapping, make scale_min > scale_max) +}; + +// Flags for ANY PlotX function +enum ImPlotItemFlags_ { + ImPlotItemFlags_None = 0, + ImPlotItemFlags_NoLegend = 1 << 0, // the item won't have a legend entry displayed + ImPlotItemFlags_NoFit = 1 << 1, // the item won't be considered for plot fits +}; + +// Flags for PlotLine +enum ImPlotLineFlags_ { + ImPlotLineFlags_None = 0, // default + ImPlotLineFlags_Segments = 1 << 10, // a line segment will be rendered from every two consecutive points + ImPlotLineFlags_Loop = 1 << 11, // the last and first point will be connected to form a closed loop + ImPlotLineFlags_SkipNaN = 1 << 12, // NaNs values will be skipped instead of rendered as missing data + ImPlotLineFlags_NoClip = 1 << 13, // markers (if displayed) on the edge of a plot will not be clipped + ImPlotLineFlags_Shaded = 1 << 14, // a filled region between the line and horizontal origin will be rendered; use PlotShaded for more advanced cases +}; + +// Flags for PlotScatter +enum ImPlotScatterFlags_ { + ImPlotScatterFlags_None = 0, // default + ImPlotScatterFlags_NoClip = 1 << 10, // markers on the edge of a plot will not be clipped +}; + +// Flags for PlotStairs +enum ImPlotStairsFlags_ { + ImPlotStairsFlags_None = 0, // default + ImPlotStairsFlags_PreStep = 1 << 10, // the y value is continued constantly to the left from every x position, i.e. the interval (x[i-1], x[i]] has the value y[i] + ImPlotStairsFlags_Shaded = 1 << 11 // a filled region between the stairs and horizontal origin will be rendered; use PlotShaded for more advanced cases +}; + +// Flags for PlotShaded (placeholder) +enum ImPlotShadedFlags_ { + ImPlotShadedFlags_None = 0 // default +}; + +// Flags for PlotBars +enum ImPlotBarsFlags_ { + ImPlotBarsFlags_None = 0, // default + ImPlotBarsFlags_Horizontal = 1 << 10, // bars will be rendered horizontally on the current y-axis +}; + +// Flags for PlotBarGroups +enum ImPlotBarGroupsFlags_ { + ImPlotBarGroupsFlags_None = 0, // default + ImPlotBarGroupsFlags_Horizontal = 1 << 10, // bar groups will be rendered horizontally on the current y-axis + ImPlotBarGroupsFlags_Stacked = 1 << 11, // items in a group will be stacked on top of each other +}; + +// Flags for PlotErrorBars +enum ImPlotErrorBarsFlags_ { + ImPlotErrorBarsFlags_None = 0, // default + ImPlotErrorBarsFlags_Horizontal = 1 << 10, // error bars will be rendered horizontally on the current y-axis +}; + +// Flags for PlotStems +enum ImPlotStemsFlags_ { + ImPlotStemsFlags_None = 0, // default + ImPlotStemsFlags_Horizontal = 1 << 10, // stems will be rendered horizontally on the current y-axis +}; + +// Flags for PlotInfLines +enum ImPlotInfLinesFlags_ { + ImPlotInfLinesFlags_None = 0, // default + ImPlotInfLinesFlags_Horizontal = 1 << 10 // lines will be rendered horizontally on the current y-axis +}; + +// Flags for PlotPieChart +enum ImPlotPieChartFlags_ { + ImPlotPieChartFlags_None = 0, // default + ImPlotPieChartFlags_Normalize = 1 << 10, // force normalization of pie chart values (i.e. always make a full circle if sum < 0) + ImPlotPieChartFlags_IgnoreHidden = 1 << 11 // ignore hidden slices when drawing the pie chart (as if they were not there) +}; + +// Flags for PlotHeatmap +enum ImPlotHeatmapFlags_ { + ImPlotHeatmapFlags_None = 0, // default + ImPlotHeatmapFlags_ColMajor = 1 << 10, // data will be read in column major order +}; + +// Flags for PlotHistogram and PlotHistogram2D +enum ImPlotHistogramFlags_ { + ImPlotHistogramFlags_None = 0, // default + ImPlotHistogramFlags_Horizontal = 1 << 10, // histogram bars will be rendered horizontally (not supported by PlotHistogram2D) + ImPlotHistogramFlags_Cumulative = 1 << 11, // each bin will contain its count plus the counts of all previous bins (not supported by PlotHistogram2D) + ImPlotHistogramFlags_Density = 1 << 12, // counts will be normalized, i.e. the PDF will be visualized, or the CDF will be visualized if Cumulative is also set + ImPlotHistogramFlags_NoOutliers = 1 << 13, // exclude values outside the specifed histogram range from the count toward normalizing and cumulative counts + ImPlotHistogramFlags_ColMajor = 1 << 14 // data will be read in column major order (not supported by PlotHistogram) +}; + +// Flags for PlotDigital (placeholder) +enum ImPlotDigitalFlags_ { + ImPlotDigitalFlags_None = 0 // default +}; + +// Flags for PlotImage (placeholder) +enum ImPlotImageFlags_ { + ImPlotImageFlags_None = 0 // default +}; + +// Flags for PlotText +enum ImPlotTextFlags_ { + ImPlotTextFlags_None = 0, // default + ImPlotTextFlags_Vertical = 1 << 10 // text will be rendered vertically +}; + +// Flags for PlotDummy (placeholder) +enum ImPlotDummyFlags_ { + ImPlotDummyFlags_None = 0 // default +}; + +// Represents a condition for SetupAxisLimits etc. (same as ImGuiCond, but we only support a subset of those enums) +enum ImPlotCond_ +{ + ImPlotCond_None = ImGuiCond_None, // No condition (always set the variable), same as _Always + ImPlotCond_Always = ImGuiCond_Always, // No condition (always set the variable) + ImPlotCond_Once = ImGuiCond_Once, // Set the variable once per runtime session (only the first call will succeed) +}; + +// Plot styling colors. +enum ImPlotCol_ { + // item styling colors + ImPlotCol_Line, // plot line/outline color (defaults to next unused color in current colormap) + ImPlotCol_Fill, // plot fill color for bars (defaults to the current line color) + ImPlotCol_MarkerOutline, // marker outline color (defaults to the current line color) + ImPlotCol_MarkerFill, // marker fill color (defaults to the current line color) + ImPlotCol_ErrorBar, // error bar color (defaults to ImGuiCol_Text) + // plot styling colors + ImPlotCol_FrameBg, // plot frame background color (defaults to ImGuiCol_FrameBg) + ImPlotCol_PlotBg, // plot area background color (defaults to ImGuiCol_WindowBg) + ImPlotCol_PlotBorder, // plot area border color (defaults to ImGuiCol_Border) + ImPlotCol_LegendBg, // legend background color (defaults to ImGuiCol_PopupBg) + ImPlotCol_LegendBorder, // legend border color (defaults to ImPlotCol_PlotBorder) + ImPlotCol_LegendText, // legend text color (defaults to ImPlotCol_InlayText) + ImPlotCol_TitleText, // plot title text color (defaults to ImGuiCol_Text) + ImPlotCol_InlayText, // color of text appearing inside of plots (defaults to ImGuiCol_Text) + ImPlotCol_AxisText, // axis label and tick lables color (defaults to ImGuiCol_Text) + ImPlotCol_AxisGrid, // axis grid color (defaults to 25% ImPlotCol_AxisText) + ImPlotCol_AxisTick, // axis tick color (defaults to AxisGrid) + ImPlotCol_AxisBg, // background color of axis hover region (defaults to transparent) + ImPlotCol_AxisBgHovered, // axis hover color (defaults to ImGuiCol_ButtonHovered) + ImPlotCol_AxisBgActive, // axis active color (defaults to ImGuiCol_ButtonActive) + ImPlotCol_Selection, // box-selection color (defaults to yellow) + ImPlotCol_Crosshairs, // crosshairs color (defaults to ImPlotCol_PlotBorder) + ImPlotCol_COUNT +}; + +// Plot styling variables. +enum ImPlotStyleVar_ { + // item styling variables + ImPlotStyleVar_LineWeight, // float, plot item line weight in pixels + ImPlotStyleVar_Marker, // int, marker specification + ImPlotStyleVar_MarkerSize, // float, marker size in pixels (roughly the marker's "radius") + ImPlotStyleVar_MarkerWeight, // float, plot outline weight of markers in pixels + ImPlotStyleVar_FillAlpha, // float, alpha modifier applied to all plot item fills + ImPlotStyleVar_ErrorBarSize, // float, error bar whisker width in pixels + ImPlotStyleVar_ErrorBarWeight, // float, error bar whisker weight in pixels + ImPlotStyleVar_DigitalBitHeight, // float, digital channels bit height (at 1) in pixels + ImPlotStyleVar_DigitalBitGap, // float, digital channels bit padding gap in pixels + // plot styling variables + ImPlotStyleVar_PlotBorderSize, // float, thickness of border around plot area + ImPlotStyleVar_MinorAlpha, // float, alpha multiplier applied to minor axis grid lines + ImPlotStyleVar_MajorTickLen, // ImVec2, major tick lengths for X and Y axes + ImPlotStyleVar_MinorTickLen, // ImVec2, minor tick lengths for X and Y axes + ImPlotStyleVar_MajorTickSize, // ImVec2, line thickness of major ticks + ImPlotStyleVar_MinorTickSize, // ImVec2, line thickness of minor ticks + ImPlotStyleVar_MajorGridSize, // ImVec2, line thickness of major grid lines + ImPlotStyleVar_MinorGridSize, // ImVec2, line thickness of minor grid lines + ImPlotStyleVar_PlotPadding, // ImVec2, padding between widget frame and plot area, labels, or outside legends (i.e. main padding) + ImPlotStyleVar_LabelPadding, // ImVec2, padding between axes labels, tick labels, and plot edge + ImPlotStyleVar_LegendPadding, // ImVec2, legend padding from plot edges + ImPlotStyleVar_LegendInnerPadding, // ImVec2, legend inner padding from legend edges + ImPlotStyleVar_LegendSpacing, // ImVec2, spacing between legend entries + ImPlotStyleVar_MousePosPadding, // ImVec2, padding between plot edge and interior info text + ImPlotStyleVar_AnnotationPadding, // ImVec2, text padding around annotation labels + ImPlotStyleVar_FitPadding, // ImVec2, additional fit padding as a percentage of the fit extents (e.g. ImVec2(0.1f,0.1f) adds 10% to the fit extents of X and Y) + ImPlotStyleVar_PlotDefaultSize, // ImVec2, default size used when ImVec2(0,0) is passed to BeginPlot + ImPlotStyleVar_PlotMinSize, // ImVec2, minimum size plot frame can be when shrunk + ImPlotStyleVar_COUNT +}; + +// Axis scale +enum ImPlotScale_ { + ImPlotScale_Linear = 0, // default linear scale + ImPlotScale_Time, // date/time scale + ImPlotScale_Log10, // base 10 logartithmic scale + ImPlotScale_SymLog, // symmetric log scale +}; + +// Marker specifications. +enum ImPlotMarker_ { + ImPlotMarker_None = -1, // no marker + ImPlotMarker_Circle, // a circle marker (default) + ImPlotMarker_Square, // a square maker + ImPlotMarker_Diamond, // a diamond marker + ImPlotMarker_Up, // an upward-pointing triangle marker + ImPlotMarker_Down, // an downward-pointing triangle marker + ImPlotMarker_Left, // an leftward-pointing triangle marker + ImPlotMarker_Right, // an rightward-pointing triangle marker + ImPlotMarker_Cross, // a cross marker (not fillable) + ImPlotMarker_Plus, // a plus marker (not fillable) + ImPlotMarker_Asterisk, // a asterisk marker (not fillable) + ImPlotMarker_COUNT +}; + +// Built-in colormaps +enum ImPlotColormap_ { + ImPlotColormap_Deep = 0, // a.k.a. seaborn deep (qual=true, n=10) (default) + ImPlotColormap_Dark = 1, // a.k.a. matplotlib "Set1" (qual=true, n=9 ) + ImPlotColormap_Pastel = 2, // a.k.a. matplotlib "Pastel1" (qual=true, n=9 ) + ImPlotColormap_Paired = 3, // a.k.a. matplotlib "Paired" (qual=true, n=12) + ImPlotColormap_Viridis = 4, // a.k.a. matplotlib "viridis" (qual=false, n=11) + ImPlotColormap_Plasma = 5, // a.k.a. matplotlib "plasma" (qual=false, n=11) + ImPlotColormap_Hot = 6, // a.k.a. matplotlib/MATLAB "hot" (qual=false, n=11) + ImPlotColormap_Cool = 7, // a.k.a. matplotlib/MATLAB "cool" (qual=false, n=11) + ImPlotColormap_Pink = 8, // a.k.a. matplotlib/MATLAB "pink" (qual=false, n=11) + ImPlotColormap_Jet = 9, // a.k.a. MATLAB "jet" (qual=false, n=11) + ImPlotColormap_Twilight = 10, // a.k.a. matplotlib "twilight" (qual=false, n=11) + ImPlotColormap_RdBu = 11, // red/blue, Color Brewer (qual=false, n=11) + ImPlotColormap_BrBG = 12, // brown/blue-green, Color Brewer (qual=false, n=11) + ImPlotColormap_PiYG = 13, // pink/yellow-green, Color Brewer (qual=false, n=11) + ImPlotColormap_Spectral = 14, // color spectrum, Color Brewer (qual=false, n=11) + ImPlotColormap_Greys = 15, // white/black (qual=false, n=2 ) +}; + +// Used to position items on a plot (e.g. legends, labels, etc.) +enum ImPlotLocation_ { + ImPlotLocation_Center = 0, // center-center + ImPlotLocation_North = 1 << 0, // top-center + ImPlotLocation_South = 1 << 1, // bottom-center + ImPlotLocation_West = 1 << 2, // center-left + ImPlotLocation_East = 1 << 3, // center-right + ImPlotLocation_NorthWest = ImPlotLocation_North | ImPlotLocation_West, // top-left + ImPlotLocation_NorthEast = ImPlotLocation_North | ImPlotLocation_East, // top-right + ImPlotLocation_SouthWest = ImPlotLocation_South | ImPlotLocation_West, // bottom-left + ImPlotLocation_SouthEast = ImPlotLocation_South | ImPlotLocation_East // bottom-right +}; + +// Enums for different automatic histogram binning methods (k = bin count or w = bin width) +enum ImPlotBin_ { + ImPlotBin_Sqrt = -1, // k = sqrt(n) + ImPlotBin_Sturges = -2, // k = 1 + log2(n) + ImPlotBin_Rice = -3, // k = 2 * cbrt(n) + ImPlotBin_Scott = -4, // w = 3.49 * sigma / cbrt(n) +}; + +// Double precision version of ImVec2 used by ImPlot. Extensible by end users. +IM_MSVC_RUNTIME_CHECKS_OFF +struct ImPlotPoint { + double x, y; + constexpr ImPlotPoint() : x(0.0), y(0.0) { } + constexpr ImPlotPoint(double _x, double _y) : x(_x), y(_y) { } + constexpr ImPlotPoint(const ImVec2& p) : x((double)p.x), y((double)p.y) { } + double& operator[] (size_t idx) { IM_ASSERT(idx == 0 || idx == 1); return ((double*)(void*)(char*)this)[idx]; } + double operator[] (size_t idx) const { IM_ASSERT(idx == 0 || idx == 1); return ((const double*)(const void*)(const char*)this)[idx]; } +#ifdef IMPLOT_POINT_CLASS_EXTRA + IMPLOT_POINT_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h + // to convert back and forth between your math types and ImPlotPoint. +#endif +}; +IM_MSVC_RUNTIME_CHECKS_RESTORE + +// Range defined by a min/max value. +struct ImPlotRange { + double Min, Max; + constexpr ImPlotRange() : Min(0.0), Max(0.0) { } + constexpr ImPlotRange(double _min, double _max) : Min(_min), Max(_max) { } + bool Contains(double value) const { return value >= Min && value <= Max; } + double Size() const { return Max - Min; } + double Clamp(double value) const { return (value < Min) ? Min : (value > Max) ? Max : value; } +}; + +// Combination of two range limits for X and Y axes. Also an AABB defined by Min()/Max(). +struct ImPlotRect { + ImPlotRange X, Y; + constexpr ImPlotRect() : X(0.0,0.0), Y(0.0,0.0) { } + constexpr ImPlotRect(double x_min, double x_max, double y_min, double y_max) : X(x_min, x_max), Y(y_min, y_max) { } + bool Contains(const ImPlotPoint& p) const { return Contains(p.x, p.y); } + bool Contains(double x, double y) const { return X.Contains(x) && Y.Contains(y); } + ImPlotPoint Size() const { return ImPlotPoint(X.Size(), Y.Size()); } + ImPlotPoint Clamp(const ImPlotPoint& p) { return Clamp(p.x, p.y); } + ImPlotPoint Clamp(double x, double y) { return ImPlotPoint(X.Clamp(x),Y.Clamp(y)); } + ImPlotPoint Min() const { return ImPlotPoint(X.Min, Y.Min); } + ImPlotPoint Max() const { return ImPlotPoint(X.Max, Y.Max); } +}; + +// Plot style structure +struct ImPlotStyle { + // item styling variables + float LineWeight; // = 1, item line weight in pixels + int Marker; // = ImPlotMarker_None, marker specification + float MarkerSize; // = 4, marker size in pixels (roughly the marker's "radius") + float MarkerWeight; // = 1, outline weight of markers in pixels + float FillAlpha; // = 1, alpha modifier applied to plot fills + float ErrorBarSize; // = 5, error bar whisker width in pixels + float ErrorBarWeight; // = 1.5, error bar whisker weight in pixels + float DigitalBitHeight; // = 8, digital channels bit height (at y = 1.0f) in pixels + float DigitalBitGap; // = 4, digital channels bit padding gap in pixels + // plot styling variables + float PlotBorderSize; // = 1, line thickness of border around plot area + float MinorAlpha; // = 0.25 alpha multiplier applied to minor axis grid lines + ImVec2 MajorTickLen; // = 10,10 major tick lengths for X and Y axes + ImVec2 MinorTickLen; // = 5,5 minor tick lengths for X and Y axes + ImVec2 MajorTickSize; // = 1,1 line thickness of major ticks + ImVec2 MinorTickSize; // = 1,1 line thickness of minor ticks + ImVec2 MajorGridSize; // = 1,1 line thickness of major grid lines + ImVec2 MinorGridSize; // = 1,1 line thickness of minor grid lines + ImVec2 PlotPadding; // = 10,10 padding between widget frame and plot area, labels, or outside legends (i.e. main padding) + ImVec2 LabelPadding; // = 5,5 padding between axes labels, tick labels, and plot edge + ImVec2 LegendPadding; // = 10,10 legend padding from plot edges + ImVec2 LegendInnerPadding; // = 5,5 legend inner padding from legend edges + ImVec2 LegendSpacing; // = 5,0 spacing between legend entries + ImVec2 MousePosPadding; // = 10,10 padding between plot edge and interior mouse location text + ImVec2 AnnotationPadding; // = 2,2 text padding around annotation labels + ImVec2 FitPadding; // = 0,0 additional fit padding as a percentage of the fit extents (e.g. ImVec2(0.1f,0.1f) adds 10% to the fit extents of X and Y) + ImVec2 PlotDefaultSize; // = 400,300 default size used when ImVec2(0,0) is passed to BeginPlot + ImVec2 PlotMinSize; // = 200,150 minimum size plot frame can be when shrunk + // style colors + ImVec4 Colors[ImPlotCol_COUNT]; // Array of styling colors. Indexable with ImPlotCol_ enums. + // colormap + ImPlotColormap Colormap; // The current colormap. Set this to either an ImPlotColormap_ enum or an index returned by AddColormap. + // settings/flags + bool UseLocalTime; // = false, axis labels will be formatted for your timezone when ImPlotAxisFlag_Time is enabled + bool UseISO8601; // = false, dates will be formatted according to ISO 8601 where applicable (e.g. YYYY-MM-DD, YYYY-MM, --MM-DD, etc.) + bool Use24HourClock; // = false, times will be formatted using a 24 hour clock + IMPLOT_API ImPlotStyle(); +}; + +// Support for legacy versions +#if (IMGUI_VERSION_NUM < 18716) // Renamed in 1.88 +#define ImGuiMod_None 0 +#define ImGuiMod_Ctrl ImGuiKeyModFlags_Ctrl +#define ImGuiMod_Shift ImGuiKeyModFlags_Shift +#define ImGuiMod_Alt ImGuiKeyModFlags_Alt +#define ImGuiMod_Super ImGuiKeyModFlags_Super +#elif (IMGUI_VERSION_NUM < 18823) // Renamed in 1.89, sorry +#define ImGuiMod_None 0 +#define ImGuiMod_Ctrl ImGuiModFlags_Ctrl +#define ImGuiMod_Shift ImGuiModFlags_Shift +#define ImGuiMod_Alt ImGuiModFlags_Alt +#define ImGuiMod_Super ImGuiModFlags_Super +#endif + +// Input mapping structure. Default values listed. See also MapInputDefault, MapInputReverse. +struct ImPlotInputMap { + ImGuiMouseButton Pan; // LMB enables panning when held, + int PanMod; // none optional modifier that must be held for panning/fitting + ImGuiMouseButton Fit; // LMB initiates fit when double clicked + ImGuiMouseButton Select; // RMB begins box selection when pressed and confirms selection when released + ImGuiMouseButton SelectCancel; // LMB cancels active box selection when pressed; cannot be same as Select + int SelectMod; // none optional modifier that must be held for box selection + int SelectHorzMod; // Alt expands active box selection horizontally to plot edge when held + int SelectVertMod; // Shift expands active box selection vertically to plot edge when held + ImGuiMouseButton Menu; // RMB opens context menus (if enabled) when clicked + int OverrideMod; // Ctrl when held, all input is ignored; used to enable axis/plots as DND sources + int ZoomMod; // none optional modifier that must be held for scroll wheel zooming + float ZoomRate; // 0.1f zoom rate for scroll (e.g. 0.1f = 10% plot range every scroll click); make negative to invert + IMPLOT_API ImPlotInputMap(); +}; + +//----------------------------------------------------------------------------- +// [SECTION] Callbacks +//----------------------------------------------------------------------------- + +// Callback signature for axis tick label formatter. +typedef int (*ImPlotFormatter)(double value, char* buff, int size, void* user_data); + +// Callback signature for data getter. +typedef ImPlotPoint (*ImPlotGetter)(int idx, void* user_data); + +// Callback signature for axis transform. +typedef double (*ImPlotTransform)(double value, void* user_data); + +namespace ImPlot { + +//----------------------------------------------------------------------------- +// [SECTION] Contexts +//----------------------------------------------------------------------------- + +// Creates a new ImPlot context. Call this after ImGui::CreateContext. +IMPLOT_API ImPlotContext* CreateContext(); +// Destroys an ImPlot context. Call this before ImGui::DestroyContext. nullptr = destroy current context. +IMPLOT_API void DestroyContext(ImPlotContext* ctx = nullptr); +// Returns the current ImPlot context. nullptr if no context has ben set. +IMPLOT_API ImPlotContext* GetCurrentContext(); +// Sets the current ImPlot context. +IMPLOT_API void SetCurrentContext(ImPlotContext* ctx); + +// Sets the current **ImGui** context. This is ONLY necessary if you are compiling +// ImPlot as a DLL (not recommended) separate from your ImGui compilation. It +// sets the global variable GImGui, which is not shared across DLL boundaries. +// See GImGui documentation in imgui.cpp for more details. +IMPLOT_API void SetImGuiContext(ImGuiContext* ctx); + +//----------------------------------------------------------------------------- +// [SECTION] Begin/End Plot +//----------------------------------------------------------------------------- + +// Starts a 2D plotting context. If this function returns true, EndPlot() MUST +// be called! You are encouraged to use the following convention: +// +// if (BeginPlot(...)) { +// PlotLine(...); +// ... +// EndPlot(); +// } +// +// Important notes: +// +// - #title_id must be unique to the current ImGui ID scope. If you need to avoid ID +// collisions or don't want to display a title in the plot, use double hashes +// (e.g. "MyPlot##HiddenIdText" or "##NoTitle"). +// - #size is the **frame** size of the plot widget, not the plot area. The default +// size of plots (i.e. when ImVec2(0,0)) can be modified in your ImPlotStyle. +IMPLOT_API bool BeginPlot(const char* title_id, const ImVec2& size=ImVec2(-1,0), ImPlotFlags flags=0); + +// Only call EndPlot() if BeginPlot() returns true! Typically called at the end +// of an if statement conditioned on BeginPlot(). See example above. +IMPLOT_API void EndPlot(); + +//----------------------------------------------------------------------------- +// [SECTION] Begin/End Subplots +//----------------------------------------------------------------------------- + +// Starts a subdivided plotting context. If the function returns true, +// EndSubplots() MUST be called! Call BeginPlot/EndPlot AT MOST [rows*cols] +// times in between the begining and end of the subplot context. Plots are +// added in row major order. +// +// Example: +// +// if (BeginSubplots("My Subplot",2,3,ImVec2(800,400)) { +// for (int i = 0; i < 6; ++i) { +// if (BeginPlot(...)) { +// ImPlot::PlotLine(...); +// ... +// EndPlot(); +// } +// } +// EndSubplots(); +// } +// +// Produces: +// +// [0] | [1] | [2] +// ----|-----|---- +// [3] | [4] | [5] +// +// Important notes: +// +// - #title_id must be unique to the current ImGui ID scope. If you need to avoid ID +// collisions or don't want to display a title in the plot, use double hashes +// (e.g. "MySubplot##HiddenIdText" or "##NoTitle"). +// - #rows and #cols must be greater than 0. +// - #size is the size of the entire grid of subplots, not the individual plots +// - #row_ratios and #col_ratios must have AT LEAST #rows and #cols elements, +// respectively. These are the sizes of the rows and columns expressed in ratios. +// If the user adjusts the dimensions, the arrays are updated with new ratios. +// +// Important notes regarding BeginPlot from inside of BeginSubplots: +// +// - The #title_id parameter of _BeginPlot_ (see above) does NOT have to be +// unique when called inside of a subplot context. Subplot IDs are hashed +// for your convenience so you don't have call PushID or generate unique title +// strings. Simply pass an empty string to BeginPlot unless you want to title +// each subplot. +// - The #size parameter of _BeginPlot_ (see above) is ignored when inside of a +// subplot context. The actual size of the subplot will be based on the +// #size value you pass to _BeginSubplots_ and #row/#col_ratios if provided. + +IMPLOT_API bool BeginSubplots(const char* title_id, + int rows, + int cols, + const ImVec2& size, + ImPlotSubplotFlags flags = 0, + float* row_ratios = nullptr, + float* col_ratios = nullptr); + +// Only call EndSubplots() if BeginSubplots() returns true! Typically called at the end +// of an if statement conditioned on BeginSublots(). See example above. +IMPLOT_API void EndSubplots(); + +//----------------------------------------------------------------------------- +// [SECTION] Setup +//----------------------------------------------------------------------------- + +// The following API allows you to setup and customize various aspects of the +// current plot. The functions should be called immediately after BeginPlot +// and before any other API calls. Typical usage is as follows: + +// if (BeginPlot(...)) { 1) begin a new plot +// SetupAxis(ImAxis_X1, "My X-Axis"); 2) make Setup calls +// SetupAxis(ImAxis_Y1, "My Y-Axis"); +// SetupLegend(ImPlotLocation_North); +// ... +// SetupFinish(); 3) [optional] explicitly finish setup +// PlotLine(...); 4) plot items +// ... +// EndPlot(); 5) end the plot +// } +// +// Important notes: +// +// - Always call Setup code at the top of your BeginPlot conditional statement. +// - Setup is locked once you start plotting or explicitly call SetupFinish. +// Do NOT call Setup code after you begin plotting or after you make +// any non-Setup API calls (e.g. utils like PlotToPixels also lock Setup) +// - Calling SetupFinish is OPTIONAL, but probably good practice. If you do not +// call it yourself, then the first subsequent plotting or utility function will +// call it for you. + +// Enables an axis or sets the label and/or flags for an existing axis. Leave #label = nullptr for no label. +IMPLOT_API void SetupAxis(ImAxis axis, const char* label=nullptr, ImPlotAxisFlags flags=0); +// Sets an axis range limits. If ImPlotCond_Always is used, the axes limits will be locked. Inversion with v_min > v_max is not supported; use SetupAxisLimits instead. +IMPLOT_API void SetupAxisLimits(ImAxis axis, double v_min, double v_max, ImPlotCond cond = ImPlotCond_Once); +// Links an axis range limits to external values. Set to nullptr for no linkage. The pointer data must remain valid until EndPlot. +IMPLOT_API void SetupAxisLinks(ImAxis axis, double* link_min, double* link_max); +// Sets the format of numeric axis labels via formater specifier (default="%g"). Formated values will be double (i.e. use %f). +IMPLOT_API void SetupAxisFormat(ImAxis axis, const char* fmt); +// Sets the format of numeric axis labels via formatter callback. Given #value, write a label into #buff. Optionally pass user data. +IMPLOT_API void SetupAxisFormat(ImAxis axis, ImPlotFormatter formatter, void* data=nullptr); +// Sets an axis' ticks and optionally the labels. To keep the default ticks, set #keep_default=true. +IMPLOT_API void SetupAxisTicks(ImAxis axis, const double* values, int n_ticks, const char* const labels[]=nullptr, bool keep_default=false); +// Sets an axis' ticks and optionally the labels for the next plot. To keep the default ticks, set #keep_default=true. +IMPLOT_API void SetupAxisTicks(ImAxis axis, double v_min, double v_max, int n_ticks, const char* const labels[]=nullptr, bool keep_default=false); +// Sets an axis' scale using built-in options. +IMPLOT_API void SetupAxisScale(ImAxis axis, ImPlotScale scale); +// Sets an axis' scale using user supplied forward and inverse transfroms. +IMPLOT_API void SetupAxisScale(ImAxis axis, ImPlotTransform forward, ImPlotTransform inverse, void* data=nullptr); +// Sets an axis' limits constraints. +IMPLOT_API void SetupAxisLimitsConstraints(ImAxis axis, double v_min, double v_max); +// Sets an axis' zoom constraints. +IMPLOT_API void SetupAxisZoomConstraints(ImAxis axis, double z_min, double z_max); + +// Sets the label and/or flags for primary X and Y axes (shorthand for two calls to SetupAxis). +IMPLOT_API void SetupAxes(const char* x_label, const char* y_label, ImPlotAxisFlags x_flags=0, ImPlotAxisFlags y_flags=0); +// Sets the primary X and Y axes range limits. If ImPlotCond_Always is used, the axes limits will be locked (shorthand for two calls to SetupAxisLimits). +IMPLOT_API void SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, ImPlotCond cond = ImPlotCond_Once); + +// Sets up the plot legend. This can also be called immediately after BeginSubplots when using ImPlotSubplotFlags_ShareItems. +IMPLOT_API void SetupLegend(ImPlotLocation location, ImPlotLegendFlags flags=0); +// Set the location of the current plot's mouse position text (default = South|East). +IMPLOT_API void SetupMouseText(ImPlotLocation location, ImPlotMouseTextFlags flags=0); + +// Explicitly finalize plot setup. Once you call this, you cannot make anymore Setup calls for the current plot! +// Note that calling this function is OPTIONAL; it will be called by the first subsequent setup-locking API call. +IMPLOT_API void SetupFinish(); + +//----------------------------------------------------------------------------- +// [SECTION] SetNext +//----------------------------------------------------------------------------- + +// Though you should default to the `Setup` API above, there are some scenarios +// where (re)configuring a plot or axis before `BeginPlot` is needed (e.g. if +// using a preceding button or slider widget to change the plot limits). In +// this case, you can use the `SetNext` API below. While this is not as feature +// rich as the Setup API, most common needs are provided. These functions can be +// called anwhere except for inside of `Begin/EndPlot`. For example: + +// if (ImGui::Button("Center Plot")) +// ImPlot::SetNextPlotLimits(-1,1,-1,1); +// if (ImPlot::BeginPlot(...)) { +// ... +// ImPlot::EndPlot(); +// } +// +// Important notes: +// +// - You must still enable non-default axes with SetupAxis for these functions +// to work properly. + +// Sets an upcoming axis range limits. If ImPlotCond_Always is used, the axes limits will be locked. +IMPLOT_API void SetNextAxisLimits(ImAxis axis, double v_min, double v_max, ImPlotCond cond = ImPlotCond_Once); +// Links an upcoming axis range limits to external values. Set to nullptr for no linkage. The pointer data must remain valid until EndPlot! +IMPLOT_API void SetNextAxisLinks(ImAxis axis, double* link_min, double* link_max); +// Set an upcoming axis to auto fit to its data. +IMPLOT_API void SetNextAxisToFit(ImAxis axis); + +// Sets the upcoming primary X and Y axes range limits. If ImPlotCond_Always is used, the axes limits will be locked (shorthand for two calls to SetupAxisLimits). +IMPLOT_API void SetNextAxesLimits(double x_min, double x_max, double y_min, double y_max, ImPlotCond cond = ImPlotCond_Once); +// Sets all upcoming axes to auto fit to their data. +IMPLOT_API void SetNextAxesToFit(); + +//----------------------------------------------------------------------------- +// [SECTION] Plot Items +//----------------------------------------------------------------------------- + +// The main plotting API is provied below. Call these functions between +// Begin/EndPlot and after any Setup API calls. Each plots data on the current +// x and y axes, which can be changed with `SetAxis/Axes`. +// +// The templated functions are explicitly instantiated in implot_items.cpp. +// They are not intended to be used generically with custom types. You will get +// a linker error if you try! All functions support the following scalar types: +// +// float, double, ImS8, ImU8, ImS16, ImU16, ImS32, ImU32, ImS64, ImU64 +// +// +// If you need to plot custom or non-homogenous data you have a few options: +// +// 1. If your data is a simple struct/class (e.g. Vector2f), you can use striding. +// This is the most performant option if applicable. +// +// struct Vector2f { float X, Y; }; +// ... +// Vector2f data[42]; +// ImPlot::PlotLine("line", &data[0].x, &data[0].y, 42, 0, 0, sizeof(Vector2f)); +// +// 2. Write a custom getter C function or C++ lambda and pass it and optionally your data to +// an ImPlot function post-fixed with a G (e.g. PlotScatterG). This has a slight performance +// cost, but probably not enough to worry about unless your data is very large. Examples: +// +// ImPlotPoint MyDataGetter(void* data, int idx) { +// MyData* my_data = (MyData*)data; +// ImPlotPoint p; +// p.x = my_data->GetTime(idx); +// p.y = my_data->GetValue(idx); +// return p +// } +// ... +// auto my_lambda = [](int idx, void*) { +// double t = idx / 999.0; +// return ImPlotPoint(t, 0.5+0.5*std::sin(2*PI*10*t)); +// }; +// ... +// if (ImPlot::BeginPlot("MyPlot")) { +// MyData my_data; +// ImPlot::PlotScatterG("scatter", MyDataGetter, &my_data, my_data.Size()); +// ImPlot::PlotLineG("line", my_lambda, nullptr, 1000); +// ImPlot::EndPlot(); +// } +// +// NB: All types are converted to double before plotting. You may lose information +// if you try plotting extremely large 64-bit integral types. Proceed with caution! + +// Plots a standard 2D line plot. +IMPLOT_TMP void PlotLine(const char* label_id, const T* values, int count, double xscale=1, double xstart=0, ImPlotLineFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotLine(const char* label_id, const T* xs, const T* ys, int count, ImPlotLineFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_API void PlotLineG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotLineFlags flags=0); + +// Plots a standard 2D scatter plot. Default marker is ImPlotMarker_Circle. +IMPLOT_TMP void PlotScatter(const char* label_id, const T* values, int count, double xscale=1, double xstart=0, ImPlotScatterFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotScatter(const char* label_id, const T* xs, const T* ys, int count, ImPlotScatterFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_API void PlotScatterG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotScatterFlags flags=0); + +// Plots a a stairstep graph. The y value is continued constantly to the right from every x position, i.e. the interval [x[i], x[i+1]) has the value y[i] +IMPLOT_TMP void PlotStairs(const char* label_id, const T* values, int count, double xscale=1, double xstart=0, ImPlotStairsFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotStairs(const char* label_id, const T* xs, const T* ys, int count, ImPlotStairsFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_API void PlotStairsG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotStairsFlags flags=0); + +// Plots a shaded (filled) region between two lines, or a line and a horizontal reference. Set yref to +/-INFINITY for infinite fill extents. +IMPLOT_TMP void PlotShaded(const char* label_id, const T* values, int count, double yref=0, double xscale=1, double xstart=0, ImPlotShadedFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotShaded(const char* label_id, const T* xs, const T* ys, int count, double yref=0, ImPlotShadedFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotShaded(const char* label_id, const T* xs, const T* ys1, const T* ys2, int count, ImPlotShadedFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_API void PlotShadedG(const char* label_id, ImPlotGetter getter1, void* data1, ImPlotGetter getter2, void* data2, int count, ImPlotShadedFlags flags=0); + +// Plots a bar graph. Vertical by default. #bar_size and #shift are in plot units. +IMPLOT_TMP void PlotBars(const char* label_id, const T* values, int count, double bar_size=0.67, double shift=0, ImPlotBarsFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotBars(const char* label_id, const T* xs, const T* ys, int count, double bar_size, ImPlotBarsFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_API void PlotBarsG(const char* label_id, ImPlotGetter getter, void* data, int count, double bar_size, ImPlotBarsFlags flags=0); + +// Plots a group of bars. #values is a row-major matrix with #item_count rows and #group_count cols. #label_ids should have #item_count elements. +IMPLOT_TMP void PlotBarGroups(const char* const label_ids[], const T* values, int item_count, int group_count, double group_size=0.67, double shift=0, ImPlotBarGroupsFlags flags=0); + +// Plots vertical error bar. The label_id should be the same as the label_id of the associated line or bar plot. +IMPLOT_TMP void PlotErrorBars(const char* label_id, const T* xs, const T* ys, const T* err, int count, ImPlotErrorBarsFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotErrorBars(const char* label_id, const T* xs, const T* ys, const T* neg, const T* pos, int count, ImPlotErrorBarsFlags flags=0, int offset=0, int stride=sizeof(T)); + +// Plots stems. Vertical by default. +IMPLOT_TMP void PlotStems(const char* label_id, const T* values, int count, double ref=0, double scale=1, double start=0, ImPlotStemsFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_TMP void PlotStems(const char* label_id, const T* xs, const T* ys, int count, double ref=0, ImPlotStemsFlags flags=0, int offset=0, int stride=sizeof(T)); + +// Plots infinite vertical or horizontal lines (e.g. for references or asymptotes). +IMPLOT_TMP void PlotInfLines(const char* label_id, const T* values, int count, ImPlotInfLinesFlags flags=0, int offset=0, int stride=sizeof(T)); + +// Plots a pie chart. Center and radius are in plot units. #label_fmt can be set to nullptr for no labels. +IMPLOT_TMP void PlotPieChart(const char* const label_ids[], const T* values, int count, double x, double y, double radius, ImPlotFormatter fmt, void* fmt_data=nullptr, double angle0=90, ImPlotPieChartFlags flags=0); +IMPLOT_TMP void PlotPieChart(const char* const label_ids[], const T* values, int count, double x, double y, double radius, const char* label_fmt="%.1f", double angle0=90, ImPlotPieChartFlags flags=0); + +// Plots a 2D heatmap chart. Values are expected to be in row-major order by default. Leave #scale_min and scale_max both at 0 for automatic color scaling, or set them to a predefined range. #label_fmt can be set to nullptr for no labels. +IMPLOT_TMP void PlotHeatmap(const char* label_id, const T* values, int rows, int cols, double scale_min=0, double scale_max=0, const char* label_fmt="%.1f", const ImPlotPoint& bounds_min=ImPlotPoint(0,0), const ImPlotPoint& bounds_max=ImPlotPoint(1,1), ImPlotHeatmapFlags flags=0); + +// Plots a horizontal histogram. #bins can be a positive integer or an ImPlotBin_ method. If #range is left unspecified, the min/max of #values will be used as the range. +// Otherwise, outlier values outside of the range are not binned. The largest bin count or density is returned. +IMPLOT_TMP double PlotHistogram(const char* label_id, const T* values, int count, int bins=ImPlotBin_Sturges, double bar_scale=1.0, ImPlotRange range=ImPlotRange(), ImPlotHistogramFlags flags=0); + +// Plots two dimensional, bivariate histogram as a heatmap. #x_bins and #y_bins can be a positive integer or an ImPlotBin. If #range is left unspecified, the min/max of +// #xs an #ys will be used as the ranges. Otherwise, outlier values outside of range are not binned. The largest bin count or density is returned. +IMPLOT_TMP double PlotHistogram2D(const char* label_id, const T* xs, const T* ys, int count, int x_bins=ImPlotBin_Sturges, int y_bins=ImPlotBin_Sturges, ImPlotRect range=ImPlotRect(), ImPlotHistogramFlags flags=0); + +// Plots digital data. Digital plots do not respond to y drag or zoom, and are always referenced to the bottom of the plot. +IMPLOT_TMP void PlotDigital(const char* label_id, const T* xs, const T* ys, int count, ImPlotDigitalFlags flags=0, int offset=0, int stride=sizeof(T)); +IMPLOT_API void PlotDigitalG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotDigitalFlags flags=0); + +// Plots an axis-aligned image. #bounds_min/bounds_max are in plot coordinates (y-up) and #uv0/uv1 are in texture coordinates (y-down). +IMPLOT_API void PlotImage(const char* label_id, ImTextureID user_texture_id, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max, const ImVec2& uv0=ImVec2(0,0), const ImVec2& uv1=ImVec2(1,1), const ImVec4& tint_col=ImVec4(1,1,1,1), ImPlotImageFlags flags=0); + +// Plots a centered text label at point x,y with an optional pixel offset. Text color can be changed with ImPlot::PushStyleColor(ImPlotCol_InlayText, ...). +IMPLOT_API void PlotText(const char* text, double x, double y, const ImVec2& pix_offset=ImVec2(0,0), ImPlotTextFlags flags=0); + +// Plots a dummy item (i.e. adds a legend entry colored by ImPlotCol_Line) +IMPLOT_API void PlotDummy(const char* label_id, ImPlotDummyFlags flags=0); + +//----------------------------------------------------------------------------- +// [SECTION] Plot Tools +//----------------------------------------------------------------------------- + +// The following can be used to render interactive elements and/or annotations. +// Like the item plotting functions above, they apply to the current x and y +// axes, which can be changed with `SetAxis/SetAxes`. These functions return true +// when user interaction causes the provided coordinates to change. Additional +// user interactions can be retrieved through the optional output parameters. + +// Shows a draggable point at x,y. #col defaults to ImGuiCol_Text. +IMPLOT_API bool DragPoint(int id, double* x, double* y, const ImVec4& col, float size = 4, ImPlotDragToolFlags flags = 0, bool* out_clicked = nullptr, bool* out_hovered = nullptr, bool* held = nullptr); +// Shows a draggable vertical guide line at an x-value. #col defaults to ImGuiCol_Text. +IMPLOT_API bool DragLineX(int id, double* x, const ImVec4& col, float thickness = 1, ImPlotDragToolFlags flags = 0, bool* out_clicked = nullptr, bool* out_hovered = nullptr, bool* held = nullptr); +// Shows a draggable horizontal guide line at a y-value. #col defaults to ImGuiCol_Text. +IMPLOT_API bool DragLineY(int id, double* y, const ImVec4& col, float thickness = 1, ImPlotDragToolFlags flags = 0, bool* out_clicked = nullptr, bool* out_hovered = nullptr, bool* held = nullptr); +// Shows a draggable and resizeable rectangle. +IMPLOT_API bool DragRect(int id, double* x1, double* y1, double* x2, double* y2, const ImVec4& col, ImPlotDragToolFlags flags = 0, bool* out_clicked = nullptr, bool* out_hovered = nullptr, bool* held = nullptr); + +// Shows an annotation callout at a chosen point. Clamping keeps annotations in the plot area. Annotations are always rendered on top. +IMPLOT_API void Annotation(double x, double y, const ImVec4& col, const ImVec2& pix_offset, bool clamp, bool round = false); +IMPLOT_API void Annotation(double x, double y, const ImVec4& col, const ImVec2& pix_offset, bool clamp, const char* fmt, ...) IM_FMTARGS(6); +IMPLOT_API void AnnotationV(double x, double y, const ImVec4& col, const ImVec2& pix_offset, bool clamp, const char* fmt, va_list args) IM_FMTLIST(6); + +// Shows a x-axis tag at the specified coordinate value. +IMPLOT_API void TagX(double x, const ImVec4& col, bool round = false); +IMPLOT_API void TagX(double x, const ImVec4& col, const char* fmt, ...) IM_FMTARGS(3); +IMPLOT_API void TagXV(double x, const ImVec4& col, const char* fmt, va_list args) IM_FMTLIST(3); + +// Shows a y-axis tag at the specified coordinate value. +IMPLOT_API void TagY(double y, const ImVec4& col, bool round = false); +IMPLOT_API void TagY(double y, const ImVec4& col, const char* fmt, ...) IM_FMTARGS(3); +IMPLOT_API void TagYV(double y, const ImVec4& col, const char* fmt, va_list args) IM_FMTLIST(3); + +//----------------------------------------------------------------------------- +// [SECTION] Plot Utils +//----------------------------------------------------------------------------- + +// Select which axis/axes will be used for subsequent plot elements. +IMPLOT_API void SetAxis(ImAxis axis); +IMPLOT_API void SetAxes(ImAxis x_axis, ImAxis y_axis); + +// Convert pixels to a position in the current plot's coordinate system. Passing IMPLOT_AUTO uses the current axes. +IMPLOT_API ImPlotPoint PixelsToPlot(const ImVec2& pix, ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO); +IMPLOT_API ImPlotPoint PixelsToPlot(float x, float y, ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO); + +// Convert a position in the current plot's coordinate system to pixels. Passing IMPLOT_AUTO uses the current axes. +IMPLOT_API ImVec2 PlotToPixels(const ImPlotPoint& plt, ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO); +IMPLOT_API ImVec2 PlotToPixels(double x, double y, ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO); + +// Get the current Plot position (top-left) in pixels. +IMPLOT_API ImVec2 GetPlotPos(); +// Get the curent Plot size in pixels. +IMPLOT_API ImVec2 GetPlotSize(); + +// Returns the mouse position in x,y coordinates of the current plot. Passing IMPLOT_AUTO uses the current axes. +IMPLOT_API ImPlotPoint GetPlotMousePos(ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO); +// Returns the current plot axis range. +IMPLOT_API ImPlotRect GetPlotLimits(ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO); + +// Returns true if the plot area in the current plot is hovered. +IMPLOT_API bool IsPlotHovered(); +// Returns true if the axis label area in the current plot is hovered. +IMPLOT_API bool IsAxisHovered(ImAxis axis); +// Returns true if the bounding frame of a subplot is hovered. +IMPLOT_API bool IsSubplotsHovered(); + +// Returns true if the current plot is being box selected. +IMPLOT_API bool IsPlotSelected(); +// Returns the current plot box selection bounds. Passing IMPLOT_AUTO uses the current axes. +IMPLOT_API ImPlotRect GetPlotSelection(ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO); +// Cancels a the current plot box selection. +IMPLOT_API void CancelPlotSelection(); + +// Hides or shows the next plot item (i.e. as if it were toggled from the legend). +// Use ImPlotCond_Always if you need to forcefully set this every frame. +IMPLOT_API void HideNextItem(bool hidden = true, ImPlotCond cond = ImPlotCond_Once); + +// Use the following around calls to Begin/EndPlot to align l/r/t/b padding. +// Consider using Begin/EndSubplots first. They are more feature rich and +// accomplish the same behaviour by default. The functions below offer lower +// level control of plot alignment. + +// Align axis padding over multiple plots in a single row or column. #group_id must +// be unique. If this function returns true, EndAlignedPlots() must be called. +IMPLOT_API bool BeginAlignedPlots(const char* group_id, bool vertical = true); +// Only call EndAlignedPlots() if BeginAlignedPlots() returns true! +IMPLOT_API void EndAlignedPlots(); + +//----------------------------------------------------------------------------- +// [SECTION] Legend Utils +//----------------------------------------------------------------------------- + +// Begin a popup for a legend entry. +IMPLOT_API bool BeginLegendPopup(const char* label_id, ImGuiMouseButton mouse_button=1); +// End a popup for a legend entry. +IMPLOT_API void EndLegendPopup(); +// Returns true if a plot item legend entry is hovered. +IMPLOT_API bool IsLegendEntryHovered(const char* label_id); + +//----------------------------------------------------------------------------- +// [SECTION] Drag and Drop +//----------------------------------------------------------------------------- + +// Turns the current plot's plotting area into a drag and drop target. Don't forget to call EndDragDropTarget! +IMPLOT_API bool BeginDragDropTargetPlot(); +// Turns the current plot's X-axis into a drag and drop target. Don't forget to call EndDragDropTarget! +IMPLOT_API bool BeginDragDropTargetAxis(ImAxis axis); +// Turns the current plot's legend into a drag and drop target. Don't forget to call EndDragDropTarget! +IMPLOT_API bool BeginDragDropTargetLegend(); +// Ends a drag and drop target (currently just an alias for ImGui::EndDragDropTarget). +IMPLOT_API void EndDragDropTarget(); + +// NB: By default, plot and axes drag and drop *sources* require holding the Ctrl modifier to initiate the drag. +// You can change the modifier if desired. If ImGuiMod_None is provided, the axes will be locked from panning. + +// Turns the current plot's plotting area into a drag and drop source. You must hold Ctrl. Don't forget to call EndDragDropSource! +IMPLOT_API bool BeginDragDropSourcePlot(ImGuiDragDropFlags flags=0); +// Turns the current plot's X-axis into a drag and drop source. You must hold Ctrl. Don't forget to call EndDragDropSource! +IMPLOT_API bool BeginDragDropSourceAxis(ImAxis axis, ImGuiDragDropFlags flags=0); +// Turns an item in the current plot's legend into drag and drop source. Don't forget to call EndDragDropSource! +IMPLOT_API bool BeginDragDropSourceItem(const char* label_id, ImGuiDragDropFlags flags=0); +// Ends a drag and drop source (currently just an alias for ImGui::EndDragDropSource). +IMPLOT_API void EndDragDropSource(); + +//----------------------------------------------------------------------------- +// [SECTION] Styling +//----------------------------------------------------------------------------- + +// Styling colors in ImPlot works similarly to styling colors in ImGui, but +// with one important difference. Like ImGui, all style colors are stored in an +// indexable array in ImPlotStyle. You can permanently modify these values through +// GetStyle().Colors, or temporarily modify them with Push/Pop functions below. +// However, by default all style colors in ImPlot default to a special color +// IMPLOT_AUTO_COL. The behavior of this color depends upon the style color to +// which it as applied: +// +// 1) For style colors associated with plot items (e.g. ImPlotCol_Line), +// IMPLOT_AUTO_COL tells ImPlot to color the item with the next unused +// color in the current colormap. Thus, every item will have a different +// color up to the number of colors in the colormap, at which point the +// colormap will roll over. For most use cases, you should not need to +// set these style colors to anything but IMPLOT_COL_AUTO; you are +// probably better off changing the current colormap. However, if you +// need to explicitly color a particular item you may either Push/Pop +// the style color around the item in question, or use the SetNextXXXStyle +// API below. If you permanently set one of these style colors to a specific +// color, or forget to call Pop, then all subsequent items will be styled +// with the color you set. +// +// 2) For style colors associated with plot styling (e.g. ImPlotCol_PlotBg), +// IMPLOT_AUTO_COL tells ImPlot to set that color from color data in your +// **ImGuiStyle**. The ImGuiCol_ that these style colors default to are +// detailed above, and in general have been mapped to produce plots visually +// consistent with your current ImGui style. Of course, you are free to +// manually set these colors to whatever you like, and further can Push/Pop +// them around individual plots for plot-specific styling (e.g. coloring axes). + +// Provides access to plot style structure for permanant modifications to colors, sizes, etc. +IMPLOT_API ImPlotStyle& GetStyle(); + +// Style plot colors for current ImGui style (default). +IMPLOT_API void StyleColorsAuto(ImPlotStyle* dst = nullptr); +// Style plot colors for ImGui "Classic". +IMPLOT_API void StyleColorsClassic(ImPlotStyle* dst = nullptr); +// Style plot colors for ImGui "Dark". +IMPLOT_API void StyleColorsDark(ImPlotStyle* dst = nullptr); +// Style plot colors for ImGui "Light". +IMPLOT_API void StyleColorsLight(ImPlotStyle* dst = nullptr); + +// Use PushStyleX to temporarily modify your ImPlotStyle. The modification +// will last until the matching call to PopStyleX. You MUST call a pop for +// every push, otherwise you will leak memory! This behaves just like ImGui. + +// Temporarily modify a style color. Don't forget to call PopStyleColor! +IMPLOT_API void PushStyleColor(ImPlotCol idx, ImU32 col); +IMPLOT_API void PushStyleColor(ImPlotCol idx, const ImVec4& col); +// Undo temporary style color modification(s). Undo multiple pushes at once by increasing count. +IMPLOT_API void PopStyleColor(int count = 1); + +// Temporarily modify a style variable of float type. Don't forget to call PopStyleVar! +IMPLOT_API void PushStyleVar(ImPlotStyleVar idx, float val); +// Temporarily modify a style variable of int type. Don't forget to call PopStyleVar! +IMPLOT_API void PushStyleVar(ImPlotStyleVar idx, int val); +// Temporarily modify a style variable of ImVec2 type. Don't forget to call PopStyleVar! +IMPLOT_API void PushStyleVar(ImPlotStyleVar idx, const ImVec2& val); +// Undo temporary style variable modification(s). Undo multiple pushes at once by increasing count. +IMPLOT_API void PopStyleVar(int count = 1); + +// The following can be used to modify the style of the next plot item ONLY. They do +// NOT require calls to PopStyleX. Leave style attributes you don't want modified to +// IMPLOT_AUTO or IMPLOT_AUTO_COL. Automatic styles will be deduced from the current +// values in your ImPlotStyle or from Colormap data. + +// Set the line color and weight for the next item only. +IMPLOT_API void SetNextLineStyle(const ImVec4& col = IMPLOT_AUTO_COL, float weight = IMPLOT_AUTO); +// Set the fill color for the next item only. +IMPLOT_API void SetNextFillStyle(const ImVec4& col = IMPLOT_AUTO_COL, float alpha_mod = IMPLOT_AUTO); +// Set the marker style for the next item only. +IMPLOT_API void SetNextMarkerStyle(ImPlotMarker marker = IMPLOT_AUTO, float size = IMPLOT_AUTO, const ImVec4& fill = IMPLOT_AUTO_COL, float weight = IMPLOT_AUTO, const ImVec4& outline = IMPLOT_AUTO_COL); +// Set the error bar style for the next item only. +IMPLOT_API void SetNextErrorBarStyle(const ImVec4& col = IMPLOT_AUTO_COL, float size = IMPLOT_AUTO, float weight = IMPLOT_AUTO); + +// Gets the last item primary color (i.e. its legend icon color) +IMPLOT_API ImVec4 GetLastItemColor(); + +// Returns the null terminated string name for an ImPlotCol. +IMPLOT_API const char* GetStyleColorName(ImPlotCol idx); +// Returns the null terminated string name for an ImPlotMarker. +IMPLOT_API const char* GetMarkerName(ImPlotMarker idx); + +//----------------------------------------------------------------------------- +// [SECTION] Colormaps +//----------------------------------------------------------------------------- + +// Item styling is based on colormaps when the relevant ImPlotCol_XXX is set to +// IMPLOT_AUTO_COL (default). Several built-in colormaps are available. You can +// add and then push/pop your own colormaps as well. To permanently set a colormap, +// modify the Colormap index member of your ImPlotStyle. + +// Colormap data will be ignored and a custom color will be used if you have done one of the following: +// 1) Modified an item style color in your ImPlotStyle to anything other than IMPLOT_AUTO_COL. +// 2) Pushed an item style color using PushStyleColor(). +// 3) Set the next item style with a SetNextXXXStyle function. + +// Add a new colormap. The color data will be copied. The colormap can be used by pushing either the returned index or the +// string name with PushColormap. The colormap name must be unique and the size must be greater than 1. You will receive +// an assert otherwise! By default colormaps are considered to be qualitative (i.e. discrete). If you want to create a +// continuous colormap, set #qual=false. This will treat the colors you provide as keys, and ImPlot will build a linearly +// interpolated lookup table. The memory footprint of this table will be exactly ((size-1)*255+1)*4 bytes. +IMPLOT_API ImPlotColormap AddColormap(const char* name, const ImVec4* cols, int size, bool qual=true); +IMPLOT_API ImPlotColormap AddColormap(const char* name, const ImU32* cols, int size, bool qual=true); + +// Returns the number of available colormaps (i.e. the built-in + user-added count). +IMPLOT_API int GetColormapCount(); +// Returns a null terminated string name for a colormap given an index. Returns nullptr if index is invalid. +IMPLOT_API const char* GetColormapName(ImPlotColormap cmap); +// Returns an index number for a colormap given a valid string name. Returns -1 if name is invalid. +IMPLOT_API ImPlotColormap GetColormapIndex(const char* name); + +// Temporarily switch to one of the built-in (i.e. ImPlotColormap_XXX) or user-added colormaps (i.e. a return value of AddColormap). Don't forget to call PopColormap! +IMPLOT_API void PushColormap(ImPlotColormap cmap); +// Push a colormap by string name. Use built-in names such as "Default", "Deep", "Jet", etc. or a string you provided to AddColormap. Don't forget to call PopColormap! +IMPLOT_API void PushColormap(const char* name); +// Undo temporary colormap modification(s). Undo multiple pushes at once by increasing count. +IMPLOT_API void PopColormap(int count = 1); + +// Returns the next color from the current colormap and advances the colormap for the current plot. +// Can also be used with no return value to skip colors if desired. You need to call this between Begin/EndPlot! +IMPLOT_API ImVec4 NextColormapColor(); + +// Colormap utils. If cmap = IMPLOT_AUTO (default), the current colormap is assumed. +// Pass an explicit colormap index (built-in or user-added) to specify otherwise. + +// Returns the size of a colormap. +IMPLOT_API int GetColormapSize(ImPlotColormap cmap = IMPLOT_AUTO); +// Returns a color from a colormap given an index >= 0 (modulo will be performed). +IMPLOT_API ImVec4 GetColormapColor(int idx, ImPlotColormap cmap = IMPLOT_AUTO); +// Sample a color from the current colormap given t between 0 and 1. +IMPLOT_API ImVec4 SampleColormap(float t, ImPlotColormap cmap = IMPLOT_AUTO); + +// Shows a vertical color scale with linear spaced ticks using the specified color map. Use double hashes to hide label (e.g. "##NoLabel"). If scale_min > scale_max, the scale to color mapping will be reversed. +IMPLOT_API void ColormapScale(const char* label, double scale_min, double scale_max, const ImVec2& size = ImVec2(0,0), const char* format = "%g", ImPlotColormapScaleFlags flags = 0, ImPlotColormap cmap = IMPLOT_AUTO); +// Shows a horizontal slider with a colormap gradient background. Optionally returns the color sampled at t in [0 1]. +IMPLOT_API bool ColormapSlider(const char* label, float* t, ImVec4* out = nullptr, const char* format = "", ImPlotColormap cmap = IMPLOT_AUTO); +// Shows a button with a colormap gradient brackground. +IMPLOT_API bool ColormapButton(const char* label, const ImVec2& size = ImVec2(0,0), ImPlotColormap cmap = IMPLOT_AUTO); + +// When items in a plot sample their color from a colormap, the color is cached and does not change +// unless explicitly overriden. Therefore, if you change the colormap after the item has already been plotted, +// item colors will NOT update. If you need item colors to resample the new colormap, then use this +// function to bust the cached colors. If #plot_title_id is nullptr, then every item in EVERY existing plot +// will be cache busted. Otherwise only the plot specified by #plot_title_id will be busted. For the +// latter, this function must be called in the same ImGui ID scope that the plot is in. You should rarely if ever +// need this function, but it is available for applications that require runtime colormap swaps (e.g. Heatmaps demo). +IMPLOT_API void BustColorCache(const char* plot_title_id = nullptr); + +//----------------------------------------------------------------------------- +// [SECTION] Input Mapping +//----------------------------------------------------------------------------- + +// Provides access to input mapping structure for permanant modifications to controls for pan, select, etc. +IMPLOT_API ImPlotInputMap& GetInputMap(); + +// Default input mapping: pan = LMB drag, box select = RMB drag, fit = LMB double click, context menu = RMB click, zoom = scroll. +IMPLOT_API void MapInputDefault(ImPlotInputMap* dst = nullptr); +// Reverse input mapping: pan = RMB drag, box select = LMB drag, fit = LMB double click, context menu = RMB click, zoom = scroll. +IMPLOT_API void MapInputReverse(ImPlotInputMap* dst = nullptr); + +//----------------------------------------------------------------------------- +// [SECTION] Miscellaneous +//----------------------------------------------------------------------------- + +// Render icons similar to those that appear in legends (nifty for data lists). +IMPLOT_API void ItemIcon(const ImVec4& col); +IMPLOT_API void ItemIcon(ImU32 col); +IMPLOT_API void ColormapIcon(ImPlotColormap cmap); + +// Get the plot draw list for custom rendering to the current plot area. Call between Begin/EndPlot. +IMPLOT_API ImDrawList* GetPlotDrawList(); +// Push clip rect for rendering to current plot area. The rect can be expanded or contracted by #expand pixels. Call between Begin/EndPlot. +IMPLOT_API void PushPlotClipRect(float expand=0); +// Pop plot clip rect. Call between Begin/EndPlot. +IMPLOT_API void PopPlotClipRect(); + +// Shows ImPlot style selector dropdown menu. +IMPLOT_API bool ShowStyleSelector(const char* label); +// Shows ImPlot colormap selector dropdown menu. +IMPLOT_API bool ShowColormapSelector(const char* label); +// Shows ImPlot input map selector dropdown menu. +IMPLOT_API bool ShowInputMapSelector(const char* label); +// Shows ImPlot style editor block (not a window). +IMPLOT_API void ShowStyleEditor(ImPlotStyle* ref = nullptr); +// Add basic help/info block for end users (not a window). +IMPLOT_API void ShowUserGuide(); +// Shows ImPlot metrics/debug information window. +IMPLOT_API void ShowMetricsWindow(bool* p_popen = nullptr); + +//----------------------------------------------------------------------------- +// [SECTION] Demo +//----------------------------------------------------------------------------- + +// Shows the ImPlot demo window (add implot_demo.cpp to your sources!) +IMPLOT_API void ShowDemoWindow(bool* p_open = nullptr); + +} // namespace ImPlot + +//----------------------------------------------------------------------------- +// [SECTION] Obsolete API +//----------------------------------------------------------------------------- + +// The following functions will be removed! Keep your copy of implot up to date! +// Occasionally set '#define IMPLOT_DISABLE_OBSOLETE_FUNCTIONS' to stay ahead. +// If you absolutely must use these functions and do not want to receive compiler +// warnings, set '#define IMPLOT_DISABLE_OBSOLETE_WARNINGS'. + +#ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS + +#ifndef IMPLOT_DISABLE_DEPRECATED_WARNINGS +#if __cplusplus > 201402L +#define IMPLOT_DEPRECATED(method) [[deprecated]] method +#elif defined( __GNUC__ ) && !defined( __INTEL_COMPILER ) && ( __GNUC__ > 3 || ( __GNUC__ == 3 && __GNUC_MINOR__ >= 1 ) ) +#define IMPLOT_DEPRECATED(method) method __attribute__( ( deprecated ) ) +#elif defined( _MSC_VER ) +#define IMPLOT_DEPRECATED(method) __declspec(deprecated) method +#else +#define IMPLOT_DEPRECATED(method) method +#endif +#else +#define IMPLOT_DEPRECATED(method) method +#endif + +enum ImPlotFlagsObsolete_ { + ImPlotFlags_YAxis2 = 1 << 20, + ImPlotFlags_YAxis3 = 1 << 21, +}; + +namespace ImPlot { + +// OBSOLETED in v0.13 -> PLANNED REMOVAL in v1.0 +IMPLOT_DEPRECATED( IMPLOT_API bool BeginPlot(const char* title_id, + const char* x_label, // = nullptr, + const char* y_label, // = nullptr, + const ImVec2& size = ImVec2(-1,0), + ImPlotFlags flags = ImPlotFlags_None, + ImPlotAxisFlags x_flags = 0, + ImPlotAxisFlags y_flags = 0, + ImPlotAxisFlags y2_flags = ImPlotAxisFlags_AuxDefault, + ImPlotAxisFlags y3_flags = ImPlotAxisFlags_AuxDefault, + const char* y2_label = nullptr, + const char* y3_label = nullptr) ); + +} // namespace ImPlot + +#endif diff --git a/libs/implot/implot_demo.cpp b/libs/implot/implot_demo.cpp new file mode 100644 index 0000000..c72ffd0 --- /dev/null +++ b/libs/implot/implot_demo.cpp @@ -0,0 +1,2479 @@ +// MIT License + +// Copyright (c) 2023 Evan Pezent + +// 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. + +// ImPlot v0.17 + +// We define this so that the demo does not accidentally use deprecated API +#ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS +#define IMPLOT_DISABLE_OBSOLETE_FUNCTIONS +#endif + +#include "implot.h" +#include +#include +#include +#include + +#ifdef _MSC_VER +#define sprintf sprintf_s +#endif + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +#define CHECKBOX_FLAG(flags, flag) ImGui::CheckboxFlags(#flag, (unsigned int*)&flags, flag) + +// Encapsulates examples for customizing ImPlot. +namespace MyImPlot { + +// Example for Custom Data and Getters section. +struct Vector2f { + Vector2f(float _x, float _y) { x = _x; y = _y; } + float x, y; +}; + +// Example for Custom Data and Getters section. +struct WaveData { + double X, Amp, Freq, Offset; + WaveData(double x, double amp, double freq, double offset) { X = x; Amp = amp; Freq = freq; Offset = offset; } +}; +ImPlotPoint SineWave(int idx, void* wave_data); +ImPlotPoint SawWave(int idx, void* wave_data); +ImPlotPoint Spiral(int idx, void* wave_data); + +// Example for Tables section. +void Sparkline(const char* id, const float* values, int count, float min_v, float max_v, int offset, const ImVec4& col, const ImVec2& size); + +// Example for Custom Plotters and Tooltips section. +void PlotCandlestick(const char* label_id, const double* xs, const double* opens, const double* closes, const double* lows, const double* highs, int count, bool tooltip = true, float width_percent = 0.25f, ImVec4 bullCol = ImVec4(0,1,0,1), ImVec4 bearCol = ImVec4(1,0,0,1)); + +// Example for Custom Styles section. +void StyleSeaborn(); + +} // namespace MyImPlot + +namespace ImPlot { + +template +inline T RandomRange(T min, T max) { + T scale = rand() / (T) RAND_MAX; + return min + scale * ( max - min ); +} + +ImVec4 RandomColor() { + ImVec4 col; + col.x = RandomRange(0.0f,1.0f); + col.y = RandomRange(0.0f,1.0f); + col.z = RandomRange(0.0f,1.0f); + col.w = 1.0f; + return col; +} + +double RandomGauss() { + static double V1, V2, S; + static int phase = 0; + double X; + if(phase == 0) { + do { + double U1 = (double)rand() / RAND_MAX; + double U2 = (double)rand() / RAND_MAX; + V1 = 2 * U1 - 1; + V2 = 2 * U2 - 1; + S = V1 * V1 + V2 * V2; + } while(S >= 1 || S == 0); + + X = V1 * sqrt(-2 * log(S) / S); + } else + X = V2 * sqrt(-2 * log(S) / S); + phase = 1 - phase; + return X; +} + +template +struct NormalDistribution { + NormalDistribution(double mean, double sd) { + for (int i = 0; i < N; ++i) + Data[i] = RandomGauss()*sd + mean; + } + double Data[N]; +}; + +// utility structure for realtime plot +struct ScrollingBuffer { + int MaxSize; + int Offset; + ImVector Data; + ScrollingBuffer(int max_size = 2000) { + MaxSize = max_size; + Offset = 0; + Data.reserve(MaxSize); + } + void AddPoint(float x, float y) { + if (Data.size() < MaxSize) + Data.push_back(ImVec2(x,y)); + else { + Data[Offset] = ImVec2(x,y); + Offset = (Offset + 1) % MaxSize; + } + } + void Erase() { + if (Data.size() > 0) { + Data.shrink(0); + Offset = 0; + } + } +}; + +// utility structure for realtime plot +struct RollingBuffer { + float Span; + ImVector Data; + RollingBuffer() { + Span = 10.0f; + Data.reserve(2000); + } + void AddPoint(float x, float y) { + float xmod = fmodf(x, Span); + if (!Data.empty() && xmod < Data.back().x) + Data.shrink(0); + Data.push_back(ImVec2(xmod, y)); + } +}; + +// Huge data used by Time Formatting example (~500 MB allocation!) +struct HugeTimeData { + HugeTimeData(double min) { + Ts = new double[Size]; + Ys = new double[Size]; + for (int i = 0; i < Size; ++i) { + Ts[i] = min + i; + Ys[i] = GetY(Ts[i]); + } + } + ~HugeTimeData() { delete[] Ts; delete[] Ys; } + static double GetY(double t) { + return 0.5 + 0.25 * sin(t/86400/12) + 0.005 * sin(t/3600); + } + double* Ts; + double* Ys; + static const int Size = 60*60*24*366; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Demo Functions +//----------------------------------------------------------------------------- + +void Demo_Help() { + ImGui::Text("ABOUT THIS DEMO:"); + ImGui::BulletText("Sections below are demonstrating many aspects of the library."); + ImGui::BulletText("The \"Tools\" menu above gives access to: Style Editors (ImPlot/ImGui)\n" + "and Metrics (general purpose Dear ImGui debugging tool)."); + ImGui::Separator(); + ImGui::Text("PROGRAMMER GUIDE:"); + ImGui::BulletText("See the ShowDemoWindow() code in implot_demo.cpp. <- you are here!"); + ImGui::BulletText("If you see visual artifacts, do one of the following:"); + ImGui::Indent(); + ImGui::BulletText("Handle ImGuiBackendFlags_RendererHasVtxOffset for 16-bit indices in your backend."); + ImGui::BulletText("Or, enable 32-bit indices in imconfig.h."); + ImGui::BulletText("Your current configuration is:"); + ImGui::Indent(); + ImGui::BulletText("ImDrawIdx: %d-bit", (int)(sizeof(ImDrawIdx) * 8)); + ImGui::BulletText("ImGuiBackendFlags_RendererHasVtxOffset: %s", (ImGui::GetIO().BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) ? "True" : "False"); + ImGui::Unindent(); + ImGui::Unindent(); + ImGui::Separator(); + ImGui::Text("USER GUIDE:"); + ShowUserGuide(); +} + +//----------------------------------------------------------------------------- + +void ButtonSelector(const char* label, ImGuiMouseButton* b) { + ImGui::PushID(label); + if (ImGui::RadioButton("LMB",*b == ImGuiMouseButton_Left)) + *b = ImGuiMouseButton_Left; + ImGui::SameLine(); + if (ImGui::RadioButton("RMB",*b == ImGuiMouseButton_Right)) + *b = ImGuiMouseButton_Right; + ImGui::SameLine(); + if (ImGui::RadioButton("MMB",*b == ImGuiMouseButton_Middle)) + *b = ImGuiMouseButton_Middle; + ImGui::PopID(); +} + +void ModSelector(const char* label, int* k) { + ImGui::PushID(label); + ImGui::CheckboxFlags("Ctrl", (unsigned int*)k, ImGuiMod_Ctrl); ImGui::SameLine(); + ImGui::CheckboxFlags("Shift", (unsigned int*)k, ImGuiMod_Shift); ImGui::SameLine(); + ImGui::CheckboxFlags("Alt", (unsigned int*)k, ImGuiMod_Alt); ImGui::SameLine(); + ImGui::CheckboxFlags("Super", (unsigned int*)k, ImGuiMod_Super); + ImGui::PopID(); +} + +void InputMapping(const char* label, ImGuiMouseButton* b, int* k) { + ImGui::LabelText("##","%s",label); + if (b != nullptr) { + ImGui::SameLine(100); + ButtonSelector(label,b); + } + if (k != nullptr) { + ImGui::SameLine(300); + ModSelector(label,k); + } +} + +void ShowInputMapping() { + ImPlotInputMap& map = ImPlot::GetInputMap(); + InputMapping("Pan",&map.Pan,&map.PanMod); + InputMapping("Fit",&map.Fit,nullptr); + InputMapping("Select",&map.Select,&map.SelectMod); + InputMapping("SelectHorzMod",nullptr,&map.SelectHorzMod); + InputMapping("SelectVertMod",nullptr,&map.SelectVertMod); + InputMapping("SelectCancel",&map.SelectCancel,nullptr); + InputMapping("Menu",&map.Menu,nullptr); + InputMapping("OverrideMod",nullptr,&map.OverrideMod); + InputMapping("ZoomMod",nullptr,&map.ZoomMod); + ImGui::SliderFloat("ZoomRate",&map.ZoomRate,-1,1); +} + +void Demo_Config() { + ImGui::ShowFontSelector("Font"); + ImGui::ShowStyleSelector("ImGui Style"); + ImPlot::ShowStyleSelector("ImPlot Style"); + ImPlot::ShowColormapSelector("ImPlot Colormap"); + ImPlot::ShowInputMapSelector("Input Map"); + ImGui::Separator(); + ImGui::Checkbox("Use Local Time", &ImPlot::GetStyle().UseLocalTime); + ImGui::Checkbox("Use ISO 8601", &ImPlot::GetStyle().UseISO8601); + ImGui::Checkbox("Use 24 Hour Clock", &ImPlot::GetStyle().Use24HourClock); + ImGui::Separator(); + if (ImPlot::BeginPlot("Preview")) { + static double now = (double)time(nullptr); + ImPlot::SetupAxisScale(ImAxis_X1, ImPlotScale_Time); + ImPlot::SetupAxisLimits(ImAxis_X1, now, now + 24*3600); + for (int i = 0; i < 10; ++i) { + double x[2] = {now, now + 24*3600}; + double y[2] = {0,i/9.0}; + ImGui::PushID(i); + ImPlot::PlotLine("##Line",x,y,2); + ImGui::PopID(); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_LinePlots() { + static float xs1[1001], ys1[1001]; + for (int i = 0; i < 1001; ++i) { + xs1[i] = i * 0.001f; + ys1[i] = 0.5f + 0.5f * sinf(50 * (xs1[i] + (float)ImGui::GetTime() / 10)); + } + static double xs2[20], ys2[20]; + for (int i = 0; i < 20; ++i) { + xs2[i] = i * 1/19.0f; + ys2[i] = xs2[i] * xs2[i]; + } + if (ImPlot::BeginPlot("Line Plots")) { + ImPlot::SetupAxes("x","y"); + ImPlot::PlotLine("f(x)", xs1, ys1, 1001); + ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle); + ImPlot::PlotLine("g(x)", xs2, ys2, 20,ImPlotLineFlags_Segments); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_FilledLinePlots() { + static double xs1[101], ys1[101], ys2[101], ys3[101]; + srand(0); + for (int i = 0; i < 101; ++i) { + xs1[i] = (float)i; + ys1[i] = RandomRange(400.0,450.0); + ys2[i] = RandomRange(275.0,350.0); + ys3[i] = RandomRange(150.0,225.0); + } + static bool show_lines = true; + static bool show_fills = true; + static float fill_ref = 0; + static int shade_mode = 0; + static ImPlotShadedFlags flags = 0; + ImGui::Checkbox("Lines",&show_lines); ImGui::SameLine(); + ImGui::Checkbox("Fills",&show_fills); + if (show_fills) { + ImGui::SameLine(); + if (ImGui::RadioButton("To -INF",shade_mode == 0)) + shade_mode = 0; + ImGui::SameLine(); + if (ImGui::RadioButton("To +INF",shade_mode == 1)) + shade_mode = 1; + ImGui::SameLine(); + if (ImGui::RadioButton("To Ref",shade_mode == 2)) + shade_mode = 2; + if (shade_mode == 2) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(100); + ImGui::DragFloat("##Ref",&fill_ref, 1, -100, 500); + } + } + + if (ImPlot::BeginPlot("Stock Prices")) { + ImPlot::SetupAxes("Days","Price"); + ImPlot::SetupAxesLimits(0,100,0,500); + if (show_fills) { + ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f); + ImPlot::PlotShaded("Stock 1", xs1, ys1, 101, shade_mode == 0 ? -INFINITY : shade_mode == 1 ? INFINITY : fill_ref, flags); + ImPlot::PlotShaded("Stock 2", xs1, ys2, 101, shade_mode == 0 ? -INFINITY : shade_mode == 1 ? INFINITY : fill_ref, flags); + ImPlot::PlotShaded("Stock 3", xs1, ys3, 101, shade_mode == 0 ? -INFINITY : shade_mode == 1 ? INFINITY : fill_ref, flags); + ImPlot::PopStyleVar(); + } + if (show_lines) { + ImPlot::PlotLine("Stock 1", xs1, ys1, 101); + ImPlot::PlotLine("Stock 2", xs1, ys2, 101); + ImPlot::PlotLine("Stock 3", xs1, ys3, 101); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_ShadedPlots() { + static float xs[1001], ys[1001], ys1[1001], ys2[1001], ys3[1001], ys4[1001]; + srand(0); + for (int i = 0; i < 1001; ++i) { + xs[i] = i * 0.001f; + ys[i] = 0.25f + 0.25f * sinf(25 * xs[i]) * sinf(5 * xs[i]) + RandomRange(-0.01f, 0.01f); + ys1[i] = ys[i] + RandomRange(0.1f, 0.12f); + ys2[i] = ys[i] - RandomRange(0.1f, 0.12f); + ys3[i] = 0.75f + 0.2f * sinf(25 * xs[i]); + ys4[i] = 0.75f + 0.1f * cosf(25 * xs[i]); + } + static float alpha = 0.25f; + ImGui::DragFloat("Alpha",&alpha,0.01f,0,1); + + if (ImPlot::BeginPlot("Shaded Plots")) { + ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, alpha); + ImPlot::PlotShaded("Uncertain Data",xs,ys1,ys2,1001); + ImPlot::PlotLine("Uncertain Data", xs, ys, 1001); + ImPlot::PlotShaded("Overlapping",xs,ys3,ys4,1001); + ImPlot::PlotLine("Overlapping",xs,ys3,1001); + ImPlot::PlotLine("Overlapping",xs,ys4,1001); + ImPlot::PopStyleVar(); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_ScatterPlots() { + srand(0); + static float xs1[100], ys1[100]; + for (int i = 0; i < 100; ++i) { + xs1[i] = i * 0.01f; + ys1[i] = xs1[i] + 0.1f * ((float)rand() / (float)RAND_MAX); + } + static float xs2[50], ys2[50]; + for (int i = 0; i < 50; i++) { + xs2[i] = 0.25f + 0.2f * ((float)rand() / (float)RAND_MAX); + ys2[i] = 0.75f + 0.2f * ((float)rand() / (float)RAND_MAX); + } + + if (ImPlot::BeginPlot("Scatter Plot")) { + ImPlot::PlotScatter("Data 1", xs1, ys1, 100); + ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f); + ImPlot::SetNextMarkerStyle(ImPlotMarker_Square, 6, ImPlot::GetColormapColor(1), IMPLOT_AUTO, ImPlot::GetColormapColor(1)); + ImPlot::PlotScatter("Data 2", xs2, ys2, 50); + ImPlot::PopStyleVar(); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_StairstepPlots() { + static float ys1[21], ys2[21]; + for (int i = 0; i < 21; ++i) { + ys1[i] = 0.75f + 0.2f * sinf(10 * i * 0.05f); + ys2[i] = 0.25f + 0.2f * sinf(10 * i * 0.05f); + } + static ImPlotStairsFlags flags = 0; + CHECKBOX_FLAG(flags, ImPlotStairsFlags_Shaded); + if (ImPlot::BeginPlot("Stairstep Plot")) { + ImPlot::SetupAxes("x","f(x)"); + ImPlot::SetupAxesLimits(0,1,0,1); + + ImPlot::PushStyleColor(ImPlotCol_Line, ImVec4(0.5f,0.5f,0.5f,1.0f)); + ImPlot::PlotLine("##1",ys1,21,0.05f); + ImPlot::PlotLine("##2",ys2,21,0.05f); + ImPlot::PopStyleColor(); + + ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle); + ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL, 0.25f); + ImPlot::PlotStairs("Post Step (default)", ys1, 21, 0.05f, 0, flags); + ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle); + ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL, 0.25f); + ImPlot::PlotStairs("Pre Step", ys2, 21, 0.05f, 0, flags|ImPlotStairsFlags_PreStep); + + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_BarPlots() { + static ImS8 data[10] = {1,2,3,4,5,6,7,8,9,10}; + if (ImPlot::BeginPlot("Bar Plot")) { + ImPlot::PlotBars("Vertical",data,10,0.7,1); + ImPlot::PlotBars("Horizontal",data,10,0.4,1,ImPlotBarsFlags_Horizontal); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_BarGroups() { + static ImS8 data[30] = {83, 67, 23, 89, 83, 78, 91, 82, 85, 90, // midterm + 80, 62, 56, 99, 55, 78, 88, 78, 90, 100, // final + 80, 69, 52, 92, 72, 78, 75, 76, 89, 95}; // course + + static const char* ilabels[] = {"Midterm Exam","Final Exam","Course Grade"}; + static const char* glabels[] = {"S1","S2","S3","S4","S5","S6","S7","S8","S9","S10"}; + static const double positions[] = {0,1,2,3,4,5,6,7,8,9}; + + static int items = 3; + static int groups = 10; + static float size = 0.67f; + + static ImPlotBarGroupsFlags flags = 0; + static bool horz = false; + + ImGui::CheckboxFlags("Stacked", (unsigned int*)&flags, ImPlotBarGroupsFlags_Stacked); + ImGui::SameLine(); + ImGui::Checkbox("Horizontal",&horz); + + ImGui::SliderInt("Items",&items,1,3); + ImGui::SliderFloat("Size",&size,0,1); + + if (ImPlot::BeginPlot("Bar Group")) { + ImPlot::SetupLegend(ImPlotLocation_East, ImPlotLegendFlags_Outside); + if (horz) { + ImPlot::SetupAxes("Score","Student",ImPlotAxisFlags_AutoFit,ImPlotAxisFlags_AutoFit); + ImPlot::SetupAxisTicks(ImAxis_Y1,positions, groups, glabels); + ImPlot::PlotBarGroups(ilabels,data,items,groups,size,0,flags|ImPlotBarGroupsFlags_Horizontal); + } + else { + ImPlot::SetupAxes("Student","Score",ImPlotAxisFlags_AutoFit,ImPlotAxisFlags_AutoFit); + ImPlot::SetupAxisTicks(ImAxis_X1,positions, groups, glabels); + ImPlot::PlotBarGroups(ilabels,data,items,groups,size,0,flags); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_BarStacks() { + + static ImPlotColormap Liars = -1; + if (Liars == -1) { + static const ImU32 Liars_Data[6] = { 4282515870, 4282609140, 4287357182, 4294630301, 4294945280, 4294921472 }; + Liars = ImPlot::AddColormap("Liars", Liars_Data, 6); + } + + static bool diverging = true; + ImGui::Checkbox("Diverging",&diverging); + + static const char* politicians[] = {"Trump","Bachman","Cruz","Gingrich","Palin","Santorum","Walker","Perry","Ryan","McCain","Rubio","Romney","Rand Paul","Christie","Biden","Kasich","Sanders","J Bush","H Clinton","Obama"}; + static int data_reg[] = {18,26,7,14,10,8,6,11,4,4,3,8,6,8,6,5,0,3,1,2, // Pants on Fire + 43,36,30,21,30,27,25,17,11,22,15,16,16,17,12,12,14,6,13,12, // False + 16,13,28,22,15,21,15,18,30,17,24,18,13,10,14,15,17,22,14,12, // Mostly False + 17,10,13,25,12,22,19,26,23,17,22,27,20,26,29,17,18,22,21,27, // Half True + 5,7,16,10,10,12,23,13,17,20,22,16,23,19,20,26,36,29,27,26, // Mostly True + 1,8,6,8,23,10,12,15,15,20,14,15,22,20,19,25,15,18,24,21}; // True + static const char* labels_reg[] = {"Pants on Fire","False","Mostly False","Half True","Mostly True","True"}; + + + static int data_div[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // Pants on Fire (dummy, to order legend logically) + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // False (dummy, to order legend logically) + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // Mostly False (dummy, to order legend logically) + -16,-13,-28,-22,-15,-21,-15,-18,-30,-17,-24,-18,-13,-10,-14,-15,-17,-22,-14,-12, // Mostly False + -43,-36,-30,-21,-30,-27,-25,-17,-11,-22,-15,-16,-16,-17,-12,-12,-14,-6,-13,-12, // False + -18,-26,-7,-14,-10,-8,-6,-11,-4,-4,-3,-8,-6,-8,-6,-5,0,-3,-1,-2, // Pants on Fire + 17,10,13,25,12,22,19,26,23,17,22,27,20,26,29,17,18,22,21,27, // Half True + 5,7,16,10,10,12,23,13,17,20,22,16,23,19,20,26,36,29,27,26, // Mostly True + 1,8,6,8,23,10,12,15,15,20,14,15,22,20,19,25,15,18,24,21}; // True + static const char* labels_div[] = {"Pants on Fire","False","Mostly False","Mostly False","False","Pants on Fire","Half True","Mostly True","True"}; + + ImPlot::PushColormap(Liars); + if (ImPlot::BeginPlot("PolitiFact: Who Lies More?",ImVec2(-1,400),ImPlotFlags_NoMouseText)) { + ImPlot::SetupLegend(ImPlotLocation_South, ImPlotLegendFlags_Outside|ImPlotLegendFlags_Horizontal); + ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_AutoFit|ImPlotAxisFlags_NoDecorations,ImPlotAxisFlags_AutoFit|ImPlotAxisFlags_Invert); + ImPlot::SetupAxisTicks(ImAxis_Y1,0,19,20,politicians,false); + if (diverging) + ImPlot::PlotBarGroups(labels_div,data_div,9,20,0.75,0,ImPlotBarGroupsFlags_Stacked|ImPlotBarGroupsFlags_Horizontal); + else + ImPlot::PlotBarGroups(labels_reg,data_reg,6,20,0.75,0,ImPlotBarGroupsFlags_Stacked|ImPlotBarGroupsFlags_Horizontal); + ImPlot::EndPlot(); + } + ImPlot::PopColormap(); +} + +//----------------------------------------------------------------------------- + +void Demo_ErrorBars() { + static float xs[5] = {1,2,3,4,5}; + static float bar[5] = {1,2,5,3,4}; + static float lin1[5] = {8,8,9,7,8}; + static float lin2[5] = {6,7,6,9,6}; + static float err1[5] = {0.2f, 0.4f, 0.2f, 0.6f, 0.4f}; + static float err2[5] = {0.4f, 0.2f, 0.4f, 0.8f, 0.6f}; + static float err3[5] = {0.09f, 0.14f, 0.09f, 0.12f, 0.16f}; + static float err4[5] = {0.02f, 0.08f, 0.15f, 0.05f, 0.2f}; + + + if (ImPlot::BeginPlot("##ErrorBars")) { + ImPlot::SetupAxesLimits(0, 6, 0, 10); + ImPlot::PlotBars("Bar", xs, bar, 5, 0.5f); + ImPlot::PlotErrorBars("Bar", xs, bar, err1, 5); + ImPlot::SetNextErrorBarStyle(ImPlot::GetColormapColor(1), 0); + ImPlot::PlotErrorBars("Line", xs, lin1, err1, err2, 5); + ImPlot::SetNextMarkerStyle(ImPlotMarker_Square); + ImPlot::PlotLine("Line", xs, lin1, 5); + ImPlot::PushStyleColor(ImPlotCol_ErrorBar, ImPlot::GetColormapColor(2)); + ImPlot::PlotErrorBars("Scatter", xs, lin2, err2, 5); + ImPlot::PlotErrorBars("Scatter", xs, lin2, err3, err4, 5, ImPlotErrorBarsFlags_Horizontal); + ImPlot::PopStyleColor(); + ImPlot::PlotScatter("Scatter", xs, lin2, 5); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_StemPlots() { + static double xs[51], ys1[51], ys2[51]; + for (int i = 0; i < 51; ++i) { + xs[i] = i * 0.02; + ys1[i] = 1.0 + 0.5 * sin(25*xs[i])*cos(2*xs[i]); + ys2[i] = 0.5 + 0.25 * sin(10*xs[i]) * sin(xs[i]); + } + if (ImPlot::BeginPlot("Stem Plots")) { + ImPlot::SetupAxisLimits(ImAxis_X1,0,1.0); + ImPlot::SetupAxisLimits(ImAxis_Y1,0,1.6); + ImPlot::PlotStems("Stems 1",xs,ys1,51); + ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle); + ImPlot::PlotStems("Stems 2", xs, ys2,51); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_InfiniteLines() { + static double vals[] = {0.25, 0.5, 0.75}; + if (ImPlot::BeginPlot("##Infinite")) { + ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_NoInitialFit,ImPlotAxisFlags_NoInitialFit); + ImPlot::PlotInfLines("Vertical",vals,3); + ImPlot::PlotInfLines("Horizontal",vals,3,ImPlotInfLinesFlags_Horizontal); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_PieCharts() { + static const char* labels1[] = {"Frogs","Hogs","Dogs","Logs"}; + static float data1[] = {0.15f, 0.30f, 0.2f, 0.05f}; + static ImPlotPieChartFlags flags = 0; + ImGui::SetNextItemWidth(250); + ImGui::DragFloat4("Values", data1, 0.01f, 0, 1); + CHECKBOX_FLAG(flags, ImPlotPieChartFlags_Normalize); + CHECKBOX_FLAG(flags, ImPlotPieChartFlags_IgnoreHidden); + + if (ImPlot::BeginPlot("##Pie1", ImVec2(250,250), ImPlotFlags_Equal | ImPlotFlags_NoMouseText)) { + ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations); + ImPlot::SetupAxesLimits(0, 1, 0, 1); + ImPlot::PlotPieChart(labels1, data1, 4, 0.5, 0.5, 0.4, "%.2f", 90, flags); + ImPlot::EndPlot(); + } + + ImGui::SameLine(); + + static const char* labels2[] = {"A","B","C","D","E"}; + static int data2[] = {1,1,2,3,5}; + + ImPlot::PushColormap(ImPlotColormap_Pastel); + if (ImPlot::BeginPlot("##Pie2", ImVec2(250,250), ImPlotFlags_Equal | ImPlotFlags_NoMouseText)) { + ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations); + ImPlot::SetupAxesLimits(0, 1, 0, 1); + ImPlot::PlotPieChart(labels2, data2, 5, 0.5, 0.5, 0.4, "%.0f", 180, flags); + ImPlot::EndPlot(); + } + ImPlot::PopColormap(); +} + +//----------------------------------------------------------------------------- + +void Demo_Heatmaps() { + static float values1[7][7] = {{0.8f, 2.4f, 2.5f, 3.9f, 0.0f, 4.0f, 0.0f}, + {2.4f, 0.0f, 4.0f, 1.0f, 2.7f, 0.0f, 0.0f}, + {1.1f, 2.4f, 0.8f, 4.3f, 1.9f, 4.4f, 0.0f}, + {0.6f, 0.0f, 0.3f, 0.0f, 3.1f, 0.0f, 0.0f}, + {0.7f, 1.7f, 0.6f, 2.6f, 2.2f, 6.2f, 0.0f}, + {1.3f, 1.2f, 0.0f, 0.0f, 0.0f, 3.2f, 5.1f}, + {0.1f, 2.0f, 0.0f, 1.4f, 0.0f, 1.9f, 6.3f}}; + static float scale_min = 0; + static float scale_max = 6.3f; + static const char* xlabels[] = {"C1","C2","C3","C4","C5","C6","C7"}; + static const char* ylabels[] = {"R1","R2","R3","R4","R5","R6","R7"}; + + static ImPlotColormap map = ImPlotColormap_Viridis; + if (ImPlot::ColormapButton(ImPlot::GetColormapName(map),ImVec2(225,0),map)) { + map = (map + 1) % ImPlot::GetColormapCount(); + // We bust the color cache of our plots so that item colors will + // resample the new colormap in the event that they have already + // been created. See documentation in implot.h. + BustColorCache("##Heatmap1"); + BustColorCache("##Heatmap2"); + } + + ImGui::SameLine(); + ImGui::LabelText("##Colormap Index", "%s", "Change Colormap"); + ImGui::SetNextItemWidth(225); + ImGui::DragFloatRange2("Min / Max",&scale_min, &scale_max, 0.01f, -20, 20); + + static ImPlotHeatmapFlags hm_flags = 0; + + ImGui::CheckboxFlags("Column Major", (unsigned int*)&hm_flags, ImPlotHeatmapFlags_ColMajor); + + static ImPlotAxisFlags axes_flags = ImPlotAxisFlags_Lock | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoTickMarks; + + ImPlot::PushColormap(map); + + if (ImPlot::BeginPlot("##Heatmap1",ImVec2(225,225),ImPlotFlags_NoLegend|ImPlotFlags_NoMouseText)) { + ImPlot::SetupAxes(nullptr, nullptr, axes_flags, axes_flags); + ImPlot::SetupAxisTicks(ImAxis_X1,0 + 1.0/14.0, 1 - 1.0/14.0, 7, xlabels); + ImPlot::SetupAxisTicks(ImAxis_Y1,1 - 1.0/14.0, 0 + 1.0/14.0, 7, ylabels); + ImPlot::PlotHeatmap("heat",values1[0],7,7,scale_min,scale_max,"%g",ImPlotPoint(0,0),ImPlotPoint(1,1),hm_flags); + ImPlot::EndPlot(); + } + ImGui::SameLine(); + ImPlot::ColormapScale("##HeatScale",scale_min, scale_max, ImVec2(60,225)); + + ImGui::SameLine(); + + const int size = 80; + static double values2[size*size]; + srand((unsigned int)(ImGui::GetTime()*1000000)); + for (int i = 0; i < size*size; ++i) + values2[i] = RandomRange(0.0,1.0); + + if (ImPlot::BeginPlot("##Heatmap2",ImVec2(225,225))) { + ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations); + ImPlot::SetupAxesLimits(-1,1,-1,1); + ImPlot::PlotHeatmap("heat1",values2,size,size,0,1,nullptr); + ImPlot::PlotHeatmap("heat2",values2,size,size,0,1,nullptr, ImPlotPoint(-1,-1), ImPlotPoint(0,0)); + ImPlot::EndPlot(); + } + ImPlot::PopColormap(); + +} + +//----------------------------------------------------------------------------- + +void Demo_Histogram() { + static ImPlotHistogramFlags hist_flags = ImPlotHistogramFlags_Density; + static int bins = 50; + static double mu = 5; + static double sigma = 2; + ImGui::SetNextItemWidth(200); + if (ImGui::RadioButton("Sqrt",bins==ImPlotBin_Sqrt)) { bins = ImPlotBin_Sqrt; } ImGui::SameLine(); + if (ImGui::RadioButton("Sturges",bins==ImPlotBin_Sturges)) { bins = ImPlotBin_Sturges; } ImGui::SameLine(); + if (ImGui::RadioButton("Rice",bins==ImPlotBin_Rice)) { bins = ImPlotBin_Rice; } ImGui::SameLine(); + if (ImGui::RadioButton("Scott",bins==ImPlotBin_Scott)) { bins = ImPlotBin_Scott; } ImGui::SameLine(); + if (ImGui::RadioButton("N Bins",bins>=0)) { bins = 50; } + if (bins>=0) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(200); + ImGui::SliderInt("##Bins", &bins, 1, 100); + } + ImGui::CheckboxFlags("Horizontal", (unsigned int*)&hist_flags, ImPlotHistogramFlags_Horizontal); + ImGui::SameLine(); + ImGui::CheckboxFlags("Density", (unsigned int*)&hist_flags, ImPlotHistogramFlags_Density); + ImGui::SameLine(); + ImGui::CheckboxFlags("Cumulative", (unsigned int*)&hist_flags, ImPlotHistogramFlags_Cumulative); + + static bool range = false; + ImGui::Checkbox("Range", &range); + static float rmin = -3; + static float rmax = 13; + if (range) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(200); + ImGui::DragFloat2("##Range",&rmin,0.1f,-3,13); + ImGui::SameLine(); + ImGui::CheckboxFlags("Exclude Outliers", (unsigned int*)&hist_flags, ImPlotHistogramFlags_NoOutliers); + } + static NormalDistribution<10000> dist(mu, sigma); + static double x[100]; + static double y[100]; + if (hist_flags & ImPlotHistogramFlags_Density) { + for (int i = 0; i < 100; ++i) { + x[i] = -3 + 16 * (double)i/99.0; + y[i] = exp( - (x[i]-mu)*(x[i]-mu) / (2*sigma*sigma)) / (sigma * sqrt(2*3.141592653589793238)); + } + if (hist_flags & ImPlotHistogramFlags_Cumulative) { + for (int i = 1; i < 100; ++i) + y[i] += y[i-1]; + for (int i = 0; i < 100; ++i) + y[i] /= y[99]; + } + } + + if (ImPlot::BeginPlot("##Histograms")) { + ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_AutoFit,ImPlotAxisFlags_AutoFit); + ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL,0.5f); + ImPlot::PlotHistogram("Empirical", dist.Data, 10000, bins, 1.0, range ? ImPlotRange(rmin,rmax) : ImPlotRange(), hist_flags); + if ((hist_flags & ImPlotHistogramFlags_Density) && !(hist_flags & ImPlotHistogramFlags_NoOutliers)) { + if (hist_flags & ImPlotHistogramFlags_Horizontal) + ImPlot::PlotLine("Theoretical",y,x,100); + else + ImPlot::PlotLine("Theoretical",x,y,100); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_Histogram2D() { + static int count = 50000; + static int xybins[2] = {100,100}; + + static ImPlotHistogramFlags hist_flags = 0; + + ImGui::SliderInt("Count",&count,100,100000); + ImGui::SliderInt2("Bins",xybins,1,500); + ImGui::SameLine(); + ImGui::CheckboxFlags("Density", (unsigned int*)&hist_flags, ImPlotHistogramFlags_Density); + + static NormalDistribution<100000> dist1(1, 2); + static NormalDistribution<100000> dist2(1, 1); + double max_count = 0; + ImPlotAxisFlags flags = ImPlotAxisFlags_AutoFit|ImPlotAxisFlags_Foreground; + ImPlot::PushColormap("Hot"); + if (ImPlot::BeginPlot("##Hist2D",ImVec2(ImGui::GetContentRegionAvail().x-100-ImGui::GetStyle().ItemSpacing.x,0))) { + ImPlot::SetupAxes(nullptr, nullptr, flags, flags); + ImPlot::SetupAxesLimits(-6,6,-6,6); + max_count = ImPlot::PlotHistogram2D("Hist2D",dist1.Data,dist2.Data,count,xybins[0],xybins[1],ImPlotRect(-6,6,-6,6), hist_flags); + ImPlot::EndPlot(); + } + ImGui::SameLine(); + ImPlot::ColormapScale(hist_flags & ImPlotHistogramFlags_Density ? "Density" : "Count",0,max_count,ImVec2(100,0)); + ImPlot::PopColormap(); +} + +//----------------------------------------------------------------------------- + +void Demo_DigitalPlots() { + ImGui::BulletText("Digital plots do not respond to Y drag and zoom, so that"); + ImGui::Indent(); + ImGui::Text("you can drag analog plots over the rising/falling digital edge."); + ImGui::Unindent(); + + static bool paused = false; + static ScrollingBuffer dataDigital[2]; + static ScrollingBuffer dataAnalog[2]; + static bool showDigital[2] = {true, false}; + static bool showAnalog[2] = {true, false}; + + char label[32]; + ImGui::Checkbox("digital_0", &showDigital[0]); ImGui::SameLine(); + ImGui::Checkbox("digital_1", &showDigital[1]); ImGui::SameLine(); + ImGui::Checkbox("analog_0", &showAnalog[0]); ImGui::SameLine(); + ImGui::Checkbox("analog_1", &showAnalog[1]); + + static float t = 0; + if (!paused) { + t += ImGui::GetIO().DeltaTime; + //digital signal values + if (showDigital[0]) + dataDigital[0].AddPoint(t, sinf(2*t) > 0.45); + if (showDigital[1]) + dataDigital[1].AddPoint(t, sinf(2*t) < 0.45); + //Analog signal values + if (showAnalog[0]) + dataAnalog[0].AddPoint(t, sinf(2*t)); + if (showAnalog[1]) + dataAnalog[1].AddPoint(t, cosf(2*t)); + } + if (ImPlot::BeginPlot("##Digital")) { + ImPlot::SetupAxisLimits(ImAxis_X1, t - 10.0, t, paused ? ImGuiCond_Once : ImGuiCond_Always); + ImPlot::SetupAxisLimits(ImAxis_Y1, -1, 1); + for (int i = 0; i < 2; ++i) { + if (showDigital[i] && dataDigital[i].Data.size() > 0) { + snprintf(label, sizeof(label), "digital_%d", i); + ImPlot::PlotDigital(label, &dataDigital[i].Data[0].x, &dataDigital[i].Data[0].y, dataDigital[i].Data.size(), 0, dataDigital[i].Offset, 2 * sizeof(float)); + } + } + for (int i = 0; i < 2; ++i) { + if (showAnalog[i]) { + snprintf(label, sizeof(label), "analog_%d", i); + if (dataAnalog[i].Data.size() > 0) + ImPlot::PlotLine(label, &dataAnalog[i].Data[0].x, &dataAnalog[i].Data[0].y, dataAnalog[i].Data.size(), 0, dataAnalog[i].Offset, 2 * sizeof(float)); + } + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_Images() { + ImGui::BulletText("Below we are displaying the font texture, which is the only texture we have\naccess to in this demo."); + ImGui::BulletText("Use the 'ImTextureID' type as storage to pass pointers or identifiers to your\nown texture data."); + ImGui::BulletText("See ImGui Wiki page 'Image Loading and Displaying Examples'."); + static ImVec2 bmin(0,0); + static ImVec2 bmax(1,1); + static ImVec2 uv0(0,0); + static ImVec2 uv1(1,1); + static ImVec4 tint(1,1,1,1); + ImGui::SliderFloat2("Min", &bmin.x, -2, 2, "%.1f"); + ImGui::SliderFloat2("Max", &bmax.x, -2, 2, "%.1f"); + ImGui::SliderFloat2("UV0", &uv0.x, -2, 2, "%.1f"); + ImGui::SliderFloat2("UV1", &uv1.x, -2, 2, "%.1f"); + ImGui::ColorEdit4("Tint",&tint.x); + if (ImPlot::BeginPlot("##image")) { + ImPlot::PlotImage("my image",ImGui::GetIO().Fonts->TexID, bmin, bmax, uv0, uv1, tint); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_RealtimePlots() { + ImGui::BulletText("Move your mouse to change the data!"); + ImGui::BulletText("This example assumes 60 FPS. Higher FPS requires larger buffer size."); + static ScrollingBuffer sdata1, sdata2; + static RollingBuffer rdata1, rdata2; + ImVec2 mouse = ImGui::GetMousePos(); + static float t = 0; + t += ImGui::GetIO().DeltaTime; + sdata1.AddPoint(t, mouse.x * 0.0005f); + rdata1.AddPoint(t, mouse.x * 0.0005f); + sdata2.AddPoint(t, mouse.y * 0.0005f); + rdata2.AddPoint(t, mouse.y * 0.0005f); + + static float history = 10.0f; + ImGui::SliderFloat("History",&history,1,30,"%.1f s"); + rdata1.Span = history; + rdata2.Span = history; + + static ImPlotAxisFlags flags = ImPlotAxisFlags_NoTickLabels; + + if (ImPlot::BeginPlot("##Scrolling", ImVec2(-1,150))) { + ImPlot::SetupAxes(nullptr, nullptr, flags, flags); + ImPlot::SetupAxisLimits(ImAxis_X1,t - history, t, ImGuiCond_Always); + ImPlot::SetupAxisLimits(ImAxis_Y1,0,1); + ImPlot::SetNextFillStyle(IMPLOT_AUTO_COL,0.5f); + ImPlot::PlotShaded("Mouse X", &sdata1.Data[0].x, &sdata1.Data[0].y, sdata1.Data.size(), -INFINITY, 0, sdata1.Offset, 2 * sizeof(float)); + ImPlot::PlotLine("Mouse Y", &sdata2.Data[0].x, &sdata2.Data[0].y, sdata2.Data.size(), 0, sdata2.Offset, 2*sizeof(float)); + ImPlot::EndPlot(); + } + if (ImPlot::BeginPlot("##Rolling", ImVec2(-1,150))) { + ImPlot::SetupAxes(nullptr, nullptr, flags, flags); + ImPlot::SetupAxisLimits(ImAxis_X1,0,history, ImGuiCond_Always); + ImPlot::SetupAxisLimits(ImAxis_Y1,0,1); + ImPlot::PlotLine("Mouse X", &rdata1.Data[0].x, &rdata1.Data[0].y, rdata1.Data.size(), 0, 0, 2 * sizeof(float)); + ImPlot::PlotLine("Mouse Y", &rdata2.Data[0].x, &rdata2.Data[0].y, rdata2.Data.size(), 0, 0, 2 * sizeof(float)); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_MarkersAndText() { + static float mk_size = ImPlot::GetStyle().MarkerSize; + static float mk_weight = ImPlot::GetStyle().MarkerWeight; + ImGui::DragFloat("Marker Size",&mk_size,0.1f,2.0f,10.0f,"%.2f px"); + ImGui::DragFloat("Marker Weight", &mk_weight,0.05f,0.5f,3.0f,"%.2f px"); + + if (ImPlot::BeginPlot("##MarkerStyles", ImVec2(-1,0), ImPlotFlags_CanvasOnly)) { + + ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations); + ImPlot::SetupAxesLimits(0, 10, 0, 12); + + ImS8 xs[2] = {1,4}; + ImS8 ys[2] = {10,11}; + + // filled markers + for (int m = 0; m < ImPlotMarker_COUNT; ++m) { + ImGui::PushID(m); + ImPlot::SetNextMarkerStyle(m, mk_size, IMPLOT_AUTO_COL, mk_weight); + ImPlot::PlotLine("##Filled", xs, ys, 2); + ImGui::PopID(); + ys[0]--; ys[1]--; + } + xs[0] = 6; xs[1] = 9; ys[0] = 10; ys[1] = 11; + // open markers + for (int m = 0; m < ImPlotMarker_COUNT; ++m) { + ImGui::PushID(m); + ImPlot::SetNextMarkerStyle(m, mk_size, ImVec4(0,0,0,0), mk_weight); + ImPlot::PlotLine("##Open", xs, ys, 2); + ImGui::PopID(); + ys[0]--; ys[1]--; + } + + ImPlot::PlotText("Filled Markers", 2.5f, 6.0f); + ImPlot::PlotText("Open Markers", 7.5f, 6.0f); + + ImPlot::PushStyleColor(ImPlotCol_InlayText, ImVec4(1,0,1,1)); + ImPlot::PlotText("Vertical Text", 5.0f, 6.0f, ImVec2(0,0), ImPlotTextFlags_Vertical); + ImPlot::PopStyleColor(); + + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_NaNValues() { + + static bool include_nan = true; + static ImPlotLineFlags flags = 0; + + float data1[5] = {0.0f,0.25f,0.5f,0.75f,1.0f}; + float data2[5] = {0.0f,0.25f,0.5f,0.75f,1.0f}; + + if (include_nan) + data1[2] = NAN; + + ImGui::Checkbox("Include NaN",&include_nan); + ImGui::SameLine(); + ImGui::CheckboxFlags("Skip NaN", (unsigned int*)&flags, ImPlotLineFlags_SkipNaN); + + if (ImPlot::BeginPlot("##NaNValues")) { + ImPlot::SetNextMarkerStyle(ImPlotMarker_Square); + ImPlot::PlotLine("line", data1, data2, 5, flags); + ImPlot::PlotBars("bars", data1, 5); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_LogScale() { + static double xs[1001], ys1[1001], ys2[1001], ys3[1001]; + for (int i = 0; i < 1001; ++i) { + xs[i] = i*0.1f; + ys1[i] = sin(xs[i]) + 1; + ys2[i] = log(xs[i]); + ys3[i] = pow(10.0, xs[i]); + } + if (ImPlot::BeginPlot("Log Plot", ImVec2(-1,0))) { + ImPlot::SetupAxisScale(ImAxis_X1, ImPlotScale_Log10); + ImPlot::SetupAxesLimits(0.1, 100, 0, 10); + ImPlot::PlotLine("f(x) = x", xs, xs, 1001); + ImPlot::PlotLine("f(x) = sin(x)+1", xs, ys1, 1001); + ImPlot::PlotLine("f(x) = log(x)", xs, ys2, 1001); + ImPlot::PlotLine("f(x) = 10^x", xs, ys3, 21); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_SymmetricLogScale() { + static double xs[1001], ys1[1001], ys2[1001]; + for (int i = 0; i < 1001; ++i) { + xs[i] = i*0.1f-50; + ys1[i] = sin(xs[i]); + ys2[i] = i*0.002 - 1; + } + if (ImPlot::BeginPlot("SymLog Plot", ImVec2(-1,0))) { + ImPlot::SetupAxisScale(ImAxis_X1, ImPlotScale_SymLog); + ImPlot::PlotLine("f(x) = a*x+b",xs,ys2,1001); + ImPlot::PlotLine("f(x) = sin(x)",xs,ys1,1001); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_TimeScale() { + + static double t_min = 1609459200; // 01/01/2021 @ 12:00:00am (UTC) + static double t_max = 1640995200; // 01/01/2022 @ 12:00:00am (UTC) + + ImGui::BulletText("When ImPlotAxisFlags_Time is enabled on the X-Axis, values are interpreted as\n" + "UNIX timestamps in seconds and axis labels are formated as date/time."); + ImGui::BulletText("By default, labels are in UTC time but can be set to use local time instead."); + + ImGui::Checkbox("Local Time",&ImPlot::GetStyle().UseLocalTime); + ImGui::SameLine(); + ImGui::Checkbox("ISO 8601",&ImPlot::GetStyle().UseISO8601); + ImGui::SameLine(); + ImGui::Checkbox("24 Hour Clock",&ImPlot::GetStyle().Use24HourClock); + + static HugeTimeData* data = nullptr; + if (data == nullptr) { + ImGui::SameLine(); + if (ImGui::Button("Generate Huge Data (~500MB!)")) { + static HugeTimeData sdata(t_min); + data = &sdata; + } + } + + if (ImPlot::BeginPlot("##Time", ImVec2(-1,0))) { + ImPlot::SetupAxisScale(ImAxis_X1, ImPlotScale_Time); + ImPlot::SetupAxesLimits(t_min,t_max,0,1); + if (data != nullptr) { + // downsample our data + int downsample = (int)ImPlot::GetPlotLimits().X.Size() / 1000 + 1; + int start = (int)(ImPlot::GetPlotLimits().X.Min - t_min); + start = start < 0 ? 0 : start > HugeTimeData::Size - 1 ? HugeTimeData::Size - 1 : start; + int end = (int)(ImPlot::GetPlotLimits().X.Max - t_min) + 1000; + end = end < 0 ? 0 : end > HugeTimeData::Size - 1 ? HugeTimeData::Size - 1 : end; + int size = (end - start)/downsample; + // plot it + ImPlot::PlotLine("Time Series", &data->Ts[start], &data->Ys[start], size, 0, 0, sizeof(double)*downsample); + } + // plot time now + double t_now = (double)time(nullptr); + double y_now = HugeTimeData::GetY(t_now); + ImPlot::PlotScatter("Now",&t_now,&y_now,1); + ImPlot::Annotation(t_now,y_now,ImPlot::GetLastItemColor(),ImVec2(10,10),false,"Now"); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +static inline double TransformForward_Sqrt(double v, void*) { + return sqrt(v); +} + +static inline double TransformInverse_Sqrt(double v, void*) { + return v*v; +} + +void Demo_CustomScale() { + static float v[100]; + for (int i = 0; i < 100; ++i) { + v[i] = i*0.01f; + } + if (ImPlot::BeginPlot("Sqrt")) { + ImPlot::SetupAxis(ImAxis_X1, "Linear"); + ImPlot::SetupAxis(ImAxis_Y1, "Sqrt"); + ImPlot::SetupAxisScale(ImAxis_Y1, TransformForward_Sqrt, TransformInverse_Sqrt); + ImPlot::SetupAxisLimitsConstraints(ImAxis_Y1, 0, INFINITY); + ImPlot::PlotLine("##data",v,v,100); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_MultipleAxes() { + static float xs[1001], xs2[1001], ys1[1001], ys2[1001], ys3[1001]; + for (int i = 0; i < 1001; ++i) { + xs[i] = (i*0.1f); + xs2[i] = xs[i] + 10.0f; + ys1[i] = sinf(xs[i]) * 3 + 1; + ys2[i] = cosf(xs[i]) * 0.2f + 0.5f; + ys3[i] = sinf(xs[i]+0.5f) * 100 + 200; + } + + static bool x2_axis = true; + static bool y2_axis = true; + static bool y3_axis = true; + + ImGui::Checkbox("X-Axis 2", &x2_axis); + ImGui::SameLine(); + ImGui::Checkbox("Y-Axis 2", &y2_axis); + ImGui::SameLine(); + ImGui::Checkbox("Y-Axis 3", &y3_axis); + + ImGui::BulletText("You can drag axes to the opposite side of the plot."); + ImGui::BulletText("Hover over legend items to see which axis they are plotted on."); + + if (ImPlot::BeginPlot("Multi-Axis Plot", ImVec2(-1,0))) { + ImPlot::SetupAxes("X-Axis 1", "Y-Axis 1"); + ImPlot::SetupAxesLimits(0, 100, 0, 10); + if (x2_axis) { + ImPlot::SetupAxis(ImAxis_X2, "X-Axis 2",ImPlotAxisFlags_AuxDefault); + ImPlot::SetupAxisLimits(ImAxis_X2, 0, 100); + } + if (y2_axis) { + ImPlot::SetupAxis(ImAxis_Y2, "Y-Axis 2",ImPlotAxisFlags_AuxDefault); + ImPlot::SetupAxisLimits(ImAxis_Y2, 0, 1); + } + if (y3_axis) { + ImPlot::SetupAxis(ImAxis_Y3, "Y-Axis 3",ImPlotAxisFlags_AuxDefault); + ImPlot::SetupAxisLimits(ImAxis_Y3, 0, 300); + } + + ImPlot::PlotLine("f(x) = x", xs, xs, 1001); + if (x2_axis) { + ImPlot::SetAxes(ImAxis_X2, ImAxis_Y1); + ImPlot::PlotLine("f(x) = sin(x)*3+1", xs2, ys1, 1001); + } + if (y2_axis) { + ImPlot::SetAxes(ImAxis_X1, ImAxis_Y2); + ImPlot::PlotLine("f(x) = cos(x)*.2+.5", xs, ys2, 1001); + } + if (x2_axis && y3_axis) { + ImPlot::SetAxes(ImAxis_X2, ImAxis_Y3); + ImPlot::PlotLine("f(x) = sin(x+.5)*100+200 ", xs2, ys3, 1001); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_LinkedAxes() { + static ImPlotRect lims(0,1,0,1); + static bool linkx = true, linky = true; + int data[2] = {0,1}; + ImGui::Checkbox("Link X", &linkx); + ImGui::SameLine(); + ImGui::Checkbox("Link Y", &linky); + + ImGui::DragScalarN("Limits",ImGuiDataType_Double,&lims.X.Min,4,0.01f); + + if (BeginAlignedPlots("AlignedGroup")) { + if (ImPlot::BeginPlot("Plot A")) { + ImPlot::SetupAxisLinks(ImAxis_X1, linkx ? &lims.X.Min : nullptr, linkx ? &lims.X.Max : nullptr); + ImPlot::SetupAxisLinks(ImAxis_Y1, linky ? &lims.Y.Min : nullptr, linky ? &lims.Y.Max : nullptr); + ImPlot::PlotLine("Line",data,2); + ImPlot::EndPlot(); + } + if (ImPlot::BeginPlot("Plot B")) { + ImPlot::SetupAxisLinks(ImAxis_X1, linkx ? &lims.X.Min : nullptr, linkx ? &lims.X.Max : nullptr); + ImPlot::SetupAxisLinks(ImAxis_Y1, linky ? &lims.Y.Min : nullptr, linky ? &lims.Y.Max : nullptr); + ImPlot::PlotLine("Line",data,2); + ImPlot::EndPlot(); + } + ImPlot::EndAlignedPlots(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_AxisConstraints() { + static float constraints[4] = {-10,10,1,20}; + static ImPlotAxisFlags flags; + ImGui::DragFloat2("Limits Constraints", &constraints[0], 0.01f); + ImGui::DragFloat2("Zoom Constraints", &constraints[2], 0.01f); + CHECKBOX_FLAG(flags, ImPlotAxisFlags_PanStretch); + if (ImPlot::BeginPlot("##AxisConstraints",ImVec2(-1,0))) { + ImPlot::SetupAxes("X","Y",flags,flags); + ImPlot::SetupAxesLimits(-1,1,-1,1); + ImPlot::SetupAxisLimitsConstraints(ImAxis_X1,constraints[0], constraints[1]); + ImPlot::SetupAxisZoomConstraints(ImAxis_X1,constraints[2], constraints[3]); + ImPlot::SetupAxisLimitsConstraints(ImAxis_Y1,constraints[0], constraints[1]); + ImPlot::SetupAxisZoomConstraints(ImAxis_Y1,constraints[2], constraints[3]); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_EqualAxes() { + ImGui::BulletText("Equal constraint applies to axis pairs (e.g ImAxis_X1/Y1, ImAxis_X2/Y2)"); + static double xs1[360], ys1[360]; + for (int i = 0; i < 360; ++i) { + double angle = i * 2 * PI / 359.0; + xs1[i] = cos(angle); ys1[i] = sin(angle); + } + float xs2[] = {-1,0,1,0,-1}; + float ys2[] = {0,1,0,-1,0}; + if (ImPlot::BeginPlot("##EqualAxes",ImVec2(-1,0),ImPlotFlags_Equal)) { + ImPlot::SetupAxis(ImAxis_X2, nullptr, ImPlotAxisFlags_AuxDefault); + ImPlot::SetupAxis(ImAxis_Y2, nullptr, ImPlotAxisFlags_AuxDefault); + ImPlot::PlotLine("Circle",xs1,ys1,360); + ImPlot::SetAxes(ImAxis_X2, ImAxis_Y2); + ImPlot::PlotLine("Diamond",xs2,ys2,5); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_AutoFittingData() { + ImGui::BulletText("The Y-axis has been configured to auto-fit to only the data visible in X-axis range."); + ImGui::BulletText("Zoom and pan the X-axis. Disable Stems to see a difference in fit."); + ImGui::BulletText("If ImPlotAxisFlags_RangeFit is disabled, the axis will fit ALL data."); + + static ImPlotAxisFlags xflags = ImPlotAxisFlags_None; + static ImPlotAxisFlags yflags = ImPlotAxisFlags_AutoFit|ImPlotAxisFlags_RangeFit; + + ImGui::TextUnformatted("X: "); ImGui::SameLine(); + ImGui::CheckboxFlags("ImPlotAxisFlags_AutoFit##X", (unsigned int*)&xflags, ImPlotAxisFlags_AutoFit); ImGui::SameLine(); + ImGui::CheckboxFlags("ImPlotAxisFlags_RangeFit##X", (unsigned int*)&xflags, ImPlotAxisFlags_RangeFit); + + ImGui::TextUnformatted("Y: "); ImGui::SameLine(); + ImGui::CheckboxFlags("ImPlotAxisFlags_AutoFit##Y", (unsigned int*)&yflags, ImPlotAxisFlags_AutoFit); ImGui::SameLine(); + ImGui::CheckboxFlags("ImPlotAxisFlags_RangeFit##Y", (unsigned int*)&yflags, ImPlotAxisFlags_RangeFit); + + static double data[101]; + srand(0); + for (int i = 0; i < 101; ++i) + data[i] = 1 + sin(i/10.0f); + + if (ImPlot::BeginPlot("##DataFitting")) { + ImPlot::SetupAxes("X","Y",xflags,yflags); + ImPlot::PlotLine("Line",data,101); + ImPlot::PlotStems("Stems",data,101); + ImPlot::EndPlot(); + }; +} + +//----------------------------------------------------------------------------- + +ImPlotPoint SinewaveGetter(int i, void* data) { + float f = *(float*)data; + return ImPlotPoint(i,sinf(f*i)); +} + +void Demo_SubplotsSizing() { + + static ImPlotSubplotFlags flags = ImPlotSubplotFlags_ShareItems|ImPlotSubplotFlags_NoLegend; + ImGui::CheckboxFlags("ImPlotSubplotFlags_NoResize", (unsigned int*)&flags, ImPlotSubplotFlags_NoResize); + ImGui::CheckboxFlags("ImPlotSubplotFlags_NoTitle", (unsigned int*)&flags, ImPlotSubplotFlags_NoTitle); + + static int rows = 3; + static int cols = 3; + ImGui::SliderInt("Rows",&rows,1,5); + ImGui::SliderInt("Cols",&cols,1,5); + if (rows < 1 || cols < 1) { + ImGui::TextColored(ImVec4(1,0,0,1), "Nice try, but the number of rows and columns must be greater than 0!"); + return; + } + static float rratios[] = {5,1,1,1,1,1}; + static float cratios[] = {5,1,1,1,1,1}; + ImGui::DragScalarN("Row Ratios",ImGuiDataType_Float,rratios,rows,0.01f,nullptr); + ImGui::DragScalarN("Col Ratios",ImGuiDataType_Float,cratios,cols,0.01f,nullptr); + if (ImPlot::BeginSubplots("My Subplots", rows, cols, ImVec2(-1,400), flags, rratios, cratios)) { + int id = 0; + for (int i = 0; i < rows*cols; ++i) { + if (ImPlot::BeginPlot("",ImVec2(),ImPlotFlags_NoLegend)) { + ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_NoDecorations,ImPlotAxisFlags_NoDecorations); + float fi = 0.01f * (i+1); + if (rows*cols > 1) { + ImPlot::SetNextLineStyle(SampleColormap((float)i/(float)(rows*cols-1),ImPlotColormap_Jet)); + } + char label[16]; + snprintf(label, sizeof(label), "data%d", id++); + ImPlot::PlotLineG(label,SinewaveGetter,&fi,1000); + ImPlot::EndPlot(); + } + } + ImPlot::EndSubplots(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_SubplotItemSharing() { + static ImPlotSubplotFlags flags = ImPlotSubplotFlags_ShareItems; + ImGui::CheckboxFlags("ImPlotSubplotFlags_ShareItems", (unsigned int*)&flags, ImPlotSubplotFlags_ShareItems); + ImGui::CheckboxFlags("ImPlotSubplotFlags_ColMajor", (unsigned int*)&flags, ImPlotSubplotFlags_ColMajor); + ImGui::BulletText("Drag and drop items from the legend onto plots (except for 'common')"); + static int rows = 2; + static int cols = 3; + static int id[] = {0,1,2,3,4,5}; + static int curj = -1; + if (ImPlot::BeginSubplots("##ItemSharing", rows, cols, ImVec2(-1,400), flags)) { + ImPlot::SetupLegend(ImPlotLocation_South, ImPlotLegendFlags_Sort|ImPlotLegendFlags_Horizontal); + for (int i = 0; i < rows*cols; ++i) { + if (ImPlot::BeginPlot("")) { + float fc = 0.01f; + ImPlot::PlotLineG("common",SinewaveGetter,&fc,1000); + for (int j = 0; j < 6; ++j) { + if (id[j] == i) { + char label[8]; + float fj = 0.01f * (j+2); + snprintf(label, sizeof(label), "data%d", j); + ImPlot::PlotLineG(label,SinewaveGetter,&fj,1000); + if (ImPlot::BeginDragDropSourceItem(label)) { + curj = j; + ImGui::SetDragDropPayload("MY_DND",nullptr,0); + ImPlot::ItemIcon(GetLastItemColor()); ImGui::SameLine(); + ImGui::TextUnformatted(label); + ImPlot::EndDragDropSource(); + } + } + } + if (ImPlot::BeginDragDropTargetPlot()) { + if (ImGui::AcceptDragDropPayload("MY_DND")) + id[curj] = i; + ImPlot::EndDragDropTarget(); + } + ImPlot::EndPlot(); + } + } + ImPlot::EndSubplots(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_SubplotAxisLinking() { + static ImPlotSubplotFlags flags = ImPlotSubplotFlags_LinkRows | ImPlotSubplotFlags_LinkCols; + ImGui::CheckboxFlags("ImPlotSubplotFlags_LinkRows", (unsigned int*)&flags, ImPlotSubplotFlags_LinkRows); + ImGui::CheckboxFlags("ImPlotSubplotFlags_LinkCols", (unsigned int*)&flags, ImPlotSubplotFlags_LinkCols); + ImGui::CheckboxFlags("ImPlotSubplotFlags_LinkAllX", (unsigned int*)&flags, ImPlotSubplotFlags_LinkAllX); + ImGui::CheckboxFlags("ImPlotSubplotFlags_LinkAllY", (unsigned int*)&flags, ImPlotSubplotFlags_LinkAllY); + + static int rows = 2; + static int cols = 2; + if (ImPlot::BeginSubplots("##AxisLinking", rows, cols, ImVec2(-1,400), flags)) { + for (int i = 0; i < rows*cols; ++i) { + if (ImPlot::BeginPlot("")) { + ImPlot::SetupAxesLimits(0,1000,-1,1); + float fc = 0.01f; + ImPlot::PlotLineG("common",SinewaveGetter,&fc,1000); + ImPlot::EndPlot(); + } + } + ImPlot::EndSubplots(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_LegendOptions() { + static ImPlotLocation loc = ImPlotLocation_East; + ImGui::CheckboxFlags("North", (unsigned int*)&loc, ImPlotLocation_North); ImGui::SameLine(); + ImGui::CheckboxFlags("South", (unsigned int*)&loc, ImPlotLocation_South); ImGui::SameLine(); + ImGui::CheckboxFlags("West", (unsigned int*)&loc, ImPlotLocation_West); ImGui::SameLine(); + ImGui::CheckboxFlags("East", (unsigned int*)&loc, ImPlotLocation_East); + + static ImPlotLegendFlags flags = 0; + + CHECKBOX_FLAG(flags, ImPlotLegendFlags_Horizontal); + CHECKBOX_FLAG(flags, ImPlotLegendFlags_Outside); + CHECKBOX_FLAG(flags, ImPlotLegendFlags_Sort); + + ImGui::SliderFloat2("LegendPadding", (float*)&GetStyle().LegendPadding, 0.0f, 20.0f, "%.0f"); + ImGui::SliderFloat2("LegendInnerPadding", (float*)&GetStyle().LegendInnerPadding, 0.0f, 10.0f, "%.0f"); + ImGui::SliderFloat2("LegendSpacing", (float*)&GetStyle().LegendSpacing, 0.0f, 5.0f, "%.0f"); + + static int num_dummy_items = 25; + ImGui::SliderInt("Num Dummy Items (Demo Scrolling)", &num_dummy_items, 0, 100); + + if (ImPlot::BeginPlot("##Legend",ImVec2(-1,0))) { + ImPlot::SetupLegend(loc, flags); + static MyImPlot::WaveData data1(0.001, 0.2, 4, 0.2); + static MyImPlot::WaveData data2(0.001, 0.2, 4, 0.4); + static MyImPlot::WaveData data3(0.001, 0.2, 4, 0.6); + static MyImPlot::WaveData data4(0.001, 0.2, 4, 0.8); + static MyImPlot::WaveData data5(0.001, 0.2, 4, 1.0); + + ImPlot::PlotLineG("Item 002", MyImPlot::SawWave, &data1, 1000); // "Item B" added to legend + ImPlot::PlotLineG("Item 001##IDText", MyImPlot::SawWave, &data2, 1000); // "Item A" added to legend, text after ## used for ID only + ImPlot::PlotLineG("##NotListed", MyImPlot::SawWave, &data3, 1000); // plotted, but not added to legend + ImPlot::PlotLineG("Item 003", MyImPlot::SawWave, &data4, 1000); // "Item C" added to legend + ImPlot::PlotLineG("Item 003", MyImPlot::SawWave, &data5, 1000); // combined with previous "Item C" + + for (int i = 0; i < num_dummy_items; ++i) { + char label[16]; + snprintf(label, sizeof(label), "Item %03d", i+4); + ImPlot::PlotDummy(label); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_DragPoints() { + ImGui::BulletText("Click and drag each point."); + static ImPlotDragToolFlags flags = ImPlotDragToolFlags_None; + ImGui::CheckboxFlags("NoCursors", (unsigned int*)&flags, ImPlotDragToolFlags_NoCursors); ImGui::SameLine(); + ImGui::CheckboxFlags("NoFit", (unsigned int*)&flags, ImPlotDragToolFlags_NoFit); ImGui::SameLine(); + ImGui::CheckboxFlags("NoInput", (unsigned int*)&flags, ImPlotDragToolFlags_NoInputs); + ImPlotAxisFlags ax_flags = ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoTickMarks; + bool clicked[4] = {false, false, false, false}; + bool hovered[4] = {false, false, false, false}; + bool held[4] = {false, false, false, false}; + if (ImPlot::BeginPlot("##Bezier",ImVec2(-1,0),ImPlotFlags_CanvasOnly)) { + ImPlot::SetupAxes(nullptr,nullptr,ax_flags,ax_flags); + ImPlot::SetupAxesLimits(0,1,0,1); + static ImPlotPoint P[] = {ImPlotPoint(.05f,.05f), ImPlotPoint(0.2,0.4), ImPlotPoint(0.8,0.6), ImPlotPoint(.95f,.95f)}; + + ImPlot::DragPoint(0,&P[0].x,&P[0].y, ImVec4(0,0.9f,0,1),4,flags, &clicked[0], &hovered[0], &held[0]); + ImPlot::DragPoint(1,&P[1].x,&P[1].y, ImVec4(1,0.5f,1,1),4,flags, &clicked[1], &hovered[1], &held[1]); + ImPlot::DragPoint(2,&P[2].x,&P[2].y, ImVec4(0,0.5f,1,1),4,flags, &clicked[2], &hovered[2], &held[2]); + ImPlot::DragPoint(3,&P[3].x,&P[3].y, ImVec4(0,0.9f,0,1),4,flags, &clicked[3], &hovered[3], &held[3]); + + static ImPlotPoint B[100]; + for (int i = 0; i < 100; ++i) { + double t = i / 99.0; + double u = 1 - t; + double w1 = u*u*u; + double w2 = 3*u*u*t; + double w3 = 3*u*t*t; + double w4 = t*t*t; + B[i] = ImPlotPoint(w1*P[0].x + w2*P[1].x + w3*P[2].x + w4*P[3].x, w1*P[0].y + w2*P[1].y + w3*P[2].y + w4*P[3].y); + } + + ImPlot::SetNextLineStyle(ImVec4(1,0.5f,1,1),hovered[1]||held[1] ? 2.0f : 1.0f); + ImPlot::PlotLine("##h1",&P[0].x, &P[0].y, 2, 0, 0, sizeof(ImPlotPoint)); + ImPlot::SetNextLineStyle(ImVec4(0,0.5f,1,1), hovered[2]||held[2] ? 2.0f : 1.0f); + ImPlot::PlotLine("##h2",&P[2].x, &P[2].y, 2, 0, 0, sizeof(ImPlotPoint)); + ImPlot::SetNextLineStyle(ImVec4(0,0.9f,0,1), hovered[0]||held[0]||hovered[3]||held[3] ? 3.0f : 2.0f); + ImPlot::PlotLine("##bez",&B[0].x, &B[0].y, 100, 0, 0, sizeof(ImPlotPoint)); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_DragLines() { + ImGui::BulletText("Click and drag the horizontal and vertical lines."); + static double x1 = 0.2; + static double x2 = 0.8; + static double y1 = 0.25; + static double y2 = 0.75; + static double f = 0.1; + bool clicked = false; + bool hovered = false; + bool held = false; + static ImPlotDragToolFlags flags = ImPlotDragToolFlags_None; + ImGui::CheckboxFlags("NoCursors", (unsigned int*)&flags, ImPlotDragToolFlags_NoCursors); ImGui::SameLine(); + ImGui::CheckboxFlags("NoFit", (unsigned int*)&flags, ImPlotDragToolFlags_NoFit); ImGui::SameLine(); + ImGui::CheckboxFlags("NoInput", (unsigned int*)&flags, ImPlotDragToolFlags_NoInputs); + if (ImPlot::BeginPlot("##lines",ImVec2(-1,0))) { + ImPlot::SetupAxesLimits(0,1,0,1); + ImPlot::DragLineX(0,&x1,ImVec4(1,1,1,1),1,flags); + ImPlot::DragLineX(1,&x2,ImVec4(1,1,1,1),1,flags); + ImPlot::DragLineY(2,&y1,ImVec4(1,1,1,1),1,flags); + ImPlot::DragLineY(3,&y2,ImVec4(1,1,1,1),1,flags); + double xs[1000], ys[1000]; + for (int i = 0; i < 1000; ++i) { + xs[i] = (x2+x1)/2+fabs(x2-x1)*(i/1000.0f - 0.5f); + ys[i] = (y1+y2)/2+fabs(y2-y1)/2*sin(f*i/10); + } + ImPlot::DragLineY(120482,&f,ImVec4(1,0.5f,1,1),1,flags, &clicked, &hovered, &held); + ImPlot::SetNextLineStyle(IMPLOT_AUTO_COL, hovered||held ? 2.0f : 1.0f); + ImPlot::PlotLine("Interactive Data", xs, ys, 1000); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_DragRects() { + + static float x_data[512]; + static float y_data1[512]; + static float y_data2[512]; + static float y_data3[512]; + static float sampling_freq = 44100; + static float freq = 500; + bool clicked = false; + bool hovered = false; + bool held = false; + for (size_t i = 0; i < 512; ++i) { + const float t = i / sampling_freq; + x_data[i] = t; + const float arg = 2 * 3.14f * freq * t; + y_data1[i] = sinf(arg); + y_data2[i] = y_data1[i] * -0.6f + sinf(2 * arg) * 0.4f; + y_data3[i] = y_data2[i] * -0.6f + sinf(3 * arg) * 0.4f; + } + ImGui::BulletText("Click and drag the edges, corners, and center of the rect."); + ImGui::BulletText("Double click edges to expand rect to plot extents."); + static ImPlotRect rect(0.0025,0.0045,0,0.5); + static ImPlotDragToolFlags flags = ImPlotDragToolFlags_None; + ImGui::CheckboxFlags("NoCursors", (unsigned int*)&flags, ImPlotDragToolFlags_NoCursors); ImGui::SameLine(); + ImGui::CheckboxFlags("NoFit", (unsigned int*)&flags, ImPlotDragToolFlags_NoFit); ImGui::SameLine(); + ImGui::CheckboxFlags("NoInput", (unsigned int*)&flags, ImPlotDragToolFlags_NoInputs); + + if (ImPlot::BeginPlot("##Main",ImVec2(-1,150))) { + ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_NoTickLabels,ImPlotAxisFlags_NoTickLabels); + ImPlot::SetupAxesLimits(0,0.01,-1,1); + ImPlot::PlotLine("Signal 1", x_data, y_data1, 512); + ImPlot::PlotLine("Signal 2", x_data, y_data2, 512); + ImPlot::PlotLine("Signal 3", x_data, y_data3, 512); + ImPlot::DragRect(0,&rect.X.Min,&rect.Y.Min,&rect.X.Max,&rect.Y.Max,ImVec4(1,0,1,1),flags, &clicked, &hovered, &held); + ImPlot::EndPlot(); + } + ImVec4 bg_col = held ? ImVec4(0.5f,0,0.5f,1) : (hovered ? ImVec4(0.25f,0,0.25f,1) : ImPlot::GetStyle().Colors[ImPlotCol_PlotBg]); + ImPlot::PushStyleColor(ImPlotCol_PlotBg, bg_col); + if (ImPlot::BeginPlot("##rect",ImVec2(-1,150), ImPlotFlags_CanvasOnly)) { + ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_NoDecorations,ImPlotAxisFlags_NoDecorations); + ImPlot::SetupAxesLimits(rect.X.Min, rect.X.Max, rect.Y.Min, rect.Y.Max, ImGuiCond_Always); + ImPlot::PlotLine("Signal 1", x_data, y_data1, 512); + ImPlot::PlotLine("Signal 2", x_data, y_data2, 512); + ImPlot::PlotLine("Signal 3", x_data, y_data3, 512); + ImPlot::EndPlot(); + } + ImPlot::PopStyleColor(); + ImGui::Text("Rect is %sclicked, %shovered, %sheld", clicked ? "" : "not ", hovered ? "" : "not ", held ? "" : "not "); +} + +//----------------------------------------------------------------------------- + +ImPlotPoint FindCentroid(const ImVector& data, const ImPlotRect& bounds, int& cnt) { + cnt = 0; + ImPlotPoint avg; + ImPlotRect bounds_fixed; + bounds_fixed.X.Min = bounds.X.Min < bounds.X.Max ? bounds.X.Min : bounds.X.Max; + bounds_fixed.X.Max = bounds.X.Min < bounds.X.Max ? bounds.X.Max : bounds.X.Min; + bounds_fixed.Y.Min = bounds.Y.Min < bounds.Y.Max ? bounds.Y.Min : bounds.Y.Max; + bounds_fixed.Y.Max = bounds.Y.Min < bounds.Y.Max ? bounds.Y.Max : bounds.Y.Min; + for (int i = 0; i < data.size(); ++i) { + if (bounds_fixed.Contains(data[i].x, data[i].y)) { + avg.x += data[i].x; + avg.y += data[i].y; + cnt++; + } + } + if (cnt > 0) { + avg.x = avg.x / cnt; + avg.y = avg.y / cnt; + } + return avg; +} + +//----------------------------------------------------------------------------- + +void Demo_Querying() { + static ImVector data; + static ImVector rects; + static ImPlotRect limits, select; + static bool init = true; + if (init) { + for (int i = 0; i < 50; ++i) + { + double x = RandomRange(0.1, 0.9); + double y = RandomRange(0.1, 0.9); + data.push_back(ImPlotPoint(x,y)); + } + init = false; + } + + ImGui::BulletText("Box select and left click mouse to create a new query rect."); + ImGui::BulletText("Ctrl + click in the plot area to draw points."); + + if (ImGui::Button("Clear Queries")) + rects.shrink(0); + + if (ImPlot::BeginPlot("##Centroid")) { + ImPlot::SetupAxesLimits(0,1,0,1); + if (ImPlot::IsPlotHovered() && ImGui::IsMouseClicked(0) && ImGui::GetIO().KeyCtrl) { + ImPlotPoint pt = ImPlot::GetPlotMousePos(); + data.push_back(pt); + } + ImPlot::PlotScatter("Points", &data[0].x, &data[0].y, data.size(), 0, 0, 2 * sizeof(double)); + if (ImPlot::IsPlotSelected()) { + select = ImPlot::GetPlotSelection(); + int cnt; + ImPlotPoint centroid = FindCentroid(data,select,cnt); + if (cnt > 0) { + ImPlot::SetNextMarkerStyle(ImPlotMarker_Square,6); + ImPlot::PlotScatter("Centroid", ¢roid.x, ¢roid.y, 1); + } + if (ImGui::IsMouseClicked(ImPlot::GetInputMap().SelectCancel)) { + CancelPlotSelection(); + rects.push_back(select); + } + } + for (int i = 0; i < rects.size(); ++i) { + int cnt; + ImPlotPoint centroid = FindCentroid(data,rects[i],cnt); + if (cnt > 0) { + ImPlot::SetNextMarkerStyle(ImPlotMarker_Square,6); + ImPlot::PlotScatter("Centroid", ¢roid.x, ¢roid.y, 1); + } + ImPlot::DragRect(i,&rects[i].X.Min,&rects[i].Y.Min,&rects[i].X.Max,&rects[i].Y.Max,ImVec4(1,0,1,1)); + } + limits = ImPlot::GetPlotLimits(); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_Annotations() { + static bool clamp = false; + ImGui::Checkbox("Clamp",&clamp); + if (ImPlot::BeginPlot("##Annotations")) { + ImPlot::SetupAxesLimits(0,2,0,1); + static float p[] = {0.25f, 0.25f, 0.75f, 0.75f, 0.25f}; + ImPlot::PlotScatter("##Points",&p[0],&p[1],4); + ImVec4 col = GetLastItemColor(); + ImPlot::Annotation(0.25,0.25,col,ImVec2(-15,15),clamp,"BL"); + ImPlot::Annotation(0.75,0.25,col,ImVec2(15,15),clamp,"BR"); + ImPlot::Annotation(0.75,0.75,col,ImVec2(15,-15),clamp,"TR"); + ImPlot::Annotation(0.25,0.75,col,ImVec2(-15,-15),clamp,"TL"); + ImPlot::Annotation(0.5,0.5,col,ImVec2(0,0),clamp,"Center"); + + ImPlot::Annotation(1.25,0.75,ImVec4(0,1,0,1),ImVec2(0,0),clamp); + + float bx[] = {1.2f,1.5f,1.8f}; + float by[] = {0.25f, 0.5f, 0.75f}; + ImPlot::PlotBars("##Bars",bx,by,3,0.2); + for (int i = 0; i < 3; ++i) + ImPlot::Annotation(bx[i],by[i],ImVec4(0,0,0,0),ImVec2(0,-5),clamp,"B[%d]=%.2f",i,by[i]); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_Tags() { + static bool show = true; + ImGui::Checkbox("Show Tags",&show); + if (ImPlot::BeginPlot("##Tags")) { + ImPlot::SetupAxis(ImAxis_X2); + ImPlot::SetupAxis(ImAxis_Y2); + if (show) { + ImPlot::TagX(0.25, ImVec4(1,1,0,1)); + ImPlot::TagY(0.75, ImVec4(1,1,0,1)); + static double drag_tag = 0.25; + ImPlot::DragLineY(0,&drag_tag,ImVec4(1,0,0,1),1,ImPlotDragToolFlags_NoFit); + ImPlot::TagY(drag_tag, ImVec4(1,0,0,1), "Drag"); + SetAxes(ImAxis_X2, ImAxis_Y2); + ImPlot::TagX(0.5, ImVec4(0,1,1,1), "%s", "MyTag"); + ImPlot::TagY(0.5, ImVec4(0,1,1,1), "Tag: %d", 42); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_DragAndDrop() { + ImGui::BulletText("Drag/drop items from the left column."); + ImGui::BulletText("Drag/drop items between plots."); + ImGui::Indent(); + ImGui::BulletText("Plot 1 Targets: Plot, Y-Axes, Legend"); + ImGui::BulletText("Plot 1 Sources: Legend Item Labels"); + ImGui::BulletText("Plot 2 Targets: Plot, X-Axis, Y-Axis"); + ImGui::BulletText("Plot 2 Sources: Plot, X-Axis, Y-Axis (hold Ctrl)"); + ImGui::Unindent(); + + // convenience struct to manage DND items; do this however you like + struct MyDndItem { + int Idx; + int Plt; + ImAxis Yax; + char Label[16]; + ImVector Data; + ImVec4 Color; + MyDndItem() { + static int i = 0; + Idx = i++; + Plt = 0; + Yax = ImAxis_Y1; + snprintf(Label, sizeof(Label), "%02d Hz", Idx+1); + Color = RandomColor(); + Data.reserve(1001); + for (int k = 0; k < 1001; ++k) { + float t = k * 1.0f / 999; + Data.push_back(ImVec2(t, 0.5f + 0.5f * sinf(2*3.14f*t*(Idx+1)))); + } + } + void Reset() { Plt = 0; Yax = ImAxis_Y1; } + }; + + const int k_dnd = 20; + static MyDndItem dnd[k_dnd]; + static MyDndItem* dndx = nullptr; // for plot 2 + static MyDndItem* dndy = nullptr; // for plot 2 + + // child window to serve as initial source for our DND items + ImGui::BeginChild("DND_LEFT",ImVec2(100,400)); + if (ImGui::Button("Reset Data")) { + for (int k = 0; k < k_dnd; ++k) + dnd[k].Reset(); + dndx = dndy = nullptr; + } + for (int k = 0; k < k_dnd; ++k) { + if (dnd[k].Plt > 0) + continue; + ImPlot::ItemIcon(dnd[k].Color); ImGui::SameLine(); + ImGui::Selectable(dnd[k].Label, false, 0, ImVec2(100, 0)); + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { + ImGui::SetDragDropPayload("MY_DND", &k, sizeof(int)); + ImPlot::ItemIcon(dnd[k].Color); ImGui::SameLine(); + ImGui::TextUnformatted(dnd[k].Label); + ImGui::EndDragDropSource(); + } + } + ImGui::EndChild(); + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND")) { + int i = *(int*)payload->Data; dnd[i].Reset(); + } + ImGui::EndDragDropTarget(); + } + + ImGui::SameLine(); + ImGui::BeginChild("DND_RIGHT",ImVec2(-1,400)); + // plot 1 (time series) + ImPlotAxisFlags flags = ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoHighlight; + if (ImPlot::BeginPlot("##DND1", ImVec2(-1,195))) { + ImPlot::SetupAxis(ImAxis_X1, nullptr, flags|ImPlotAxisFlags_Lock); + ImPlot::SetupAxis(ImAxis_Y1, "[drop here]", flags); + ImPlot::SetupAxis(ImAxis_Y2, "[drop here]", flags|ImPlotAxisFlags_Opposite); + ImPlot::SetupAxis(ImAxis_Y3, "[drop here]", flags|ImPlotAxisFlags_Opposite); + + for (int k = 0; k < k_dnd; ++k) { + if (dnd[k].Plt == 1 && dnd[k].Data.size() > 0) { + ImPlot::SetAxis(dnd[k].Yax); + ImPlot::SetNextLineStyle(dnd[k].Color); + ImPlot::PlotLine(dnd[k].Label, &dnd[k].Data[0].x, &dnd[k].Data[0].y, dnd[k].Data.size(), 0, 0, 2 * sizeof(float)); + // allow legend item labels to be DND sources + if (ImPlot::BeginDragDropSourceItem(dnd[k].Label)) { + ImGui::SetDragDropPayload("MY_DND", &k, sizeof(int)); + ImPlot::ItemIcon(dnd[k].Color); ImGui::SameLine(); + ImGui::TextUnformatted(dnd[k].Label); + ImPlot::EndDragDropSource(); + } + } + } + // allow the main plot area to be a DND target + if (ImPlot::BeginDragDropTargetPlot()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND")) { + int i = *(int*)payload->Data; dnd[i].Plt = 1; dnd[i].Yax = ImAxis_Y1; + } + ImPlot::EndDragDropTarget(); + } + // allow each y-axis to be a DND target + for (int y = ImAxis_Y1; y <= ImAxis_Y3; ++y) { + if (ImPlot::BeginDragDropTargetAxis(y)) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND")) { + int i = *(int*)payload->Data; dnd[i].Plt = 1; dnd[i].Yax = y; + } + ImPlot::EndDragDropTarget(); + } + } + // allow the legend to be a DND target + if (ImPlot::BeginDragDropTargetLegend()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND")) { + int i = *(int*)payload->Data; dnd[i].Plt = 1; dnd[i].Yax = ImAxis_Y1; + } + ImPlot::EndDragDropTarget(); + } + ImPlot::EndPlot(); + } + // plot 2 (Lissajous) + if (ImPlot::BeginPlot("##DND2", ImVec2(-1,195))) { + ImPlot::PushStyleColor(ImPlotCol_AxisBg, dndx != nullptr ? dndx->Color : ImPlot::GetStyle().Colors[ImPlotCol_AxisBg]); + ImPlot::SetupAxis(ImAxis_X1, dndx == nullptr ? "[drop here]" : dndx->Label, flags); + ImPlot::PushStyleColor(ImPlotCol_AxisBg, dndy != nullptr ? dndy->Color : ImPlot::GetStyle().Colors[ImPlotCol_AxisBg]); + ImPlot::SetupAxis(ImAxis_Y1, dndy == nullptr ? "[drop here]" : dndy->Label, flags); + ImPlot::PopStyleColor(2); + if (dndx != nullptr && dndy != nullptr) { + ImVec4 mixed((dndx->Color.x + dndy->Color.x)/2,(dndx->Color.y + dndy->Color.y)/2,(dndx->Color.z + dndy->Color.z)/2,(dndx->Color.w + dndy->Color.w)/2); + ImPlot::SetNextLineStyle(mixed); + ImPlot::PlotLine("##dndxy", &dndx->Data[0].y, &dndy->Data[0].y, dndx->Data.size(), 0, 0, 2 * sizeof(float)); + } + // allow the x-axis to be a DND target + if (ImPlot::BeginDragDropTargetAxis(ImAxis_X1)) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND")) { + int i = *(int*)payload->Data; dndx = &dnd[i]; + } + ImPlot::EndDragDropTarget(); + } + // allow the x-axis to be a DND source + if (dndx != nullptr && ImPlot::BeginDragDropSourceAxis(ImAxis_X1)) { + ImGui::SetDragDropPayload("MY_DND", &dndx->Idx, sizeof(int)); + ImPlot::ItemIcon(dndx->Color); ImGui::SameLine(); + ImGui::TextUnformatted(dndx->Label); + ImPlot::EndDragDropSource(); + } + // allow the y-axis to be a DND target + if (ImPlot::BeginDragDropTargetAxis(ImAxis_Y1)) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND")) { + int i = *(int*)payload->Data; dndy = &dnd[i]; + } + ImPlot::EndDragDropTarget(); + } + // allow the y-axis to be a DND source + if (dndy != nullptr && ImPlot::BeginDragDropSourceAxis(ImAxis_Y1)) { + ImGui::SetDragDropPayload("MY_DND", &dndy->Idx, sizeof(int)); + ImPlot::ItemIcon(dndy->Color); ImGui::SameLine(); + ImGui::TextUnformatted(dndy->Label); + ImPlot::EndDragDropSource(); + } + // allow the plot area to be a DND target + if (ImPlot::BeginDragDropTargetPlot()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND")) { + int i = *(int*)payload->Data; dndx = dndy = &dnd[i]; + } + } + // allow the plot area to be a DND source + if (ImPlot::BeginDragDropSourcePlot()) { + ImGui::TextUnformatted("Yes, you can\ndrag this!"); + ImPlot::EndDragDropSource(); + } + ImPlot::EndPlot(); + } + ImGui::EndChild(); +} + +//----------------------------------------------------------------------------- + +void Demo_Tables() { +#ifdef IMGUI_HAS_TABLE + static ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | + ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable; + static bool anim = true; + static int offset = 0; + ImGui::BulletText("Plots can be used inside of ImGui tables as another means of creating subplots."); + ImGui::Checkbox("Animate",&anim); + if (anim) + offset = (offset + 1) % 100; + if (ImGui::BeginTable("##table", 3, flags, ImVec2(-1,0))) { + ImGui::TableSetupColumn("Electrode", ImGuiTableColumnFlags_WidthFixed, 75.0f); + ImGui::TableSetupColumn("Voltage", ImGuiTableColumnFlags_WidthFixed, 75.0f); + ImGui::TableSetupColumn("EMG Signal"); + ImGui::TableHeadersRow(); + ImPlot::PushColormap(ImPlotColormap_Cool); + for (int row = 0; row < 10; row++) { + ImGui::TableNextRow(); + static float data[100]; + srand(row); + for (int i = 0; i < 100; ++i) + data[i] = RandomRange(0.0f,10.0f); + ImGui::TableSetColumnIndex(0); + ImGui::Text("EMG %d", row); + ImGui::TableSetColumnIndex(1); + ImGui::Text("%.3f V", data[offset]); + ImGui::TableSetColumnIndex(2); + ImGui::PushID(row); + MyImPlot::Sparkline("##spark",data,100,0,11.0f,offset,ImPlot::GetColormapColor(row),ImVec2(-1, 35)); + ImGui::PopID(); + } + ImPlot::PopColormap(); + ImGui::EndTable(); + } +#else + ImGui::BulletText("You need to merge the ImGui 'tables' branch for this section."); +#endif +} + +//----------------------------------------------------------------------------- + +void Demo_OffsetAndStride() { + static const int k_circles = 11; + static const int k_points_per = 50; + static const int k_size = 2 * k_points_per * k_circles; + static double interleaved_data[k_size]; + for (int p = 0; p < k_points_per; ++p) { + for (int c = 0; c < k_circles; ++c) { + double r = (double)c / (k_circles - 1) * 0.2 + 0.2; + interleaved_data[p*2*k_circles + 2*c + 0] = 0.5 + r * cos((double)p/k_points_per * 6.28); + interleaved_data[p*2*k_circles + 2*c + 1] = 0.5 + r * sin((double)p/k_points_per * 6.28); + } + } + static int offset = 0; + ImGui::BulletText("Offsetting is useful for realtime plots (see above) and circular buffers."); + ImGui::BulletText("Striding is useful for interleaved data (e.g. audio) or plotting structs."); + ImGui::BulletText("Here, all circle data is stored in a single interleaved buffer:"); + ImGui::BulletText("[c0.x0 c0.y0 ... cn.x0 cn.y0 c0.x1 c0.y1 ... cn.x1 cn.y1 ... cn.xm cn.ym]"); + ImGui::BulletText("The offset value indicates which circle point index is considered the first."); + ImGui::BulletText("Offsets can be negative and/or larger than the actual data count."); + ImGui::SliderInt("Offset", &offset, -2*k_points_per, 2*k_points_per); + if (ImPlot::BeginPlot("##strideoffset",ImVec2(-1,0),ImPlotFlags_Equal)) { + ImPlot::PushColormap(ImPlotColormap_Jet); + char buff[32]; + for (int c = 0; c < k_circles; ++c) { + snprintf(buff, sizeof(buff), "Circle %d", c); + ImPlot::PlotLine(buff, &interleaved_data[c*2 + 0], &interleaved_data[c*2 + 1], k_points_per, 0, offset, 2*k_circles*sizeof(double)); + } + ImPlot::EndPlot(); + ImPlot::PopColormap(); + } + // offset++; uncomment for animation! +} + +//----------------------------------------------------------------------------- + +void Demo_CustomDataAndGetters() { + ImGui::BulletText("You can plot custom structs using the stride feature."); + ImGui::BulletText("Most plotters can also be passed a function pointer for getting data."); + ImGui::Indent(); + ImGui::BulletText("You can optionally pass user data to be given to your getter function."); + ImGui::BulletText("C++ lambdas can be passed as function pointers as well!"); + ImGui::Unindent(); + + MyImPlot::Vector2f vec2_data[2] = { MyImPlot::Vector2f(0,0), MyImPlot::Vector2f(1,1) }; + + if (ImPlot::BeginPlot("##Custom Data")) { + + // custom structs using stride example: + ImPlot::PlotLine("Vector2f", &vec2_data[0].x, &vec2_data[0].y, 2, 0, 0, sizeof(MyImPlot::Vector2f) /* or sizeof(float) * 2 */); + + // custom getter example 1: + ImPlot::PlotLineG("Spiral", MyImPlot::Spiral, nullptr, 1000); + + // custom getter example 2: + static MyImPlot::WaveData data1(0.001, 0.2, 2, 0.75); + static MyImPlot::WaveData data2(0.001, 0.2, 4, 0.25); + ImPlot::PlotLineG("Waves", MyImPlot::SineWave, &data1, 1000); + ImPlot::PlotLineG("Waves", MyImPlot::SawWave, &data2, 1000); + ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f); + ImPlot::PlotShadedG("Waves", MyImPlot::SineWave, &data1, MyImPlot::SawWave, &data2, 1000); + ImPlot::PopStyleVar(); + + // you can also pass C++ lambdas: + // auto lamda = [](void* data, int idx) { ... return ImPlotPoint(x,y); }; + // ImPlot::PlotLine("My Lambda", lambda, data, 1000); + + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +int MetricFormatter(double value, char* buff, int size, void* data) { + const char* unit = (const char*)data; + static double v[] = {1000000000,1000000,1000,1,0.001,0.000001,0.000000001}; + static const char* p[] = {"G","M","k","","m","u","n"}; + if (value == 0) { + return snprintf(buff,size,"0 %s", unit); + } + for (int i = 0; i < 7; ++i) { + if (fabs(value) >= v[i]) { + return snprintf(buff,size,"%g %s%s",value/v[i],p[i],unit); + } + } + return snprintf(buff,size,"%g %s%s",value/v[6],p[6],unit); +} + +void Demo_TickLabels() { + static bool custom_fmt = true; + static bool custom_ticks = false; + static bool custom_labels = true; + ImGui::Checkbox("Show Custom Format", &custom_fmt); + ImGui::SameLine(); + ImGui::Checkbox("Show Custom Ticks", &custom_ticks); + if (custom_ticks) { + ImGui::SameLine(); + ImGui::Checkbox("Show Custom Labels", &custom_labels); + } + const double pi = 3.14; + const char* pi_str[] = {"PI"}; + static double yticks[] = {100,300,700,900}; + static const char* ylabels[] = {"One","Three","Seven","Nine"}; + static double yticks_aux[] = {0.2,0.4,0.6}; + static const char* ylabels_aux[] = {"A","B","C","D","E","F"}; + if (ImPlot::BeginPlot("##Ticks")) { + ImPlot::SetupAxesLimits(2.5,5,0,1000); + ImPlot::SetupAxis(ImAxis_Y2, nullptr, ImPlotAxisFlags_AuxDefault); + ImPlot::SetupAxis(ImAxis_Y3, nullptr, ImPlotAxisFlags_AuxDefault); + if (custom_fmt) { + ImPlot::SetupAxisFormat(ImAxis_X1, "%g ms"); + ImPlot::SetupAxisFormat(ImAxis_Y1, MetricFormatter, (void*)"Hz"); + ImPlot::SetupAxisFormat(ImAxis_Y2, "%g dB"); + ImPlot::SetupAxisFormat(ImAxis_Y3, MetricFormatter, (void*)"m"); + } + if (custom_ticks) { + ImPlot::SetupAxisTicks(ImAxis_X1, &pi,1,custom_labels ? pi_str : nullptr, true); + ImPlot::SetupAxisTicks(ImAxis_Y1, yticks, 4, custom_labels ? ylabels : nullptr, false); + ImPlot::SetupAxisTicks(ImAxis_Y2, yticks_aux, 3, custom_labels ? ylabels_aux : nullptr, false); + ImPlot::SetupAxisTicks(ImAxis_Y3, 0, 1, 6, custom_labels ? ylabels_aux : nullptr, false); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_CustomStyles() { + ImPlot::PushColormap(ImPlotColormap_Deep); + // normally you wouldn't change the entire style each frame + ImPlotStyle backup = ImPlot::GetStyle(); + MyImPlot::StyleSeaborn(); + if (ImPlot::BeginPlot("seaborn style")) { + ImPlot::SetupAxes( "x-axis", "y-axis"); + ImPlot::SetupAxesLimits(-0.5f, 9.5f, 0, 10); + unsigned int lin[10] = {8,8,9,7,8,8,8,9,7,8}; + unsigned int bar[10] = {1,2,5,3,4,1,2,5,3,4}; + unsigned int dot[10] = {7,6,6,7,8,5,6,5,8,7}; + ImPlot::PlotBars("Bars", bar, 10, 0.5f); + ImPlot::PlotLine("Line", lin, 10); + ImPlot::NextColormapColor(); // skip green + ImPlot::PlotScatter("Scatter", dot, 10); + ImPlot::EndPlot(); + } + ImPlot::GetStyle() = backup; + ImPlot::PopColormap(); +} + +//----------------------------------------------------------------------------- + +void Demo_CustomRendering() { + if (ImPlot::BeginPlot("##CustomRend")) { + ImVec2 cntr = ImPlot::PlotToPixels(ImPlotPoint(0.5f, 0.5f)); + ImVec2 rmin = ImPlot::PlotToPixels(ImPlotPoint(0.25f, 0.75f)); + ImVec2 rmax = ImPlot::PlotToPixels(ImPlotPoint(0.75f, 0.25f)); + ImPlot::PushPlotClipRect(); + ImPlot::GetPlotDrawList()->AddCircleFilled(cntr,20,IM_COL32(255,255,0,255),20); + ImPlot::GetPlotDrawList()->AddRect(rmin, rmax, IM_COL32(128,0,255,255)); + ImPlot::PopPlotClipRect(); + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_LegendPopups() { + ImGui::BulletText("You can implement legend context menus to inject per-item controls and widgets."); + ImGui::BulletText("Right click the legend label/icon to edit custom item attributes."); + + static float frequency = 0.1f; + static float amplitude = 0.5f; + static ImVec4 color = ImVec4(1,1,0,1); + static float alpha = 1.0f; + static bool line = false; + static float thickness = 1; + static bool markers = false; + static bool shaded = false; + + static float vals[101]; + for (int i = 0; i < 101; ++i) + vals[i] = amplitude * sinf(frequency * i); + + if (ImPlot::BeginPlot("Right Click the Legend")) { + ImPlot::SetupAxesLimits(0,100,-1,1); + // rendering logic + ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, alpha); + if (!line) { + ImPlot::SetNextFillStyle(color); + ImPlot::PlotBars("Right Click Me", vals, 101); + } + else { + if (markers) ImPlot::SetNextMarkerStyle(ImPlotMarker_Square); + ImPlot::SetNextLineStyle(color, thickness); + ImPlot::PlotLine("Right Click Me", vals, 101); + if (shaded) ImPlot::PlotShaded("Right Click Me",vals,101); + } + ImPlot::PopStyleVar(); + // custom legend context menu + if (ImPlot::BeginLegendPopup("Right Click Me")) { + ImGui::SliderFloat("Frequency",&frequency,0,1,"%0.2f"); + ImGui::SliderFloat("Amplitude",&litude,0,1,"%0.2f"); + ImGui::Separator(); + ImGui::ColorEdit3("Color",&color.x); + ImGui::SliderFloat("Transparency",&alpha,0,1,"%.2f"); + ImGui::Checkbox("Line Plot", &line); + if (line) { + ImGui::SliderFloat("Thickness", &thickness, 0, 5); + ImGui::Checkbox("Markers", &markers); + ImGui::Checkbox("Shaded",&shaded); + } + ImPlot::EndLegendPopup(); + } + ImPlot::EndPlot(); + } +} + +//----------------------------------------------------------------------------- + +void Demo_ColormapWidgets() { + static int cmap = ImPlotColormap_Viridis; + + if (ImPlot::ColormapButton("Button",ImVec2(0,0),cmap)) { + cmap = (cmap + 1) % ImPlot::GetColormapCount(); + } + + static float t = 0.5f; + static ImVec4 col; + ImGui::ColorButton("##Display",col,ImGuiColorEditFlags_NoInputs); + ImGui::SameLine(); + ImPlot::ColormapSlider("Slider", &t, &col, "%.3f", cmap); + + ImPlot::ColormapIcon(cmap); ImGui::SameLine(); ImGui::Text("Icon"); + + static ImPlotColormapScaleFlags flags = 0; + static float scale[2] = {0, 100}; + ImPlot::ColormapScale("Scale",scale[0],scale[1],ImVec2(0,0),"%g dB",flags,cmap); + ImGui::InputFloat2("Scale",scale); + CHECKBOX_FLAG(flags, ImPlotColormapScaleFlags_NoLabel); + CHECKBOX_FLAG(flags, ImPlotColormapScaleFlags_Opposite); + CHECKBOX_FLAG(flags, ImPlotColormapScaleFlags_Invert); +} + +//----------------------------------------------------------------------------- + +void Demo_CustomPlottersAndTooltips() { + ImGui::BulletText("You can create custom plotters or extend ImPlot using implot_internal.h."); + double dates[] = {1546300800,1546387200,1546473600,1546560000,1546819200,1546905600,1546992000,1547078400,1547164800,1547424000,1547510400,1547596800,1547683200,1547769600,1547942400,1548028800,1548115200,1548201600,1548288000,1548374400,1548633600,1548720000,1548806400,1548892800,1548979200,1549238400,1549324800,1549411200,1549497600,1549584000,1549843200,1549929600,1550016000,1550102400,1550188800,1550361600,1550448000,1550534400,1550620800,1550707200,1550793600,1551052800,1551139200,1551225600,1551312000,1551398400,1551657600,1551744000,1551830400,1551916800,1552003200,1552262400,1552348800,1552435200,1552521600,1552608000,1552867200,1552953600,1553040000,1553126400,1553212800,1553472000,1553558400,1553644800,1553731200,1553817600,1554076800,1554163200,1554249600,1554336000,1554422400,1554681600,1554768000,1554854400,1554940800,1555027200,1555286400,1555372800,1555459200,1555545600,1555632000,1555891200,1555977600,1556064000,1556150400,1556236800,1556496000,1556582400,1556668800,1556755200,1556841600,1557100800,1557187200,1557273600,1557360000,1557446400,1557705600,1557792000,1557878400,1557964800,1558051200,1558310400,1558396800,1558483200,1558569600,1558656000,1558828800,1558915200,1559001600,1559088000,1559174400,1559260800,1559520000,1559606400,1559692800,1559779200,1559865600,1560124800,1560211200,1560297600,1560384000,1560470400,1560729600,1560816000,1560902400,1560988800,1561075200,1561334400,1561420800,1561507200,1561593600,1561680000,1561939200,1562025600,1562112000,1562198400,1562284800,1562544000,1562630400,1562716800,1562803200,1562889600,1563148800,1563235200,1563321600,1563408000,1563494400,1563753600,1563840000,1563926400,1564012800,1564099200,1564358400,1564444800,1564531200,1564617600,1564704000,1564963200,1565049600,1565136000,1565222400,1565308800,1565568000,1565654400,1565740800,1565827200,1565913600,1566172800,1566259200,1566345600,1566432000,1566518400,1566777600,1566864000,1566950400,1567036800,1567123200,1567296000,1567382400,1567468800,1567555200,1567641600,1567728000,1567987200,1568073600,1568160000,1568246400,1568332800,1568592000,1568678400,1568764800,1568851200,1568937600,1569196800,1569283200,1569369600,1569456000,1569542400,1569801600,1569888000,1569974400,1570060800,1570147200,1570406400,1570492800,1570579200,1570665600,1570752000,1571011200,1571097600,1571184000,1571270400,1571356800,1571616000,1571702400,1571788800,1571875200,1571961600}; + double opens[] = {1284.7,1319.9,1318.7,1328,1317.6,1321.6,1314.3,1325,1319.3,1323.1,1324.7,1321.3,1323.5,1322,1281.3,1281.95,1311.1,1315,1314,1313.1,1331.9,1334.2,1341.3,1350.6,1349.8,1346.4,1343.4,1344.9,1335.6,1337.9,1342.5,1337,1338.6,1337,1340.4,1324.65,1324.35,1349.5,1371.3,1367.9,1351.3,1357.8,1356.1,1356,1347.6,1339.1,1320.6,1311.8,1314,1312.4,1312.3,1323.5,1319.1,1327.2,1332.1,1320.3,1323.1,1328,1330.9,1338,1333,1335.3,1345.2,1341.1,1332.5,1314,1314.4,1310.7,1314,1313.1,1315,1313.7,1320,1326.5,1329.2,1314.2,1312.3,1309.5,1297.4,1293.7,1277.9,1295.8,1295.2,1290.3,1294.2,1298,1306.4,1299.8,1302.3,1297,1289.6,1302,1300.7,1303.5,1300.5,1303.2,1306,1318.7,1315,1314.5,1304.1,1294.7,1293.7,1291.2,1290.2,1300.4,1284.2,1284.25,1301.8,1295.9,1296.2,1304.4,1323.1,1340.9,1341,1348,1351.4,1351.4,1343.5,1342.3,1349,1357.6,1357.1,1354.7,1361.4,1375.2,1403.5,1414.7,1433.2,1438,1423.6,1424.4,1418,1399.5,1435.5,1421.25,1434.1,1412.4,1409.8,1412.2,1433.4,1418.4,1429,1428.8,1420.6,1441,1460.4,1441.7,1438.4,1431,1439.3,1427.4,1431.9,1439.5,1443.7,1425.6,1457.5,1451.2,1481.1,1486.7,1512.1,1515.9,1509.2,1522.3,1513,1526.6,1533.9,1523,1506.3,1518.4,1512.4,1508.8,1545.4,1537.3,1551.8,1549.4,1536.9,1535.25,1537.95,1535.2,1556,1561.4,1525.6,1516.4,1507,1493.9,1504.9,1506.5,1513.1,1506.5,1509.7,1502,1506.8,1521.5,1529.8,1539.8,1510.9,1511.8,1501.7,1478,1485.4,1505.6,1511.6,1518.6,1498.7,1510.9,1510.8,1498.3,1492,1497.7,1484.8,1494.2,1495.6,1495.6,1487.5,1491.1,1495.1,1506.4}; + double highs[] = {1284.75,1320.6,1327,1330.8,1326.8,1321.6,1326,1328,1325.8,1327.1,1326,1326,1323.5,1322.1,1282.7,1282.95,1315.8,1316.3,1314,1333.2,1334.7,1341.7,1353.2,1354.6,1352.2,1346.4,1345.7,1344.9,1340.7,1344.2,1342.7,1342.1,1345.2,1342,1350,1324.95,1330.75,1369.6,1374.3,1368.4,1359.8,1359,1357,1356,1353.4,1340.6,1322.3,1314.1,1316.1,1312.9,1325.7,1323.5,1326.3,1336,1332.1,1330.1,1330.4,1334.7,1341.1,1344.2,1338.8,1348.4,1345.6,1342.8,1334.7,1322.3,1319.3,1314.7,1316.6,1316.4,1315,1325.4,1328.3,1332.2,1329.2,1316.9,1312.3,1309.5,1299.6,1296.9,1277.9,1299.5,1296.2,1298.4,1302.5,1308.7,1306.4,1305.9,1307,1297.2,1301.7,1305,1305.3,1310.2,1307,1308,1319.8,1321.7,1318.7,1316.2,1305.9,1295.8,1293.8,1293.7,1304.2,1302,1285.15,1286.85,1304,1302,1305.2,1323,1344.1,1345.2,1360.1,1355.3,1363.8,1353,1344.7,1353.6,1358,1373.6,1358.2,1369.6,1377.6,1408.9,1425.5,1435.9,1453.7,1438,1426,1439.1,1418,1435,1452.6,1426.65,1437.5,1421.5,1414.1,1433.3,1441.3,1431.4,1433.9,1432.4,1440.8,1462.3,1467,1443.5,1444,1442.9,1447,1437.6,1440.8,1445.7,1447.8,1458.2,1461.9,1481.8,1486.8,1522.7,1521.3,1521.1,1531.5,1546.1,1534.9,1537.7,1538.6,1523.6,1518.8,1518.4,1514.6,1540.3,1565,1554.5,1556.6,1559.8,1541.9,1542.9,1540.05,1558.9,1566.2,1561.9,1536.2,1523.8,1509.1,1506.2,1532.2,1516.6,1519.7,1515,1519.5,1512.1,1524.5,1534.4,1543.3,1543.3,1542.8,1519.5,1507.2,1493.5,1511.4,1525.8,1522.2,1518.8,1515.3,1518,1522.3,1508,1501.5,1503,1495.5,1501.1,1497.9,1498.7,1492.1,1499.4,1506.9,1520.9}; + double lows[] = {1282.85,1315,1318.7,1309.6,1317.6,1312.9,1312.4,1319.1,1319,1321,1318.1,1321.3,1319.9,1312,1280.5,1276.15,1308,1309.9,1308.5,1312.3,1329.3,1333.1,1340.2,1347,1345.9,1338,1340.8,1335,1332,1337.9,1333,1336.8,1333.2,1329.9,1340.4,1323.85,1324.05,1349,1366.3,1351.2,1349.1,1352.4,1350.7,1344.3,1338.9,1316.3,1308.4,1306.9,1309.6,1306.7,1312.3,1315.4,1319,1327.2,1317.2,1320,1323,1328,1323,1327.8,1331.7,1335.3,1336.6,1331.8,1311.4,1310,1309.5,1308,1310.6,1302.8,1306.6,1313.7,1320,1322.8,1311,1312.1,1303.6,1293.9,1293.5,1291,1277.9,1294.1,1286,1289.1,1293.5,1296.9,1298,1299.6,1292.9,1285.1,1288.5,1296.3,1297.2,1298.4,1298.6,1302,1300.3,1312,1310.8,1301.9,1292,1291.1,1286.3,1289.2,1289.9,1297.4,1283.65,1283.25,1292.9,1295.9,1290.8,1304.2,1322.7,1336.1,1341,1343.5,1345.8,1340.3,1335.1,1341.5,1347.6,1352.8,1348.2,1353.7,1356.5,1373.3,1398,1414.7,1427,1416.4,1412.7,1420.1,1396.4,1398.8,1426.6,1412.85,1400.7,1406,1399.8,1404.4,1415.5,1417.2,1421.9,1415,1413.7,1428.1,1434,1435.7,1427.5,1429.4,1423.9,1425.6,1427.5,1434.8,1422.3,1412.1,1442.5,1448.8,1468.2,1484.3,1501.6,1506.2,1498.6,1488.9,1504.5,1518.3,1513.9,1503.3,1503,1506.5,1502.1,1503,1534.8,1535.3,1541.4,1528.6,1525.6,1535.25,1528.15,1528,1542.6,1514.3,1510.7,1505.5,1492.1,1492.9,1496.8,1493.1,1503.4,1500.9,1490.7,1496.3,1505.3,1505.3,1517.9,1507.4,1507.1,1493.3,1470.5,1465,1480.5,1501.7,1501.4,1493.3,1492.1,1505.1,1495.7,1478,1487.1,1480.8,1480.6,1487,1488.3,1484.8,1484,1490.7,1490.4,1503.1}; + double closes[] = {1283.35,1315.3,1326.1,1317.4,1321.5,1317.4,1323.5,1319.2,1321.3,1323.3,1319.7,1325.1,1323.6,1313.8,1282.05,1279.05,1314.2,1315.2,1310.8,1329.1,1334.5,1340.2,1340.5,1350,1347.1,1344.3,1344.6,1339.7,1339.4,1343.7,1337,1338.9,1340.1,1338.7,1346.8,1324.25,1329.55,1369.6,1372.5,1352.4,1357.6,1354.2,1353.4,1346,1341,1323.8,1311.9,1309.1,1312.2,1310.7,1324.3,1315.7,1322.4,1333.8,1319.4,1327.1,1325.8,1330.9,1325.8,1331.6,1336.5,1346.7,1339.2,1334.7,1313.3,1316.5,1312.4,1313.4,1313.3,1312.2,1313.7,1319.9,1326.3,1331.9,1311.3,1313.4,1309.4,1295.2,1294.7,1294.1,1277.9,1295.8,1291.2,1297.4,1297.7,1306.8,1299.4,1303.6,1302.2,1289.9,1299.2,1301.8,1303.6,1299.5,1303.2,1305.3,1319.5,1313.6,1315.1,1303.5,1293,1294.6,1290.4,1291.4,1302.7,1301,1284.15,1284.95,1294.3,1297.9,1304.1,1322.6,1339.3,1340.1,1344.9,1354,1357.4,1340.7,1342.7,1348.2,1355.1,1355.9,1354.2,1362.1,1360.1,1408.3,1411.2,1429.5,1430.1,1426.8,1423.4,1425.1,1400.8,1419.8,1432.9,1423.55,1412.1,1412.2,1412.8,1424.9,1419.3,1424.8,1426.1,1423.6,1435.9,1440.8,1439.4,1439.7,1434.5,1436.5,1427.5,1432.2,1433.3,1441.8,1437.8,1432.4,1457.5,1476.5,1484.2,1519.6,1509.5,1508.5,1517.2,1514.1,1527.8,1531.2,1523.6,1511.6,1515.7,1515.7,1508.5,1537.6,1537.2,1551.8,1549.1,1536.9,1529.4,1538.05,1535.15,1555.9,1560.4,1525.5,1515.5,1511.1,1499.2,1503.2,1507.4,1499.5,1511.5,1513.4,1515.8,1506.2,1515.1,1531.5,1540.2,1512.3,1515.2,1506.4,1472.9,1489,1507.9,1513.8,1512.9,1504.4,1503.9,1512.8,1500.9,1488.7,1497.6,1483.5,1494,1498.3,1494.1,1488.1,1487.5,1495.7,1504.7,1505.3}; + static bool tooltip = true; + ImGui::Checkbox("Show Tooltip", &tooltip); + ImGui::SameLine(); + static ImVec4 bullCol = ImVec4(0.000f, 1.000f, 0.441f, 1.000f); + static ImVec4 bearCol = ImVec4(0.853f, 0.050f, 0.310f, 1.000f); + ImGui::SameLine(); ImGui::ColorEdit4("##Bull", &bullCol.x, ImGuiColorEditFlags_NoInputs); + ImGui::SameLine(); ImGui::ColorEdit4("##Bear", &bearCol.x, ImGuiColorEditFlags_NoInputs); + ImPlot::GetStyle().UseLocalTime = false; + + if (ImPlot::BeginPlot("Candlestick Chart",ImVec2(-1,0))) { + ImPlot::SetupAxes(nullptr,nullptr,0,ImPlotAxisFlags_AutoFit|ImPlotAxisFlags_RangeFit); + ImPlot::SetupAxesLimits(1546300800, 1571961600, 1250, 1600); + ImPlot::SetupAxisScale(ImAxis_X1, ImPlotScale_Time); + ImPlot::SetupAxisLimitsConstraints(ImAxis_X1, 1546300800, 1571961600); + ImPlot::SetupAxisZoomConstraints(ImAxis_X1, 60*60*24*14, 1571961600-1546300800); + ImPlot::SetupAxisFormat(ImAxis_Y1, "$%.0f"); + MyImPlot::PlotCandlestick("GOOGL",dates, opens, closes, lows, highs, 218, tooltip, 0.25f, bullCol, bearCol); + ImPlot::EndPlot(); + } + } + +//----------------------------------------------------------------------------- +// DEMO WINDOW +//----------------------------------------------------------------------------- + +void DemoHeader(const char* label, void(*demo)()) { + if (ImGui::TreeNodeEx(label)) { + demo(); + ImGui::TreePop(); + } +} + +void ShowDemoWindow(bool* p_open) { + static bool show_implot_metrics = false; + static bool show_implot_style_editor = false; + static bool show_imgui_metrics = false; + static bool show_imgui_style_editor = false; + static bool show_imgui_demo = false; + + if (show_implot_metrics) { + ImPlot::ShowMetricsWindow(&show_implot_metrics); + } + if (show_implot_style_editor) { + ImGui::SetNextWindowSize(ImVec2(415,762), ImGuiCond_Appearing); + ImGui::Begin("Style Editor (ImPlot)", &show_implot_style_editor); + ImPlot::ShowStyleEditor(); + ImGui::End(); + } + if (show_imgui_style_editor) { + ImGui::Begin("Style Editor (ImGui)", &show_imgui_style_editor); + ImGui::ShowStyleEditor(); + ImGui::End(); + } + if (show_imgui_metrics) { + ImGui::ShowMetricsWindow(&show_imgui_metrics); + } + if (show_imgui_demo) { + ImGui::ShowDemoWindow(&show_imgui_demo); + } + ImGui::SetNextWindowPos(ImVec2(50, 50), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(600, 750), ImGuiCond_FirstUseEver); + ImGui::Begin("ImPlot Demo", p_open, ImGuiWindowFlags_MenuBar); + if (ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu("Tools")) { + ImGui::MenuItem("Metrics", nullptr, &show_implot_metrics); + ImGui::MenuItem("Style Editor", nullptr, &show_implot_style_editor); + ImGui::Separator(); + ImGui::MenuItem("ImGui Metrics", nullptr, &show_imgui_metrics); + ImGui::MenuItem("ImGui Style Editor", nullptr, &show_imgui_style_editor); + ImGui::MenuItem("ImGui Demo", nullptr, &show_imgui_demo); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + //------------------------------------------------------------------------- + ImGui::Text("ImPlot says hello. (%s)", IMPLOT_VERSION); + // display warning about 16-bit indices + static bool showWarning = sizeof(ImDrawIdx)*8 == 16 && (ImGui::GetIO().BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) == false; + if (showWarning) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1,1,0,1)); + ImGui::TextWrapped("WARNING: ImDrawIdx is 16-bit and ImGuiBackendFlags_RendererHasVtxOffset is false. Expect visual glitches and artifacts! See README for more information."); + ImGui::PopStyleColor(); + } + + ImGui::Spacing(); + + if (ImGui::BeginTabBar("ImPlotDemoTabs")) { + if (ImGui::BeginTabItem("Plots")) { + DemoHeader("Line Plots", Demo_LinePlots); + DemoHeader("Filled Line Plots", Demo_FilledLinePlots); + DemoHeader("Shaded Plots##", Demo_ShadedPlots); + DemoHeader("Scatter Plots", Demo_ScatterPlots); + DemoHeader("Realtime Plots", Demo_RealtimePlots); + DemoHeader("Stairstep Plots", Demo_StairstepPlots); + DemoHeader("Bar Plots", Demo_BarPlots); + DemoHeader("Bar Groups", Demo_BarGroups); + DemoHeader("Bar Stacks", Demo_BarStacks); + DemoHeader("Error Bars", Demo_ErrorBars); + DemoHeader("Stem Plots##", Demo_StemPlots); + DemoHeader("Infinite Lines", Demo_InfiniteLines); + DemoHeader("Pie Charts", Demo_PieCharts); + DemoHeader("Heatmaps", Demo_Heatmaps); + DemoHeader("Histogram", Demo_Histogram); + DemoHeader("Histogram 2D", Demo_Histogram2D); + DemoHeader("Digital Plots", Demo_DigitalPlots); + DemoHeader("Images", Demo_Images); + DemoHeader("Markers and Text", Demo_MarkersAndText); + DemoHeader("NaN Values", Demo_NaNValues); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Subplots")) { + DemoHeader("Sizing", Demo_SubplotsSizing); + DemoHeader("Item Sharing", Demo_SubplotItemSharing); + DemoHeader("Axis Linking", Demo_SubplotAxisLinking); + DemoHeader("Tables", Demo_Tables); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Axes")) { + DemoHeader("Log Scale", Demo_LogScale); + DemoHeader("Symmetric Log Scale", Demo_SymmetricLogScale); + DemoHeader("Time Scale", Demo_TimeScale); + DemoHeader("Custom Scale", Demo_CustomScale); + DemoHeader("Multiple Axes", Demo_MultipleAxes); + DemoHeader("Tick Labels", Demo_TickLabels); + DemoHeader("Linked Axes", Demo_LinkedAxes); + DemoHeader("Axis Constraints", Demo_AxisConstraints); + DemoHeader("Equal Axes", Demo_EqualAxes); + DemoHeader("Auto-Fitting Data", Demo_AutoFittingData); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Tools")) { + DemoHeader("Offset and Stride", Demo_OffsetAndStride); + DemoHeader("Drag Points", Demo_DragPoints); + DemoHeader("Drag Lines", Demo_DragLines); + DemoHeader("Drag Rects", Demo_DragRects); + DemoHeader("Querying", Demo_Querying); + DemoHeader("Annotations", Demo_Annotations); + DemoHeader("Tags", Demo_Tags); + DemoHeader("Drag and Drop", Demo_DragAndDrop); + DemoHeader("Legend Options", Demo_LegendOptions); + DemoHeader("Legend Popups", Demo_LegendPopups); + DemoHeader("Colormap Widgets", Demo_ColormapWidgets); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Custom")) { + DemoHeader("Custom Styles", Demo_CustomStyles); + DemoHeader("Custom Data and Getters", Demo_CustomDataAndGetters); + DemoHeader("Custom Rendering", Demo_CustomRendering); + DemoHeader("Custom Plotters and Tooltips", Demo_CustomPlottersAndTooltips); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Config")) { + Demo_Config(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Help")) { + Demo_Help(); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + ImGui::End(); +} + +} // namespace ImPlot + +namespace MyImPlot { + +ImPlotPoint SineWave(int idx, void* data) { + WaveData* wd = (WaveData*)data; + double x = idx * wd->X; + return ImPlotPoint(x, wd->Offset + wd->Amp * sin(2 * 3.14 * wd->Freq * x)); +} + +ImPlotPoint SawWave(int idx, void* data) { + WaveData* wd = (WaveData*)data; + double x = idx * wd->X; + return ImPlotPoint(x, wd->Offset + wd->Amp * (-2 / 3.14 * atan(cos(3.14 * wd->Freq * x) / sin(3.14 * wd->Freq * x)))); +} + +ImPlotPoint Spiral(int idx, void*) { + float r = 0.9f; // outer radius + float a = 0; // inner radius + float b = 0.05f; // increment per rev + float n = (r - a) / b; // number of revolutions + double th = 2 * n * 3.14; // angle + float Th = float(th * idx / (1000 - 1)); + return ImPlotPoint(0.5f+(a + b*Th / (2.0f * (float) 3.14))*cos(Th), + 0.5f + (a + b*Th / (2.0f * (float)3.14))*sin(Th)); +} + +void Sparkline(const char* id, const float* values, int count, float min_v, float max_v, int offset, const ImVec4& col, const ImVec2& size) { + ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, ImVec2(0,0)); + if (ImPlot::BeginPlot(id,size,ImPlotFlags_CanvasOnly)) { + ImPlot::SetupAxes(nullptr,nullptr,ImPlotAxisFlags_NoDecorations,ImPlotAxisFlags_NoDecorations); + ImPlot::SetupAxesLimits(0, count - 1, min_v, max_v, ImGuiCond_Always); + ImPlot::SetNextLineStyle(col); + ImPlot::SetNextFillStyle(col, 0.25); + ImPlot::PlotLine(id, values, count, 1, 0, ImPlotLineFlags_Shaded, offset); + ImPlot::EndPlot(); + } + ImPlot::PopStyleVar(); +} + +void StyleSeaborn() { + + ImPlotStyle& style = ImPlot::GetStyle(); + + ImVec4* colors = style.Colors; + colors[ImPlotCol_Line] = IMPLOT_AUTO_COL; + colors[ImPlotCol_Fill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL; + colors[ImPlotCol_MarkerFill] = IMPLOT_AUTO_COL; + colors[ImPlotCol_ErrorBar] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_PlotBg] = ImVec4(0.92f, 0.92f, 0.95f, 1.00f); + colors[ImPlotCol_PlotBorder] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImPlotCol_LegendBg] = ImVec4(0.92f, 0.92f, 0.95f, 1.00f); + colors[ImPlotCol_LegendBorder] = ImVec4(0.80f, 0.81f, 0.85f, 1.00f); + colors[ImPlotCol_LegendText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_TitleText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_InlayText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_AxisText] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImPlotCol_AxisGrid] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImPlotCol_AxisBgHovered] = ImVec4(0.92f, 0.92f, 0.95f, 1.00f); + colors[ImPlotCol_AxisBgActive] = ImVec4(0.92f, 0.92f, 0.95f, 0.75f); + colors[ImPlotCol_Selection] = ImVec4(1.00f, 0.65f, 0.00f, 1.00f); + colors[ImPlotCol_Crosshairs] = ImVec4(0.23f, 0.10f, 0.64f, 0.50f); + + style.LineWeight = 1.5; + style.Marker = ImPlotMarker_None; + style.MarkerSize = 4; + style.MarkerWeight = 1; + style.FillAlpha = 1.0f; + style.ErrorBarSize = 5; + style.ErrorBarWeight = 1.5f; + style.DigitalBitHeight = 8; + style.DigitalBitGap = 4; + style.PlotBorderSize = 0; + style.MinorAlpha = 1.0f; + style.MajorTickLen = ImVec2(0,0); + style.MinorTickLen = ImVec2(0,0); + style.MajorTickSize = ImVec2(0,0); + style.MinorTickSize = ImVec2(0,0); + style.MajorGridSize = ImVec2(1.2f,1.2f); + style.MinorGridSize = ImVec2(1.2f,1.2f); + style.PlotPadding = ImVec2(12,12); + style.LabelPadding = ImVec2(5,5); + style.LegendPadding = ImVec2(5,5); + style.MousePosPadding = ImVec2(5,5); + style.PlotMinSize = ImVec2(300,225); +} + +} // namespaece MyImPlot + +// WARNING: +// +// You can use "implot_internal.h" to build custom plotting fuctions or extend ImPlot. +// However, note that forward compatibility of this file is not guaranteed and the +// internal API is subject to change. At some point we hope to bring more of this +// into the public API and expose the necessary building blocks to fully support +// custom plotters. For now, proceed at your own risk! + +#include "implot_internal.h" + +namespace MyImPlot { + +template +int BinarySearch(const T* arr, int l, int r, T x) { + if (r >= l) { + int mid = l + (r - l) / 2; + if (arr[mid] == x) + return mid; + if (arr[mid] > x) + return BinarySearch(arr, l, mid - 1, x); + return BinarySearch(arr, mid + 1, r, x); + } + return -1; +} + +void PlotCandlestick(const char* label_id, const double* xs, const double* opens, const double* closes, const double* lows, const double* highs, int count, bool tooltip, float width_percent, ImVec4 bullCol, ImVec4 bearCol) { + + // get ImGui window DrawList + ImDrawList* draw_list = ImPlot::GetPlotDrawList(); + // calc real value width + double half_width = count > 1 ? (xs[1] - xs[0]) * width_percent : width_percent; + + // custom tool + if (ImPlot::IsPlotHovered() && tooltip) { + ImPlotPoint mouse = ImPlot::GetPlotMousePos(); + mouse.x = ImPlot::RoundTime(ImPlotTime::FromDouble(mouse.x), ImPlotTimeUnit_Day).ToDouble(); + float tool_l = ImPlot::PlotToPixels(mouse.x - half_width * 1.5, mouse.y).x; + float tool_r = ImPlot::PlotToPixels(mouse.x + half_width * 1.5, mouse.y).x; + float tool_t = ImPlot::GetPlotPos().y; + float tool_b = tool_t + ImPlot::GetPlotSize().y; + ImPlot::PushPlotClipRect(); + draw_list->AddRectFilled(ImVec2(tool_l, tool_t), ImVec2(tool_r, tool_b), IM_COL32(128,128,128,64)); + ImPlot::PopPlotClipRect(); + // find mouse location index + int idx = BinarySearch(xs, 0, count - 1, mouse.x); + // render tool tip (won't be affected by plot clip rect) + if (idx != -1) { + ImGui::BeginTooltip(); + char buff[32]; + ImPlot::FormatDate(ImPlotTime::FromDouble(xs[idx]),buff,32,ImPlotDateFmt_DayMoYr,ImPlot::GetStyle().UseISO8601); + ImGui::Text("Day: %s", buff); + ImGui::Text("Open: $%.2f", opens[idx]); + ImGui::Text("Close: $%.2f", closes[idx]); + ImGui::Text("Low: $%.2f", lows[idx]); + ImGui::Text("High: $%.2f", highs[idx]); + ImGui::EndTooltip(); + } + } + + // begin plot item + if (ImPlot::BeginItem(label_id)) { + // override legend icon color + ImPlot::GetCurrentItem()->Color = IM_COL32(64,64,64,255); + // fit data if requested + if (ImPlot::FitThisFrame()) { + for (int i = 0; i < count; ++i) { + ImPlot::FitPoint(ImPlotPoint(xs[i], lows[i])); + ImPlot::FitPoint(ImPlotPoint(xs[i], highs[i])); + } + } + // render data + for (int i = 0; i < count; ++i) { + ImVec2 open_pos = ImPlot::PlotToPixels(xs[i] - half_width, opens[i]); + ImVec2 close_pos = ImPlot::PlotToPixels(xs[i] + half_width, closes[i]); + ImVec2 low_pos = ImPlot::PlotToPixels(xs[i], lows[i]); + ImVec2 high_pos = ImPlot::PlotToPixels(xs[i], highs[i]); + ImU32 color = ImGui::GetColorU32(opens[i] > closes[i] ? bearCol : bullCol); + draw_list->AddLine(low_pos, high_pos, color); + draw_list->AddRectFilled(open_pos, close_pos, color); + } + + // end plot item + ImPlot::EndItem(); + } +} + +} // namespace MyImplot diff --git a/libs/implot/implot_internal.h b/libs/implot/implot_internal.h new file mode 100644 index 0000000..cd05478 --- /dev/null +++ b/libs/implot/implot_internal.h @@ -0,0 +1,1669 @@ +// MIT License + +// Copyright (c) 2023 Evan Pezent + +// 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. + +// ImPlot v0.17 + +// You may use this file to debug, understand or extend ImPlot features but we +// don't provide any guarantee of forward compatibility! + +//----------------------------------------------------------------------------- +// [SECTION] Header Mess +//----------------------------------------------------------------------------- + +#pragma once + +#include +#include "imgui_internal.h" + +#ifndef IMPLOT_VERSION +#error Must include implot.h before implot_internal.h +#endif + + +// Support for pre-1.84 versions. ImPool's GetSize() -> GetBufSize() +#if (IMGUI_VERSION_NUM < 18303) +#define GetBufSize GetSize +#endif + +//----------------------------------------------------------------------------- +// [SECTION] Constants +//----------------------------------------------------------------------------- + +// Constants can be changed unless stated otherwise. We may move some of these +// to ImPlotStyleVar_ over time. + +// Mimimum allowable timestamp value 01/01/1970 @ 12:00am (UTC) (DO NOT DECREASE THIS) +#define IMPLOT_MIN_TIME 0 +// Maximum allowable timestamp value 01/01/3000 @ 12:00am (UTC) (DO NOT INCREASE THIS) +#define IMPLOT_MAX_TIME 32503680000 +// Default label format for axis labels +#define IMPLOT_LABEL_FORMAT "%g" +// Max character size for tick labels +#define IMPLOT_LABEL_MAX_SIZE 32 + +//----------------------------------------------------------------------------- +// [SECTION] Macros +//----------------------------------------------------------------------------- + +#define IMPLOT_NUM_X_AXES ImAxis_Y1 +#define IMPLOT_NUM_Y_AXES (ImAxis_COUNT - IMPLOT_NUM_X_AXES) + +// Split ImU32 color into RGB components [0 255] +#define IM_COL32_SPLIT_RGB(col,r,g,b) \ + ImU32 r = ((col >> IM_COL32_R_SHIFT) & 0xFF); \ + ImU32 g = ((col >> IM_COL32_G_SHIFT) & 0xFF); \ + ImU32 b = ((col >> IM_COL32_B_SHIFT) & 0xFF); + +//----------------------------------------------------------------------------- +// [SECTION] Forward Declarations +//----------------------------------------------------------------------------- + +struct ImPlotTick; +struct ImPlotAxis; +struct ImPlotAxisColor; +struct ImPlotItem; +struct ImPlotLegend; +struct ImPlotPlot; +struct ImPlotNextPlotData; +struct ImPlotTicker; + +//----------------------------------------------------------------------------- +// [SECTION] Context Pointer +//----------------------------------------------------------------------------- + +#ifndef GImPlot +extern IMPLOT_API ImPlotContext* GImPlot; // Current implicit context pointer +#endif + +//----------------------------------------------------------------------------- +// [SECTION] Generic Helpers +//----------------------------------------------------------------------------- + +// Computes the common (base-10) logarithm +static inline float ImLog10(float x) { return log10f(x); } +static inline double ImLog10(double x) { return log10(x); } +static inline float ImSinh(float x) { return sinhf(x); } +static inline double ImSinh(double x) { return sinh(x); } +static inline float ImAsinh(float x) { return asinhf(x); } +static inline double ImAsinh(double x) { return asinh(x); } +// Returns true if a flag is set +template +static inline bool ImHasFlag(TSet set, TFlag flag) { return (set & flag) == flag; } +// Flips a flag in a flagset +template +static inline void ImFlipFlag(TSet& set, TFlag flag) { ImHasFlag(set, flag) ? set &= ~flag : set |= flag; } +// Linearly remaps x from [x0 x1] to [y0 y1]. +template +static inline T ImRemap(T x, T x0, T x1, T y0, T y1) { return y0 + (x - x0) * (y1 - y0) / (x1 - x0); } +// Linear rempas x from [x0 x1] to [0 1] +template +static inline T ImRemap01(T x, T x0, T x1) { return (x - x0) / (x1 - x0); } +// Returns always positive modulo (assumes r != 0) +static inline int ImPosMod(int l, int r) { return (l % r + r) % r; } +// Returns true if val is NAN +static inline bool ImNan(double val) { return isnan(val); } +// Returns true if val is NAN or INFINITY +static inline bool ImNanOrInf(double val) { return !(val >= -DBL_MAX && val <= DBL_MAX) || ImNan(val); } +// Turns NANs to 0s +static inline double ImConstrainNan(double val) { return ImNan(val) ? 0 : val; } +// Turns infinity to floating point maximums +static inline double ImConstrainInf(double val) { return val >= DBL_MAX ? DBL_MAX : val <= -DBL_MAX ? - DBL_MAX : val; } +// Turns numbers less than or equal to 0 to 0.001 (sort of arbitrary, is there a better way?) +static inline double ImConstrainLog(double val) { return val <= 0 ? 0.001f : val; } +// Turns numbers less than 0 to zero +static inline double ImConstrainTime(double val) { return val < IMPLOT_MIN_TIME ? IMPLOT_MIN_TIME : (val > IMPLOT_MAX_TIME ? IMPLOT_MAX_TIME : val); } +// True if two numbers are approximately equal using units in the last place. +static inline bool ImAlmostEqual(double v1, double v2, int ulp = 2) { return ImAbs(v1-v2) < DBL_EPSILON * ImAbs(v1+v2) * ulp || ImAbs(v1-v2) < DBL_MIN; } +// Finds min value in an unsorted array +template +static inline T ImMinArray(const T* values, int count) { T m = values[0]; for (int i = 1; i < count; ++i) { if (values[i] < m) { m = values[i]; } } return m; } +// Finds the max value in an unsorted array +template +static inline T ImMaxArray(const T* values, int count) { T m = values[0]; for (int i = 1; i < count; ++i) { if (values[i] > m) { m = values[i]; } } return m; } +// Finds the min and max value in an unsorted array +template +static inline void ImMinMaxArray(const T* values, int count, T* min_out, T* max_out) { + T Min = values[0]; T Max = values[0]; + for (int i = 1; i < count; ++i) { + if (values[i] < Min) { Min = values[i]; } + if (values[i] > Max) { Max = values[i]; } + } + *min_out = Min; *max_out = Max; +} +// Finds the sim of an array +template +static inline T ImSum(const T* values, int count) { + T sum = 0; + for (int i = 0; i < count; ++i) + sum += values[i]; + return sum; +} +// Finds the mean of an array +template +static inline double ImMean(const T* values, int count) { + double den = 1.0 / count; + double mu = 0; + for (int i = 0; i < count; ++i) + mu += (double)values[i] * den; + return mu; +} +// Finds the sample standard deviation of an array +template +static inline double ImStdDev(const T* values, int count) { + double den = 1.0 / (count - 1.0); + double mu = ImMean(values, count); + double x = 0; + for (int i = 0; i < count; ++i) + x += ((double)values[i] - mu) * ((double)values[i] - mu) * den; + return sqrt(x); +} +// Mix color a and b by factor s in [0 256] +static inline ImU32 ImMixU32(ImU32 a, ImU32 b, ImU32 s) { +#ifdef IMPLOT_MIX64 + const ImU32 af = 256-s; + const ImU32 bf = s; + const ImU64 al = (a & 0x00ff00ff) | (((ImU64)(a & 0xff00ff00)) << 24); + const ImU64 bl = (b & 0x00ff00ff) | (((ImU64)(b & 0xff00ff00)) << 24); + const ImU64 mix = (al * af + bl * bf); + return ((mix >> 32) & 0xff00ff00) | ((mix & 0xff00ff00) >> 8); +#else + const ImU32 af = 256-s; + const ImU32 bf = s; + const ImU32 al = (a & 0x00ff00ff); + const ImU32 ah = (a & 0xff00ff00) >> 8; + const ImU32 bl = (b & 0x00ff00ff); + const ImU32 bh = (b & 0xff00ff00) >> 8; + const ImU32 ml = (al * af + bl * bf); + const ImU32 mh = (ah * af + bh * bf); + return (mh & 0xff00ff00) | ((ml & 0xff00ff00) >> 8); +#endif +} + +// Lerp across an array of 32-bit collors given t in [0.0 1.0] +static inline ImU32 ImLerpU32(const ImU32* colors, int size, float t) { + int i1 = (int)((size - 1 ) * t); + int i2 = i1 + 1; + if (i2 == size || size == 1) + return colors[i1]; + float den = 1.0f / (size - 1); + float t1 = i1 * den; + float t2 = i2 * den; + float tr = ImRemap01(t, t1, t2); + return ImMixU32(colors[i1], colors[i2], (ImU32)(tr*256)); +} + +// Set alpha channel of 32-bit color from float in range [0.0 1.0] +static inline ImU32 ImAlphaU32(ImU32 col, float alpha) { + return col & ~((ImU32)((1.0f-alpha)*255)< +static inline bool ImOverlaps(T min_a, T max_a, T min_b, T max_b) { + return min_a <= max_b && min_b <= max_a; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImPlot Enums +//----------------------------------------------------------------------------- + +typedef int ImPlotTimeUnit; // -> enum ImPlotTimeUnit_ +typedef int ImPlotDateFmt; // -> enum ImPlotDateFmt_ +typedef int ImPlotTimeFmt; // -> enum ImPlotTimeFmt_ + +enum ImPlotTimeUnit_ { + ImPlotTimeUnit_Us, // microsecond + ImPlotTimeUnit_Ms, // millisecond + ImPlotTimeUnit_S, // second + ImPlotTimeUnit_Min, // minute + ImPlotTimeUnit_Hr, // hour + ImPlotTimeUnit_Day, // day + ImPlotTimeUnit_Mo, // month + ImPlotTimeUnit_Yr, // year + ImPlotTimeUnit_COUNT +}; + +enum ImPlotDateFmt_ { // default [ ISO 8601 ] + ImPlotDateFmt_None = 0, + ImPlotDateFmt_DayMo, // 10/3 [ --10-03 ] + ImPlotDateFmt_DayMoYr, // 10/3/91 [ 1991-10-03 ] + ImPlotDateFmt_MoYr, // Oct 1991 [ 1991-10 ] + ImPlotDateFmt_Mo, // Oct [ --10 ] + ImPlotDateFmt_Yr // 1991 [ 1991 ] +}; + +enum ImPlotTimeFmt_ { // default [ 24 Hour Clock ] + ImPlotTimeFmt_None = 0, + ImPlotTimeFmt_Us, // .428 552 [ .428 552 ] + ImPlotTimeFmt_SUs, // :29.428 552 [ :29.428 552 ] + ImPlotTimeFmt_SMs, // :29.428 [ :29.428 ] + ImPlotTimeFmt_S, // :29 [ :29 ] + ImPlotTimeFmt_MinSMs, // 21:29.428 [ 21:29.428 ] + ImPlotTimeFmt_HrMinSMs, // 7:21:29.428pm [ 19:21:29.428 ] + ImPlotTimeFmt_HrMinS, // 7:21:29pm [ 19:21:29 ] + ImPlotTimeFmt_HrMin, // 7:21pm [ 19:21 ] + ImPlotTimeFmt_Hr // 7pm [ 19:00 ] +}; + +//----------------------------------------------------------------------------- +// [SECTION] Callbacks +//----------------------------------------------------------------------------- + +typedef void (*ImPlotLocator)(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data); + +//----------------------------------------------------------------------------- +// [SECTION] Structs +//----------------------------------------------------------------------------- + +// Combined date/time format spec +struct ImPlotDateTimeSpec { + ImPlotDateTimeSpec() {} + ImPlotDateTimeSpec(ImPlotDateFmt date_fmt, ImPlotTimeFmt time_fmt, bool use_24_hr_clk = false, bool use_iso_8601 = false) { + Date = date_fmt; + Time = time_fmt; + UseISO8601 = use_iso_8601; + Use24HourClock = use_24_hr_clk; + } + ImPlotDateFmt Date; + ImPlotTimeFmt Time; + bool UseISO8601; + bool Use24HourClock; +}; + +// Two part timestamp struct. +struct ImPlotTime { + time_t S; // second part + int Us; // microsecond part + ImPlotTime() { S = 0; Us = 0; } + ImPlotTime(time_t s, int us = 0) { S = s + us / 1000000; Us = us % 1000000; } + void RollOver() { S = S + Us / 1000000; Us = Us % 1000000; } + double ToDouble() const { return (double)S + (double)Us / 1000000.0; } + static ImPlotTime FromDouble(double t) { return ImPlotTime((time_t)t, (int)(t * 1000000 - floor(t) * 1000000)); } +}; + +static inline ImPlotTime operator+(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return ImPlotTime(lhs.S + rhs.S, lhs.Us + rhs.Us); } +static inline ImPlotTime operator-(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return ImPlotTime(lhs.S - rhs.S, lhs.Us - rhs.Us); } +static inline bool operator==(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return lhs.S == rhs.S && lhs.Us == rhs.Us; } +static inline bool operator<(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return lhs.S == rhs.S ? lhs.Us < rhs.Us : lhs.S < rhs.S; } +static inline bool operator>(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return rhs < lhs; } +static inline bool operator<=(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return lhs < rhs || lhs == rhs; } +static inline bool operator>=(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return lhs > rhs || lhs == rhs; } + +// Colormap data storage +struct ImPlotColormapData { + ImVector Keys; + ImVector KeyCounts; + ImVector KeyOffsets; + ImVector Tables; + ImVector TableSizes; + ImVector TableOffsets; + ImGuiTextBuffer Text; + ImVector TextOffsets; + ImVector Quals; + ImGuiStorage Map; + int Count; + + ImPlotColormapData() { Count = 0; } + + int Append(const char* name, const ImU32* keys, int count, bool qual) { + if (GetIndex(name) != -1) + return -1; + KeyOffsets.push_back(Keys.size()); + KeyCounts.push_back(count); + Keys.reserve(Keys.size()+count); + for (int i = 0; i < count; ++i) + Keys.push_back(keys[i]); + TextOffsets.push_back(Text.size()); + Text.append(name, name + strlen(name) + 1); + Quals.push_back(qual); + ImGuiID id = ImHashStr(name); + int idx = Count++; + Map.SetInt(id,idx); + _AppendTable(idx); + return idx; + } + + void _AppendTable(ImPlotColormap cmap) { + int key_count = GetKeyCount(cmap); + const ImU32* keys = GetKeys(cmap); + int off = Tables.size(); + TableOffsets.push_back(off); + if (IsQual(cmap)) { + Tables.reserve(key_count); + for (int i = 0; i < key_count; ++i) + Tables.push_back(keys[i]); + TableSizes.push_back(key_count); + } + else { + int max_size = 255 * (key_count-1) + 1; + Tables.reserve(off + max_size); + // ImU32 last = keys[0]; + // Tables.push_back(last); + // int n = 1; + for (int i = 0; i < key_count-1; ++i) { + for (int s = 0; s < 255; ++s) { + ImU32 a = keys[i]; + ImU32 b = keys[i+1]; + ImU32 c = ImMixU32(a,b,s); + // if (c != last) { + Tables.push_back(c); + // last = c; + // n++; + // } + } + } + ImU32 c = keys[key_count-1]; + // if (c != last) { + Tables.push_back(c); + // n++; + // } + // TableSizes.push_back(n); + TableSizes.push_back(max_size); + } + } + + void RebuildTables() { + Tables.resize(0); + TableSizes.resize(0); + TableOffsets.resize(0); + for (int i = 0; i < Count; ++i) + _AppendTable(i); + } + + inline bool IsQual(ImPlotColormap cmap) const { return Quals[cmap]; } + inline const char* GetName(ImPlotColormap cmap) const { return cmap < Count ? Text.Buf.Data + TextOffsets[cmap] : nullptr; } + inline ImPlotColormap GetIndex(const char* name) const { ImGuiID key = ImHashStr(name); return Map.GetInt(key,-1); } + + inline const ImU32* GetKeys(ImPlotColormap cmap) const { return &Keys[KeyOffsets[cmap]]; } + inline int GetKeyCount(ImPlotColormap cmap) const { return KeyCounts[cmap]; } + inline ImU32 GetKeyColor(ImPlotColormap cmap, int idx) const { return Keys[KeyOffsets[cmap]+idx]; } + inline void SetKeyColor(ImPlotColormap cmap, int idx, ImU32 value) { Keys[KeyOffsets[cmap]+idx] = value; RebuildTables(); } + + inline const ImU32* GetTable(ImPlotColormap cmap) const { return &Tables[TableOffsets[cmap]]; } + inline int GetTableSize(ImPlotColormap cmap) const { return TableSizes[cmap]; } + inline ImU32 GetTableColor(ImPlotColormap cmap, int idx) const { return Tables[TableOffsets[cmap]+idx]; } + + inline ImU32 LerpTable(ImPlotColormap cmap, float t) const { + int off = TableOffsets[cmap]; + int siz = TableSizes[cmap]; + int idx = Quals[cmap] ? ImClamp((int)(siz*t),0,siz-1) : (int)((siz - 1) * t + 0.5f); + return Tables[off + idx]; + } +}; + +// ImPlotPoint with positive/negative error values +struct ImPlotPointError { + double X, Y, Neg, Pos; + ImPlotPointError(double x, double y, double neg, double pos) { + X = x; Y = y; Neg = neg; Pos = pos; + } +}; + +// Interior plot label/annotation +struct ImPlotAnnotation { + ImVec2 Pos; + ImVec2 Offset; + ImU32 ColorBg; + ImU32 ColorFg; + int TextOffset; + bool Clamp; + ImPlotAnnotation() { + ColorBg = ColorFg = 0; + TextOffset = 0; + Clamp = false; + } +}; + +// Collection of plot labels +struct ImPlotAnnotationCollection { + + ImVector Annotations; + ImGuiTextBuffer TextBuffer; + int Size; + + ImPlotAnnotationCollection() { Reset(); } + + void AppendV(const ImVec2& pos, const ImVec2& off, ImU32 bg, ImU32 fg, bool clamp, const char* fmt, va_list args) IM_FMTLIST(7) { + ImPlotAnnotation an; + an.Pos = pos; an.Offset = off; + an.ColorBg = bg; an.ColorFg = fg; + an.TextOffset = TextBuffer.size(); + an.Clamp = clamp; + Annotations.push_back(an); + TextBuffer.appendfv(fmt, args); + const char nul[] = ""; + TextBuffer.append(nul,nul+1); + Size++; + } + + void Append(const ImVec2& pos, const ImVec2& off, ImU32 bg, ImU32 fg, bool clamp, const char* fmt, ...) IM_FMTARGS(7) { + va_list args; + va_start(args, fmt); + AppendV(pos, off, bg, fg, clamp, fmt, args); + va_end(args); + } + + const char* GetText(int idx) { + return TextBuffer.Buf.Data + Annotations[idx].TextOffset; + } + + void Reset() { + Annotations.shrink(0); + TextBuffer.Buf.shrink(0); + Size = 0; + } +}; + +struct ImPlotTag { + ImAxis Axis; + double Value; + ImU32 ColorBg; + ImU32 ColorFg; + int TextOffset; +}; + +struct ImPlotTagCollection { + + ImVector Tags; + ImGuiTextBuffer TextBuffer; + int Size; + + ImPlotTagCollection() { Reset(); } + + void AppendV(ImAxis axis, double value, ImU32 bg, ImU32 fg, const char* fmt, va_list args) IM_FMTLIST(6) { + ImPlotTag tag; + tag.Axis = axis; + tag.Value = value; + tag.ColorBg = bg; + tag.ColorFg = fg; + tag.TextOffset = TextBuffer.size(); + Tags.push_back(tag); + TextBuffer.appendfv(fmt, args); + const char nul[] = ""; + TextBuffer.append(nul,nul+1); + Size++; + } + + void Append(ImAxis axis, double value, ImU32 bg, ImU32 fg, const char* fmt, ...) IM_FMTARGS(6) { + va_list args; + va_start(args, fmt); + AppendV(axis, value, bg, fg, fmt, args); + va_end(args); + } + + const char* GetText(int idx) { + return TextBuffer.Buf.Data + Tags[idx].TextOffset; + } + + void Reset() { + Tags.shrink(0); + TextBuffer.Buf.shrink(0); + Size = 0; + } +}; + +// Tick mark info +struct ImPlotTick +{ + double PlotPos; + float PixelPos; + ImVec2 LabelSize; + int TextOffset; + bool Major; + bool ShowLabel; + int Level; + int Idx; + + ImPlotTick(double value, bool major, int level, bool show_label) { + PixelPos = 0; + PlotPos = value; + Major = major; + ShowLabel = show_label; + Level = level; + TextOffset = -1; + } +}; + +// Collection of ticks +struct ImPlotTicker { + ImVector Ticks; + ImGuiTextBuffer TextBuffer; + ImVec2 MaxSize; + ImVec2 LateSize; + int Levels; + + ImPlotTicker() { + Reset(); + } + + ImPlotTick& AddTick(double value, bool major, int level, bool show_label, const char* label) { + ImPlotTick tick(value, major, level, show_label); + if (show_label && label != nullptr) { + tick.TextOffset = TextBuffer.size(); + TextBuffer.append(label, label + strlen(label) + 1); + tick.LabelSize = ImGui::CalcTextSize(TextBuffer.Buf.Data + tick.TextOffset); + } + return AddTick(tick); + } + + ImPlotTick& AddTick(double value, bool major, int level, bool show_label, ImPlotFormatter formatter, void* data) { + ImPlotTick tick(value, major, level, show_label); + if (show_label && formatter != nullptr) { + char buff[IMPLOT_LABEL_MAX_SIZE]; + tick.TextOffset = TextBuffer.size(); + formatter(tick.PlotPos, buff, sizeof(buff), data); + TextBuffer.append(buff, buff + strlen(buff) + 1); + tick.LabelSize = ImGui::CalcTextSize(TextBuffer.Buf.Data + tick.TextOffset); + } + return AddTick(tick); + } + + inline ImPlotTick& AddTick(ImPlotTick tick) { + if (tick.ShowLabel) { + MaxSize.x = tick.LabelSize.x > MaxSize.x ? tick.LabelSize.x : MaxSize.x; + MaxSize.y = tick.LabelSize.y > MaxSize.y ? tick.LabelSize.y : MaxSize.y; + } + tick.Idx = Ticks.size(); + Ticks.push_back(tick); + return Ticks.back(); + } + + const char* GetText(int idx) const { + return TextBuffer.Buf.Data + Ticks[idx].TextOffset; + } + + const char* GetText(const ImPlotTick& tick) { + return GetText(tick.Idx); + } + + void OverrideSizeLate(const ImVec2& size) { + LateSize.x = size.x > LateSize.x ? size.x : LateSize.x; + LateSize.y = size.y > LateSize.y ? size.y : LateSize.y; + } + + void Reset() { + Ticks.shrink(0); + TextBuffer.Buf.shrink(0); + MaxSize = LateSize; + LateSize = ImVec2(0,0); + Levels = 1; + } + + int TickCount() const { + return Ticks.Size; + } +}; + +// Axis state information that must persist after EndPlot +struct ImPlotAxis +{ + ImGuiID ID; + ImPlotAxisFlags Flags; + ImPlotAxisFlags PreviousFlags; + ImPlotRange Range; + ImPlotCond RangeCond; + ImPlotScale Scale; + ImPlotRange FitExtents; + ImPlotAxis* OrthoAxis; + ImPlotRange ConstraintRange; + ImPlotRange ConstraintZoom; + + ImPlotTicker Ticker; + ImPlotFormatter Formatter; + void* FormatterData; + char FormatSpec[16]; + ImPlotLocator Locator; + + double* LinkedMin; + double* LinkedMax; + + int PickerLevel; + ImPlotTime PickerTimeMin, PickerTimeMax; + + ImPlotTransform TransformForward; + ImPlotTransform TransformInverse; + void* TransformData; + float PixelMin, PixelMax; + double ScaleMin, ScaleMax; + double ScaleToPixel; + float Datum1, Datum2; + + ImRect HoverRect; + int LabelOffset; + ImU32 ColorMaj, ColorMin, ColorTick, ColorTxt, ColorBg, ColorHov, ColorAct, ColorHiLi; + + bool Enabled; + bool Vertical; + bool FitThisFrame; + bool HasRange; + bool HasFormatSpec; + bool ShowDefaultTicks; + bool Hovered; + bool Held; + + ImPlotAxis() { + ID = 0; + Flags = PreviousFlags = ImPlotAxisFlags_None; + Range.Min = 0; + Range.Max = 1; + Scale = ImPlotScale_Linear; + TransformForward = TransformInverse = nullptr; + TransformData = nullptr; + FitExtents.Min = HUGE_VAL; + FitExtents.Max = -HUGE_VAL; + OrthoAxis = nullptr; + ConstraintRange = ImPlotRange(-INFINITY,INFINITY); + ConstraintZoom = ImPlotRange(DBL_MIN,INFINITY); + LinkedMin = LinkedMax = nullptr; + PickerLevel = 0; + Datum1 = Datum2 = 0; + PixelMin = PixelMax = 0; + LabelOffset = -1; + ColorMaj = ColorMin = ColorTick = ColorTxt = ColorBg = ColorHov = ColorAct = 0; + ColorHiLi = IM_COL32_BLACK_TRANS; + Formatter = nullptr; + FormatterData = nullptr; + Locator = nullptr; + Enabled = Hovered = Held = FitThisFrame = HasRange = HasFormatSpec = false; + ShowDefaultTicks = true; + } + + inline void Reset() { + Enabled = false; + Scale = ImPlotScale_Linear; + TransformForward = TransformInverse = nullptr; + TransformData = nullptr; + LabelOffset = -1; + HasFormatSpec = false; + Formatter = nullptr; + FormatterData = nullptr; + Locator = nullptr; + ShowDefaultTicks = true; + FitThisFrame = false; + FitExtents.Min = HUGE_VAL; + FitExtents.Max = -HUGE_VAL; + OrthoAxis = nullptr; + ConstraintRange = ImPlotRange(-INFINITY,INFINITY); + ConstraintZoom = ImPlotRange(DBL_MIN,INFINITY); + Ticker.Reset(); + } + + inline bool SetMin(double _min, bool force=false) { + if (!force && IsLockedMin()) + return false; + _min = ImConstrainNan(ImConstrainInf(_min)); + if (_min < ConstraintRange.Min) + _min = ConstraintRange.Min; + double z = Range.Max - _min; + if (z < ConstraintZoom.Min) + _min = Range.Max - ConstraintZoom.Min; + if (z > ConstraintZoom.Max) + _min = Range.Max - ConstraintZoom.Max; + if (_min >= Range.Max) + return false; + Range.Min = _min; + PickerTimeMin = ImPlotTime::FromDouble(Range.Min); + UpdateTransformCache(); + return true; + }; + + inline bool SetMax(double _max, bool force=false) { + if (!force && IsLockedMax()) + return false; + _max = ImConstrainNan(ImConstrainInf(_max)); + if (_max > ConstraintRange.Max) + _max = ConstraintRange.Max; + double z = _max - Range.Min; + if (z < ConstraintZoom.Min) + _max = Range.Min + ConstraintZoom.Min; + if (z > ConstraintZoom.Max) + _max = Range.Min + ConstraintZoom.Max; + if (_max <= Range.Min) + return false; + Range.Max = _max; + PickerTimeMax = ImPlotTime::FromDouble(Range.Max); + UpdateTransformCache(); + return true; + }; + + inline void SetRange(double v1, double v2) { + Range.Min = ImMin(v1,v2); + Range.Max = ImMax(v1,v2); + Constrain(); + PickerTimeMin = ImPlotTime::FromDouble(Range.Min); + PickerTimeMax = ImPlotTime::FromDouble(Range.Max); + UpdateTransformCache(); + } + + inline void SetRange(const ImPlotRange& range) { + SetRange(range.Min, range.Max); + } + + inline void SetAspect(double unit_per_pix) { + double new_size = unit_per_pix * PixelSize(); + double delta = (new_size - Range.Size()) * 0.5; + if (IsLocked()) + return; + else if (IsLockedMin() && !IsLockedMax()) + SetRange(Range.Min, Range.Max + 2*delta); + else if (!IsLockedMin() && IsLockedMax()) + SetRange(Range.Min - 2*delta, Range.Max); + else + SetRange(Range.Min - delta, Range.Max + delta); + } + + inline float PixelSize() const { return ImAbs(PixelMax - PixelMin); } + + inline double GetAspect() const { return Range.Size() / PixelSize(); } + + inline void Constrain() { + Range.Min = ImConstrainNan(ImConstrainInf(Range.Min)); + Range.Max = ImConstrainNan(ImConstrainInf(Range.Max)); + if (Range.Min < ConstraintRange.Min) + Range.Min = ConstraintRange.Min; + if (Range.Max > ConstraintRange.Max) + Range.Max = ConstraintRange.Max; + double z = Range.Size(); + if (z < ConstraintZoom.Min) { + double delta = (ConstraintZoom.Min - z) * 0.5; + Range.Min -= delta; + Range.Max += delta; + } + if (z > ConstraintZoom.Max) { + double delta = (z - ConstraintZoom.Max) * 0.5; + Range.Min += delta; + Range.Max -= delta; + } + if (Range.Max <= Range.Min) + Range.Max = Range.Min + DBL_EPSILON; + } + + inline void UpdateTransformCache() { + ScaleToPixel = (PixelMax - PixelMin) / Range.Size(); + if (TransformForward != nullptr) { + ScaleMin = TransformForward(Range.Min, TransformData); + ScaleMax = TransformForward(Range.Max, TransformData); + } + else { + ScaleMin = Range.Min; + ScaleMax = Range.Max; + } + } + + inline float PlotToPixels(double plt) const { + if (TransformForward != nullptr) { + double s = TransformForward(plt, TransformData); + double t = (s - ScaleMin) / (ScaleMax - ScaleMin); + plt = Range.Min + Range.Size() * t; + } + return (float)(PixelMin + ScaleToPixel * (plt - Range.Min)); + } + + + inline double PixelsToPlot(float pix) const { + double plt = (pix - PixelMin) / ScaleToPixel + Range.Min; + if (TransformInverse != nullptr) { + double t = (plt - Range.Min) / Range.Size(); + double s = t * (ScaleMax - ScaleMin) + ScaleMin; + plt = TransformInverse(s, TransformData); + } + return plt; + } + + inline void ExtendFit(double v) { + if (!ImNanOrInf(v) && v >= ConstraintRange.Min && v <= ConstraintRange.Max) { + FitExtents.Min = v < FitExtents.Min ? v : FitExtents.Min; + FitExtents.Max = v > FitExtents.Max ? v : FitExtents.Max; + } + } + + inline void ExtendFitWith(ImPlotAxis& alt, double v, double v_alt) { + if (ImHasFlag(Flags, ImPlotAxisFlags_RangeFit) && !alt.Range.Contains(v_alt)) + return; + if (!ImNanOrInf(v) && v >= ConstraintRange.Min && v <= ConstraintRange.Max) { + FitExtents.Min = v < FitExtents.Min ? v : FitExtents.Min; + FitExtents.Max = v > FitExtents.Max ? v : FitExtents.Max; + } + } + + inline void ApplyFit(float padding) { + const double ext_size = FitExtents.Size() * 0.5; + FitExtents.Min -= ext_size * padding; + FitExtents.Max += ext_size * padding; + if (!IsLockedMin() && !ImNanOrInf(FitExtents.Min)) + Range.Min = FitExtents.Min; + if (!IsLockedMax() && !ImNanOrInf(FitExtents.Max)) + Range.Max = FitExtents.Max; + if (ImAlmostEqual(Range.Min, Range.Max)) { + Range.Max += 0.5; + Range.Min -= 0.5; + } + Constrain(); + UpdateTransformCache(); + } + + inline bool HasLabel() const { return LabelOffset != -1 && !ImHasFlag(Flags, ImPlotAxisFlags_NoLabel); } + inline bool HasGridLines() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoGridLines); } + inline bool HasTickLabels() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoTickLabels); } + inline bool HasTickMarks() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoTickMarks); } + inline bool WillRender() const { return Enabled && (HasGridLines() || HasTickLabels() || HasTickMarks()); } + inline bool IsOpposite() const { return ImHasFlag(Flags, ImPlotAxisFlags_Opposite); } + inline bool IsInverted() const { return ImHasFlag(Flags, ImPlotAxisFlags_Invert); } + inline bool IsForeground() const { return ImHasFlag(Flags, ImPlotAxisFlags_Foreground); } + inline bool IsAutoFitting() const { return ImHasFlag(Flags, ImPlotAxisFlags_AutoFit); } + inline bool CanInitFit() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoInitialFit) && !HasRange && !LinkedMin && !LinkedMax; } + inline bool IsRangeLocked() const { return HasRange && RangeCond == ImPlotCond_Always; } + inline bool IsLockedMin() const { return !Enabled || IsRangeLocked() || ImHasFlag(Flags, ImPlotAxisFlags_LockMin); } + inline bool IsLockedMax() const { return !Enabled || IsRangeLocked() || ImHasFlag(Flags, ImPlotAxisFlags_LockMax); } + inline bool IsLocked() const { return IsLockedMin() && IsLockedMax(); } + inline bool IsInputLockedMin() const { return IsLockedMin() || IsAutoFitting(); } + inline bool IsInputLockedMax() const { return IsLockedMax() || IsAutoFitting(); } + inline bool IsInputLocked() const { return IsLocked() || IsAutoFitting(); } + inline bool HasMenus() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoMenus); } + + inline bool IsPanLocked(bool increasing) { + if (ImHasFlag(Flags, ImPlotAxisFlags_PanStretch)) { + return IsInputLocked(); + } + else { + if (IsLockedMin() || IsLockedMax() || IsAutoFitting()) + return false; + if (increasing) + return Range.Max == ConstraintRange.Max; + else + return Range.Min == ConstraintRange.Min; + } + } + + void PushLinks() { + if (LinkedMin) { *LinkedMin = Range.Min; } + if (LinkedMax) { *LinkedMax = Range.Max; } + } + + void PullLinks() { + if (LinkedMin && LinkedMax) { SetRange(*LinkedMin, *LinkedMax); } + else if (LinkedMin) { SetMin(*LinkedMin,true); } + else if (LinkedMax) { SetMax(*LinkedMax,true); } + } +}; + +// Align plots group data +struct ImPlotAlignmentData { + bool Vertical; + float PadA; + float PadB; + float PadAMax; + float PadBMax; + ImPlotAlignmentData() { + Vertical = true; + PadA = PadB = PadAMax = PadBMax = 0; + } + void Begin() { PadAMax = PadBMax = 0; } + void Update(float& pad_a, float& pad_b, float& delta_a, float& delta_b) { + float bak_a = pad_a; float bak_b = pad_b; + if (PadAMax < pad_a) { PadAMax = pad_a; } + if (PadBMax < pad_b) { PadBMax = pad_b; } + if (pad_a < PadA) { pad_a = PadA; delta_a = pad_a - bak_a; } else { delta_a = 0; } + if (pad_b < PadB) { pad_b = PadB; delta_b = pad_b - bak_b; } else { delta_b = 0; } + } + void End() { PadA = PadAMax; PadB = PadBMax; } + void Reset() { PadA = PadB = PadAMax = PadBMax = 0; } +}; + +// State information for Plot items +struct ImPlotItem +{ + ImGuiID ID; + ImU32 Color; + ImRect LegendHoverRect; + int NameOffset; + bool Show; + bool LegendHovered; + bool SeenThisFrame; + + ImPlotItem() { + ID = 0; + Color = IM_COL32_WHITE; + NameOffset = -1; + Show = true; + SeenThisFrame = false; + LegendHovered = false; + } + + ~ImPlotItem() { ID = 0; } +}; + +// Holds Legend state +struct ImPlotLegend +{ + ImPlotLegendFlags Flags; + ImPlotLegendFlags PreviousFlags; + ImPlotLocation Location; + ImPlotLocation PreviousLocation; + ImVec2 Scroll; + ImVector Indices; + ImGuiTextBuffer Labels; + ImRect Rect; + ImRect RectClamped; + bool Hovered; + bool Held; + bool CanGoInside; + + ImPlotLegend() { + Flags = PreviousFlags = ImPlotLegendFlags_None; + CanGoInside = true; + Hovered = Held = false; + Location = PreviousLocation = ImPlotLocation_NorthWest; + Scroll = ImVec2(0,0); + } + + void Reset() { Indices.shrink(0); Labels.Buf.shrink(0); } +}; + +// Holds Items and Legend data +struct ImPlotItemGroup +{ + ImGuiID ID; + ImPlotLegend Legend; + ImPool ItemPool; + int ColormapIdx; + + ImPlotItemGroup() { ID = 0; ColormapIdx = 0; } + + int GetItemCount() const { return ItemPool.GetBufSize(); } + ImGuiID GetItemID(const char* label_id) { return ImGui::GetID(label_id); /* GetIDWithSeed */ } + ImPlotItem* GetItem(ImGuiID id) { return ItemPool.GetByKey(id); } + ImPlotItem* GetItem(const char* label_id) { return GetItem(GetItemID(label_id)); } + ImPlotItem* GetOrAddItem(ImGuiID id) { return ItemPool.GetOrAddByKey(id); } + ImPlotItem* GetItemByIndex(int i) { return ItemPool.GetByIndex(i); } + int GetItemIndex(ImPlotItem* item) { return ItemPool.GetIndex(item); } + int GetLegendCount() const { return Legend.Indices.size(); } + ImPlotItem* GetLegendItem(int i) { return ItemPool.GetByIndex(Legend.Indices[i]); } + const char* GetLegendLabel(int i) { return Legend.Labels.Buf.Data + GetLegendItem(i)->NameOffset; } + void Reset() { ItemPool.Clear(); Legend.Reset(); ColormapIdx = 0; } +}; + +// Holds Plot state information that must persist after EndPlot +struct ImPlotPlot +{ + ImGuiID ID; + ImPlotFlags Flags; + ImPlotFlags PreviousFlags; + ImPlotLocation MouseTextLocation; + ImPlotMouseTextFlags MouseTextFlags; + ImPlotAxis Axes[ImAxis_COUNT]; + ImGuiTextBuffer TextBuffer; + ImPlotItemGroup Items; + ImAxis CurrentX; + ImAxis CurrentY; + ImRect FrameRect; + ImRect CanvasRect; + ImRect PlotRect; + ImRect AxesRect; + ImRect SelectRect; + ImVec2 SelectStart; + int TitleOffset; + bool JustCreated; + bool Initialized; + bool SetupLocked; + bool FitThisFrame; + bool Hovered; + bool Held; + bool Selecting; + bool Selected; + bool ContextLocked; + + ImPlotPlot() { + Flags = PreviousFlags = ImPlotFlags_None; + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) + XAxis(i).Vertical = false; + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) + YAxis(i).Vertical = true; + SelectStart = ImVec2(0,0); + CurrentX = ImAxis_X1; + CurrentY = ImAxis_Y1; + MouseTextLocation = ImPlotLocation_South | ImPlotLocation_East; + MouseTextFlags = ImPlotMouseTextFlags_None; + TitleOffset = -1; + JustCreated = true; + Initialized = SetupLocked = FitThisFrame = false; + Hovered = Held = Selected = Selecting = ContextLocked = false; + } + + inline bool IsInputLocked() const { + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) { + if (!XAxis(i).IsInputLocked()) + return false; + } + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) { + if (!YAxis(i).IsInputLocked()) + return false; + } + return true; + } + + inline void ClearTextBuffer() { TextBuffer.Buf.shrink(0); } + + inline void SetTitle(const char* title) { + if (title && ImGui::FindRenderedTextEnd(title, nullptr) != title) { + TitleOffset = TextBuffer.size(); + TextBuffer.append(title, title + strlen(title) + 1); + } + else { + TitleOffset = -1; + } + } + inline bool HasTitle() const { return TitleOffset != -1 && !ImHasFlag(Flags, ImPlotFlags_NoTitle); } + inline const char* GetTitle() const { return TextBuffer.Buf.Data + TitleOffset; } + + inline ImPlotAxis& XAxis(int i) { return Axes[ImAxis_X1 + i]; } + inline const ImPlotAxis& XAxis(int i) const { return Axes[ImAxis_X1 + i]; } + inline ImPlotAxis& YAxis(int i) { return Axes[ImAxis_Y1 + i]; } + inline const ImPlotAxis& YAxis(int i) const { return Axes[ImAxis_Y1 + i]; } + + inline int EnabledAxesX() { + int cnt = 0; + for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) + cnt += XAxis(i).Enabled; + return cnt; + } + + inline int EnabledAxesY() { + int cnt = 0; + for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) + cnt += YAxis(i).Enabled; + return cnt; + } + + inline void SetAxisLabel(ImPlotAxis& axis, const char* label) { + if (label && ImGui::FindRenderedTextEnd(label, nullptr) != label) { + axis.LabelOffset = TextBuffer.size(); + TextBuffer.append(label, label + strlen(label) + 1); + } + else { + axis.LabelOffset = -1; + } + } + + inline const char* GetAxisLabel(const ImPlotAxis& axis) const { return TextBuffer.Buf.Data + axis.LabelOffset; } +}; + +// Holds subplot data that must persist after EndSubplot +struct ImPlotSubplot { + ImGuiID ID; + ImPlotSubplotFlags Flags; + ImPlotSubplotFlags PreviousFlags; + ImPlotItemGroup Items; + int Rows; + int Cols; + int CurrentIdx; + ImRect FrameRect; + ImRect GridRect; + ImVec2 CellSize; + ImVector RowAlignmentData; + ImVector ColAlignmentData; + ImVector RowRatios; + ImVector ColRatios; + ImVector RowLinkData; + ImVector ColLinkData; + float TempSizes[2]; + bool FrameHovered; + bool HasTitle; + + ImPlotSubplot() { + ID = 0; + Flags = PreviousFlags = ImPlotSubplotFlags_None; + Rows = Cols = CurrentIdx = 0; + Items.Legend.Location = ImPlotLocation_North; + Items.Legend.Flags = ImPlotLegendFlags_Horizontal|ImPlotLegendFlags_Outside; + Items.Legend.CanGoInside = false; + TempSizes[0] = TempSizes[1] = 0; + FrameHovered = false; + HasTitle = false; + } +}; + +// Temporary data storage for upcoming plot +struct ImPlotNextPlotData +{ + ImPlotCond RangeCond[ImAxis_COUNT]; + ImPlotRange Range[ImAxis_COUNT]; + bool HasRange[ImAxis_COUNT]; + bool Fit[ImAxis_COUNT]; + double* LinkedMin[ImAxis_COUNT]; + double* LinkedMax[ImAxis_COUNT]; + + ImPlotNextPlotData() { Reset(); } + + void Reset() { + for (int i = 0; i < ImAxis_COUNT; ++i) { + HasRange[i] = false; + Fit[i] = false; + LinkedMin[i] = LinkedMax[i] = nullptr; + } + } + +}; + +// Temporary data storage for upcoming item +struct ImPlotNextItemData { + ImVec4 Colors[5]; // ImPlotCol_Line, ImPlotCol_Fill, ImPlotCol_MarkerOutline, ImPlotCol_MarkerFill, ImPlotCol_ErrorBar + float LineWeight; + ImPlotMarker Marker; + float MarkerSize; + float MarkerWeight; + float FillAlpha; + float ErrorBarSize; + float ErrorBarWeight; + float DigitalBitHeight; + float DigitalBitGap; + bool RenderLine; + bool RenderFill; + bool RenderMarkerLine; + bool RenderMarkerFill; + bool HasHidden; + bool Hidden; + ImPlotCond HiddenCond; + ImPlotNextItemData() { Reset(); } + void Reset() { + for (int i = 0; i < 5; ++i) + Colors[i] = IMPLOT_AUTO_COL; + LineWeight = MarkerSize = MarkerWeight = FillAlpha = ErrorBarSize = ErrorBarWeight = DigitalBitHeight = DigitalBitGap = IMPLOT_AUTO; + Marker = IMPLOT_AUTO; + HasHidden = Hidden = false; + } +}; + +// Holds state information that must persist between calls to BeginPlot()/EndPlot() +struct ImPlotContext { + // Plot States + ImPool Plots; + ImPool Subplots; + ImPlotPlot* CurrentPlot; + ImPlotSubplot* CurrentSubplot; + ImPlotItemGroup* CurrentItems; + ImPlotItem* CurrentItem; + ImPlotItem* PreviousItem; + + // Tick Marks and Labels + ImPlotTicker CTicker; + + // Annotation and Tabs + ImPlotAnnotationCollection Annotations; + ImPlotTagCollection Tags; + + // Style and Colormaps + ImPlotStyle Style; + ImVector ColorModifiers; + ImVector StyleModifiers; + ImPlotColormapData ColormapData; + ImVector ColormapModifiers; + + // Time + tm Tm; + + // Temp data for general use + ImVector TempDouble1, TempDouble2; + ImVector TempInt1; + + // Misc + int DigitalPlotItemCnt; + int DigitalPlotOffset; + ImPlotNextPlotData NextPlotData; + ImPlotNextItemData NextItemData; + ImPlotInputMap InputMap; + bool OpenContextThisFrame; + ImGuiTextBuffer MousePosStringBuilder; + ImPlotItemGroup* SortItems; + + // Align plots + ImPool AlignmentData; + ImPlotAlignmentData* CurrentAlignmentH; + ImPlotAlignmentData* CurrentAlignmentV; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Internal API +// No guarantee of forward compatibility here! +//----------------------------------------------------------------------------- + +namespace ImPlot { + +//----------------------------------------------------------------------------- +// [SECTION] Context Utils +//----------------------------------------------------------------------------- + +// Initializes an ImPlotContext +IMPLOT_API void Initialize(ImPlotContext* ctx); +// Resets an ImPlot context for the next call to BeginPlot +IMPLOT_API void ResetCtxForNextPlot(ImPlotContext* ctx); +// Resets an ImPlot context for the next call to BeginAlignedPlots +IMPLOT_API void ResetCtxForNextAlignedPlots(ImPlotContext* ctx); +// Resets an ImPlot context for the next call to BeginSubplot +IMPLOT_API void ResetCtxForNextSubplot(ImPlotContext* ctx); + +//----------------------------------------------------------------------------- +// [SECTION] Plot Utils +//----------------------------------------------------------------------------- + +// Gets a plot from the current ImPlotContext +IMPLOT_API ImPlotPlot* GetPlot(const char* title); +// Gets the current plot from the current ImPlotContext +IMPLOT_API ImPlotPlot* GetCurrentPlot(); +// Busts the cache for every plot in the current context +IMPLOT_API void BustPlotCache(); + +// Shows a plot's context menu. +IMPLOT_API void ShowPlotContextMenu(ImPlotPlot& plot); + +//----------------------------------------------------------------------------- +// [SECTION] Setup Utils +//----------------------------------------------------------------------------- + +// Lock Setup and call SetupFinish if necessary. +static inline void SetupLock() { + ImPlotContext& gp = *GImPlot; + if (!gp.CurrentPlot->SetupLocked) + SetupFinish(); + gp.CurrentPlot->SetupLocked = true; +} + +//----------------------------------------------------------------------------- +// [SECTION] Subplot Utils +//----------------------------------------------------------------------------- + +// Advances to next subplot +IMPLOT_API void SubplotNextCell(); + +// Shows a subplot's context menu. +IMPLOT_API void ShowSubplotsContextMenu(ImPlotSubplot& subplot); + +//----------------------------------------------------------------------------- +// [SECTION] Item Utils +//----------------------------------------------------------------------------- + +// Begins a new item. Returns false if the item should not be plotted. Pushes PlotClipRect. +IMPLOT_API bool BeginItem(const char* label_id, ImPlotItemFlags flags=0, ImPlotCol recolor_from=IMPLOT_AUTO); + +// Same as above but with fitting functionality. +template +bool BeginItemEx(const char* label_id, const _Fitter& fitter, ImPlotItemFlags flags=0, ImPlotCol recolor_from=IMPLOT_AUTO) { + if (BeginItem(label_id, flags, recolor_from)) { + ImPlotPlot& plot = *GetCurrentPlot(); + if (plot.FitThisFrame && !ImHasFlag(flags, ImPlotItemFlags_NoFit)) + fitter.Fit(plot.Axes[plot.CurrentX], plot.Axes[plot.CurrentY]); + return true; + } + return false; +} + +// Ends an item (call only if BeginItem returns true). Pops PlotClipRect. +IMPLOT_API void EndItem(); + +// Register or get an existing item from the current plot. +IMPLOT_API ImPlotItem* RegisterOrGetItem(const char* label_id, ImPlotItemFlags flags, bool* just_created = nullptr); +// Get a plot item from the current plot. +IMPLOT_API ImPlotItem* GetItem(const char* label_id); +// Gets the current item. +IMPLOT_API ImPlotItem* GetCurrentItem(); +// Busts the cache for every item for every plot in the current context. +IMPLOT_API void BustItemCache(); + +//----------------------------------------------------------------------------- +// [SECTION] Axis Utils +//----------------------------------------------------------------------------- + +// Returns true if any enabled axis is locked from user input. +static inline bool AnyAxesInputLocked(ImPlotAxis* axes, int count) { + for (int i = 0; i < count; ++i) { + if (axes[i].Enabled && axes[i].IsInputLocked()) + return true; + } + return false; +} + +// Returns true if all enabled axes are locked from user input. +static inline bool AllAxesInputLocked(ImPlotAxis* axes, int count) { + for (int i = 0; i < count; ++i) { + if (axes[i].Enabled && !axes[i].IsInputLocked()) + return false; + } + return true; +} + +static inline bool AnyAxesHeld(ImPlotAxis* axes, int count) { + for (int i = 0; i < count; ++i) { + if (axes[i].Enabled && axes[i].Held) + return true; + } + return false; +} + +static inline bool AnyAxesHovered(ImPlotAxis* axes, int count) { + for (int i = 0; i < count; ++i) { + if (axes[i].Enabled && axes[i].Hovered) + return true; + } + return false; +} + +// Returns true if the user has requested data to be fit. +static inline bool FitThisFrame() { + return GImPlot->CurrentPlot->FitThisFrame; +} + +// Extends the current plot's axes so that it encompasses a vertical line at x +static inline void FitPointX(double x) { + ImPlotPlot& plot = *GetCurrentPlot(); + ImPlotAxis& x_axis = plot.Axes[plot.CurrentX]; + x_axis.ExtendFit(x); +} + +// Extends the current plot's axes so that it encompasses a horizontal line at y +static inline void FitPointY(double y) { + ImPlotPlot& plot = *GetCurrentPlot(); + ImPlotAxis& y_axis = plot.Axes[plot.CurrentY]; + y_axis.ExtendFit(y); +} + +// Extends the current plot's axes so that it encompasses point p +static inline void FitPoint(const ImPlotPoint& p) { + ImPlotPlot& plot = *GetCurrentPlot(); + ImPlotAxis& x_axis = plot.Axes[plot.CurrentX]; + ImPlotAxis& y_axis = plot.Axes[plot.CurrentY]; + x_axis.ExtendFitWith(y_axis, p.x, p.y); + y_axis.ExtendFitWith(x_axis, p.y, p.x); +} + +// Returns true if two ranges overlap +static inline bool RangesOverlap(const ImPlotRange& r1, const ImPlotRange& r2) +{ return r1.Min <= r2.Max && r2.Min <= r1.Max; } + +// Shows an axis's context menu. +IMPLOT_API void ShowAxisContextMenu(ImPlotAxis& axis, ImPlotAxis* equal_axis, bool time_allowed = false); + +//----------------------------------------------------------------------------- +// [SECTION] Legend Utils +//----------------------------------------------------------------------------- + +// Gets the position of an inner rect that is located inside of an outer rect according to an ImPlotLocation and padding amount. +IMPLOT_API ImVec2 GetLocationPos(const ImRect& outer_rect, const ImVec2& inner_size, ImPlotLocation location, const ImVec2& pad = ImVec2(0,0)); +// Calculates the bounding box size of a legend _before_ clipping. +IMPLOT_API ImVec2 CalcLegendSize(ImPlotItemGroup& items, const ImVec2& pad, const ImVec2& spacing, bool vertical); +// Clips calculated legend size +IMPLOT_API bool ClampLegendRect(ImRect& legend_rect, const ImRect& outer_rect, const ImVec2& pad); +// Renders legend entries into a bounding box +IMPLOT_API bool ShowLegendEntries(ImPlotItemGroup& items, const ImRect& legend_bb, bool interactable, const ImVec2& pad, const ImVec2& spacing, bool vertical, ImDrawList& DrawList); +// Shows an alternate legend for the plot identified by #title_id, outside of the plot frame (can be called before or after of Begin/EndPlot but must occur in the same ImGui window! This is not thoroughly tested nor scrollable!). +IMPLOT_API void ShowAltLegend(const char* title_id, bool vertical = true, const ImVec2 size = ImVec2(0,0), bool interactable = true); +// Shows a legend's context menu. +IMPLOT_API bool ShowLegendContextMenu(ImPlotLegend& legend, bool visible); + +//----------------------------------------------------------------------------- +// [SECTION] Label Utils +//----------------------------------------------------------------------------- + +// Create a a string label for a an axis value +IMPLOT_API void LabelAxisValue(const ImPlotAxis& axis, double value, char* buff, int size, bool round = false); + +//----------------------------------------------------------------------------- +// [SECTION] Styling Utils +//----------------------------------------------------------------------------- + +// Get styling data for next item (call between Begin/EndItem) +static inline const ImPlotNextItemData& GetItemData() { return GImPlot->NextItemData; } + +// Returns true if a color is set to be automatically determined +static inline bool IsColorAuto(const ImVec4& col) { return col.w == -1; } +// Returns true if a style color is set to be automatically determined +static inline bool IsColorAuto(ImPlotCol idx) { return IsColorAuto(GImPlot->Style.Colors[idx]); } +// Returns the automatically deduced style color +IMPLOT_API ImVec4 GetAutoColor(ImPlotCol idx); + +// Returns the style color whether it is automatic or custom set +static inline ImVec4 GetStyleColorVec4(ImPlotCol idx) { return IsColorAuto(idx) ? GetAutoColor(idx) : GImPlot->Style.Colors[idx]; } +static inline ImU32 GetStyleColorU32(ImPlotCol idx) { return ImGui::ColorConvertFloat4ToU32(GetStyleColorVec4(idx)); } + +// Draws vertical text. The position is the bottom left of the text rect. +IMPLOT_API void AddTextVertical(ImDrawList *DrawList, ImVec2 pos, ImU32 col, const char* text_begin, const char* text_end = nullptr); +// Draws multiline horizontal text centered. +IMPLOT_API void AddTextCentered(ImDrawList* DrawList, ImVec2 top_center, ImU32 col, const char* text_begin, const char* text_end = nullptr); +// Calculates the size of vertical text +static inline ImVec2 CalcTextSizeVertical(const char *text) { + ImVec2 sz = ImGui::CalcTextSize(text); + return ImVec2(sz.y, sz.x); +} +// Returns white or black text given background color +static inline ImU32 CalcTextColor(const ImVec4& bg) { return (bg.x * 0.299f + bg.y * 0.587f + bg.z * 0.114f) > 0.5f ? IM_COL32_BLACK : IM_COL32_WHITE; } +static inline ImU32 CalcTextColor(ImU32 bg) { return CalcTextColor(ImGui::ColorConvertU32ToFloat4(bg)); } +// Lightens or darkens a color for hover +static inline ImU32 CalcHoverColor(ImU32 col) { return ImMixU32(col, CalcTextColor(col), 32); } + +// Clamps a label position so that it fits a rect defined by Min/Max +static inline ImVec2 ClampLabelPos(ImVec2 pos, const ImVec2& size, const ImVec2& Min, const ImVec2& Max) { + if (pos.x < Min.x) pos.x = Min.x; + if (pos.y < Min.y) pos.y = Min.y; + if ((pos.x + size.x) > Max.x) pos.x = Max.x - size.x; + if ((pos.y + size.y) > Max.y) pos.y = Max.y - size.y; + return pos; +} + +// Returns a color from the Color map given an index >= 0 (modulo will be performed). +IMPLOT_API ImU32 GetColormapColorU32(int idx, ImPlotColormap cmap); +// Returns the next unused colormap color and advances the colormap. Can be used to skip colors if desired. +IMPLOT_API ImU32 NextColormapColorU32(); +// Linearly interpolates a color from the current colormap given t between 0 and 1. +IMPLOT_API ImU32 SampleColormapU32(float t, ImPlotColormap cmap); + +// Render a colormap bar +IMPLOT_API void RenderColorBar(const ImU32* colors, int size, ImDrawList& DrawList, const ImRect& bounds, bool vert, bool reversed, bool continuous); + +//----------------------------------------------------------------------------- +// [SECTION] Math and Misc Utils +//----------------------------------------------------------------------------- + +// Rounds x to powers of 2,5 and 10 for generating axis labels (from Graphics Gems 1 Chapter 11.2) +IMPLOT_API double NiceNum(double x, bool round); +// Computes order of magnitude of double. +static inline int OrderOfMagnitude(double val) { return val == 0 ? 0 : (int)(floor(log10(fabs(val)))); } +// Returns the precision required for a order of magnitude. +static inline int OrderToPrecision(int order) { return order > 0 ? 0 : 1 - order; } +// Returns a floating point precision to use given a value +static inline int Precision(double val) { return OrderToPrecision(OrderOfMagnitude(val)); } +// Round a value to a given precision +static inline double RoundTo(double val, int prec) { double p = pow(10,(double)prec); return floor(val*p+0.5)/p; } + +// Returns the intersection point of two lines A and B (assumes they are not parallel!) +static inline ImVec2 Intersection(const ImVec2& a1, const ImVec2& a2, const ImVec2& b1, const ImVec2& b2) { + float v1 = (a1.x * a2.y - a1.y * a2.x); float v2 = (b1.x * b2.y - b1.y * b2.x); + float v3 = ((a1.x - a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x - b2.x)); + return ImVec2((v1 * (b1.x - b2.x) - v2 * (a1.x - a2.x)) / v3, (v1 * (b1.y - b2.y) - v2 * (a1.y - a2.y)) / v3); +} + +// Fills a buffer with n samples linear interpolated from vmin to vmax +template +void FillRange(ImVector& buffer, int n, T vmin, T vmax) { + buffer.resize(n); + T step = (vmax - vmin) / (n - 1); + for (int i = 0; i < n; ++i) { + buffer[i] = vmin + i * step; + } +} + +// Calculate histogram bin counts and widths +template +static inline void CalculateBins(const T* values, int count, ImPlotBin meth, const ImPlotRange& range, int& bins_out, double& width_out) { + switch (meth) { + case ImPlotBin_Sqrt: + bins_out = (int)ceil(sqrt(count)); + break; + case ImPlotBin_Sturges: + bins_out = (int)ceil(1.0 + log2(count)); + break; + case ImPlotBin_Rice: + bins_out = (int)ceil(2 * cbrt(count)); + break; + case ImPlotBin_Scott: + width_out = 3.49 * ImStdDev(values, count) / cbrt(count); + bins_out = (int)round(range.Size() / width_out); + break; + } + width_out = range.Size() / bins_out; +} + +//----------------------------------------------------------------------------- +// Time Utils +//----------------------------------------------------------------------------- + +// Returns true if year is leap year (366 days long) +static inline bool IsLeapYear(int year) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); +} +// Returns the number of days in a month, accounting for Feb. leap years. #month is zero indexed. +static inline int GetDaysInMonth(int year, int month) { + static const int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + return days[month] + (int)(month == 1 && IsLeapYear(year)); +} + +// Make a UNIX timestamp from a tm struct expressed in UTC time (i.e. GMT timezone). +IMPLOT_API ImPlotTime MkGmtTime(struct tm *ptm); +// Make a tm struct expressed in UTC time (i.e. GMT timezone) from a UNIX timestamp. +IMPLOT_API tm* GetGmtTime(const ImPlotTime& t, tm* ptm); + +// Make a UNIX timestamp from a tm struct expressed in local time. +IMPLOT_API ImPlotTime MkLocTime(struct tm *ptm); +// Make a tm struct expressed in local time from a UNIX timestamp. +IMPLOT_API tm* GetLocTime(const ImPlotTime& t, tm* ptm); + +// NB: The following functions only work if there is a current ImPlotContext because the +// internal tm struct is owned by the context! They are aware of ImPlotStyle.UseLocalTime. + +// Make a timestamp from time components. +// year[1970-3000], month[0-11], day[1-31], hour[0-23], min[0-59], sec[0-59], us[0,999999] +IMPLOT_API ImPlotTime MakeTime(int year, int month = 0, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0); +// Get year component from timestamp [1970-3000] +IMPLOT_API int GetYear(const ImPlotTime& t); + +// Adds or subtracts time from a timestamp. #count > 0 to add, < 0 to subtract. +IMPLOT_API ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count); +// Rounds a timestamp down to nearest unit. +IMPLOT_API ImPlotTime FloorTime(const ImPlotTime& t, ImPlotTimeUnit unit); +// Rounds a timestamp up to the nearest unit. +IMPLOT_API ImPlotTime CeilTime(const ImPlotTime& t, ImPlotTimeUnit unit); +// Rounds a timestamp up or down to the nearest unit. +IMPLOT_API ImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit); +// Combines the date of one timestamp with the time-of-day of another timestamp. +IMPLOT_API ImPlotTime CombineDateTime(const ImPlotTime& date_part, const ImPlotTime& time_part); + +// Formats the time part of timestamp t into a buffer according to #fmt +IMPLOT_API int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt, bool use_24_hr_clk); +// Formats the date part of timestamp t into a buffer according to #fmt +IMPLOT_API int FormatDate(const ImPlotTime& t, char* buffer, int size, ImPlotDateFmt fmt, bool use_iso_8601); +// Formats the time and/or date parts of a timestamp t into a buffer according to #fmt +IMPLOT_API int FormatDateTime(const ImPlotTime& t, char* buffer, int size, ImPlotDateTimeSpec fmt); + +// Shows a date picker widget block (year/month/day). +// #level = 0 for day, 1 for month, 2 for year. Modified by user interaction. +// #t will be set when a day is clicked and the function will return true. +// #t1 and #t2 are optional dates to highlight. +IMPLOT_API bool ShowDatePicker(const char* id, int* level, ImPlotTime* t, const ImPlotTime* t1 = nullptr, const ImPlotTime* t2 = nullptr); +// Shows a time picker widget block (hour/min/sec). +// #t will be set when a new hour, minute, or sec is selected or am/pm is toggled, and the function will return true. +IMPLOT_API bool ShowTimePicker(const char* id, ImPlotTime* t); + +//----------------------------------------------------------------------------- +// [SECTION] Transforms +//----------------------------------------------------------------------------- + +static inline double TransformForward_Log10(double v, void*) { + v = v <= 0.0 ? DBL_MIN : v; + return ImLog10(v); +} + +static inline double TransformInverse_Log10(double v, void*) { + return ImPow(10, v); +} + +static inline double TransformForward_SymLog(double v, void*) { + return 2.0 * ImAsinh(v / 2.0); +} + +static inline double TransformInverse_SymLog(double v, void*) { + return 2.0 * ImSinh(v / 2.0); +} + +static inline double TransformForward_Logit(double v, void*) { + v = ImClamp(v, DBL_MIN, 1.0 - DBL_EPSILON); + return ImLog10(v / (1 - v)); +} + +static inline double TransformInverse_Logit(double v, void*) { + return 1.0 / (1.0 + ImPow(10,-v)); +} + +//----------------------------------------------------------------------------- +// [SECTION] Formatters +//----------------------------------------------------------------------------- + +static inline int Formatter_Default(double value, char* buff, int size, void* data) { + char* fmt = (char*)data; + return ImFormatString(buff, size, fmt, value); +} + +static inline int Formatter_Logit(double value, char* buff, int size, void*) { + if (value == 0.5) + return ImFormatString(buff,size,"1/2"); + else if (value < 0.5) + return ImFormatString(buff,size,"%g", value); + else + return ImFormatString(buff,size,"1 - %g", 1 - value); +} + +struct Formatter_Time_Data { + ImPlotTime Time; + ImPlotDateTimeSpec Spec; + ImPlotFormatter UserFormatter; + void* UserFormatterData; +}; + +static inline int Formatter_Time(double, char* buff, int size, void* data) { + Formatter_Time_Data* ftd = (Formatter_Time_Data*)data; + return FormatDateTime(ftd->Time, buff, size, ftd->Spec); +} + +//------------------------------------------------------------------------------ +// [SECTION] Locator +//------------------------------------------------------------------------------ + +void Locator_Default(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data); +void Locator_Time(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data); +void Locator_Log10(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data); +void Locator_SymLog(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data); + +} // namespace ImPlot diff --git a/libs/implot/implot_items.cpp b/libs/implot/implot_items.cpp new file mode 100644 index 0000000..1871d15 --- /dev/null +++ b/libs/implot/implot_items.cpp @@ -0,0 +1,2808 @@ +// MIT License + +// Copyright (c) 2023 Evan Pezent + +// 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. + +// ImPlot v0.17 + +#define IMGUI_DEFINE_MATH_OPERATORS +#include "implot.h" +#include "implot_internal.h" + +//----------------------------------------------------------------------------- +// [SECTION] Macros and Defines +//----------------------------------------------------------------------------- + +#define SQRT_1_2 0.70710678118f +#define SQRT_3_2 0.86602540378f + +#ifndef IMPLOT_NO_FORCE_INLINE + #ifdef _MSC_VER + #define IMPLOT_INLINE __forceinline + #elif defined(__GNUC__) + #define IMPLOT_INLINE inline __attribute__((__always_inline__)) + #elif defined(__CLANG__) + #if __has_attribute(__always_inline__) + #define IMPLOT_INLINE inline __attribute__((__always_inline__)) + #else + #define IMPLOT_INLINE inline + #endif + #else + #define IMPLOT_INLINE inline + #endif +#else + #define IMPLOT_INLINE inline +#endif + +#if defined __SSE__ || defined __x86_64__ || defined _M_X64 +#ifndef IMGUI_ENABLE_SSE +#include +#endif +static IMPLOT_INLINE float ImInvSqrt(float x) { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); } +#else +static IMPLOT_INLINE float ImInvSqrt(float x) { return 1.0f / sqrtf(x); } +#endif + +#define IMPLOT_NORMALIZE2F_OVER_ZERO(VX,VY) do { float d2 = VX*VX + VY*VY; if (d2 > 0.0f) { float inv_len = ImInvSqrt(d2); VX *= inv_len; VY *= inv_len; } } while (0) + +// Support for pre-1.82 versions. Users on 1.82+ can use 0 (default) flags to mean "all corners" but in order to support older versions we are more explicit. +#if (IMGUI_VERSION_NUM < 18102) && !defined(ImDrawFlags_RoundCornersAll) +#define ImDrawFlags_RoundCornersAll ImDrawCornerFlags_All +#endif + +//----------------------------------------------------------------------------- +// [SECTION] Template instantiation utility +//----------------------------------------------------------------------------- + +// By default, templates are instantiated for `float`, `double`, and for the following integer types, which are defined in imgui.h: +// signed char ImS8; // 8-bit signed integer +// unsigned char ImU8; // 8-bit unsigned integer +// signed short ImS16; // 16-bit signed integer +// unsigned short ImU16; // 16-bit unsigned integer +// signed int ImS32; // 32-bit signed integer == int +// unsigned int ImU32; // 32-bit unsigned integer +// signed long long ImS64; // 64-bit signed integer +// unsigned long long ImU64; // 64-bit unsigned integer +// (note: this list does *not* include `long`, `unsigned long` and `long double`) +// +// You can customize the supported types by defining IMPLOT_CUSTOM_NUMERIC_TYPES at compile time to define your own type list. +// As an example, you could use the compile time define given by the line below in order to support only float and double. +// -DIMPLOT_CUSTOM_NUMERIC_TYPES="(float)(double)" +// In order to support all known C++ types, use: +// -DIMPLOT_CUSTOM_NUMERIC_TYPES="(signed char)(unsigned char)(signed short)(unsigned short)(signed int)(unsigned int)(signed long)(unsigned long)(signed long long)(unsigned long long)(float)(double)(long double)" + +#ifdef IMPLOT_CUSTOM_NUMERIC_TYPES + #define IMPLOT_NUMERIC_TYPES IMPLOT_CUSTOM_NUMERIC_TYPES +#else + #define IMPLOT_NUMERIC_TYPES (ImS8)(ImU8)(ImS16)(ImU16)(ImS32)(ImU32)(ImS64)(ImU64)(float)(double) +#endif + +// CALL_INSTANTIATE_FOR_NUMERIC_TYPES will duplicate the template instantion code `INSTANTIATE_MACRO(T)` on supported types. +#define _CAT(x, y) _CAT_(x, y) +#define _CAT_(x,y) x ## y +#define _INSTANTIATE_FOR_NUMERIC_TYPES(chain) _CAT(_INSTANTIATE_FOR_NUMERIC_TYPES_1 chain, _END) +#define _INSTANTIATE_FOR_NUMERIC_TYPES_1(T) INSTANTIATE_MACRO(T) _INSTANTIATE_FOR_NUMERIC_TYPES_2 +#define _INSTANTIATE_FOR_NUMERIC_TYPES_2(T) INSTANTIATE_MACRO(T) _INSTANTIATE_FOR_NUMERIC_TYPES_1 +#define _INSTANTIATE_FOR_NUMERIC_TYPES_1_END +#define _INSTANTIATE_FOR_NUMERIC_TYPES_2_END +#define CALL_INSTANTIATE_FOR_NUMERIC_TYPES() _INSTANTIATE_FOR_NUMERIC_TYPES(IMPLOT_NUMERIC_TYPES) + +namespace ImPlot { + +//----------------------------------------------------------------------------- +// [SECTION] Utils +//----------------------------------------------------------------------------- + +// Calc maximum index size of ImDrawIdx +template +struct MaxIdx { static const unsigned int Value; }; +template <> const unsigned int MaxIdx::Value = 65535; +template <> const unsigned int MaxIdx::Value = 4294967295; + +IMPLOT_INLINE void GetLineRenderProps(const ImDrawList& draw_list, float& half_weight, ImVec2& tex_uv0, ImVec2& tex_uv1) { + const bool aa = ImHasFlag(draw_list.Flags, ImDrawListFlags_AntiAliasedLines) && + ImHasFlag(draw_list.Flags, ImDrawListFlags_AntiAliasedLinesUseTex); + if (aa) { + ImVec4 tex_uvs = draw_list._Data->TexUvLines[(int)(half_weight*2)]; + tex_uv0 = ImVec2(tex_uvs.x, tex_uvs.y); + tex_uv1 = ImVec2(tex_uvs.z, tex_uvs.w); + half_weight += 1; + } + else { + tex_uv0 = tex_uv1 = draw_list._Data->TexUvWhitePixel; + } +} + +IMPLOT_INLINE void PrimLine(ImDrawList& draw_list, const ImVec2& P1, const ImVec2& P2, float half_weight, ImU32 col, const ImVec2& tex_uv0, const ImVec2 tex_uv1) { + float dx = P2.x - P1.x; + float dy = P2.y - P1.y; + IMPLOT_NORMALIZE2F_OVER_ZERO(dx, dy); + dx *= half_weight; + dy *= half_weight; + draw_list._VtxWritePtr[0].pos.x = P1.x + dy; + draw_list._VtxWritePtr[0].pos.y = P1.y - dx; + draw_list._VtxWritePtr[0].uv = tex_uv0; + draw_list._VtxWritePtr[0].col = col; + draw_list._VtxWritePtr[1].pos.x = P2.x + dy; + draw_list._VtxWritePtr[1].pos.y = P2.y - dx; + draw_list._VtxWritePtr[1].uv = tex_uv0; + draw_list._VtxWritePtr[1].col = col; + draw_list._VtxWritePtr[2].pos.x = P2.x - dy; + draw_list._VtxWritePtr[2].pos.y = P2.y + dx; + draw_list._VtxWritePtr[2].uv = tex_uv1; + draw_list._VtxWritePtr[2].col = col; + draw_list._VtxWritePtr[3].pos.x = P1.x - dy; + draw_list._VtxWritePtr[3].pos.y = P1.y + dx; + draw_list._VtxWritePtr[3].uv = tex_uv1; + draw_list._VtxWritePtr[3].col = col; + draw_list._VtxWritePtr += 4; + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2); + draw_list._IdxWritePtr[3] = (ImDrawIdx)(draw_list._VtxCurrentIdx); + draw_list._IdxWritePtr[4] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2); + draw_list._IdxWritePtr[5] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3); + draw_list._IdxWritePtr += 6; + draw_list._VtxCurrentIdx += 4; +} + +IMPLOT_INLINE void PrimRectFill(ImDrawList& draw_list, const ImVec2& Pmin, const ImVec2& Pmax, ImU32 col, const ImVec2& uv) { + draw_list._VtxWritePtr[0].pos = Pmin; + draw_list._VtxWritePtr[0].uv = uv; + draw_list._VtxWritePtr[0].col = col; + draw_list._VtxWritePtr[1].pos = Pmax; + draw_list._VtxWritePtr[1].uv = uv; + draw_list._VtxWritePtr[1].col = col; + draw_list._VtxWritePtr[2].pos.x = Pmin.x; + draw_list._VtxWritePtr[2].pos.y = Pmax.y; + draw_list._VtxWritePtr[2].uv = uv; + draw_list._VtxWritePtr[2].col = col; + draw_list._VtxWritePtr[3].pos.x = Pmax.x; + draw_list._VtxWritePtr[3].pos.y = Pmin.y; + draw_list._VtxWritePtr[3].uv = uv; + draw_list._VtxWritePtr[3].col = col; + draw_list._VtxWritePtr += 4; + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2); + draw_list._IdxWritePtr[3] = (ImDrawIdx)(draw_list._VtxCurrentIdx); + draw_list._IdxWritePtr[4] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1); + draw_list._IdxWritePtr[5] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3); + draw_list._IdxWritePtr += 6; + draw_list._VtxCurrentIdx += 4; +} + +IMPLOT_INLINE void PrimRectLine(ImDrawList& draw_list, const ImVec2& Pmin, const ImVec2& Pmax, float weight, ImU32 col, const ImVec2& uv) { + + draw_list._VtxWritePtr[0].pos.x = Pmin.x; + draw_list._VtxWritePtr[0].pos.y = Pmin.y; + draw_list._VtxWritePtr[0].uv = uv; + draw_list._VtxWritePtr[0].col = col; + + draw_list._VtxWritePtr[1].pos.x = Pmin.x; + draw_list._VtxWritePtr[1].pos.y = Pmax.y; + draw_list._VtxWritePtr[1].uv = uv; + draw_list._VtxWritePtr[1].col = col; + + draw_list._VtxWritePtr[2].pos.x = Pmax.x; + draw_list._VtxWritePtr[2].pos.y = Pmax.y; + draw_list._VtxWritePtr[2].uv = uv; + draw_list._VtxWritePtr[2].col = col; + + draw_list._VtxWritePtr[3].pos.x = Pmax.x; + draw_list._VtxWritePtr[3].pos.y = Pmin.y; + draw_list._VtxWritePtr[3].uv = uv; + draw_list._VtxWritePtr[3].col = col; + + draw_list._VtxWritePtr[4].pos.x = Pmin.x + weight; + draw_list._VtxWritePtr[4].pos.y = Pmin.y + weight; + draw_list._VtxWritePtr[4].uv = uv; + draw_list._VtxWritePtr[4].col = col; + + draw_list._VtxWritePtr[5].pos.x = Pmin.x + weight; + draw_list._VtxWritePtr[5].pos.y = Pmax.y - weight; + draw_list._VtxWritePtr[5].uv = uv; + draw_list._VtxWritePtr[5].col = col; + + draw_list._VtxWritePtr[6].pos.x = Pmax.x - weight; + draw_list._VtxWritePtr[6].pos.y = Pmax.y - weight; + draw_list._VtxWritePtr[6].uv = uv; + draw_list._VtxWritePtr[6].col = col; + + draw_list._VtxWritePtr[7].pos.x = Pmax.x - weight; + draw_list._VtxWritePtr[7].pos.y = Pmin.y + weight; + draw_list._VtxWritePtr[7].uv = uv; + draw_list._VtxWritePtr[7].col = col; + + draw_list._VtxWritePtr += 8; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 0); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 5); + draw_list._IdxWritePtr += 3; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 0); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 5); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 4); + draw_list._IdxWritePtr += 3; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 6); + draw_list._IdxWritePtr += 3; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 6); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 5); + draw_list._IdxWritePtr += 3; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 7); + draw_list._IdxWritePtr += 3; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 7); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 6); + draw_list._IdxWritePtr += 3; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 0); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 4); + draw_list._IdxWritePtr += 3; + + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 4); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 7); + draw_list._IdxWritePtr += 3; + + draw_list._VtxCurrentIdx += 8; +} + + +//----------------------------------------------------------------------------- +// [SECTION] Item Utils +//----------------------------------------------------------------------------- + +ImPlotItem* RegisterOrGetItem(const char* label_id, ImPlotItemFlags flags, bool* just_created) { + ImPlotContext& gp = *GImPlot; + ImPlotItemGroup& Items = *gp.CurrentItems; + ImGuiID id = Items.GetItemID(label_id); + if (just_created != nullptr) + *just_created = Items.GetItem(id) == nullptr; + ImPlotItem* item = Items.GetOrAddItem(id); + if (item->SeenThisFrame) + return item; + item->SeenThisFrame = true; + int idx = Items.GetItemIndex(item); + item->ID = id; + if (!ImHasFlag(flags, ImPlotItemFlags_NoLegend) && ImGui::FindRenderedTextEnd(label_id, nullptr) != label_id) { + Items.Legend.Indices.push_back(idx); + item->NameOffset = Items.Legend.Labels.size(); + Items.Legend.Labels.append(label_id, label_id + strlen(label_id) + 1); + } + else { + item->Show = true; + } + return item; +} + +ImPlotItem* GetItem(const char* label_id) { + ImPlotContext& gp = *GImPlot; + return gp.CurrentItems->GetItem(label_id); +} + +bool IsItemHidden(const char* label_id) { + ImPlotItem* item = GetItem(label_id); + return item != nullptr && !item->Show; +} + +ImPlotItem* GetCurrentItem() { + ImPlotContext& gp = *GImPlot; + return gp.CurrentItem; +} + +void SetNextLineStyle(const ImVec4& col, float weight) { + ImPlotContext& gp = *GImPlot; + gp.NextItemData.Colors[ImPlotCol_Line] = col; + gp.NextItemData.LineWeight = weight; +} + +void SetNextFillStyle(const ImVec4& col, float alpha) { + ImPlotContext& gp = *GImPlot; + gp.NextItemData.Colors[ImPlotCol_Fill] = col; + gp.NextItemData.FillAlpha = alpha; +} + +void SetNextMarkerStyle(ImPlotMarker marker, float size, const ImVec4& fill, float weight, const ImVec4& outline) { + ImPlotContext& gp = *GImPlot; + gp.NextItemData.Marker = marker; + gp.NextItemData.Colors[ImPlotCol_MarkerFill] = fill; + gp.NextItemData.MarkerSize = size; + gp.NextItemData.Colors[ImPlotCol_MarkerOutline] = outline; + gp.NextItemData.MarkerWeight = weight; +} + +void SetNextErrorBarStyle(const ImVec4& col, float size, float weight) { + ImPlotContext& gp = *GImPlot; + gp.NextItemData.Colors[ImPlotCol_ErrorBar] = col; + gp.NextItemData.ErrorBarSize = size; + gp.NextItemData.ErrorBarWeight = weight; +} + +ImVec4 GetLastItemColor() { + ImPlotContext& gp = *GImPlot; + if (gp.PreviousItem) + return ImGui::ColorConvertU32ToFloat4(gp.PreviousItem->Color); + return ImVec4(); +} + +void BustItemCache() { + ImPlotContext& gp = *GImPlot; + for (int p = 0; p < gp.Plots.GetBufSize(); ++p) { + ImPlotPlot& plot = *gp.Plots.GetByIndex(p); + plot.Items.Reset(); + } + for (int p = 0; p < gp.Subplots.GetBufSize(); ++p) { + ImPlotSubplot& subplot = *gp.Subplots.GetByIndex(p); + subplot.Items.Reset(); + } +} + +void BustColorCache(const char* plot_title_id) { + ImPlotContext& gp = *GImPlot; + if (plot_title_id == nullptr) { + BustItemCache(); + } + else { + ImGuiID id = ImGui::GetCurrentWindow()->GetID(plot_title_id); + ImPlotPlot* plot = gp.Plots.GetByKey(id); + if (plot != nullptr) + plot->Items.Reset(); + else { + ImPlotSubplot* subplot = gp.Subplots.GetByKey(id); + if (subplot != nullptr) + subplot->Items.Reset(); + } + } +} + +//----------------------------------------------------------------------------- +// [SECTION] BeginItem / EndItem +//----------------------------------------------------------------------------- + +static const float ITEM_HIGHLIGHT_LINE_SCALE = 2.0f; +static const float ITEM_HIGHLIGHT_MARK_SCALE = 1.25f; + +// Begins a new item. Returns false if the item should not be plotted. +bool BeginItem(const char* label_id, ImPlotItemFlags flags, ImPlotCol recolor_from) { + ImPlotContext& gp = *GImPlot; + IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "PlotX() needs to be called between BeginPlot() and EndPlot()!"); + SetupLock(); + bool just_created; + ImPlotItem* item = RegisterOrGetItem(label_id, flags, &just_created); + // set current item + gp.CurrentItem = item; + ImPlotNextItemData& s = gp.NextItemData; + // set/override item color + if (recolor_from != -1) { + if (!IsColorAuto(s.Colors[recolor_from])) + item->Color = ImGui::ColorConvertFloat4ToU32(s.Colors[recolor_from]); + else if (!IsColorAuto(gp.Style.Colors[recolor_from])) + item->Color = ImGui::ColorConvertFloat4ToU32(gp.Style.Colors[recolor_from]); + else if (just_created) + item->Color = NextColormapColorU32(); + } + else if (just_created) { + item->Color = NextColormapColorU32(); + } + // hide/show item + if (gp.NextItemData.HasHidden) { + if (just_created || gp.NextItemData.HiddenCond == ImGuiCond_Always) + item->Show = !gp.NextItemData.Hidden; + } + if (!item->Show) { + // reset next item data + gp.NextItemData.Reset(); + gp.PreviousItem = item; + gp.CurrentItem = nullptr; + return false; + } + else { + ImVec4 item_color = ImGui::ColorConvertU32ToFloat4(item->Color); + // stage next item colors + s.Colors[ImPlotCol_Line] = IsColorAuto(s.Colors[ImPlotCol_Line]) ? ( IsColorAuto(ImPlotCol_Line) ? item_color : gp.Style.Colors[ImPlotCol_Line] ) : s.Colors[ImPlotCol_Line]; + s.Colors[ImPlotCol_Fill] = IsColorAuto(s.Colors[ImPlotCol_Fill]) ? ( IsColorAuto(ImPlotCol_Fill) ? item_color : gp.Style.Colors[ImPlotCol_Fill] ) : s.Colors[ImPlotCol_Fill]; + s.Colors[ImPlotCol_MarkerOutline] = IsColorAuto(s.Colors[ImPlotCol_MarkerOutline]) ? ( IsColorAuto(ImPlotCol_MarkerOutline) ? s.Colors[ImPlotCol_Line] : gp.Style.Colors[ImPlotCol_MarkerOutline] ) : s.Colors[ImPlotCol_MarkerOutline]; + s.Colors[ImPlotCol_MarkerFill] = IsColorAuto(s.Colors[ImPlotCol_MarkerFill]) ? ( IsColorAuto(ImPlotCol_MarkerFill) ? s.Colors[ImPlotCol_Line] : gp.Style.Colors[ImPlotCol_MarkerFill] ) : s.Colors[ImPlotCol_MarkerFill]; + s.Colors[ImPlotCol_ErrorBar] = IsColorAuto(s.Colors[ImPlotCol_ErrorBar]) ? ( GetStyleColorVec4(ImPlotCol_ErrorBar) ) : s.Colors[ImPlotCol_ErrorBar]; + // stage next item style vars + s.LineWeight = s.LineWeight < 0 ? gp.Style.LineWeight : s.LineWeight; + s.Marker = s.Marker < 0 ? gp.Style.Marker : s.Marker; + s.MarkerSize = s.MarkerSize < 0 ? gp.Style.MarkerSize : s.MarkerSize; + s.MarkerWeight = s.MarkerWeight < 0 ? gp.Style.MarkerWeight : s.MarkerWeight; + s.FillAlpha = s.FillAlpha < 0 ? gp.Style.FillAlpha : s.FillAlpha; + s.ErrorBarSize = s.ErrorBarSize < 0 ? gp.Style.ErrorBarSize : s.ErrorBarSize; + s.ErrorBarWeight = s.ErrorBarWeight < 0 ? gp.Style.ErrorBarWeight : s.ErrorBarWeight; + s.DigitalBitHeight = s.DigitalBitHeight < 0 ? gp.Style.DigitalBitHeight : s.DigitalBitHeight; + s.DigitalBitGap = s.DigitalBitGap < 0 ? gp.Style.DigitalBitGap : s.DigitalBitGap; + // apply alpha modifier(s) + s.Colors[ImPlotCol_Fill].w *= s.FillAlpha; + s.Colors[ImPlotCol_MarkerFill].w *= s.FillAlpha; // TODO: this should be separate, if it at all + // apply highlight mods + if (item->LegendHovered) { + if (!ImHasFlag(gp.CurrentItems->Legend.Flags, ImPlotLegendFlags_NoHighlightItem)) { + s.LineWeight *= ITEM_HIGHLIGHT_LINE_SCALE; + s.MarkerSize *= ITEM_HIGHLIGHT_MARK_SCALE; + s.MarkerWeight *= ITEM_HIGHLIGHT_LINE_SCALE; + // TODO: how to highlight fills? + } + if (!ImHasFlag(gp.CurrentItems->Legend.Flags, ImPlotLegendFlags_NoHighlightAxis)) { + if (gp.CurrentPlot->EnabledAxesX() > 1) + gp.CurrentPlot->Axes[gp.CurrentPlot->CurrentX].ColorHiLi = item->Color; + if (gp.CurrentPlot->EnabledAxesY() > 1) + gp.CurrentPlot->Axes[gp.CurrentPlot->CurrentY].ColorHiLi = item->Color; + } + } + // set render flags + s.RenderLine = s.Colors[ImPlotCol_Line].w > 0 && s.LineWeight > 0; + s.RenderFill = s.Colors[ImPlotCol_Fill].w > 0; + s.RenderMarkerFill = s.Colors[ImPlotCol_MarkerFill].w > 0; + s.RenderMarkerLine = s.Colors[ImPlotCol_MarkerOutline].w > 0 && s.MarkerWeight > 0; + // push rendering clip rect + PushPlotClipRect(); + return true; + } +} + +// Ends an item (call only if BeginItem returns true) +void EndItem() { + ImPlotContext& gp = *GImPlot; + // pop rendering clip rect + PopPlotClipRect(); + // reset next item data + gp.NextItemData.Reset(); + // set current item + gp.PreviousItem = gp.CurrentItem; + gp.CurrentItem = nullptr; +} + +//----------------------------------------------------------------------------- +// [SECTION] Indexers +//----------------------------------------------------------------------------- + +template +IMPLOT_INLINE T IndexData(const T* data, int idx, int count, int offset, int stride) { + const int s = ((offset == 0) << 0) | ((stride == sizeof(T)) << 1); + switch (s) { + case 3 : return data[idx]; + case 2 : return data[(offset + idx) % count]; + case 1 : return *(const T*)(const void*)((const unsigned char*)data + (size_t)((idx) ) * stride); + case 0 : return *(const T*)(const void*)((const unsigned char*)data + (size_t)((offset + idx) % count) * stride); + default: return T(0); + } +} + +template +struct IndexerIdx { + IndexerIdx(const T* data, int count, int offset = 0, int stride = sizeof(T)) : + Data(data), + Count(count), + Offset(count ? ImPosMod(offset, count) : 0), + Stride(stride) + { } + template IMPLOT_INLINE double operator()(I idx) const { + return (double)IndexData(Data, idx, Count, Offset, Stride); + } + const T* Data; + int Count; + int Offset; + int Stride; +}; + +template +struct IndexerAdd { + IndexerAdd(const _Indexer1& indexer1, const _Indexer2& indexer2, double scale1 = 1, double scale2 = 1) + : Indexer1(indexer1), + Indexer2(indexer2), + Scale1(scale1), + Scale2(scale2), + Count(ImMin(Indexer1.Count, Indexer2.Count)) + { } + template IMPLOT_INLINE double operator()(I idx) const { + return Scale1 * Indexer1(idx) + Scale2 * Indexer2(idx); + } + const _Indexer1& Indexer1; + const _Indexer2& Indexer2; + double Scale1; + double Scale2; + int Count; +}; + +struct IndexerLin { + IndexerLin(double m, double b) : M(m), B(b) { } + template IMPLOT_INLINE double operator()(I idx) const { + return M * idx + B; + } + const double M; + const double B; +}; + +struct IndexerConst { + IndexerConst(double ref) : Ref(ref) { } + template IMPLOT_INLINE double operator()(I) const { return Ref; } + const double Ref; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Getters +//----------------------------------------------------------------------------- + +template +struct GetterXY { + GetterXY(_IndexerX x, _IndexerY y, int count) : IndxerX(x), IndxerY(y), Count(count) { } + template IMPLOT_INLINE ImPlotPoint operator()(I idx) const { + return ImPlotPoint(IndxerX(idx),IndxerY(idx)); + } + const _IndexerX IndxerX; + const _IndexerY IndxerY; + const int Count; +}; + +/// Interprets a user's function pointer as ImPlotPoints +struct GetterFuncPtr { + GetterFuncPtr(ImPlotGetter getter, void* data, int count) : + Getter(getter), + Data(data), + Count(count) + { } + template IMPLOT_INLINE ImPlotPoint operator()(I idx) const { + return Getter(idx, Data); + } + ImPlotGetter Getter; + void* const Data; + const int Count; +}; + +template +struct GetterOverrideX { + GetterOverrideX(_Getter getter, double x) : Getter(getter), X(x), Count(getter.Count) { } + template IMPLOT_INLINE ImPlotPoint operator()(I idx) const { + ImPlotPoint p = Getter(idx); + p.x = X; + return p; + } + const _Getter Getter; + const double X; + const int Count; +}; + +template +struct GetterOverrideY { + GetterOverrideY(_Getter getter, double y) : Getter(getter), Y(y), Count(getter.Count) { } + template IMPLOT_INLINE ImPlotPoint operator()(I idx) const { + ImPlotPoint p = Getter(idx); + p.y = Y; + return p; + } + const _Getter Getter; + const double Y; + const int Count; +}; + +template +struct GetterLoop { + GetterLoop(_Getter getter) : Getter(getter), Count(getter.Count + 1) { } + template IMPLOT_INLINE ImPlotPoint operator()(I idx) const { + idx = idx % (Count - 1); + return Getter(idx); + } + const _Getter Getter; + const int Count; +}; + +template +struct GetterError { + GetterError(const T* xs, const T* ys, const T* neg, const T* pos, int count, int offset, int stride) : + Xs(xs), + Ys(ys), + Neg(neg), + Pos(pos), + Count(count), + Offset(count ? ImPosMod(offset, count) : 0), + Stride(stride) + { } + template IMPLOT_INLINE ImPlotPointError operator()(I idx) const { + return ImPlotPointError((double)IndexData(Xs, idx, Count, Offset, Stride), + (double)IndexData(Ys, idx, Count, Offset, Stride), + (double)IndexData(Neg, idx, Count, Offset, Stride), + (double)IndexData(Pos, idx, Count, Offset, Stride)); + } + const T* const Xs; + const T* const Ys; + const T* const Neg; + const T* const Pos; + const int Count; + const int Offset; + const int Stride; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Fitters +//----------------------------------------------------------------------------- + +template +struct Fitter1 { + Fitter1(const _Getter1& getter) : Getter(getter) { } + void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { + for (int i = 0; i < Getter.Count; ++i) { + ImPlotPoint p = Getter(i); + x_axis.ExtendFitWith(y_axis, p.x, p.y); + y_axis.ExtendFitWith(x_axis, p.y, p.x); + } + } + const _Getter1& Getter; +}; + +template +struct FitterX { + FitterX(const _Getter1& getter) : Getter(getter) { } + void Fit(ImPlotAxis& x_axis, ImPlotAxis&) const { + for (int i = 0; i < Getter.Count; ++i) { + ImPlotPoint p = Getter(i); + x_axis.ExtendFit(p.x); + } + } + const _Getter1& Getter; +}; + +template +struct FitterY { + FitterY(const _Getter1& getter) : Getter(getter) { } + void Fit(ImPlotAxis&, ImPlotAxis& y_axis) const { + for (int i = 0; i < Getter.Count; ++i) { + ImPlotPoint p = Getter(i); + y_axis.ExtendFit(p.y); + } + } + const _Getter1& Getter; +}; + +template +struct Fitter2 { + Fitter2(const _Getter1& getter1, const _Getter2& getter2) : Getter1(getter1), Getter2(getter2) { } + void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { + for (int i = 0; i < Getter1.Count; ++i) { + ImPlotPoint p = Getter1(i); + x_axis.ExtendFitWith(y_axis, p.x, p.y); + y_axis.ExtendFitWith(x_axis, p.y, p.x); + } + for (int i = 0; i < Getter2.Count; ++i) { + ImPlotPoint p = Getter2(i); + x_axis.ExtendFitWith(y_axis, p.x, p.y); + y_axis.ExtendFitWith(x_axis, p.y, p.x); + } + } + const _Getter1& Getter1; + const _Getter2& Getter2; +}; + +template +struct FitterBarV { + FitterBarV(const _Getter1& getter1, const _Getter2& getter2, double width) : + Getter1(getter1), + Getter2(getter2), + HalfWidth(width*0.5) + { } + void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { + int count = ImMin(Getter1.Count, Getter2.Count); + for (int i = 0; i < count; ++i) { + ImPlotPoint p1 = Getter1(i); p1.x -= HalfWidth; + ImPlotPoint p2 = Getter2(i); p2.x += HalfWidth; + x_axis.ExtendFitWith(y_axis, p1.x, p1.y); + y_axis.ExtendFitWith(x_axis, p1.y, p1.x); + x_axis.ExtendFitWith(y_axis, p2.x, p2.y); + y_axis.ExtendFitWith(x_axis, p2.y, p2.x); + } + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const double HalfWidth; +}; + +template +struct FitterBarH { + FitterBarH(const _Getter1& getter1, const _Getter2& getter2, double height) : + Getter1(getter1), + Getter2(getter2), + HalfHeight(height*0.5) + { } + void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { + int count = ImMin(Getter1.Count, Getter2.Count); + for (int i = 0; i < count; ++i) { + ImPlotPoint p1 = Getter1(i); p1.y -= HalfHeight; + ImPlotPoint p2 = Getter2(i); p2.y += HalfHeight; + x_axis.ExtendFitWith(y_axis, p1.x, p1.y); + y_axis.ExtendFitWith(x_axis, p1.y, p1.x); + x_axis.ExtendFitWith(y_axis, p2.x, p2.y); + y_axis.ExtendFitWith(x_axis, p2.y, p2.x); + } + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const double HalfHeight; +}; + +struct FitterRect { + FitterRect(const ImPlotPoint& pmin, const ImPlotPoint& pmax) : + Pmin(pmin), + Pmax(pmax) + { } + FitterRect(const ImPlotRect& rect) : + FitterRect(rect.Min(), rect.Max()) + { } + void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const { + x_axis.ExtendFitWith(y_axis, Pmin.x, Pmin.y); + y_axis.ExtendFitWith(x_axis, Pmin.y, Pmin.x); + x_axis.ExtendFitWith(y_axis, Pmax.x, Pmax.y); + y_axis.ExtendFitWith(x_axis, Pmax.y, Pmax.x); + } + const ImPlotPoint Pmin; + const ImPlotPoint Pmax; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Transformers +//----------------------------------------------------------------------------- + +struct Transformer1 { + Transformer1(double pixMin, double pltMin, double pltMax, double m, double scaMin, double scaMax, ImPlotTransform fwd, void* data) : + ScaMin(scaMin), + ScaMax(scaMax), + PltMin(pltMin), + PltMax(pltMax), + PixMin(pixMin), + M(m), + TransformFwd(fwd), + TransformData(data) + { } + + template IMPLOT_INLINE float operator()(T p) const { + if (TransformFwd != nullptr) { + double s = TransformFwd(p, TransformData); + double t = (s - ScaMin) / (ScaMax - ScaMin); + p = PltMin + (PltMax - PltMin) * t; + } + return (float)(PixMin + M * (p - PltMin)); + } + + double ScaMin, ScaMax, PltMin, PltMax, PixMin, M; + ImPlotTransform TransformFwd; + void* TransformData; +}; + +struct Transformer2 { + Transformer2(const ImPlotAxis& x_axis, const ImPlotAxis& y_axis) : + Tx(x_axis.PixelMin, + x_axis.Range.Min, + x_axis.Range.Max, + x_axis.ScaleToPixel, + x_axis.ScaleMin, + x_axis.ScaleMax, + x_axis.TransformForward, + x_axis.TransformData), + Ty(y_axis.PixelMin, + y_axis.Range.Min, + y_axis.Range.Max, + y_axis.ScaleToPixel, + y_axis.ScaleMin, + y_axis.ScaleMax, + y_axis.TransformForward, + y_axis.TransformData) + { } + + Transformer2(const ImPlotPlot& plot) : + Transformer2(plot.Axes[plot.CurrentX], plot.Axes[plot.CurrentY]) + { } + + Transformer2() : + Transformer2(*GImPlot->CurrentPlot) + { } + + template IMPLOT_INLINE ImVec2 operator()(const P& plt) const { + ImVec2 out; + out.x = Tx(plt.x); + out.y = Ty(plt.y); + return out; + } + + template IMPLOT_INLINE ImVec2 operator()(T x, T y) const { + ImVec2 out; + out.x = Tx(x); + out.y = Ty(y); + return out; + } + + Transformer1 Tx; + Transformer1 Ty; +}; + +//----------------------------------------------------------------------------- +// [SECTION] Renderers +//----------------------------------------------------------------------------- + +struct RendererBase { + RendererBase(int prims, int idx_consumed, int vtx_consumed) : + Prims(prims), + IdxConsumed(idx_consumed), + VtxConsumed(vtx_consumed) + { } + const int Prims; + Transformer2 Transformer; + const int IdxConsumed; + const int VtxConsumed; +}; + +template +struct RendererLineStrip : RendererBase { + RendererLineStrip(const _Getter& getter, ImU32 col, float weight) : + RendererBase(getter.Count - 1, 6, 4), + Getter(getter), + Col(col), + HalfWeight(ImMax(1.0f,weight)*0.5f) + { + P1 = this->Transformer(Getter(0)); + } + void Init(ImDrawList& draw_list) const { + GetLineRenderProps(draw_list, HalfWeight, UV0, UV1); + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P2 = this->Transformer(Getter(prim + 1)); + if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) { + P1 = P2; + return false; + } + PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1); + P1 = P2; + return true; + } + const _Getter& Getter; + const ImU32 Col; + mutable float HalfWeight; + mutable ImVec2 P1; + mutable ImVec2 UV0; + mutable ImVec2 UV1; +}; + +template +struct RendererLineStripSkip : RendererBase { + RendererLineStripSkip(const _Getter& getter, ImU32 col, float weight) : + RendererBase(getter.Count - 1, 6, 4), + Getter(getter), + Col(col), + HalfWeight(ImMax(1.0f,weight)*0.5f) + { + P1 = this->Transformer(Getter(0)); + } + void Init(ImDrawList& draw_list) const { + GetLineRenderProps(draw_list, HalfWeight, UV0, UV1); + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P2 = this->Transformer(Getter(prim + 1)); + if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) { + if (!ImNan(P2.x) && !ImNan(P2.y)) + P1 = P2; + return false; + } + PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1); + if (!ImNan(P2.x) && !ImNan(P2.y)) + P1 = P2; + return true; + } + const _Getter& Getter; + const ImU32 Col; + mutable float HalfWeight; + mutable ImVec2 P1; + mutable ImVec2 UV0; + mutable ImVec2 UV1; +}; + +template +struct RendererLineSegments1 : RendererBase { + RendererLineSegments1(const _Getter& getter, ImU32 col, float weight) : + RendererBase(getter.Count / 2, 6, 4), + Getter(getter), + Col(col), + HalfWeight(ImMax(1.0f,weight)*0.5f) + { } + void Init(ImDrawList& draw_list) const { + GetLineRenderProps(draw_list, HalfWeight, UV0, UV1); + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P1 = this->Transformer(Getter(prim*2+0)); + ImVec2 P2 = this->Transformer(Getter(prim*2+1)); + if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) + return false; + PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1); + return true; + } + const _Getter& Getter; + const ImU32 Col; + mutable float HalfWeight; + mutable ImVec2 UV0; + mutable ImVec2 UV1; +}; + +template +struct RendererLineSegments2 : RendererBase { + RendererLineSegments2(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, float weight) : + RendererBase(ImMin(getter1.Count, getter1.Count), 6, 4), + Getter1(getter1), + Getter2(getter2), + Col(col), + HalfWeight(ImMax(1.0f,weight)*0.5f) + {} + void Init(ImDrawList& draw_list) const { + GetLineRenderProps(draw_list, HalfWeight, UV0, UV1); + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P1 = this->Transformer(Getter1(prim)); + ImVec2 P2 = this->Transformer(Getter2(prim)); + if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) + return false; + PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1); + return true; + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const ImU32 Col; + mutable float HalfWeight; + mutable ImVec2 UV0; + mutable ImVec2 UV1; +}; + +template +struct RendererBarsFillV : RendererBase { + RendererBarsFillV(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double width) : + RendererBase(ImMin(getter1.Count, getter1.Count), 6, 4), + Getter1(getter1), + Getter2(getter2), + Col(col), + HalfWidth(width/2) + {} + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImPlotPoint p1 = Getter1(prim); + ImPlotPoint p2 = Getter2(prim); + p1.x += HalfWidth; + p2.x -= HalfWidth; + ImVec2 P1 = this->Transformer(p1); + ImVec2 P2 = this->Transformer(p2); + float width_px = ImAbs(P1.x-P2.x); + if (width_px < 1.0f) { + P1.x += P1.x > P2.x ? (1-width_px) / 2 : (width_px-1) / 2; + P2.x += P2.x > P1.x ? (1-width_px) / 2 : (width_px-1) / 2; + } + ImVec2 PMin = ImMin(P1, P2); + ImVec2 PMax = ImMax(P1, P2); + if (!cull_rect.Overlaps(ImRect(PMin, PMax))) + return false; + PrimRectFill(draw_list,PMin,PMax,Col,UV); + return true; + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const ImU32 Col; + const double HalfWidth; + mutable ImVec2 UV; +}; + +template +struct RendererBarsFillH : RendererBase { + RendererBarsFillH(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double height) : + RendererBase(ImMin(getter1.Count, getter1.Count), 6, 4), + Getter1(getter1), + Getter2(getter2), + Col(col), + HalfHeight(height/2) + {} + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImPlotPoint p1 = Getter1(prim); + ImPlotPoint p2 = Getter2(prim); + p1.y += HalfHeight; + p2.y -= HalfHeight; + ImVec2 P1 = this->Transformer(p1); + ImVec2 P2 = this->Transformer(p2); + float height_px = ImAbs(P1.y-P2.y); + if (height_px < 1.0f) { + P1.y += P1.y > P2.y ? (1-height_px) / 2 : (height_px-1) / 2; + P2.y += P2.y > P1.y ? (1-height_px) / 2 : (height_px-1) / 2; + } + ImVec2 PMin = ImMin(P1, P2); + ImVec2 PMax = ImMax(P1, P2); + if (!cull_rect.Overlaps(ImRect(PMin, PMax))) + return false; + PrimRectFill(draw_list,PMin,PMax,Col,UV); + return true; + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const ImU32 Col; + const double HalfHeight; + mutable ImVec2 UV; +}; + +template +struct RendererBarsLineV : RendererBase { + RendererBarsLineV(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double width, float weight) : + RendererBase(ImMin(getter1.Count, getter1.Count), 24, 8), + Getter1(getter1), + Getter2(getter2), + Col(col), + HalfWidth(width/2), + Weight(weight) + {} + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImPlotPoint p1 = Getter1(prim); + ImPlotPoint p2 = Getter2(prim); + p1.x += HalfWidth; + p2.x -= HalfWidth; + ImVec2 P1 = this->Transformer(p1); + ImVec2 P2 = this->Transformer(p2); + float width_px = ImAbs(P1.x-P2.x); + if (width_px < 1.0f) { + P1.x += P1.x > P2.x ? (1-width_px) / 2 : (width_px-1) / 2; + P2.x += P2.x > P1.x ? (1-width_px) / 2 : (width_px-1) / 2; + } + ImVec2 PMin = ImMin(P1, P2); + ImVec2 PMax = ImMax(P1, P2); + if (!cull_rect.Overlaps(ImRect(PMin, PMax))) + return false; + PrimRectLine(draw_list,PMin,PMax,Weight,Col,UV); + return true; + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const ImU32 Col; + const double HalfWidth; + const float Weight; + mutable ImVec2 UV; +}; + +template +struct RendererBarsLineH : RendererBase { + RendererBarsLineH(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double height, float weight) : + RendererBase(ImMin(getter1.Count, getter1.Count), 24, 8), + Getter1(getter1), + Getter2(getter2), + Col(col), + HalfHeight(height/2), + Weight(weight) + {} + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImPlotPoint p1 = Getter1(prim); + ImPlotPoint p2 = Getter2(prim); + p1.y += HalfHeight; + p2.y -= HalfHeight; + ImVec2 P1 = this->Transformer(p1); + ImVec2 P2 = this->Transformer(p2); + float height_px = ImAbs(P1.y-P2.y); + if (height_px < 1.0f) { + P1.y += P1.y > P2.y ? (1-height_px) / 2 : (height_px-1) / 2; + P2.y += P2.y > P1.y ? (1-height_px) / 2 : (height_px-1) / 2; + } + ImVec2 PMin = ImMin(P1, P2); + ImVec2 PMax = ImMax(P1, P2); + if (!cull_rect.Overlaps(ImRect(PMin, PMax))) + return false; + PrimRectLine(draw_list,PMin,PMax,Weight,Col,UV); + return true; + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const ImU32 Col; + const double HalfHeight; + const float Weight; + mutable ImVec2 UV; +}; + + +template +struct RendererStairsPre : RendererBase { + RendererStairsPre(const _Getter& getter, ImU32 col, float weight) : + RendererBase(getter.Count - 1, 12, 8), + Getter(getter), + Col(col), + HalfWeight(ImMax(1.0f,weight)*0.5f) + { + P1 = this->Transformer(Getter(0)); + } + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P2 = this->Transformer(Getter(prim + 1)); + if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) { + P1 = P2; + return false; + } + PrimRectFill(draw_list, ImVec2(P1.x - HalfWeight, P1.y), ImVec2(P1.x + HalfWeight, P2.y), Col, UV); + PrimRectFill(draw_list, ImVec2(P1.x, P2.y + HalfWeight), ImVec2(P2.x, P2.y - HalfWeight), Col, UV); + P1 = P2; + return true; + } + const _Getter& Getter; + const ImU32 Col; + mutable float HalfWeight; + mutable ImVec2 P1; + mutable ImVec2 UV; +}; + +template +struct RendererStairsPost : RendererBase { + RendererStairsPost(const _Getter& getter, ImU32 col, float weight) : + RendererBase(getter.Count - 1, 12, 8), + Getter(getter), + Col(col), + HalfWeight(ImMax(1.0f,weight) * 0.5f) + { + P1 = this->Transformer(Getter(0)); + } + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P2 = this->Transformer(Getter(prim + 1)); + if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) { + P1 = P2; + return false; + } + PrimRectFill(draw_list, ImVec2(P1.x, P1.y + HalfWeight), ImVec2(P2.x, P1.y - HalfWeight), Col, UV); + PrimRectFill(draw_list, ImVec2(P2.x - HalfWeight, P2.y), ImVec2(P2.x + HalfWeight, P1.y), Col, UV); + P1 = P2; + return true; + } + const _Getter& Getter; + const ImU32 Col; + mutable float HalfWeight; + mutable ImVec2 P1; + mutable ImVec2 UV; +}; + +template +struct RendererStairsPreShaded : RendererBase { + RendererStairsPreShaded(const _Getter& getter, ImU32 col) : + RendererBase(getter.Count - 1, 6, 4), + Getter(getter), + Col(col) + { + P1 = this->Transformer(Getter(0)); + Y0 = this->Transformer(ImPlotPoint(0,0)).y; + } + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P2 = this->Transformer(Getter(prim + 1)); + ImVec2 PMin(ImMin(P1.x, P2.x), ImMin(Y0, P2.y)); + ImVec2 PMax(ImMax(P1.x, P2.x), ImMax(Y0, P2.y)); + if (!cull_rect.Overlaps(ImRect(PMin, PMax))) { + P1 = P2; + return false; + } + PrimRectFill(draw_list, PMin, PMax, Col, UV); + P1 = P2; + return true; + } + const _Getter& Getter; + const ImU32 Col; + float Y0; + mutable ImVec2 P1; + mutable ImVec2 UV; +}; + +template +struct RendererStairsPostShaded : RendererBase { + RendererStairsPostShaded(const _Getter& getter, ImU32 col) : + RendererBase(getter.Count - 1, 6, 4), + Getter(getter), + Col(col) + { + P1 = this->Transformer(Getter(0)); + Y0 = this->Transformer(ImPlotPoint(0,0)).y; + } + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P2 = this->Transformer(Getter(prim + 1)); + ImVec2 PMin(ImMin(P1.x, P2.x), ImMin(P1.y, Y0)); + ImVec2 PMax(ImMax(P1.x, P2.x), ImMax(P1.y, Y0)); + if (!cull_rect.Overlaps(ImRect(PMin, PMax))) { + P1 = P2; + return false; + } + PrimRectFill(draw_list, PMin, PMax, Col, UV); + P1 = P2; + return true; + } + const _Getter& Getter; + const ImU32 Col; + float Y0; + mutable ImVec2 P1; + mutable ImVec2 UV; +}; + + + +template +struct RendererShaded : RendererBase { + RendererShaded(const _Getter1& getter1, const _Getter2& getter2, ImU32 col) : + RendererBase(ImMin(getter1.Count, getter2.Count) - 1, 6, 5), + Getter1(getter1), + Getter2(getter2), + Col(col) + { + P11 = this->Transformer(Getter1(0)); + P12 = this->Transformer(Getter2(0)); + } + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + ImVec2 P21 = this->Transformer(Getter1(prim+1)); + ImVec2 P22 = this->Transformer(Getter2(prim+1)); + ImRect rect(ImMin(ImMin(ImMin(P11,P12),P21),P22), ImMax(ImMax(ImMax(P11,P12),P21),P22)); + if (!cull_rect.Overlaps(rect)) { + P11 = P21; + P12 = P22; + return false; + } + const int intersect = (P11.y > P12.y && P22.y > P21.y) || (P12.y > P11.y && P21.y > P22.y); + const ImVec2 intersection = intersect == 0 ? ImVec2(0,0) : Intersection(P11,P21,P12,P22); + draw_list._VtxWritePtr[0].pos = P11; + draw_list._VtxWritePtr[0].uv = UV; + draw_list._VtxWritePtr[0].col = Col; + draw_list._VtxWritePtr[1].pos = P21; + draw_list._VtxWritePtr[1].uv = UV; + draw_list._VtxWritePtr[1].col = Col; + draw_list._VtxWritePtr[2].pos = intersection; + draw_list._VtxWritePtr[2].uv = UV; + draw_list._VtxWritePtr[2].col = Col; + draw_list._VtxWritePtr[3].pos = P12; + draw_list._VtxWritePtr[3].uv = UV; + draw_list._VtxWritePtr[3].col = Col; + draw_list._VtxWritePtr[4].pos = P22; + draw_list._VtxWritePtr[4].uv = UV; + draw_list._VtxWritePtr[4].col = Col; + draw_list._VtxWritePtr += 5; + draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx); + draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1 + intersect); + draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3); + draw_list._IdxWritePtr[3] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1); + draw_list._IdxWritePtr[4] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 4); + draw_list._IdxWritePtr[5] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3 - intersect); + draw_list._IdxWritePtr += 6; + draw_list._VtxCurrentIdx += 5; + P11 = P21; + P12 = P22; + return true; + } + const _Getter1& Getter1; + const _Getter2& Getter2; + const ImU32 Col; + mutable ImVec2 P11; + mutable ImVec2 P12; + mutable ImVec2 UV; +}; + +struct RectC { + ImPlotPoint Pos; + ImPlotPoint HalfSize; + ImU32 Color; +}; + +template +struct RendererRectC : RendererBase { + RendererRectC(const _Getter& getter) : + RendererBase(getter.Count, 6, 4), + Getter(getter) + {} + void Init(ImDrawList& draw_list) const { + UV = draw_list._Data->TexUvWhitePixel; + } + IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const { + RectC rect = Getter(prim); + ImVec2 P1 = this->Transformer(rect.Pos.x - rect.HalfSize.x , rect.Pos.y - rect.HalfSize.y); + ImVec2 P2 = this->Transformer(rect.Pos.x + rect.HalfSize.x , rect.Pos.y + rect.HalfSize.y); + if ((rect.Color & IM_COL32_A_MASK) == 0 || !cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) + return false; + PrimRectFill(draw_list,P1,P2,rect.Color,UV); + return true; + } + const _Getter& Getter; + mutable ImVec2 UV; +}; + +//----------------------------------------------------------------------------- +// [SECTION] RenderPrimitives +//----------------------------------------------------------------------------- + +/// Renders primitive shapes in bulk as efficiently as possible. +template +void RenderPrimitivesEx(const _Renderer& renderer, ImDrawList& draw_list, const ImRect& cull_rect) { + unsigned int prims = renderer.Prims; + unsigned int prims_culled = 0; + unsigned int idx = 0; + renderer.Init(draw_list); + while (prims) { + // find how many can be reserved up to end of current draw command's limit + unsigned int cnt = ImMin(prims, (MaxIdx::Value - draw_list._VtxCurrentIdx) / renderer.VtxConsumed); + // make sure at least this many elements can be rendered to avoid situations where at the end of buffer this slow path is not taken all the time + if (cnt >= ImMin(64u, prims)) { + if (prims_culled >= cnt) + prims_culled -= cnt; // reuse previous reservation + else { + // add more elements to previous reservation + draw_list.PrimReserve((cnt - prims_culled) * renderer.IdxConsumed, (cnt - prims_culled) * renderer.VtxConsumed); + prims_culled = 0; + } + } + else + { + if (prims_culled > 0) { + draw_list.PrimUnreserve(prims_culled * renderer.IdxConsumed, prims_culled * renderer.VtxConsumed); + prims_culled = 0; + } + cnt = ImMin(prims, (MaxIdx::Value - 0/*draw_list._VtxCurrentIdx*/) / renderer.VtxConsumed); + // reserve new draw command + draw_list.PrimReserve(cnt * renderer.IdxConsumed, cnt * renderer.VtxConsumed); + } + prims -= cnt; + for (unsigned int ie = idx + cnt; idx != ie; ++idx) { + if (!renderer.Render(draw_list, cull_rect, idx)) + prims_culled++; + } + } + if (prims_culled > 0) + draw_list.PrimUnreserve(prims_culled * renderer.IdxConsumed, prims_culled * renderer.VtxConsumed); +} + +template