diff options
Diffstat (limited to 'vendor/zgui/libs/imgui_test_engine')
23 files changed, 0 insertions, 16405 deletions
diff --git a/vendor/zgui/libs/imgui_test_engine/LICENSE.txt b/vendor/zgui/libs/imgui_test_engine/LICENSE.txt deleted file mode 100644 index 12cacfe..0000000 --- a/vendor/zgui/libs/imgui_test_engine/LICENSE.txt +++ /dev/null @@ -1,57 +0,0 @@ -Dear ImGui Test Engine License (v1.03) -Copyright (c) 2018-2023 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"). - -1. LICENSE MODELS - -1.1. Free license - -The Licensor grants you a free license ("Free License") if you meet ANY of the following -criterion: - -- You are a natural person; -- You are not a legal entity, or you are a not-for-profit legal entity; -- You are using this Software for educational purposes; -- You are using this Software to create Derivative Software released publicly and under - an Open Source license, as defined by the Open Source Initiative; -- You are a legal entity with a turnover inferior to 2 million USD (or equivalent) during - your last fiscal year. - -1.2. Paid license - -If you do not meet any criterion of Article 1.1, Licensor grants you a trial period of a -maximum of 45 days, at no charge. Following this trial period, you must subscribe to a paid -license ("Paid License") with the Licensor to continue using the Software. -Paid Licenses are exclusively sold by DISCO HELLO. Paid Licenses and the associated -information are available at the following URL: http://www.dearimgui.com/licenses - -2. GRANT OF 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"). - -2.2. Right of distribution - -License holders may also publish and/or distribute the Software or any Derivative -Software. The above copyright notice and this license shall be included in all copies -or substantial portions of the Software and/or Derivative Software. - -License holders may add their own attribution notices within the Derivative Software -that they distribute. Such attribution notices must not directly or indirectly imply a -modification of the License. License holders may provide to their modifications their -own copyright and/or additional or different terms and conditions, providing such -conditions complies with this License. - -3. DISCLAIMER - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE 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/vendor/zgui/libs/imgui_test_engine/imgui_capture_tool.cpp b/vendor/zgui/libs/imgui_test_engine/imgui_capture_tool.cpp deleted file mode 100644 index eb56e0e..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_capture_tool.cpp +++ /dev/null @@ -1,1114 +0,0 @@ -// dear imgui test engine -// (screen/video capture tool) -// This is usable as a standalone applet or controlled by the test engine. - -// Two mode of operation: -// - Interactive: call ImGuiCaptureToolUI::ShowCaptureToolWindow() -// - Programmatic: generally via ImGuiTestContext::CaptureXXX functions - -// FIXME: This probably needs a rewrite, it's a bit too complicated. - -/* - -Index of this file: - -// [SECTION] Includes -// [SECTION] ImGuiCaptureImageBuf -// [SECTION] ImGuiCaptureContext -// [SECTION] ImGuiCaptureToolUI - -*/ - -//----------------------------------------------------------------------------- -// [SECTION] Includes -//----------------------------------------------------------------------------- - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -#define _CRT_SECURE_NO_WARNINGS -#endif - -#define IMGUI_DEFINE_MATH_OPERATORS -#include "imgui.h" -#include "imgui_internal.h" -#include "imgui_capture_tool.h" -#include "imgui_te_utils.h" // ImPathFindFilename, ImPathFindExtension, ImPathFixSeparatorsForCurrentOS, ImFileCreateDirectoryChain, ImOsOpenInShell -#include "thirdparty/Str/Str.h" - -//----------------------------------------------------------------------------- -// [SECTION] Link stb_image_write.h -//----------------------------------------------------------------------------- - -#if IMGUI_TEST_ENGINE_ENABLE_CAPTURE - -// Compile time options: -//#define IMGUI_STB_NAMESPACE ImStb -//#define IMGUI_STB_IMAGE_WRITE_FILENAME "my_folder/stb_image_write.h" -//#define IMGUI_DISABLE_STB_IMAGE_WRITE_IMPLEMENTATION - -// stb_image_write -#ifdef _MSC_VER -#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 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wsign-conversion" -#endif - -#ifdef IMGUI_STB_NAMESPACE -namespace IMGUI_STB_NAMESPACE -{ -#endif -#ifndef STB_IMAGE_WRITE_IMPLEMENTATION // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds) -#ifndef IMGUI_DISABLE_STB_IMAGE_WRITE_IMPLEMENTATION // in case the user already have an implementation in another compilation unit -#define STB_IMAGE_WRITE_IMPLEMENTATION -#endif -#ifdef IMGUI_STB_IMAGE_WRITE_FILENAME -#include IMGUI_STB_IMAGE_WRITE_FILENAME -#else -#include "thirdparty/stb/imstb_image_write.h" -#endif // #ifdef IMGUI_STB_IMAGE_WRITE_FILENAME -#endif // #ifndef STB_IMAGE_WRITE_IMPLEMENTATION -#ifdef IMGUI_STB_NAMESPACE -} // namespace ImStb -using namespace IMGUI_STB_NAMESPACE; -#endif - -#ifdef _MSC_VER -#pragma warning (pop) -#else -#pragma GCC diagnostic pop -#endif - -#endif // #if IMGUI_TEST_ENGINE_ENABLE_CAPTURE - -//----------------------------------------------------------------------------- -// [SECTION] ImGuiCaptureImageBuf -// Helper class for simple bitmap manipulation (not particularly efficient!) -//----------------------------------------------------------------------------- - -void ImGuiCaptureImageBuf::Clear() -{ - if (Data) - IM_FREE(Data); - Data = NULL; -} - -void ImGuiCaptureImageBuf::CreateEmpty(int w, int h) -{ - Clear(); - Width = w; - Height = h; - Data = (unsigned int*)IM_ALLOC((size_t)(Width * Height * 4)); - memset(Data, 0, (size_t)(Width * Height * 4)); -} - -bool ImGuiCaptureImageBuf::SaveFile(const char* filename) -{ -#if IMGUI_TEST_ENGINE_ENABLE_CAPTURE - IM_ASSERT(Data != NULL); - ImFileCreateDirectoryChain(filename, ImPathFindFilename(filename)); - int ret = stbi_write_png(filename, Width, Height, 4, Data, Width * 4); - return ret != 0; -#else - IM_UNUSED(filename); - return false; -#endif -} - -void ImGuiCaptureImageBuf::RemoveAlpha() -{ - unsigned int* p = Data; - int n = Width * Height; - while (n-- > 0) - { - *p |= IM_COL32_A_MASK; - p++; - } -} - -//----------------------------------------------------------------------------- -// [SECTION] ImGuiCaptureContext -//----------------------------------------------------------------------------- - -#if IMGUI_TEST_ENGINE_ENABLE_CAPTURE -static void HideOtherWindows(const ImGuiCaptureArgs* args) -{ - ImGuiContext& g = *GImGui; - for (ImGuiWindow* window : g.Windows) - { - if (window->Flags & ImGuiWindowFlags_ChildWindow) - continue; - if ((window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0 && (args->InFlags & ImGuiCaptureFlags_IncludeTooltipsAndPopups) != 0) - continue; - if (args->InCaptureWindows.contains(window)) - continue; - -#ifdef IMGUI_HAS_DOCK - bool should_hide_window = true; - for (ImGuiWindow* capture_window : args->InCaptureWindows) - { - if (capture_window->DockNode != NULL && capture_window->DockNode->HostWindow->RootWindow == window) - { - should_hide_window = false; - break; - } - } - if (!should_hide_window) - continue; -#endif // IMGUI_HAS_DOCK - - // Not overwriting HiddenFramesCanSkipItems or HiddenFramesCannotSkipItems since they have side-effects (e.g. preserving ContentsSize) - if (window->WasActive || window->Active) - window->HiddenFramesForRenderOnly = 2; - } -} -#endif // IMGUI_TEST_ENGINE_ENABLE_CAPTURE - -static ImRect GetMainViewportRect() -{ - ImGuiViewport* viewport = ImGui::GetMainViewport(); - return ImRect(viewport->Pos, viewport->Pos + viewport->Size); -} - -void ImGuiCaptureContext::PreNewFrame() -{ - const ImGuiCaptureArgs* args = _CaptureArgs; - if (args == NULL) - return; - - ImGuiContext& g = *GImGui; - - // Force mouse position. Hovered window is reset in ImGui::NewFrame() based on mouse real mouse position. - if (_FrameNo > 2 && (args->InFlags & ImGuiCaptureFlags_StitchAll) != 0) - { - IM_ASSERT(args->InCaptureWindows.Size == 1); - g.IO.MousePos = args->InCaptureWindows[0]->Pos + _MouseRelativeToWindowPos; - g.HoveredWindow = _HoveredWindow; - } -} - -void ImGuiCaptureContext::PreRender() -{ - ImGuiContext& g = *GImGui; - _BackupMouseDrawCursor = g.IO.MouseDrawCursor; - if (IsCapturing()) - { - const ImGuiCaptureArgs* args = _CaptureArgs; - g.IO.MouseDrawCursor = !(args->InFlags & ImGuiCaptureFlags_HideMouseCursor); - } -} - -void ImGuiCaptureContext::PostRender() -{ - ImGuiContext& g = *GImGui; - g.IO.MouseDrawCursor = _BackupMouseDrawCursor; -} - -void ImGuiCaptureContext::RestoreBackedUpData() -{ - // Restore window positions unconditionally. We may have moved them ourselves during capture. - ImGuiContext& g = *GImGui; - for (int n = 0; n < _WindowsData.Size; n++) - { - ImGuiWindow* window = _WindowsData[n].Window; - if (window->Hidden) - continue; - ImGui::SetWindowPos(window, _WindowsData[n].BackupRect.Min, ImGuiCond_Always); - ImGui::SetWindowSize(window, _WindowsData[n].BackupRect.GetSize(), ImGuiCond_Always); - } - g.Style.DisplayWindowPadding = _BackupDisplayWindowPadding; - g.Style.DisplaySafeAreaPadding = _BackupDisplaySafeAreaPadding; -} - -void ImGuiCaptureContext::ClearState() -{ - _FrameNo = _ChunkNo = 0; - _VideoLastFrameTime = 0; - _MouseRelativeToWindowPos = ImVec2(-FLT_MAX, -FLT_MAX); - _HoveredWindow = NULL; - _CaptureArgs = NULL; -} - -// Returns true when capture is in progress. -ImGuiCaptureStatus ImGuiCaptureContext::CaptureUpdate(ImGuiCaptureArgs* args) -{ -#if IMGUI_TEST_ENGINE_ENABLE_CAPTURE - ImGuiContext& g = *GImGui; - ImGuiIO& io = g.IO; - - // Sanity checks - IM_ASSERT(args != NULL); - IM_ASSERT(ScreenCaptureFunc != NULL); - IM_ASSERT(args->InOutputImageBuf != NULL || args->InOutputFile[0]); - IM_ASSERT(args->InRecordFPSTarget != 0); - if (_VideoRecording) - { - IM_ASSERT(args->InOutputFile[0] && "Output filename must be specified when recording videos."); - IM_ASSERT(args->InOutputImageBuf == NULL && "Output buffer cannot be specified when recording videos."); - IM_ASSERT((args->InFlags & ImGuiCaptureFlags_StitchAll) == 0 && "Image stitching is not supported when recording videos."); - if (!ImFileExist(VideoCaptureEncoderPath)) - { - fprintf(stderr, "Video encoder not found at \"%s\", video capturing failed.\n", VideoCaptureEncoderPath); - return ImGuiCaptureStatus_Error; - } - } - - ImGuiCaptureImageBuf* output = args->InOutputImageBuf ? args->InOutputImageBuf : &_CaptureBuf; - const ImRect viewport_rect = GetMainViewportRect(); - - // Hide other windows so they can't be seen visible behind captured window - if ((args->InFlags & ImGuiCaptureFlags_IncludeOtherWindows) == 0 && !args->InCaptureWindows.empty()) - HideOtherWindows(args); - - // Recording will be set to false when we are stopping video capture. - const bool is_recording_video = IsCapturingVideo(); - const double current_time_sec = ImGui::GetTime(); - if (is_recording_video && _VideoLastFrameTime > 0) - { - double delta_sec = current_time_sec - _VideoLastFrameTime; - if (delta_sec < 1.0 / args->InRecordFPSTarget) - return ImGuiCaptureStatus_InProgress; - } - - // Capture can be performed in single frame if we are capturing a rect. - const bool instant_capture = (args->InFlags & ImGuiCaptureFlags_Instant) != 0; - const bool is_capturing_explicit_rect = args->InCaptureRect.GetWidth() > 0 && args->InCaptureRect.GetHeight() > 0; - if (instant_capture) - { - IM_ASSERT(args->InCaptureWindows.empty()); - IM_ASSERT(is_capturing_explicit_rect); - IM_ASSERT(is_recording_video == false); - IM_ASSERT((args->InFlags & ImGuiCaptureFlags_StitchAll) == 0); - } - - // Do not start a capture process while mouse button is pressed. In case mouse cursor is hovering a captured window, - // pressed button may cause window to be repositioned unexpectedly. This is only important in stitched mode, because - // this is the only time we move mouse cursor. - if ((args->InFlags & ImGuiCaptureFlags_StitchAll) != 0) - if (g.IO.MouseDown[0] && _FrameNo == 0) - return ImGuiCaptureStatus_InProgress; - - //----------------------------------------------------------------- - // Frame 0: Initialize capture state - //----------------------------------------------------------------- - if (_FrameNo == 0) - { - if (is_recording_video) - { - // Determinate size alignment - const char* extension = (char*)ImPathFindExtension(args->InOutputFile); - if (args->InSizeAlign == 0) - { - if (strcmp(extension, ".gif") == 0) - args->InSizeAlign = 1; - else - args->InSizeAlign = 2; // mp4 wants >= 2 - } - IM_ASSERT(args->InSizeAlign > 0); - } - - // When recording, same args should have been passed to BeginVideoCapture(). - IM_ASSERT(!_VideoRecording || _CaptureArgs == args); - - _CaptureArgs = args; - _ChunkNo = 0; - _CaptureRect = _CapturedWindowRect = ImRect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); - _WindowsData.clear(); - _BackupDisplayWindowPadding = g.Style.DisplayWindowPadding; - _BackupDisplaySafeAreaPadding = g.Style.DisplaySafeAreaPadding; - g.Style.DisplayWindowPadding = ImVec2(0, 0); // Allow windows to be positioned fully outside of visible viewport. - g.Style.DisplaySafeAreaPadding = ImVec2(0, 0); - - if (is_capturing_explicit_rect) - { - // Capture arbitrary rectangle. If any windows are specified in this mode only they will appear in captured region. - _CaptureRect = args->InCaptureRect; - if (args->InCaptureWindows.empty() && !instant_capture) - { - // Gather all top level windows. We will need to move them in order to capture regions larger than viewport. - for (ImGuiWindow* window : g.Windows) - { - // Child windows will be included by their parents. - if (window->ParentWindow != NULL) - continue; - if ((window->Flags & ImGuiWindowFlags_Popup || window->Flags & ImGuiWindowFlags_Tooltip) && !(args->InFlags & ImGuiCaptureFlags_IncludeTooltipsAndPopups)) - continue; - args->InCaptureWindows.push_back(window); - } - } - } - - // Save rectangle covering all windows and find top-left corner of combined rect which will be used to - // translate this group of windows to top-left corner of the screen. - for (ImGuiWindow* window : args->InCaptureWindows) - { - _CapturedWindowRect.Add(window->Rect()); - ImGuiCaptureWindowData window_data; - window_data.BackupRect = window->Rect(); - window_data.Window = window; - _WindowsData.push_back(window_data); - } - - if (args->InFlags & ImGuiCaptureFlags_StitchAll) - { - IM_ASSERT(is_capturing_explicit_rect == false && "ImGuiCaptureContext: capture of full window contents is not possible when capturing specified rect."); - IM_ASSERT(args->InCaptureWindows.Size == 1 && "ImGuiCaptureContext: capture of full window contents is not possible when capturing more than one window."); - - // Resize window to it's contents and capture it's entire width/height. However if window is bigger than it's contents - keep original size. - ImGuiWindow* window = args->InCaptureWindows[0]; - ImVec2 full_size = window->SizeFull; - - // Mouse cursor is relative to captured window even if it is not hovered, in which case cursor is kept off the window to prevent appearing in screenshot multiple times by accident. - _MouseRelativeToWindowPos = io.MousePos - window->Pos + window->Scroll; - - // 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()); - ImGui::SetWindowSize(window, full_size); - _HoveredWindow = g.HoveredWindow; - } - else - { - _MouseRelativeToWindowPos = ImVec2(-FLT_MAX, -FLT_MAX); - _HoveredWindow = NULL; - } - } - else - { - IM_ASSERT(args == _CaptureArgs); // Capture args can not change mid-capture. - } - - //----------------------------------------------------------------- - // Frame 1: Skipped to allow window size to update fully - //----------------------------------------------------------------- - - //----------------------------------------------------------------- - // Frame 2: Position windows, lock rectangle, create capture buffer - //----------------------------------------------------------------- - if (_FrameNo == 2 || instant_capture) - { - // Move group of windows so combined rectangle position is at the top-left corner + padding and create combined - // capture rect of entire area that will be saved to screenshot. Doing this on the second frame because when - // ImGuiCaptureFlags_StitchAll flag is used we need to allow window to reposition. - // Repositioning of a window may take multiple frames, depending on whether window was already rendered or not. - if (args->InFlags & ImGuiCaptureFlags_StitchAll) - { - ImVec2 move_offset = ImVec2(args->InPadding, args->InPadding) - _CapturedWindowRect.Min + viewport_rect.Min; - IM_ASSERT(args->InCaptureWindows.Size == _WindowsData.Size); - for (int n = 0; n < _WindowsData.Size; n++) - { - ImGuiWindow* window = _WindowsData[n].Window; - _WindowsData[n].PosDuringCapture = window->Pos + move_offset; - ImGui::SetWindowPos(window, _WindowsData[n].PosDuringCapture); - } - } - - // Determine capture rectangle if not provided by user - if (!is_capturing_explicit_rect) - { - if (args->InCaptureWindows.Size > 0) - { - for (ImGuiWindow* window : args->InCaptureWindows) - _CaptureRect.Add(window->Rect()); - _CaptureRect.Expand(args->InPadding); - } - else - { - _CaptureRect = viewport_rect; - } - } - if ((args->InFlags & ImGuiCaptureFlags_StitchAll) == 0) - { - // Can not capture area outside of screen. Clip capture rect, since we are capturing only visible rect anyway. - _CaptureRect.ClipWith(viewport_rect); - - // Align size - // FIXME: ffmpeg + codec can possibly handle that better on their side. - ImVec2 capture_size_aligned = _CaptureRect.GetSize(); - if (args->InSizeAlign > 1) - { - // Round up - IM_ASSERT(ImIsPowerOfTwo(args->InSizeAlign)); - capture_size_aligned.x = (float)IM_MEMALIGN((int)capture_size_aligned.x, args->InSizeAlign); - capture_size_aligned.y = (float)IM_MEMALIGN((int)capture_size_aligned.y, args->InSizeAlign); - - // Unless will stray off viewport, then round down - if (_CaptureRect.Min.x + capture_size_aligned.x >= viewport_rect.Max.x) - capture_size_aligned.x -= args->InSizeAlign; - if (_CaptureRect.Min.y + capture_size_aligned.y >= viewport_rect.Max.y) - capture_size_aligned.y -= args->InSizeAlign; - - IM_ASSERT(capture_size_aligned.x > 0); - IM_ASSERT(capture_size_aligned.y > 0); - _CaptureRect.Max = _CaptureRect.Min + capture_size_aligned; - } - } - - // Initialize capture buffer. - IM_ASSERT(!_CaptureRect.IsInverted()); - args->OutImageSize = _CaptureRect.GetSize(); - output->CreateEmpty((int)_CaptureRect.GetWidth(), (int)_CaptureRect.GetHeight()); - } - - //----------------------------------------------------------------- - // Frame 4+N*4: Capture a frame - //----------------------------------------------------------------- - - const ImRect clip_rect = viewport_rect; - ImRect capture_rect = _CaptureRect; - capture_rect.ClipWith(clip_rect); - const int capture_height = ImMin((int)io.DisplaySize.y, (int)_CaptureRect.GetHeight()); - const int x1 = (int)(capture_rect.Min.x - clip_rect.Min.x); - const int y1 = (int)(capture_rect.Min.y - clip_rect.Min.y); - const int w = (int)capture_rect.GetWidth(); - const int h = (int)ImMin(output->Height - _ChunkNo * capture_height, capture_height); - - // Position windows - if ((_FrameNo > 2) && (args->InFlags & ImGuiCaptureFlags_StitchAll)) - { - // Unlike SetNextWindowPos(), SetWindowPos() will still perform viewport clamping, affecting support for io.ConfigWindowsMoveFromTitleBarOnly. - IM_ASSERT(args->InCaptureWindows.Size == _WindowsData.Size); - for (int n = 0; n < _WindowsData.Size; n++) - ImGui::SetWindowPos(_WindowsData[n].Window, _WindowsData[n].PosDuringCapture - ImVec2(0, (float)capture_height * _ChunkNo)); - } - - if (((_FrameNo > 2) && (_FrameNo % 4) == 0) || (is_recording_video && _FrameNo > 2) || instant_capture) - { - // FIXME: Implement capture of regions wider than viewport. - // Capture a portion of image. Capturing of windows wider than viewport is not implemented yet. - if (h > 0) - { - IM_ASSERT(w == output->Width); - if (args->InFlags & ImGuiCaptureFlags_StitchAll) - IM_ASSERT(h <= output->Height); // When stitching, image can be taller than captured viewport. - else - IM_ASSERT(h == output->Height); - - ImGuiID viewport_id = 0; -#ifdef IMGUI_HAS_VIEWPORT - if (args->InFlags & ImGuiCaptureFlags_StitchAll) - viewport_id = _WindowsData[0].Window->ViewportId; - else - viewport_id = ImGui::GetMainViewport()->ID; -#endif - - //printf("ScreenCaptureFunc x1: %d, y1: %d, w: %d, h: %d\n", x1, y1, w, h); - if (!ScreenCaptureFunc(viewport_id, x1, y1, w, h, &output->Data[_ChunkNo * w * capture_height], ScreenCaptureUserData)) - { - fprintf(stderr, "Screen capture function failed.\n"); - RestoreBackedUpData(); - ClearState(); - return ImGuiCaptureStatus_Error; - } - - if (args->InFlags & ImGuiCaptureFlags_StitchAll) - { - // Window moves up in order to expose it's lower part. - _ChunkNo++; - _CaptureRect.TranslateY(-(float)h); - } - - if (is_recording_video && (args->InFlags & ImGuiCaptureFlags_NoSave) == 0) - { - // _VideoEncoderPipe is NULL when recording just started. Initialize recording state. - if (_VideoEncoderPipe == NULL) - { - // First video frame, initialize now that dimensions are known. - const unsigned int width = (unsigned int)capture_rect.GetWidth(); - const unsigned int height = (unsigned int)capture_rect.GetHeight(); - IM_ASSERT(VideoCaptureEncoderPath != NULL && VideoCaptureEncoderPath[0]); - Str256f encoder_exe(VideoCaptureEncoderPath), cmd(""); - ImPathFixSeparatorsForCurrentOS(encoder_exe.c_str()); - ImFileCreateDirectoryChain(args->InOutputFile, ImPathFindFilename(args->InOutputFile)); -#if _WIN32 - cmd.append("\""); // On windows, entire command wrapped in quotes allows use of quotes for parameters. -#endif - const char* extension = (char*)ImPathFindExtension(args->InOutputFile); - if (strcmp(extension, ".gif") == 0) - { - IM_ASSERT(GifCaptureEncoderParams != NULL && GifCaptureEncoderParams[0]); - cmd.appendf("\"%s\" %s", encoder_exe.c_str(), GifCaptureEncoderParams); - } - else - { - IM_ASSERT(VideoCaptureEncoderParams != NULL && VideoCaptureEncoderParams[0]); - cmd.appendf("\"%s\" %s", encoder_exe.c_str(), VideoCaptureEncoderParams); - } -#if _WIN32 - cmd.append("\""); -#endif - ImStrReplace(&cmd, "$FPS", Str16f("%d", args->InRecordFPSTarget).c_str()); - ImStrReplace(&cmd, "$WIDTH", Str16f("%d", width).c_str()); - ImStrReplace(&cmd, "$HEIGHT", Str16f("%d", height).c_str()); - ImStrReplace(&cmd, "$OUTPUT", args->InOutputFile); - fprintf(stdout, "# %s\n", cmd.c_str()); - _VideoEncoderPipe = ImOsPOpen(cmd.c_str(), "w"); - IM_ASSERT(_VideoEncoderPipe != NULL); - } - - // Save new video frame - fwrite(output->Data, 1, output->Width * output->Height * 4, _VideoEncoderPipe); - } - if (is_recording_video) - _VideoLastFrameTime = current_time_sec; - } - - // Image is finalized immediately when we are not stitching. Otherwise, image is finalized when we have captured and stitched all frames. - if (!_VideoRecording && (!(args->InFlags & ImGuiCaptureFlags_StitchAll) || h <= 0)) - { - output->RemoveAlpha(); - - if (_VideoEncoderPipe != NULL) - { - // At this point _Recording is false, but we know we were recording because _VideoEncoderPipe is not NULL. Finalize video here. - ImOsPClose(_VideoEncoderPipe); - _VideoEncoderPipe = NULL; - } - else if (args->InOutputImageBuf == NULL) - { - // Save single frame. - if ((args->InFlags & ImGuiCaptureFlags_NoSave) == 0) - output->SaveFile(args->InOutputFile); - output->Clear(); - } - - RestoreBackedUpData(); - ClearState(); - return ImGuiCaptureStatus_Done; - } - } - - // Keep going - _FrameNo++; - return ImGuiCaptureStatus_InProgress; -#else - IM_UNUSED(args); - return ImGuiCaptureStatus_Done; -#endif -} - -void ImGuiCaptureContext::BeginVideoCapture(ImGuiCaptureArgs* args) -{ - IM_ASSERT(args != NULL); - IM_ASSERT(_VideoRecording == false); - IM_ASSERT(_VideoEncoderPipe == NULL); - IM_ASSERT(args->InRecordFPSTarget >= 1 && args->InRecordFPSTarget <= 100); - - ImFileCreateDirectoryChain(args->InOutputFile, ImPathFindFilename(args->InOutputFile)); - _VideoRecording = true; - _CaptureArgs = args; -} - -void ImGuiCaptureContext::EndVideoCapture() -{ - IM_ASSERT(_CaptureArgs != NULL); - IM_ASSERT(_VideoRecording == true); - - _VideoRecording = false; -} - -bool ImGuiCaptureContext::IsCapturingVideo() -{ - return _VideoRecording; -} - -bool ImGuiCaptureContext::IsCapturing() -{ - return _CaptureArgs != NULL; -} - -//----------------------------------------------------------------------------- -// ImGuiCaptureToolUI -//----------------------------------------------------------------------------- - -ImGuiCaptureToolUI::ImGuiCaptureToolUI() -{ - // Filename template for where screenshots will be saved. May contain directories or variation of %d format. - ImStrncpy(_OutputFileTemplate, "output/captures/imgui_capture_%04d.png", IM_ARRAYSIZE(_OutputFileTemplate)); -} - -// Interactively pick a single window -void ImGuiCaptureToolUI::_CaptureWindowPicker(ImGuiCaptureArgs* args) -{ - ImGuiContext& g = *GImGui; - ImGuiIO& io = g.IO; - - const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; - const ImVec2 button_sz = ImVec2(TEXT_BASE_WIDTH * 30, 0.0f); - const ImGuiID picking_id = ImGui::GetID("##picking"); - - if (ImGui::Button("Capture Single Window..", button_sz)) - _StateIsPickingWindow = true; - - if (_StateIsPickingWindow) - { - // Picking a window - ImGuiWindow* capture_window = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL; - ImDrawList* fg_draw_list = ImGui::GetForegroundDrawList(); - ImGui::SetActiveID(picking_id, g.CurrentWindow); // Steal active ID so our click won't interact with something else. - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - ImGui::SetTooltip("Capture window: '%s'\nPress ESC to cancel.", capture_window ? capture_window->Name : "<None>"); - - // FIXME: Would be nice to have a way to enforce front-most windows. Perhaps make this Render() feature more generic. - //if (capture_window) - // g.NavWindowingTarget = capture_window; - - // Draw rect that is about to be captured - const ImRect viewport_rect = GetMainViewportRect(); - const ImU32 col_dim_overlay = IM_COL32(0, 0, 0, 40); - if (capture_window) - { - ImRect r = capture_window->Rect(); - r.Expand(args->InPadding); - r.ClipWith(ImRect(ImVec2(0, 0), io.DisplaySize)); - r.Expand(1.0f); - fg_draw_list->AddRect(r.Min, r.Max, IM_COL32_WHITE, 0.0f, 0, 2.0f); - ImGui::RenderRectFilledWithHole(fg_draw_list, viewport_rect, r, col_dim_overlay, 0.0f); - } - else - { - fg_draw_list->AddRectFilled(viewport_rect.Min, viewport_rect.Max, col_dim_overlay); - } - - if (ImGui::IsMouseClicked(0) && capture_window && _InitializeOutputFile()) - { - ImGui::FocusWindow(capture_window); - _SelectedWindows.resize(0); - _StateIsPickingWindow = false; - _StateIsCapturing = true; - args->InCaptureWindows.clear(); - args->InCaptureWindows.push_back(capture_window); - } - if (ImGui::IsKeyPressed(ImGuiKey_Escape)) - _StateIsPickingWindow = _StateIsCapturing = false; - } - else - { - if (ImGui::GetActiveID() == picking_id) - ImGui::ClearActiveID(); - } -} - -void ImGuiCaptureToolUI::_CaptureWindowsSelector(ImGuiCaptureContext* context, ImGuiCaptureArgs* args) -{ - ImGuiContext& g = *GImGui; - ImGuiIO& io = g.IO; - - // Gather selected windows - ImRect capture_rect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); - for (ImGuiWindow* window : g.Windows) - { - if (window->WasActive == false) - continue; - if (window->Flags & ImGuiWindowFlags_ChildWindow) - continue; - const bool is_popup = (window->Flags & ImGuiWindowFlags_Popup) || (window->Flags & ImGuiWindowFlags_Tooltip); - if ((args->InFlags & ImGuiCaptureFlags_IncludeTooltipsAndPopups) && is_popup) - { - capture_rect.Add(window->Rect()); - args->InCaptureWindows.push_back(window); - continue; - } - if (is_popup) - continue; - if (_SelectedWindows.contains(window->RootWindow->ID)) - { - capture_rect.Add(window->Rect()); - args->InCaptureWindows.push_back(window); - } - } - const bool allow_capture = !capture_rect.IsInverted() && args->InCaptureWindows.Size > 0 && _OutputFileTemplate[0]; - - const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; - const ImVec2 button_sz = ImVec2(TEXT_BASE_WIDTH * 30, 0.0f); - - // Capture Multiple Button - { - char label[64]; - ImFormatString(label, 64, "Capture Multiple (%d)###CaptureMultiple", args->InCaptureWindows.Size); - - if (!allow_capture) - ImGui::BeginDisabled(); - bool do_capture = ImGui::Button(label, button_sz); - do_capture |= io.KeyAlt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C)); - if (!allow_capture) - ImGui::EndDisabled(); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Alternatively press Alt+C to capture selection."); - if (do_capture && _InitializeOutputFile()) - _StateIsCapturing = true; - } - - // Record video button - // (Prefer 100/FPS to be an integer) - { - const bool is_capturing_video = context->IsCapturingVideo(); - if (is_capturing_video) - { - if (ImGui::Button("Stop capturing video###CaptureVideo", button_sz)) - context->EndVideoCapture(); - } - else - { - char label[64]; - ImFormatString(label, 64, "Capture video (%d)###CaptureVideo", args->InCaptureWindows.Size); - if (!allow_capture) - ImGui::BeginDisabled(); - if (ImGui::Button(label, button_sz) && _InitializeOutputFile()) - { - // File template will most likely end with .png, but we need a different extension for videos. - IM_ASSERT(VideoCaptureExtension != NULL && VideoCaptureExtension[0]); - char* ext = (char*)ImPathFindExtension(args->InOutputFile); - ImStrncpy(ext, VideoCaptureExtension, (size_t)(ext - args->InOutputFile)); - _StateIsCapturing = true; - context->BeginVideoCapture(args); - } - if (!allow_capture) - ImGui::EndDisabled(); - } - } - - // Draw capture rectangle - ImDrawList* draw_list = ImGui::GetForegroundDrawList(); - if (allow_capture && !_StateIsPickingWindow && !_StateIsCapturing) - { - IM_ASSERT(capture_rect.GetWidth() > 0); - IM_ASSERT(capture_rect.GetHeight() > 0); - const ImRect viewport_rect = GetMainViewportRect(); - capture_rect.Expand(args->InPadding); - capture_rect.ClipWith(viewport_rect); - draw_list->AddRect(capture_rect.Min - ImVec2(1.0f, 1.0f), capture_rect.Max + ImVec2(1.0f, 1.0f), IM_COL32_WHITE); - } - - ImGui::Separator(); - - // Show window list and update rectangles - ImGui::Text("Windows:"); - if (ImGui::BeginTable("split", 2)) - { - ImGui::TableSetupColumn(NULL, ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn(NULL, ImGuiTableColumnFlags_WidthStretch); - for (ImGuiWindow* window : g.Windows) - { - if (!window->WasActive) - continue; - - const bool is_popup = (window->Flags & ImGuiWindowFlags_Popup) || (window->Flags & ImGuiWindowFlags_Tooltip); - if (is_popup) - continue; - - if (window->Flags & ImGuiWindowFlags_ChildWindow) - continue; - - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::PushID(window); - - // Ensure that text after the ## is actually displayed to the user (FIXME: won't be able to check/uncheck from that portion of the text) - bool is_selected = _SelectedWindows.contains(window->RootWindow->ID); - if (ImGui::Checkbox(window->Name, &is_selected)) - { - if (is_selected) - _SelectedWindows.push_back(window->RootWindow->ID); - else - _SelectedWindows.find_erase_unsorted(window->RootWindow->ID); - } - - if (const char* remaining_text = ImGui::FindRenderedTextEnd(window->Name)) - if (remaining_text[0] != 0) - { - if (remaining_text > window->Name) - ImGui::SameLine(0, 1); - else - ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::TextUnformatted(remaining_text); - } - - ImGui::TableSetColumnIndex(1); - ImGui::SetNextItemWidth(TEXT_BASE_WIDTH * 9.0f); - ImGui::DragFloat2("Pos", &window->Pos.x, 0.05f, 0.0f, 0.0f, "%.0f"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(TEXT_BASE_WIDTH * 9.0f); - ImGui::DragFloat2("Size", &window->SizeFull.x, 0.05f, 0.0f, 0.0f, "%.0f"); - ImGui::PopID(); - } - ImGui::EndTable(); - } -} - -void ImGuiCaptureToolUI::ShowCaptureToolWindow(ImGuiCaptureContext* context, bool* p_open) -{ - // Update capturing - if (_StateIsCapturing) - { - ImGuiCaptureArgs* args = &_CaptureArgs; - if (context->IsCapturingVideo() || args->InCaptureWindows.Size > 1) - args->InFlags &= ~ImGuiCaptureFlags_StitchAll; - - if (context->_VideoRecording && ImGui::IsKeyPressed(ImGuiKey_Escape)) - context->EndVideoCapture(); - - ImGuiCaptureStatus status = context->CaptureUpdate(args); - if (status != ImGuiCaptureStatus_InProgress) - { - if (status == ImGuiCaptureStatus_Done) - ImStrncpy(OutputLastFilename, args->InOutputFile, IM_ARRAYSIZE(OutputLastFilename)); - _StateIsCapturing = false; - _FileCounter++; - } - } - - // Update UI - if (!ImGui::Begin("Dear ImGui Capture Tool", p_open)) - { - ImGui::End(); - return; - } - if (context->ScreenCaptureFunc == NULL) - { - ImGui::TextColored(ImVec4(1, 0, 0, 1), "Backend is missing ScreenCaptureFunc!"); - ImGui::End(); - return; - } - - ImGuiIO& io = ImGui::GetIO(); - ImGuiStyle& style = ImGui::GetStyle(); - - // Options - ImGui::SetNextItemOpen(true, ImGuiCond_Once); - if (ImGui::TreeNode("Options")) - { - // Open Last - { - const bool has_last_file_name = (OutputLastFilename[0] != 0); - if (!has_last_file_name) - ImGui::BeginDisabled(); - if (ImGui::Button("Open Last")) - ImOsOpenInShell(OutputLastFilename); - if (!has_last_file_name) - ImGui::EndDisabled(); - if (has_last_file_name && ImGui::IsItemHovered()) - ImGui::SetTooltip("Open %s", OutputLastFilename); - ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); - } - - // Open Directory - { - char output_dir[256]; - strcpy(output_dir, _OutputFileTemplate); - char* output_filename = (char*)ImPathFindFilename(output_dir); - if (output_filename > output_dir) - output_filename[-1] = 0; - else - strcpy(output_dir, "."); - if (ImGui::Button("Open Directory")) - { - ImPathFixSeparatorsForCurrentOS(output_dir); - ImOsOpenInShell(output_dir); - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Open %s/", output_dir); - } - - const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; - const float BUTTON_WIDTH = (float)(int)-(TEXT_BASE_WIDTH * 26); - - 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."); - - _ShowEncoderConfigFields(context); - - ImGui::DragFloat("Padding", &_CaptureArgs.InPadding, 0.1f, 0, 32, "%.0f"); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("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."); - - if (ImGui::Button("Snap Windows To Grid", ImVec2(BUTTON_WIDTH, 0))) - _SnapWindowsToGrid(SnapGridSize); - ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); - ImGui::SetNextItemWidth((float)(int)-(TEXT_BASE_WIDTH * 5)); - ImGui::DragFloat("##SnapGridSize", &SnapGridSize, 1.0f, 1.0f, 128.0f, "%.0f"); - - ImGui::Checkbox("Software Mouse Cursor", &io.MouseDrawCursor); - - bool content_stitching_available = _CaptureArgs.InCaptureWindows.Size <= 1; -#ifdef IMGUI_HAS_VIEWPORT - content_stitching_available &= !(io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable); -#endif - 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."); - - 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::PopItemWidth(); - ImGui::TreePop(); - } - - ImGui::Separator(); - - if (!_StateIsCapturing) - _CaptureArgs.InCaptureWindows.clear(); - _CaptureWindowPicker(&_CaptureArgs); - _CaptureWindowsSelector(context, &_CaptureArgs); - - ImGui::Separator(); - - ImGui::End(); -} - -// Move/resize all windows so they are neatly aligned on a grid -// This is an easy way of ensuring some form of alignment without specifying detailed constraints. -void ImGuiCaptureToolUI::_SnapWindowsToGrid(float cell_size) -{ - ImGuiContext& g = *GImGui; - for (ImGuiWindow* window : g.Windows) - { - if (!window->WasActive) - continue; - - if (window->Flags & ImGuiWindowFlags_ChildWindow) - continue; - - if ((window->Flags & ImGuiWindowFlags_Popup) || (window->Flags & ImGuiWindowFlags_Tooltip)) - continue; - - ImRect rect = window->Rect(); - rect.Min.x = ImFloor(rect.Min.x / cell_size) * cell_size; - rect.Min.y = ImFloor(rect.Min.y / cell_size) * cell_size; - rect.Max.x = ImFloor(rect.Max.x / cell_size) * cell_size; - rect.Max.y = ImFloor(rect.Max.y / cell_size) * cell_size; - ImGui::SetWindowPos(window, rect.Min); - ImGui::SetWindowSize(window, rect.GetSize()); - } -} - -bool ImGuiCaptureToolUI::_InitializeOutputFile() -{ - // Create output folder and decide of output filename - ImFormatString(_CaptureArgs.InOutputFile, IM_ARRAYSIZE(_CaptureArgs.InOutputFile), _OutputFileTemplate, - _FileCounter + 1); - ImPathFixSeparatorsForCurrentOS(_CaptureArgs.InOutputFile); - if (!ImFileCreateDirectoryChain(_CaptureArgs.InOutputFile, ImPathFindFilename(_CaptureArgs.InOutputFile))) - { - fprintf(stderr, "ImGuiCaptureContext: unable to create directory for file '%s'.\n", - _CaptureArgs.InOutputFile); - return false; - } - return true; -} - -bool ImGuiCaptureToolUI::_ShowEncoderConfigFields(ImGuiCaptureContext* context) -{ - ImGuiContext& g = *GImGui; - const float TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; - const float BUTTON_WIDTH = (float)(int)-(TEXT_BASE_WIDTH * 26); - - bool modified = false; - if (context->VideoCaptureEncoderPathSize) - { - ImGui::PushItemWidth(BUTTON_WIDTH); - modified |= ImGui::InputText("Video Encoder Path", context->VideoCaptureEncoderPath, context->VideoCaptureEncoderPathSize); - 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!" : ""); - } - - struct CmdLineParamsInfo - { - const char* Title = NULL; - char* Params = NULL; - int ParamsSize = 0; - const char* DefaultCmdLineParams = NULL; - const char* VideoFileExt = NULL; - CmdLineParamsInfo(const char* title, char* params, int params_size, const char* default_cmd, const char* ext) { Title = title; Params = params; ParamsSize = params_size; DefaultCmdLineParams = default_cmd; VideoFileExt = ext; } - }; - CmdLineParamsInfo params_info[] = - { - { "Video Encoder params", context->VideoCaptureEncoderParams, context->VideoCaptureEncoderParamsSize, IMGUI_CAPTURE_DEFAULT_VIDEO_PARAMS_FOR_FFMPEG, ".mp4" }, - { "Gif Encoder params", context->GifCaptureEncoderParams, context->GifCaptureEncoderParamsSize, IMGUI_CAPTURE_DEFAULT_GIF_PARAMS_FOR_FFMPEG, ".gif" }, - }; - for (CmdLineParamsInfo& info : params_info) - { - if (info.ParamsSize == 0) - continue; // Can not be edited. - IM_ASSERT(info.Params != NULL); - ImGui::PushID(&info); - float small_button_width = ImGui::CalcTextSize("..").x + ImGui::GetStyle().FramePadding.x * 2.0f; - ImGui::PushItemWidth(BUTTON_WIDTH - small_button_width); - modified |= ImGui::InputText("###Params", info.Params, info.ParamsSize); - ImGui::SameLine(0.0f, 0.0f); - ImRect input_rect = g.LastItemData.Rect; - if (ImGui::Button("..")) - ImGui::OpenPopup("CmdParamsPopup"); - input_rect.Add(g.LastItemData.Rect); - ImGui::SetNextWindowSize(ImVec2(input_rect.GetWidth(), 0.0f)); - ImGui::SetNextWindowPos(input_rect.GetBL()); - if (ImGui::BeginPopup("CmdParamsPopup")) - { - ImGui::Text("Reset to default params for FFMPEG and %s file format:", info.VideoFileExt); - ImGui::Indent(); - float wrap_width = ImGui::GetContentRegionAvail().x - g.Style.FramePadding.x * 2; - ImVec2 text_size = ImGui::CalcTextSize(info.DefaultCmdLineParams, NULL, false, wrap_width); - if (ImGui::Selectable("###Reset", false, 0, text_size + g.Style.FramePadding * 2)) - { - ImStrncpy(info.Params, info.DefaultCmdLineParams, info.ParamsSize); - ImGui::CloseCurrentPopup(); - } - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - draw_list->AddText(NULL, 0, g.LastItemData.Rect.GetTL() + g.Style.FramePadding, ImGui::GetColorU32(ImGuiCol_Text), info.DefaultCmdLineParams, NULL, wrap_width); - ImGui::Unindent(); - - ImGui::Separator(); - ImGui::TextUnformatted( - "Command line parameters passed to video encoder executable.\n" - "Following variables may be used:\n" - "$FPS - target FPS\n" - "$WIDTH - width of captured frame\n" - "$HEIGHT - height of captured frame\n" - "$OUTPUT - video output file"); - ImGui::EndPopup(); - } - ImGui::SameLine(0, g.Style.ItemInnerSpacing.x); - ImGui::TextUnformatted(info.Title); - if (!info.Params[0]) - ImGui::ItemErrorFrame(IM_COL32(255, 0, 0, 255)); - ImGui::PopID(); - } - - if (VideoCaptureExtensionSize) - { - IM_ASSERT(VideoCaptureExtension != NULL); - ImGui::PushItemWidth(BUTTON_WIDTH); - if (ImGui::BeginCombo("Video format (default)", VideoCaptureExtension)) - { - const char* supported_exts[] = { ".gif", ".mp4" }; - for (auto& ext: supported_exts) - if (ImGui::Selectable(ext, strcmp(VideoCaptureExtension, ext) == 0)) - { - ImStrncpy(VideoCaptureExtension, ext, VideoCaptureExtensionSize); - modified = true; - } - ImGui::EndCombo(); - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("File extension for captured video file."); - } - return modified; -} - -//----------------------------------------------------------------------------- diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_capture_tool.h b/vendor/zgui/libs/imgui_test_engine/imgui_capture_tool.h deleted file mode 100644 index e5489c5..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_capture_tool.h +++ /dev/null @@ -1,180 +0,0 @@ -// dear imgui test engine -// (screen/video capture tool) -// This is usable as a standalone applet or controlled by the test engine. - -#pragma once - -#include "imgui_te_utils.h" // ImFuncPtr - -//----------------------------------------------------------------------------- -// Forward declarations -//----------------------------------------------------------------------------- - -// Our types -struct ImGuiCaptureArgs; // Parameters for Capture -struct ImGuiCaptureContext; // State of an active capture tool -struct ImGuiCaptureImageBuf; // Simple helper to store an RGBA image in memory -struct ImGuiCaptureToolUI; // Capture tool instance + UI window - -typedef unsigned int ImGuiCaptureFlags; // See enum: ImGuiCaptureFlags_ - -// Capture function which needs to be provided by user application -typedef bool (ImGuiScreenCaptureFunc)(ImGuiID viewport_id, int x, int y, int w, int h, unsigned int* pixels, void* user_data); - -// External types -struct ImGuiWindow; // imgui.h - -//----------------------------------------------------------------------------- - -// [Internal] -// Helper class for simple bitmap manipulation (not particularly efficient!) -struct IMGUI_API ImGuiCaptureImageBuf -{ - int Width; - int Height; - unsigned int* Data; // RGBA8 - - ImGuiCaptureImageBuf() { Width = Height = 0; Data = NULL; } - ~ImGuiCaptureImageBuf() { Clear(); } - - void Clear(); // Free allocated memory buffer if such exists. - void CreateEmpty(int w, int h); // Reallocate buffer for pixel data and zero it. - bool SaveFile(const char* filename); // Save pixel data to specified image file. - void RemoveAlpha(); // Clear alpha channel from all pixels. -}; - -enum ImGuiCaptureFlags_ : unsigned int -{ - ImGuiCaptureFlags_None = 0, - ImGuiCaptureFlags_StitchAll = 1 << 0, // Capture entire window scroll area (by scrolling and taking multiple screenshot). Only works for a single window. - ImGuiCaptureFlags_IncludeOtherWindows = 1 << 1, // Disable hiding other windows (when CaptureAddWindow has been called by default other windows are hidden) - ImGuiCaptureFlags_IncludeTooltipsAndPopups = 1 << 2, // Expand capture area to automatically include visible popups and tooltips (use with ImGuiCaptureflags_HideOtherWindows) - ImGuiCaptureFlags_HideMouseCursor = 1 << 3, // Hide render software mouse cursor during capture. - ImGuiCaptureFlags_Instant = 1 << 4, // Perform capture on very same frame. Only works when capturing a rectangular region. Unsupported features: content stitching, window hiding, window relocation. - ImGuiCaptureFlags_NoSave = 1 << 5 // Do not save output image. -}; - -// Defines input and output arguments for capture process. -// When capturing from tests you can usually use the ImGuiTestContext::CaptureXXX() helpers functions. -struct ImGuiCaptureArgs -{ - // [Input] - ImGuiCaptureFlags InFlags = 0; // Flags for customizing behavior of screenshot tool. - ImVector<ImGuiWindow*> InCaptureWindows; // Windows to capture. All other windows will be hidden. May be used with InCaptureRect to capture only some windows in specified rect. - ImRect InCaptureRect; // Screen rect to capture. Does not include padding. - float InPadding = 16.0f; // Extra padding at the edges of the screenshot. Ensure that there is available space around capture rect horizontally, also vertically if ImGuiCaptureFlags_StitchFullContents is not used. - char InOutputFile[256] = ""; // Output will be saved to a file if InOutputImageBuf is NULL. - ImGuiCaptureImageBuf* InOutputImageBuf = NULL; // _OR_ Output will be saved to image buffer if specified. - int InRecordFPSTarget = 30; // FPS target for recording videos. - int InSizeAlign = 0; // Resolution alignment (0 = auto, 1 = no alignment, >= 2 = align width/height to be multiple of given value) - - // [Output] - ImVec2 OutImageSize; // Produced image size. -}; - -enum ImGuiCaptureStatus -{ - ImGuiCaptureStatus_InProgress, - ImGuiCaptureStatus_Done, - ImGuiCaptureStatus_Error -}; - -struct ImGuiCaptureWindowData -{ - ImGuiWindow* Window; - ImRect BackupRect; - ImVec2 PosDuringCapture; -}; - -// Implements functionality for capturing images -struct IMGUI_API ImGuiCaptureContext -{ - // IO - ImFuncPtr(ImGuiScreenCaptureFunc) ScreenCaptureFunc = NULL; // Graphics backend specific function that captures specified portion of framebuffer and writes RGBA data to `pixels` buffer. - void* ScreenCaptureUserData = NULL; // Custom user pointer which is passed to ScreenCaptureFunc. (Optional) - char* VideoCaptureEncoderPath = NULL; // Video encoder path (not owned, stored externally). - int VideoCaptureEncoderPathSize = 0; // Optional. Set in order to edit this parameter from UI. - char* VideoCaptureEncoderParams = NULL; // Video encoder params (not owned, stored externally). - int VideoCaptureEncoderParamsSize = 0; // Optional. Set in order to edit this parameter from UI. - char* GifCaptureEncoderParams = NULL; // Video encoder params for GIF output (not owned, stored externally). - int GifCaptureEncoderParamsSize = 0; // Optional. Set in order to edit this parameter from UI. - - // [Internal] - ImRect _CaptureRect; // Viewport rect that is being captured. - ImRect _CapturedWindowRect; // Top-left corner of region that covers all windows included in capture. This is not same as _CaptureRect.Min when capturing explicitly specified rect. - int _ChunkNo = 0; // Number of chunk that is being captured when capture spans multiple frames. - int _FrameNo = 0; // Frame number during capture process that spans multiple frames. - ImVec2 _MouseRelativeToWindowPos; // Mouse cursor position relative to captured window (when _StitchAll is in use). - ImGuiWindow* _HoveredWindow = NULL; // Window which was hovered at capture start. - ImGuiCaptureImageBuf _CaptureBuf; // Output image buffer. - const ImGuiCaptureArgs* _CaptureArgs = NULL; // Current capture args. Set only if capture is in progress. - ImVector<ImGuiCaptureWindowData> _WindowsData; // Backup windows that will have their rect modified and restored. args->InCaptureWindows can not be used because popups may get closed during capture and no longer appear in that list. - - // [Internal] Video recording - bool _VideoRecording = false; // Flag indicating that video recording is in progress. - double _VideoLastFrameTime = 0; // Time when last video frame was recorded. - FILE* _VideoEncoderPipe = NULL; // File writing to stdin of video encoder process. - - // [Internal] Backups - bool _BackupMouseDrawCursor = false; // Initial value of g.IO.MouseDrawCursor - ImVec2 _BackupDisplayWindowPadding; // Backup padding. We set it to {0, 0} during capture. - ImVec2 _BackupDisplaySafeAreaPadding; // Backup padding. We set it to {0, 0} during capture. - - //------------------------------------------------------------------------- - // Functions - //------------------------------------------------------------------------- - - ImGuiCaptureContext(ImGuiScreenCaptureFunc capture_func = NULL) { ScreenCaptureFunc = capture_func; _MouseRelativeToWindowPos = ImVec2(-FLT_MAX, -FLT_MAX); } - - // These functions should be called from appropriate context hooks. See ImGui::AddContextHook() for more info. - // (ImGuiTestEngine automatically calls that for you, so this only apply to independently created instance) - void PreNewFrame(); - void PreRender(); - void PostRender(); - - // Update capturing. If this function returns true then it should be called again with same arguments on the next frame. - ImGuiCaptureStatus CaptureUpdate(ImGuiCaptureArgs* args); - void RestoreBackedUpData(); - void ClearState(); - - // Begin video capture. Call CaptureUpdate() every frame afterwards until it returns false. - void BeginVideoCapture(ImGuiCaptureArgs* args); - void EndVideoCapture(); - bool IsCapturingVideo(); - bool IsCapturing(); -}; - -//----------------------------------------------------------------------------- -// ImGuiCaptureToolUI -//----------------------------------------------------------------------------- - -// Implements UI for capturing images -// (when using ImGuiTestEngine scripting API you may not need to use this at all) -struct IMGUI_API ImGuiCaptureToolUI -{ - float SnapGridSize = 32.0f; // Size of the grid cell for "snap to grid" functionality. - char OutputLastFilename[256] = ""; // File name of last captured file. - char* VideoCaptureExtension = NULL; // Video file extension (e.g. ".gif" or ".mp4") - int VideoCaptureExtensionSize = 0; // Optional. Set in order to edit this parameter from UI. - - ImGuiCaptureArgs _CaptureArgs; // Capture args - bool _StateIsPickingWindow = false; - bool _StateIsCapturing = false; - ImVector<ImGuiID> _SelectedWindows; - char _OutputFileTemplate[256] = ""; // - int _FileCounter = 0; // Counter which may be appended to file name when saving. By default, counting starts from 1. When done this field holds number of saved files. - - // Public - ImGuiCaptureToolUI(); - void ShowCaptureToolWindow(ImGuiCaptureContext* context, bool* p_open = NULL); // Render a capture tool window with various options and utilities. - - // [Internal] - void _CaptureWindowPicker(ImGuiCaptureArgs* args); // Render a window picker that captures picked window to file specified in file_name. - void _CaptureWindowsSelector(ImGuiCaptureContext* context, ImGuiCaptureArgs* args); // Render a selector for selecting multiple windows for capture. - void _SnapWindowsToGrid(float cell_size); // Snap edges of all visible windows to a virtual grid. - bool _InitializeOutputFile(); // Format output file template into capture args struct and ensure target directory exists. - bool _ShowEncoderConfigFields(ImGuiCaptureContext* context); -}; - -#define IMGUI_CAPTURE_DEFAULT_VIDEO_PARAMS_FOR_FFMPEG "-hide_banner -loglevel error -r $FPS -f rawvideo -pix_fmt rgba -s $WIDTHx$HEIGHT -i - -threads 0 -y -preset ultrafast -pix_fmt yuv420p -crf 20 $OUTPUT" -#define IMGUI_CAPTURE_DEFAULT_GIF_PARAMS_FOR_FFMPEG "-hide_banner -loglevel error -r $FPS -f rawvideo -pix_fmt rgba -s $WIDTHx$HEIGHT -i - -threads 0 -y -filter_complex \"split=2 [a] [b]; [a] palettegen [pal]; [b] [pal] paletteuse\" $OUTPUT" diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_context.cpp b/vendor/zgui/libs/imgui_test_engine/imgui_te_context.cpp deleted file mode 100644 index 7493321..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_context.cpp +++ /dev/null @@ -1,3960 +0,0 @@ -// dear imgui -// (context when a running test + end user automation API) -// This is the main (if not only) interface that your Tests will be using. - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -#define _CRT_SECURE_NO_WARNINGS -#endif - -#define IMGUI_DEFINE_MATH_OPERATORS -#include "imgui_te_context.h" -#include "imgui.h" -#include "imgui_internal.h" -#include "imgui_te_engine.h" -#include "imgui_te_internal.h" -#include "imgui_te_perftool.h" -#include "imgui_te_utils.h" -#include "thirdparty/Str/Str.h" - -//------------------------------------------------------------------------- -// [SECTION] ImGuiTestRefDesc -//------------------------------------------------------------------------- - -ImGuiTestRefDesc::ImGuiTestRefDesc(const ImGuiTestRef& ref, const ImGuiTestItemInfo* item) -{ - if (ref.Path) - ImFormatString(Buf, IM_ARRAYSIZE(Buf), "'%s' > %08X", ref.Path, ref.ID); - else - ImFormatString(Buf, IM_ARRAYSIZE(Buf), "%08X > '%s'", ref.ID, item ? item->DebugLabel : "NULL"); -} - -//------------------------------------------------------------------------- -// [SECTION] ImGuiTestContextDepthScope -//------------------------------------------------------------------------- - -// Helper to increment/decrement the function depth (so our log entry can be padded accordingly) -#define IM_TOKENCONCAT_INTERNAL(x, y) x ## y -#define IM_TOKENCONCAT(x, y) IM_TOKENCONCAT_INTERNAL(x, y) -#define IMGUI_TEST_CONTEXT_REGISTER_DEPTH(_THIS) ImGuiTestContextDepthScope IM_TOKENCONCAT(depth_register, __LINE__)(_THIS) - -struct ImGuiTestContextDepthScope -{ - ImGuiTestContext* TestContext; - ImGuiTestContextDepthScope(ImGuiTestContext* ctx) { TestContext = ctx; TestContext->ActionDepth++; } - ~ImGuiTestContextDepthScope() { TestContext->ActionDepth--; } -}; - -//------------------------------------------------------------------------- -// [SECTION] Enum names helpers -//------------------------------------------------------------------------- - -inline const char* GetActionName(ImGuiTestAction action) -{ - switch (action) - { - case ImGuiTestAction_Unknown: return "Unknown"; - case ImGuiTestAction_Hover: return "Hover"; - case ImGuiTestAction_Click: return "Click"; - case ImGuiTestAction_DoubleClick: return "DoubleClick"; - case ImGuiTestAction_Check: return "Check"; - case ImGuiTestAction_Uncheck: return "Uncheck"; - case ImGuiTestAction_Open: return "Open"; - case ImGuiTestAction_Close: return "Close"; - case ImGuiTestAction_Input: return "Input"; - case ImGuiTestAction_NavActivate: return "NavActivate"; - case ImGuiTestAction_COUNT: - default: return "N/A"; - } -} - -inline const char* GetActionVerb(ImGuiTestAction action) -{ - switch (action) - { - case ImGuiTestAction_Unknown: return "Unknown"; - case ImGuiTestAction_Hover: return "Hovered"; - case ImGuiTestAction_Click: return "Clicked"; - case ImGuiTestAction_DoubleClick: return "DoubleClicked"; - case ImGuiTestAction_Check: return "Checked"; - case ImGuiTestAction_Uncheck: return "Unchecked"; - case ImGuiTestAction_Open: return "Opened"; - case ImGuiTestAction_Close: return "Closed"; - case ImGuiTestAction_Input: return "Input"; - case ImGuiTestAction_NavActivate: return "NavActivated"; - case ImGuiTestAction_COUNT: - default: return "N/A"; - } -} - - -//------------------------------------------------------------------------- -// [SECTION] ImGuiTestContext -// This is the interface that most tests will interact with. -//------------------------------------------------------------------------- - -void ImGuiTestContext::LogEx(ImGuiTestVerboseLevel level, ImGuiTestLogFlags flags, const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - LogExV(level, flags, fmt, args); - va_end(args); -} - -void ImGuiTestContext::LogExV(ImGuiTestVerboseLevel level, ImGuiTestLogFlags flags, const char* fmt, va_list args) -{ - ImGuiTestContext* ctx = this; - //ImGuiTest* test = ctx->Test; - - IM_ASSERT(level > ImGuiTestVerboseLevel_Silent && level < ImGuiTestVerboseLevel_COUNT); - - if (level == ImGuiTestVerboseLevel_Debug && ctx->ActionDepth > 1) - level = ImGuiTestVerboseLevel_Trace; - - // Log all messages that we may want to print in future. - if (EngineIO->ConfigVerboseLevelOnError < level) - return; - - ImGuiTestLog* log = &ctx->TestOutput->Log; - const int prev_size = log->Buffer.size(); - - //const char verbose_level_char = ImGuiTestEngine_GetVerboseLevelName(level)[0]; - //if (flags & ImGuiTestLogFlags_NoHeader) - // log->Buffer.appendf("[%c] ", verbose_level_char); - //else - // log->Buffer.appendf("[%c] [%04d] ", verbose_level_char, ctx->FrameCount); - if ((flags & ImGuiTestLogFlags_NoHeader) == 0) - log->Buffer.appendf("[%04d] ", ctx->FrameCount); - - if (level >= ImGuiTestVerboseLevel_Debug) - log->Buffer.appendf("-- %*s", ImMax(0, (ctx->ActionDepth - 1) * 2), ""); - log->Buffer.appendfv(fmt, args); - log->Buffer.append("\n"); - - log->UpdateLineOffsets(EngineIO, level, log->Buffer.begin() + prev_size); - LogToTTY(level, log->Buffer.c_str() + prev_size); - LogToDebugger(level, log->Buffer.c_str() + prev_size); -} - -void ImGuiTestContext::LogDebug(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - LogExV(ImGuiTestVerboseLevel_Debug, ImGuiTestLogFlags_None, fmt, args); - va_end(args); -} - -void ImGuiTestContext::LogInfo(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - LogExV(ImGuiTestVerboseLevel_Info, ImGuiTestLogFlags_None, fmt, args); - va_end(args); -} - -void ImGuiTestContext::LogWarning(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - LogExV(ImGuiTestVerboseLevel_Warning, ImGuiTestLogFlags_None, fmt, args); - va_end(args); -} - -void ImGuiTestContext::LogError(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - LogExV(ImGuiTestVerboseLevel_Error, ImGuiTestLogFlags_None, fmt, args); - va_end(args); -} - -void ImGuiTestContext::LogToTTY(ImGuiTestVerboseLevel level, const char* message, const char* message_end) -{ - IM_ASSERT(level > ImGuiTestVerboseLevel_Silent && level < ImGuiTestVerboseLevel_COUNT); - - if (!EngineIO->ConfigLogToTTY) - return; - - ImGuiTestContext* ctx = this; - ImGuiTestOutput* test_output = ctx->TestOutput; - ImGuiTestLog* log = &test_output->Log; - - if (test_output->Status == ImGuiTestStatus_Error) - { - // Current test failed. - if (!CachedLinesPrintedToTTY) - { - // Print all previous logged messages first - // FIXME: Can't use ExtractLinesAboveVerboseLevel() because we want to keep error level... - CachedLinesPrintedToTTY = true; - for (int i = 0; i < log->LineInfo.Size; i++) - { - ImGuiTestLogLineInfo& line_info = log->LineInfo[i]; - if (line_info.Level > EngineIO->ConfigVerboseLevelOnError) - continue; - char* line_begin = log->Buffer.Buf.Data + line_info.LineOffset; - char* line_end = strchr(line_begin, '\n'); - LogToTTY(line_info.Level, line_begin, line_end + 1); - } - // We already printed current line as well, so return now. - return; - } - // Otherwise print only current message. If we are executing here log level already is within range of - // ConfigVerboseLevelOnError setting. - } - else if (EngineIO->ConfigVerboseLevel < level) - { - // Skip printing messages of lower level than configured. - return; - } - - switch (level) - { - case ImGuiTestVerboseLevel_Warning: - ImOsConsoleSetTextColor(ImOsConsoleStream_StandardOutput, ImOsConsoleTextColor_BrightYellow); - break; - case ImGuiTestVerboseLevel_Error: - ImOsConsoleSetTextColor(ImOsConsoleStream_StandardOutput, ImOsConsoleTextColor_BrightRed); - break; - default: - ImOsConsoleSetTextColor(ImOsConsoleStream_StandardOutput, ImOsConsoleTextColor_White); - break; - } - if (message_end) - fprintf(stdout, "%.*s", (int)(message_end - message), message); - else - fprintf(stdout, "%s", message); - ImOsConsoleSetTextColor(ImOsConsoleStream_StandardOutput, ImOsConsoleTextColor_White); - fflush(stdout); -} - -void ImGuiTestContext::LogToDebugger(ImGuiTestVerboseLevel level, const char* message) -{ - IM_ASSERT(level > ImGuiTestVerboseLevel_Silent && level < ImGuiTestVerboseLevel_COUNT); - - if (!EngineIO->ConfigLogToDebugger) - return; - - if (EngineIO->ConfigVerboseLevel < level) - return; - - switch (level) - { - default: - break; - case ImGuiTestVerboseLevel_Error: - ImOsOutputDebugString("[error] "); - break; - case ImGuiTestVerboseLevel_Warning: - ImOsOutputDebugString("[warn.] "); - break; - case ImGuiTestVerboseLevel_Info: - ImOsOutputDebugString("[info ] "); - break; - case ImGuiTestVerboseLevel_Debug: - ImOsOutputDebugString("[debug] "); - break; - case ImGuiTestVerboseLevel_Trace: - ImOsOutputDebugString("[trace] "); - break; - } - - ImOsOutputDebugString(message); -} - -void ImGuiTestContext::LogBasicUiState() -{ - ImGuiID item_hovered_id = UiContext->HoveredIdPreviousFrame; - ImGuiID item_active_id = UiContext->ActiveId; - ImGuiTestItemInfo* item_hovered_info = item_hovered_id ? ImGuiTestEngine_FindItemInfo(Engine, item_hovered_id, "") : NULL; - ImGuiTestItemInfo* item_active_info = item_active_id ? ImGuiTestEngine_FindItemInfo(Engine, item_active_id, "") : NULL; - LogDebug("Hovered: 0x%08X (\"%s\"), Active: 0x%08X(\"%s\")", - item_hovered_id, item_hovered_info->ID != 0 ? item_hovered_info->DebugLabel : "", - item_active_id, item_active_info->ID != 0 ? item_active_info->DebugLabel : ""); -} - -void ImGuiTestContext::LogItemList(ImGuiTestItemList* items) -{ - for (const ImGuiTestItemInfo& info : *items) - LogDebug("- 0x%08X: depth %d: '%s' in window '%s'\n", info.ID, info.Depth, info.DebugLabel, info.Window->Name); -} - -void ImGuiTestContext::Finish(ImGuiTestStatus status) -{ - if (ActiveFunc == ImGuiTestActiveFunc_GuiFunc) - { - IM_ASSERT(status == ImGuiTestStatus_Success || status == ImGuiTestStatus_Unknown); - if (RunFlags & ImGuiTestRunFlags_GuiFuncOnly) - return; - if (TestOutput->Status == ImGuiTestStatus_Running) - TestOutput->Status = status; - } - else if (ActiveFunc == ImGuiTestActiveFunc_TestFunc) - { - IM_ASSERT(status == ImGuiTestStatus_Unknown); // To set Success from a TestFunc() you can 'return' from it. - if (TestOutput->Status == ImGuiTestStatus_Running) - TestOutput->Status = status; - } -} - -static void LogWarningFunc(void* user_data, const char* fmt, ...) -{ - ImGuiTestContext* ctx = (ImGuiTestContext*)user_data; - va_list args; - va_start(args, fmt); - ctx->LogExV(ImGuiTestVerboseLevel_Warning, ImGuiTestLogFlags_None, fmt, args); - va_end(args); -} - -static void LogNotAsWarningFunc(void* user_data, const char* fmt, ...) -{ - ImGuiTestContext* ctx = (ImGuiTestContext*)user_data; - va_list args; - va_start(args, fmt); - ctx->LogExV(ImGuiTestVerboseLevel_Debug, ImGuiTestLogFlags_None, fmt, args); - va_end(args); -} - -void ImGuiTestContext::RecoverFromUiContextErrors() -{ - IM_ASSERT(Test != NULL); - - // If we are _already_ in a test error state, recovering is normal so we'll hide the log. - const bool verbose = (TestOutput->Status != ImGuiTestStatus_Error) || (EngineIO->ConfigVerboseLevel >= ImGuiTestVerboseLevel_Debug); - if (verbose && (Test->Flags & ImGuiTestFlags_NoRecoveryWarnings) == 0) - ImGui::ErrorCheckEndFrameRecover(LogWarningFunc, this); - else - ImGui::ErrorCheckEndFrameRecover(LogNotAsWarningFunc, this); -} - -void ImGuiTestContext::Yield(int count) -{ - IM_ASSERT(count > 0); - while (count > 0) - { - ImGuiTestEngine_Yield(Engine); - 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. -// - ImGuiTestRunFlags_ShareTestContext -ImGuiTestStatus ImGuiTestContext::RunChildTest(const char* child_test_name, ImGuiTestRunFlags run_flags) -{ - if (IsError()) - return ImGuiTestStatus_Error; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("RunChildTest %s", child_test_name); - - ImGuiTest* child_test = ImGuiTestEngine_FindTestByName(Engine, NULL, child_test_name); - IM_CHECK_SILENT_RETV(child_test != NULL, ImGuiTestStatus_Error); - IM_CHECK_SILENT_RETV(child_test != Test, ImGuiTestStatus_Error); // Can't recursively run same test. - - ImGuiTestStatus parent_status = TestOutput->Status; - TestOutput->Status = ImGuiTestStatus_Running; - ImGuiTestEngine_RunTest(Engine, this, child_test, run_flags); - ImGuiTestStatus child_status = TestOutput->Status; - - // Restore parent status - TestOutput->Status = parent_status; - if (child_status == ImGuiTestStatus_Error && (run_flags & ImGuiTestRunFlags_NoError) == 0) - TestOutput->Status = ImGuiTestStatus_Error; - - // Return child status - LogWarning("(returning to parent test)"); - return child_status; -} - -// Return true to request aborting TestFunc -// Called via IM_SUSPEND_TESTFUNC() -bool ImGuiTestContext::SuspendTestFunc(const char* file, int line) -{ - if (IsError()) - return false; - - file = ImPathFindFilename(file); - if (file != NULL) - LogError("SuspendTestFunc() at %s:%d", file, line); - else - LogError("SuspendTestFunc()"); - - // Save relevant state. - // FIXME-TESTS: Saving/restoring window z-order could be desirable. - ImVec2 mouse_pos = Inputs->MousePosValue; - ImGuiTestRunFlags run_flags = RunFlags; -#if IMGUI_VERSION_NUM >= 18992 - ImGui::TeleportMousePos(mouse_pos); -#endif - - RunFlags |= ImGuiTestRunFlags_GuiFuncOnly; - TestOutput->Status = ImGuiTestStatus_Suspended; - while (TestOutput->Status == ImGuiTestStatus_Suspended && !Abort) - Yield(); - TestOutput->Status = ImGuiTestStatus_Running; - - // Restore relevant state. - RunFlags = run_flags; - Inputs->MousePosValue = mouse_pos; - - // Terminate TestFunc on abort, continue otherwise. - return Abort; -} - -// Sleep a given amount of time (unless running in Fast mode: there it will Yield once) -void ImGuiTestContext::Sleep(float time) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Fast) - { - LogEx(ImGuiTestVerboseLevel_Trace, ImGuiTestLogFlags_None, "Sleep(%.2f) -> Yield() in fast mode", time); - //ImGuiTestEngine_AddExtraTime(Engine, time); // We could add time, for now we have no use for it... - ImGuiTestEngine_Yield(Engine); - } - else - { - LogEx(ImGuiTestVerboseLevel_Trace, ImGuiTestLogFlags_None, "Sleep(%.2f)", time); - while (time > 0.0f && !Abort) - { - ImGuiTestEngine_Yield(Engine); - time -= UiContext->IO.DeltaTime; - } - } -} - -// This is useful when you need to wait a certain amount of time (even in Fast mode) -// Sleep for a given clock time from the point of view of the Dear ImGui context, without affecting wall clock time of the running application. -// FIXME: This makes sense for apps only relying on io.DeltaTime. -void ImGuiTestContext::SleepNoSkip(float time, float framestep_in_second) -{ - if (IsError()) - return; - - while (time > 0.0f && !Abort) - { - ImGuiTestEngine_SetDeltaTime(Engine, framestep_in_second); - ImGuiTestEngine_Yield(Engine); - time -= UiContext->IO.DeltaTime; - } -} - -void ImGuiTestContext::SleepShort() -{ - if (EngineIO->ConfigRunSpeed != ImGuiTestRunSpeed_Fast) - Sleep(EngineIO->ActionDelayShort); -} - -void ImGuiTestContext::SleepStandard() -{ - if (EngineIO->ConfigRunSpeed != ImGuiTestRunSpeed_Fast) - Sleep(EngineIO->ActionDelayStandard); -} - -void ImGuiTestContext::SetInputMode(ImGuiInputSource input_mode) -{ - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("SetInputMode %d", input_mode); - - IM_ASSERT(input_mode == ImGuiInputSource_Mouse || input_mode == ImGuiInputSource_Keyboard || input_mode == ImGuiInputSource_Gamepad); - InputMode = input_mode; - - if (InputMode == ImGuiInputSource_Keyboard || InputMode == ImGuiInputSource_Gamepad) - { - UiContext->NavDisableHighlight = false; - UiContext->NavDisableMouseHover = true; - } - else - { - UiContext->NavDisableHighlight = true; - UiContext->NavDisableMouseHover = false; - } -} - -void ImGuiTestContext::SetRef(ImGuiWindow* window) -{ - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - IM_CHECK_SILENT(window != NULL); - LogDebug("SetRef '%s' %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); - IM_ASSERT(len < IM_ARRAYSIZE(RefStr) - 1); - strcpy(RefStr, window->Name); - RefID = RefWindowID = window->ID; - - MouseSetViewport(window); - - // Automatically uncollapse by default - if (!(OpFlags & ImGuiTestOpFlags_NoAutoUncollapse)) - WindowCollapse(window->ID, false); -} - -// SetRef() ok in GUI Func ONLY if pointer to a pointer. -// 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); - - if (ref.Path) - { - size_t len = strlen(ref.Path); - IM_ASSERT(len < IM_ARRAYSIZE(RefStr) - 1); - - strcpy(RefStr, ref.Path); - RefID = GetID(ref.Path, ImGuiTestRef()); - } - else - { - RefStr[0] = 0; - RefID = ref.ID; - } - RefWindowID = 0; - - // 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. - ImGuiWindow* window = GetWindowByRef(""); - if (window == NULL && ref.Path != NULL) - { - const char* name_begin = ref.Path; - while (*name_begin == '/') name_begin++; - const char* name_end = name_begin - 1; - do - { - name_end = strchr(name_end + 1, '/'); - } while (name_end != NULL && name_end > name_begin && name_end[-1] == '\\'); - window = GetWindowByRef(ImHashDecoratedPath(name_begin, name_end)); - } - - if (ActiveFunc == ImGuiTestActiveFunc_GuiFunc) - return; - - // (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; - - if (window) - { - RefWindowID = window->ID; - MouseSetViewport(window); - } - - // Automatically uncollapse by default - if (window && !(OpFlags & ImGuiTestOpFlags_NoAutoUncollapse)) - WindowCollapse(window->ID, false); -} - -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, "//"); - ImGuiWindow* window = ImGui::FindWindowByID(window_id); - return window; -} - -ImGuiID ImGuiTestContext::GetID(ImGuiTestRef ref) -{ - if (ref.ID) - return ref.ID; - - return GetID(ref, RefID); -} - -// Refer to Wiki to read details -// https://github.com/ocornut/imgui_test_engine/wiki/Named-References -// - Meaning of leading "//" ................. "//rootnode" : ignore SetRef -// - Meaning of leading "//$FOCUSED" ......... "//$FOCUSED/node" : "node" in currently focused window -// - Meaning of leading "/" .................. "/node" : move to root of window pointed by SetRef() when SetRef() uses a path -// - Meaning of $$xxxx literal encoding ...... "list/$$1" : hash of "list" + hash if (int)1, equivalent of PushID("hello"); PushID(1); -//// - Meaning of leading "../" .............. "../node" : move back 1 level from SetRef path() when SetRef() uses a path // Unimplemented -// FIXME: "//$FOCUSED/.." is currently not usable. -ImGuiID ImGuiTestContext::GetID(ImGuiTestRef ref, ImGuiTestRef seed_ref) -{ - ImGuiContext& g = *UiContext; - - if (ref.ID) - return ref.ID; // FIXME: What if seed_ref != 0 - - // Handle special $FOCUSED variable. - // (Note that we don't and can't really support a "$HOVERED" equivalent for the hovered window. - // Why? Because it is extremely fragile to use: with late translation of variable held in string, - // it is extremely common that the "expected" hovered window at the time of passing the string has - // changed in later uses of the same reference.) - // You can however easily use: - // SetRef(g.HoveredWindow->ID); - const char* FOCUSED_PREFIX = "//$FOCUSED"; - const size_t FOCUSED_PREFIX_LEN = 10; - - const char* path = ref.Path ? ref.Path : ""; - if (strncmp(path, FOCUSED_PREFIX, FOCUSED_PREFIX_LEN) == 0) - if (path[FOCUSED_PREFIX_LEN] == '/' || path[FOCUSED_PREFIX_LEN] == 0) - { - path += FOCUSED_PREFIX_LEN; - if (path[0] == '/') - path++; - if (g.NavWindow) - seed_ref = g.NavWindow->ID; - else - LogError("\"//$FOCUSED\" was used with no focused window!"); - } - - if (path[0] == '/') - { - path++; - if (path[0] == '/') - { - // "//" : Double-slash prefix resets ID seed to 0. - seed_ref = ImGuiTestRef(); - } - else - { - // "/" : Single-slash prefix sets seed to the "current window", which a parent window containing an item with RefID id. - if (ActiveFunc == ImGuiTestActiveFunc_GuiFunc) - seed_ref = ImGuiTestRef(g.CurrentWindow->ID); - else - seed_ref = RefWindowID; - } - } - - return ImHashDecoratedPath(path, NULL, seed_ref.Path ? GetID(seed_ref) : seed_ref.ID); -} - -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -ImGuiID ImGuiTestContext::GetIDByInt(int n) -{ - return ImHashData(&n, sizeof(n), GetID(RefID)); -} - -ImGuiID ImGuiTestContext::GetIDByInt(int n, ImGuiTestRef seed_ref) -{ - return ImHashData(&n, sizeof(n), GetID(seed_ref)); -} - -ImGuiID ImGuiTestContext::GetIDByPtr(void* p) -{ - return ImHashData(&p, sizeof(p), GetID(RefID)); -} - -ImGuiID ImGuiTestContext::GetIDByPtr(void* p, ImGuiTestRef seed_ref) -{ - return ImHashData(&p, sizeof(p), GetID(seed_ref)); -} -#endif - -ImVec2 ImGuiTestContext::GetMainMonitorWorkPos() -{ -#ifdef IMGUI_HAS_VIEWPORT - if (UiContext->IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - const ImGuiPlatformMonitor* monitor = ImGui::GetViewportPlatformMonitor(ImGui::GetMainViewport()); - return monitor->WorkPos; - } -#endif - return ImGui::GetMainViewport()->WorkPos; -} - -ImVec2 ImGuiTestContext::GetMainMonitorWorkSize() -{ -#ifdef IMGUI_HAS_VIEWPORT - if (UiContext->IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - const ImGuiPlatformMonitor* monitor = ImGui::GetViewportPlatformMonitor(ImGui::GetMainViewport()); - return monitor->WorkSize; - } -#endif - return ImGui::GetMainViewport()->WorkSize; -} - -static bool ImGuiTestContext_CanCaptureScreenshot(ImGuiTestContext* ctx) -{ - ImGuiTestEngineIO* io = ctx->EngineIO; - return io->ConfigCaptureEnabled; -} - -static bool ImGuiTestContext_CanCaptureVideo(ImGuiTestContext* ctx) -{ - ImGuiTestEngineIO* io = ctx->EngineIO; - return io->ConfigCaptureEnabled && ImFileExist(io->VideoCaptureEncoderPath); -} - -bool ImGuiTestContext::CaptureAddWindow(ImGuiTestRef ref) -{ - ImGuiWindow* window = GetWindowByRef(ref); - IM_CHECK_SILENT_RETV(window != NULL, false); - CaptureArgs->InCaptureWindows.push_back(window); - return true; -} - -static void CaptureInitAutoFilename(ImGuiTestContext* ctx, const char* ext) -{ - IM_ASSERT(ext != NULL && ext[0] == '.'); - - if (ctx->CaptureArgs->InOutputFile[0] == 0) - ctx->CaptureSetExtension(ext); // Reset extension of specified filename or auto-generate a new filename. -} - -bool ImGuiTestContext::CaptureScreenshot(int capture_flags) -{ - if (IsError()) - return false; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogInfo("CaptureScreenshot()"); - ImGuiCaptureArgs* args = CaptureArgs; - args->InFlags = capture_flags; - - // Auto filename - CaptureInitAutoFilename(this, ".png"); - -#if IMGUI_TEST_ENGINE_ENABLE_CAPTURE - // Way capture tool is implemented doesn't prevent ClampWindowPos() from running, - // so we disable that feature at the moment. (imgui_test_engine/#33) - ImGuiIO& io = ImGui::GetIO(); - bool backup_io_config_move_window_from_title_bar_only = io.ConfigWindowsMoveFromTitleBarOnly; - if (capture_flags & ImGuiCaptureFlags_StitchAll) - io.ConfigWindowsMoveFromTitleBarOnly = false; - - bool can_capture = ImGuiTestContext_CanCaptureScreenshot(this); - if (!can_capture) - args->InFlags |= ImGuiCaptureFlags_NoSave; - - bool ret = ImGuiTestEngine_CaptureScreenshot(Engine, args); - if (can_capture) - LogInfo("Saved '%s' (%d*%d pixels)", args->InOutputFile, (int)args->OutImageSize.x, (int)args->OutImageSize.y); - else - LogWarning("Skipped saving '%s' (%d*%d pixels) (enable in 'Misc->Options')", args->InOutputFile, (int)args->OutImageSize.x, (int)args->OutImageSize.y); - - if (capture_flags & ImGuiCaptureFlags_StitchAll) - io.ConfigWindowsMoveFromTitleBarOnly = backup_io_config_move_window_from_title_bar_only; - - return ret; -#else - IM_UNUSED(args); - LogWarning("Skipped capturing screenshot: capture disabled by IMGUI_TEST_ENGINE_ENABLE_CAPTURE=0."); - return false; -#endif -} - -void ImGuiTestContext::CaptureReset() -{ - *CaptureArgs = ImGuiCaptureArgs(); -} - -// FIXME-TESTS: Add ImGuiCaptureFlags_NoHideOtherWindows -void ImGuiTestContext::CaptureScreenshotWindow(ImGuiTestRef ref, int capture_flags) -{ - CaptureReset(); - CaptureAddWindow(ref); - CaptureScreenshot(capture_flags); -} - -void ImGuiTestContext::CaptureSetExtension(const char* ext) -{ - IM_ASSERT(ext && ext[0] == '.'); - ImGuiCaptureArgs* args = CaptureArgs; - if (args->InOutputFile[0] == 0) - { - ImFormatString(args->InOutputFile, IM_ARRAYSIZE(args->InOutputFile), "output/captures/%s_%04d%s", Test->Name, CaptureCounter, ext); - CaptureCounter++; - } - else - { - char* filename_ext = (char*)ImPathFindExtension(args->InOutputFile); - ImStrncpy(filename_ext, ext, (size_t)(filename_ext - args->InOutputFile)); - } -} - -bool ImGuiTestContext::CaptureBeginVideo() -{ - if (IsError()) - return false; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogInfo("CaptureBeginVideo()"); - ImGuiCaptureArgs* args = CaptureArgs; - - // Auto filename - CaptureInitAutoFilename(this, EngineIO->VideoCaptureExtension); - -#if IMGUI_TEST_ENGINE_ENABLE_CAPTURE - bool can_capture = ImGuiTestContext_CanCaptureVideo(this); - if (!can_capture) - args->InFlags |= ImGuiCaptureFlags_NoSave; - return ImGuiTestEngine_CaptureBeginVideo(Engine, args); -#else - IM_UNUSED(args); - LogWarning("Skipped recording GIF: capture disabled by IMGUI_TEST_ENGINE_ENABLE_CAPTURE."); - return false; -#endif -} - -bool ImGuiTestContext::CaptureEndVideo() -{ - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogInfo("CaptureEndVideo()"); - ImGuiCaptureArgs* args = CaptureArgs; - - bool ret = Engine->CaptureContext.IsCapturingVideo() && ImGuiTestEngine_CaptureEndVideo(Engine, args); - if (!ret) - return false; - - // In-progress capture was canceled by user. Delete incomplete file. - if (IsError()) - { - //ImFileDelete(args->OutSavedFileName); - return false; - } - bool can_capture = ImGuiTestContext_CanCaptureVideo(this); - if (can_capture) - { - LogInfo("Saved '%s' (%d*%d pixels)", args->InOutputFile, (int)args->OutImageSize.x, (int)args->OutImageSize.y); - } - else - { - if (!EngineIO->ConfigCaptureEnabled) - LogWarning("Skipped saving '%s' video because: io.ConfigCaptureEnabled == false (enable in Misc->Options)", args->InOutputFile); - else - LogWarning("Skipped saving '%s' video because: Video Encoder not found.", args->InOutputFile); - } - - return ret; -} - -// Handle wildcard search on the TestFunc side. -// Results will be resolved on the Gui side via the following call-chain: -// IMGUI_TEST_ENGINE_ITEM_INFO() -> ImGuiTestEngineHook_ItemInfo() -> ImGuiTestEngineHook_ItemInfo_ResolveFindByLabel() -ImGuiID ImGuiTestContext::ItemInfoHandleWildcardSearch(const char* wildcard_prefix_start, const char* wildcard_prefix_end, const char* wildcard_suffix_start) -{ - LogDebug("Wildcard matching.."); - - // Wildcard matching - // Note that task->InPrefixId may be 0 as well (= we don't know the window) - ImGuiTestFindByLabelTask* task = &Engine->FindByLabelTask; - if (wildcard_prefix_start < wildcard_prefix_end) - task->InPrefixId = ImHashDecoratedPath(wildcard_prefix_start, wildcard_prefix_end, RefID); - else - task->InPrefixId = RefID; - task->OutItemId = 0; - - // Advance pointer to point it to the last label - task->InSuffix = task->InSuffixLastItem = wildcard_suffix_start; - for (const char* c = task->InSuffix; *c; c++) - if (*c == '/') - task->InSuffixLastItem = c + 1; - task->InSuffixLastItemHash = ImHashStr(task->InSuffixLastItem, 0, 0); - - // Count number of labels - task->InSuffixDepth = 1; - for (const char* c = wildcard_suffix_start; *c; c++) - if (*c == '/') - task->InSuffixDepth++; - - int retries = 0; - while (retries < 2 && task->OutItemId == 0) - { - ImGuiTestEngine_Yield(Engine); - retries++; - } - - // Wildcard matching requires item to be visible, because clipped items are unaware of their labels. Try panning through entire window, searching for target item. - // (Scrollbar position restoration in theory may be desirable, however it interferes with typical use of found item) - // FIXME-TESTS: This doesn't recurse properly into each child.. - // FIXME: Down the line if we refactor ItemAdd() return value to distinguish render-clipping vs logic-clipping etc, we should instead temporarily enable a "no clip" - // 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); - if (window) - { - ImVec2 rect_size = window->InnerRect.GetSize(); - for (float scroll_x = 0.0f; task->OutItemId == 0; scroll_x += rect_size.x) - { - for (float scroll_y = 0.0f; task->OutItemId == 0; scroll_y += rect_size.y) - { - window->Scroll.x = scroll_x; - window->Scroll.y = scroll_y; - - retries = 0; - while (retries < 2 && task->OutItemId == 0) - { - ImGuiTestEngine_Yield(Engine); - retries++; - } - if (window->Scroll.y >= window->ScrollMax.y) - break; - } - if (window->Scroll.x >= window->ScrollMax.x) - break; - } - } - } - ImGuiID full_id = task->OutItemId; - - // FIXME: InFilterItemStatusFlags is intentionally not cleared here, because it is set in ItemAction() and reused in later calls to ItemInfo() to resolve ambiguities. - task->InPrefixId = 0; - task->InSuffix = task->InSuffixLastItem = NULL; - task->InSuffixLastItemHash = 0; - task->InSuffixDepth = 0; - task->OutItemId = 0; // -V1048 // Variable 'OutItemId' was assigned the same value. False-positive, because value of OutItemId could be modified from other thread during ImGuiTestEngine_Yield() call. - - 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) - return; - - // Prefixing the string with / ignore the reference/current ID - Str256 msg; - if (ref.Path && ref.Path[0] == '/' && ctx->RefStr[0] != 0) - msg.setf("Unable to locate item: '%s'", ref.Path); - else if (ref.Path && full_id != 0) - msg.setf("Unable to locate item: '%s/%s' (0x%08X)", ctx->RefStr, ref.Path, full_id); - else if (ref.Path) - msg.setf("Unable to locate item: '%s/%s'", ctx->RefStr, ref.Path); - else - msg.setf("Unable to locate item: 0x%08X", ref.ID); - - //if (flags & ImGuiTestOpFlags_NoError) - // ctx->LogInfo("Ignored: %s", msg.c_str()); // FIXME - //else - IM_ERRORF_NOHDR("%s", msg.c_str()); -} - -// Supported values for ImGuiTestOpFlags: -// - ImGuiTestOpFlags_NoError -ImGuiTestItemInfo* ImGuiTestContext::ItemInfo(ImGuiTestRef ref, ImGuiTestOpFlags flags) -{ - if (IsError()) - return ItemInfoNull(); - - ImGuiID full_id = 0; - - if (const char* p = ref.Path ? strstr(ref.Path, "**/") : NULL) - { - // Wildcard matching - // FIXME-TESTS: Need to verify that this is not inhibited by a \, so \**/ should not pass, but \\**/ should :) - // We could add a simple helpers that would iterate the strings, handling inhibitors, and let you check if a given characters is inhibited or not. - const char* wildcard_prefix_start = ref.Path; - const char* wildcard_prefix_end = p; - const char* wildcard_suffix_start = wildcard_prefix_end + 3; - full_id = ItemInfoHandleWildcardSearch(wildcard_prefix_start, wildcard_prefix_end, wildcard_suffix_start); - } - else - { - // Regular matching - full_id = GetID(ref); - } - - // If ui_ctx->TestEngineHooksEnabled is not already on (first ItemInfo() task in a while) we'll probably need an extra frame to warmup - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - ImGuiTestItemInfo* item = NULL; - int retries = 0; - int max_retries = 2; - int extra_retries_for_appearing = 0; - while (full_id && retries < max_retries) - { - item = ImGuiTestEngine_FindItemInfo(Engine, full_id, ref.Path); - - // While a window is appearing it is likely to be resizing and items moving. Wait an extra frame for things to settle. (FIXME: Could use another source e.g. Hidden? AutoFitFramesX?) - if (item && item->Window && item->Window->Appearing && extra_retries_for_appearing == 0) - { - item = NULL; - max_retries++; - extra_retries_for_appearing++; - } - - if (item) - return item; - ImGuiTestEngine_Yield(Engine); - retries++; - } - - ItemInfoErrorLog(this, ref, full_id, flags); - - return ItemInfoNull(); -} - -// Supported values for ImGuiTestOpFlags: -// - ImGuiTestOpFlags_NoError -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) - return item; - if (!can_open_full_path) - return ItemInfoNull(); - - // Tries to auto open intermediaries leading to final path. - // Note that openables cannot be part of the **/ (else it means we would have to open everything). - // - Openables can be before the wildcard "Node2/Node3/**/Button" - // - Openables can be after the wildcard "**/Node2/Node3/Lv4/Button" - int opened_parents = 0; - for (const char* parent_end = strstr(ref.Path, "/"); parent_end != NULL; parent_end = strstr(parent_end + 1, "/")) - { - // Skip "**/* sections - if (strncmp(ref.Path, "**/", parent_end - ref.Path) == 0) - continue; - - 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) - { -#ifdef IMGUI_HAS_DOCK - ImGuiWindow* parent_window = parent_item->Window; -#endif - 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? - { - 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) - { - // Make tab visible - ItemClick(parent_item->ID); - opened_parents++; - } -#endif - } - } - if (opened_parents > 0) - item = ItemInfo(ref, (flags & ImGuiTestOpFlags_NoError)); - - if (item->ID == 0) - ItemInfoErrorLog(this, ref, 0, flags); - - return item; -} - -// Find a window given a path or an ID. -// In the case of when a path is passed, this handle finding child windows as well. -// e.g. -// ctx->WindowInfo("//Test Window"); // OK -// ctx->WindowInfo("//Test Window/Child/SubChild"); // OK -// ctx->WindowInfo("//$FOCUSED/Child"); // OK -// ctx->SetRef("Test Window); ctx->WindowInfo("Child"); // OK -// ctx->WindowInfo(GetID("//Test Window")); // OK (find by raw ID without a path) -// ctx->WindowInfo(GetID("//Test Window/Child/SubChild)); // *INCORRECT* GetID() doesn't unmangle child names. -// ctx->WindowInfo("//Test Window/Button"); // *INCORRECT* Only finds windows, not items. -// Return: -// - Return pointer is always valid. -// - Valid fields are: -// - item->ID : window ID (may be == 0, if the window doesn't exist) -// - item->Window : window pointer (may be == NULL, if the window doesn't exist) -// - 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);' -// Todos: -// - FIXME: Missing support for wildcards. -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); - 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? - return ItemInfoNull(); - } - return ItemInfo(window->ID); - } - - // Query by Path: this is where the meat of our work is. - LogDebug("WindowInfo: by path: '%s'", ref.Path ? ref.Path : "NULL"); - ImGuiWindow* window = NULL; - ImGuiID window_idstack_back = 0; - const char* current = ref.Path; - while (*current || window == NULL) - { - // Handle SetRef(), if any (this will also handle "//$FOCUSED" syntax) - Str128 part_name; - if (window == NULL && RefID != 0 && strncmp(ref.Path, "//", 2) != 0) - { - window = GetWindowByRef(""); - window_idstack_back = window ? window->ID : 0; - } - else - { - // Find next part of the path + create a zero-terminated copy for convenience - const char* part_start = current; - const char* part_end = ImFindNextDecoratedPartInPath(current); - if (part_end == NULL) - { - current = part_end = part_start + strlen(part_start); - } - else if (part_end > part_start) - { - current = part_end; - part_end--; - IM_ASSERT(part_end[0] == '/'); - } - part_name.setf("%.*s", (int)(part_end - part_start), part_start); - - // Find root window or child window - if (window == NULL) - { - // Root: defer first element to GetID(), this will handle SetRef(), "//" and "//$FOCUSED" syntax. - ImGuiID window_id = GetID(part_name.c_str()); - window = GetWindowByRef(window_id); - window_idstack_back = window ? window->ID : 0; - } - else - { - ImGuiID child_window_id = 0; - ImGuiWindow* child_window = NULL; - { - // Child: Attempt 1: Try to BeginChild(const char*) variant and mimic its logic. - Str128 child_window_full_name; -#if (IMGUI_VERSION_NUM >= 18996) && (IMGUI_VERSION_NUM < 18999) - if (window_idstack_back == window->ID) - { - child_window_full_name.setf("%s/%s", window->Name, part_name.c_str()); - } - else -#endif - { - ImGuiID child_item_id = GetID(part_name.c_str(), window_idstack_back); - child_window_full_name.setf("%s/%s_%08X", window->Name, part_name.c_str(), child_item_id); - } - child_window_id = ImHashStr(child_window_full_name.c_str()); // We do NOT use ImHashDecoratedPath() - child_window = GetWindowByRef(child_window_id); - } - if (child_window == NULL) - { - // Child: Attempt 2: Try for BeginChild(ImGuiID id) variant and mimic its logic. - // FIXME: This only really works when ID passed to BeginChild() was derived from a string. - // We could support $$xxxx syntax to encode ID in parameter? - ImGuiID child_item_id = GetID(part_name.c_str(), window_idstack_back); - Str128f child_window_full_name("%s/%08X", window->Name, child_item_id); - child_window_id = ImHashStr(child_window_full_name.c_str()); // We do NOT use ImHashDecoratedPath() - child_window = GetWindowByRef(child_window_id); - } - if (child_window == NULL) - { - // Assume that part is an arbitrary PushID(const char*) - window_idstack_back = GetID(part_name.c_str(), window_idstack_back); - } - else - { - window = child_window; - window_idstack_back = window ? window->ID : 0; - } - } - } - - // Process result - // 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()); - return ItemInfoNull(); - } - } - - IM_ASSERT(window != NULL); - IM_ASSERT(window_idstack_back != 0); - - // 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."); - return ItemInfoNull(); - } - - return ItemInfo(window->ID); -} - -void ImGuiTestContext::ScrollToTop(ImGuiTestRef ref) -{ - if (IsError()) - return; - - ImGuiWindow* window = GetWindowByRef(ref); - IM_CHECK_SILENT(window != NULL); - if (window->Scroll.y == 0.0f) - return; - ScrollToY(ref, 0.0f); - Yield(); -} - -void ImGuiTestContext::ScrollToBottom(ImGuiTestRef ref) -{ - if (IsError()) - return; - - ImGuiWindow* window = GetWindowByRef(ref); - IM_CHECK_SILENT(window != NULL); - if (window->Scroll.y == window->ScrollMax.y) - return; - ScrollToY(ref, window->ScrollMax.y); - Yield(); -} - -bool ImGuiTestContext::ScrollErrorCheck(ImGuiAxis axis, float expected, float actual, int* remaining_attempts) -{ - if (IsError()) - { - (*remaining_attempts)--; - return false; - } - - float THRESHOLD = 1.0f; - if (ImFabs(actual - expected) < THRESHOLD) - return true; - - (*remaining_attempts)--; - if (*remaining_attempts > 0) - { - LogInfo("Failed to set Scroll%c. Requested %.2f, got %.2f. Will try again.", 'X' + axis, expected, actual); - return true; - } - else - { - IM_ERRORF("Failed to set Scroll%c. Requested %.2f, got %.2f. Aborting.", 'X' + axis, expected, actual); - return false; - } -} - -// FIXME-TESTS: Mostly the same code as ScrollbarEx() -static ImVec2 GetWindowScrollbarMousePositionForScroll(ImGuiWindow* window, ImGuiAxis axis, float scroll_v) -{ - ImGuiContext& g = *GImGui; - ImRect bb = ImGui::GetWindowScrollbarRect(window, axis); - - // From Scrollbar(): - //float* scroll_v = &window->Scroll[axis]; - const float size_avail_v = window->InnerRect.Max[axis] - window->InnerRect.Min[axis]; - const float size_contents_v = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f; - - // From ScrollbarEx() onward: - - // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) - const float scrollbar_size_v = bb.Max[axis] - bb.Min[axis]; - - // 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. - const float win_size_v = ImMax(ImMax(size_contents_v, size_avail_v), 1.0f); - const float grab_h_pixels = ImClamp(scrollbar_size_v * (size_avail_v / win_size_v), g.Style.GrabMinSize, scrollbar_size_v); - - const float scroll_max = ImMax(1.0f, size_contents_v - size_avail_v); - const float scroll_ratio = ImSaturate(scroll_v / scroll_max); - const float grab_v = scroll_ratio * (scrollbar_size_v - grab_h_pixels); // Grab position - - ImVec2 position; - position[axis] = bb.Min[axis] + grab_v + grab_h_pixels * 0.5f; - position[axis ^ 1] = bb.GetCenter()[axis ^ 1]; - - return position; -} - -#if IMGUI_VERSION_NUM < 18993 -#define ImTrunc ImFloor -#endif - -// Supported values for ImGuiTestOpFlags: -// - ImGuiTestOpFlags_NoFocusWindow -void ImGuiTestContext::ScrollTo(ImGuiTestRef ref, ImGuiAxis axis, float scroll_target, ImGuiTestOpFlags flags) -{ - ImGuiContext& g = *UiContext; - if (IsError()) - return; - - ImGuiWindow* window = GetWindowByRef(ref); - IM_CHECK_SILENT(window != NULL); - - // Early out - const float scroll_target_clamp = ImClamp(scroll_target, 0.0f, window->ScrollMax[axis]); - if (ImFabs(window->Scroll[axis] - scroll_target_clamp) < 1.0f) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - const char axis_c = (char)('X' + axis); - LogDebug("ScrollTo %c %.1f/%.1f", axis_c, scroll_target, window->ScrollMax[axis]); - - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Cinematic) - 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)) - { - 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) - { - 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) - return; - - // FIXME-TESTS: Investigate - LogWarning("Failed to set Scroll%c. Requested %.2f, got %.2f.", 'X' + axis, scroll_target_clamp, scroll_result); - } - } - - // Fallback: manual slow scroll - // FIXME-TESTS: Consider using mouse wheel, since it can work without taking focus - int remaining_failures = 3; - while (!Abort) - { - if (ImFabs(window->Scroll[axis] - scroll_target_clamp) < 1.0f) - break; - - const float scroll_speed = (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Fast) ? FLT_MAX : ImFloor(EngineIO->ScrollSpeed * g.IO.DeltaTime + 0.99f); - const float scroll_next = ImLinearSweep(window->Scroll[axis], scroll_target, scroll_speed); - if (axis == ImGuiAxis_X) - ImGui::SetScrollX(window, scroll_next); - else - ImGui::SetScrollY(window, scroll_next); - - // Error handling to avoid getting stuck in this function. - Yield(); - if (!ScrollErrorCheck(axis, scroll_next, window->Scroll[axis], &remaining_failures)) - break; - } - - // Need another frame for the result->Rect to stabilize - Yield(); -} - -// Supported values for ImGuiTestOpFlags: -// - ImGuiTestOpFlags_NoFocusWindow -void ImGuiTestContext::ScrollToItem(ImGuiTestRef ref, ImGuiAxis axis, ImGuiTestOpFlags flags) -{ - if (IsError()) - return; - - // 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); - ImGuiTestRefDesc desc(ref, item); - LogDebug("ScrollToItem %c %s", 'X' + axis, desc.c_str()); - - if (item->ID == 0) - return; - - // Ensure window size and ScrollMax are up-to-date - Yield(); - - // TabBar are a special case because they have no scrollbar and rely on ScrollButton "<" and ">" - // 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 (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) - { - ScrollToTabItem(tab_bar, item->ID); - return; - } - - 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]); - - ScrollTo(window->ID, axis, scroll_target, (flags & ImGuiTestOpFlags_NoFocusWindow)); -} - -void ImGuiTestContext::ScrollToItemX(ImGuiTestRef ref) -{ - ScrollToItem(ref, ImGuiAxis_X); -} - -void ImGuiTestContext::ScrollToItemY(ImGuiTestRef ref) -{ - ScrollToItem(ref, ImGuiAxis_Y); -} - -void ImGuiTestContext::ScrollToTabItem(ImGuiTabBar* tab_bar, ImGuiID tab_id) -{ - if (IsError()) - return; - - // Cancel if "##v", because it's outside the tab_bar rect, and will be considered as "not visible" even if it is! - //if (GetID("##v") == item->ID) - // return; - - IM_CHECK_SILENT(tab_bar != NULL); - const ImGuiTabItem* selected_tab_item = ImGui::TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId); - const ImGuiTabItem* target_tab_item = ImGui::TabBarFindTabByID(tab_bar, tab_id); - if (target_tab_item == NULL) - return; - - int selected_tab_index = tab_bar->Tabs.index_from_ptr(selected_tab_item); - int target_tab_index = tab_bar->Tabs.index_from_ptr(target_tab_item); - - ImGuiTestRef backup_ref = GetRef(); - SetRef(tab_bar->ID); - - if (selected_tab_index > target_tab_index) - { - MouseMove("##<"); - for (int i = 0; i < selected_tab_index - target_tab_index; ++i) - MouseClick(0); - } - else - { - MouseMove("##>"); - for (int i = 0; i < target_tab_index - selected_tab_index; ++i) - MouseClick(0); - } - - // Skip the scroll animation - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Fast) - { - tab_bar->ScrollingAnim = tab_bar->ScrollingTarget; - Yield(); - } - - SetRef(backup_ref); -} - -// Verify that ScrollMax is stable regardless of scrolling position -// - This can break when the layout of clipped items doesn't match layout of unclipped items -// - This can break with non-rounded calls to ItemSize(), namely when the starting position is negative (above visible area) -// We should ideally be more tolerant of non-rounded sizes passed by the users. -// - One of the net visible effect of an unstable ScrollMax is that the End key would put you at a spot that's not exactly the lowest spot, -// and so a second press to End would you move again by a few pixels. -// FIXME-TESTS: Make this an iterative, smooth scroll. -void ImGuiTestContext::ScrollVerifyScrollMax(ImGuiTestRef ref) -{ - ImGuiWindow* window = GetWindowByRef(ref); - ImGui::SetScrollY(window, 0.0f); - Yield(); - float scroll_max_0 = window->ScrollMax.y; - ImGui::SetScrollY(window, window->ScrollMax.y); - Yield(); - float scroll_max_1 = window->ScrollMax.y; - IM_CHECK_EQ(scroll_max_0, scroll_max_1); -} - -void ImGuiTestContext::NavMoveTo(ImGuiTestRef ref) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - ImGuiContext& g = *UiContext; - ImGuiTestItemInfo* item = ItemInfo(ref); - ImGuiTestRefDesc desc(ref, item); - LogDebug("NavMove to %s", desc.c_str()); - - 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); - - // 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); - g.NavDisableHighlight = false; - g.NavDisableMouseHover = g.NavMousePosDirty = true; - ImGui::ScrollToBringRectIntoView(item->Window, item->RectFull); - while (g.NavMoveSubmitted) - Yield(); - Yield(); - - if (!Abort) - { - if (g.NavId != item->ID) - IM_ERRORF_NOHDR("Unable to set NavId to %s", desc.c_str()); - } - - item->RefCount--; -} - -void ImGuiTestContext::NavActivate() -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("NavActivate"); - Yield(); // ? - KeyPress(ImGuiKey_Space); -} - -void ImGuiTestContext::NavInput() -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("NavInput"); - KeyPress(ImGuiKey_Enter); -} - -// Supported values for ImGuiTestOpFlags: -// - ImGuiTestOpFlags_MoveToEdgeL -// - ImGuiTestOpFlags_MoveToEdgeR -// - ImGuiTestOpFlags_MoveToEdgeU -// - ImGuiTestOpFlags_MoveToEdgeD -static ImVec2 GetMouseAimingPos(ImGuiTestItemInfo* item, ImGuiTestOpFlags flags) -{ - ImRect r = item->RectClipped; - ImVec2 pos; - if (flags & ImGuiTestOpFlags_MoveToEdgeL) - pos.x = (r.Min.x + 1.0f); - else if (flags & ImGuiTestOpFlags_MoveToEdgeR) - pos.x = (r.Max.x - 1.0f); - else - pos.x = (r.Min.x + r.Max.x) * 0.5f; - if (flags & ImGuiTestOpFlags_MoveToEdgeU) - pos.y = (r.Min.y + 1.0f); - else if (flags & ImGuiTestOpFlags_MoveToEdgeD) - pos.y = (r.Max.y - 1.0f); - else - pos.y = (r.Min.y + r.Max.y) * 0.5f; - return pos; -} - -// Conceptucally this could be called ItemHover() -// Supported values for ImGuiTestOpFlags: -// - ImGuiTestOpFlags_NoFocusWindow -// - ImGuiTestOpFlags_NoCheckHoveredId -// - ImGuiTestOpFlags_IsSecondAttempt [used when recursively calling ourself) -// - ImGuiTestOpFlags_MoveToEdgeXXX flags -// FIXME-TESTS: This is too eagerly trying to scroll everything even if already visible. -// FIXME: Maybe ImGuiTestOpFlags_NoCheckHoveredId could be automatic if we detect that another item is active as intended? -void ImGuiTestContext::MouseMove(ImGuiTestRef ref, ImGuiTestOpFlags flags) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - ImGuiContext& g = *UiContext; - - ImGuiTestItemInfo* item; - if (flags & ImGuiTestOpFlags_NoAutoOpenFullPath) - item = ItemInfo(ref); - else - item = ItemInfoOpenFullPath(ref); - - ImGuiTestRefDesc desc(ref, item); - LogDebug("MouseMove to %s", desc.c_str()); - if (item->ID == 0) - return; - - if (!item->Window->WasActive) - { - LogError("Window '%s' is not active!", item->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); - - // 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) - { - // 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) - { - 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); - - // 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; - - // Target point - pos = GetMouseAimingPos(item, flags); - - // Focus window - if (!(flags & ImGuiTestOpFlags_NoFocusWindow)) - { - // 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; - - // 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); - } - - // 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) - { - LogError("Window '%s' is not active (after aiming)", item->Window->Name); - return; - } - - 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; - - if (is_covered || is_inhibited) - WindowBringToFront(window->ID); - } - - // Check hovering target: may be an item (common) or a window (rare) - if (!Abort && !(flags & ImGuiTestOpFlags_NoCheckHoveredId)) - { - ImGuiID hovered_id; - bool is_hovered_item; - - // Give a few extra frames to validate hovering. - // In the vast majority of case this will be set on the first attempt, - // but e.g. blocking popups may need to close based on external logic. - for (int remaining_attempts = 3; remaining_attempts > 0; remaining_attempts--) - { - hovered_id = g.HoveredIdPreviousFrame; - is_hovered_item = (hovered_id == item->ID); - if (is_hovered_item) - break; - Yield(); - } - - 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) - is_hovered_window = true; - - if (!is_hovered_item && !is_hovered_window) - { - // Check if we are accidentally hovering resize grip (which uses ImGuiButtonFlags_FlattenChildren) - if (!(window->Flags & ImGuiWindowFlags_NoResize) && !(flags & ImGuiTestOpFlags_IsSecondAttempt)) - { - bool is_hovering_resize_corner = false; - for (int n = 0; n < 2; n++) - is_hovering_resize_corner |= (hovered_id == ImGui::GetWindowResizeCornerID(window, n)); - if (is_hovering_resize_corner) - { - LogDebug("Child obstructed by parent's ResizeGrip, trying to resize window and trying again.."); - 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; - } - } - - ImVec2 pos_old = item_initial_state.RectFull.Min; - ImVec2 pos_new = item->RectFull.Min; - ImVec2 size_old = item_initial_state.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)", - desc.c_str(), - item->ID, item->Window ? item->Window->Name : "<NULL>", 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"); - IM_ERRORF_NOHDR("%s", error_message.c_str()); - } - } - - item->RefCount--; -} - -void ImGuiTestContext::MouseSetViewport(ImGuiWindow* window) -{ - IM_CHECK_SILENT(window != NULL); -#ifdef IMGUI_HAS_VIEWPORT - ImGuiViewportP* viewport = window ? window->Viewport : NULL; - ImGuiID viewport_id = viewport ? viewport->ID : 0; - if (window->Viewport == NULL) - IM_CHECK(window->WasActive == false); // only time this is allowed is an inactive window (where the viewport was destroyed) - if (Inputs->MouseHoveredViewport != viewport_id) - { - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("MouseSetViewport changing to 0x%08X (window '%s')", viewport_id, window->Name); - Inputs->MouseHoveredViewport = viewport_id; - Yield(2); - } -#else - IM_UNUSED(window); -#endif -} - -// May be 0 to specify "automatic" (based on platform stack, rarely used) -void ImGuiTestContext::MouseSetViewportID(ImGuiID viewport_id) -{ - if (IsError()) - return; - - if (Inputs->MouseHoveredViewport != viewport_id) - { - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("MouseSetViewportID changing to 0x%08X", viewport_id); - Inputs->MouseHoveredViewport = viewport_id; - ImGuiTestEngine_Yield(Engine); - } -} - -// Make the point at 'pos' (generally expected to be within window's boundaries) visible in the viewport, -// so it can be later focused then clicked. -bool ImGuiTestContext::WindowTeleportToMakePosVisible(ImGuiTestRef ref, ImVec2 pos) -{ - ImGuiContext& g = *UiContext; - if (IsError()) - return false; - ImGuiWindow* window = GetWindowByRef(ref); - IM_CHECK_SILENT_RETV(window != NULL, false); - -#ifdef IMGUI_HAS_DOCK - // This is particularly useful for docked windows, as we have to move root dockspace window instead of docket window - // itself. As a side effect this also adds support for child windows. - window = window->RootWindowDockTree; -#endif - - ImRect visible_r; - visible_r.Min = GetMainMonitorWorkPos(); - visible_r.Max = visible_r.Min + GetMainMonitorWorkSize(); - if (!visible_r.Contains(pos)) - { - // Fallback move window directly to make our item reachable with the mouse. - // FIXME-TESTS-NOT_SAME_AS_END_USER - float pad = g.FontSize; - ImVec2 delta; - 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); - Yield(); - return true; - } - return false; -} - -// ignore_list is a NULL-terminated list of pointers -// Windows that are below all of ignore_list windows are not hidden. -// FIXME-TESTS-NOT_SAME_AS_END_USER: Aim to get rid of this. -void ImGuiTestContext::ForeignWindowsHideOverPos(ImVec2 pos, ImGuiWindow** ignore_list) -{ - ImGuiContext& g = *UiContext; - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("ForeignWindowsHideOverPos (%.0f,%.0f)", pos.x, pos.y); - IM_CHECK_SILENT(ignore_list != NULL); // It makes little sense to call this function with an empty list. - IM_CHECK_SILENT(ignore_list[0] != NULL); - //auto& ctx = this; IM_SUSPEND_TESTFUNC(); - - // Find lowest ignored window index. All windows rendering above this index will be hidden. All windows rendering - // below this index do not prevent interactions with these windows already, and they can be ignored. - int min_window_index = g.Windows.Size; - for (int i = 0; ignore_list[i]; i++) - min_window_index = ImMin(min_window_index, ImGui::FindWindowDisplayIndex(ignore_list[i])); - - bool hidden_windows = false; - for (int i = 0; i < g.Windows.Size; i++) - { - ImGuiWindow* other_window = g.Windows[i]; - if (other_window->RootWindow == other_window && other_window->WasActive) - { - ImRect r = other_window->Rect(); - r.Expand(g.WindowsHoverPadding); - if (r.Contains(pos)) - { - for (int j = 0; ignore_list[j]; j++) -#ifdef IMGUI_HAS_DOCK - if (ignore_list[j]->RootWindowDockTree == other_window->RootWindowDockTree) -#else - if (ignore_list[j] == other_window) -#endif - { - other_window = NULL; - break; - } - - if (other_window && ImGui::FindWindowDisplayIndex(other_window) < min_window_index) - other_window = NULL; - - if (other_window) - { - ForeignWindowsToHide.push_back(other_window); - hidden_windows = true; - } - } - } - } - if (hidden_windows) - Yield(); -} - -void ImGuiTestContext::ForeignWindowsUnhideAll() -{ - ForeignWindowsToHide.clear(); - Yield(); -} - -void ImGuiTestContext::MouseMoveToPos(ImVec2 target) -{ - ImGuiContext& g = *UiContext; - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("MouseMoveToPos from (%.0f,%.0f) to (%.0f,%.0f)", Inputs->MousePosValue.x, Inputs->MousePosValue.y, target.x, target.y); - - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Cinematic) - SleepStandard(); - - // Enforce a mouse move if we are already at destination, to enforce g.NavDisableMouseHover gets cleared. - if (g.NavDisableMouseHover && ImLengthSqr(Inputs->MousePosValue - target) < 1.0f) - { - Inputs->MousePosValue = target + ImVec2(1.0f, 0.0f); - ImGuiTestEngine_Yield(Engine); - } - - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Fast) - { - Inputs->MousePosValue = target; - ImGuiTestEngine_Yield(Engine); - ImGuiTestEngine_Yield(Engine); - return; - } - - // Simulate slower movements. We use a slightly curved movement to make the movement look less robotic. - - // Calculate some basic parameters - const ImVec2 start_pos = Inputs->MousePosValue; - const ImVec2 delta = target - start_pos; - const float length2 = ImLengthSqr(delta); - const float length = (length2 > 0.0001f) ? ImSqrt(length2) : 1.0f; - const float inv_length = 1.0f / length; - - // Short distance alter speed and wobble - float base_speed = EngineIO->MouseSpeed; - float base_wobble = EngineIO->MouseWobble; - if (length < base_speed * 1.0f) - { - // Time = 1.0f -> wobble max, Time = 0.0f -> no wobble - base_wobble *= length / base_speed; - - // Slow down for short movements(all movement in the 0.0f..1.0f range are remapped to a 0.5f..1.0f seconds) - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Cinematic) - { - float approx_time = length / base_speed; - approx_time = 0.5f + ImSaturate(approx_time * 0.5f); - base_speed = length / approx_time; - } - } - - // Calculate a vector perpendicular to the motion delta - const ImVec2 perp = ImVec2(delta.y, -delta.x) * inv_length; - - // Calculate how much wobble we want, clamped to max out when the delta is 100 pixels (shorter movements get less wobble) - const float position_offset_magnitude = ImClamp(length, 1.0f, 100.0f) * base_wobble; - - // Wobble positions, using a sine wave based on position as a cheap way to get a deterministic offset - ImVec2 intermediate_pos_a = start_pos + (delta * 0.3f); - ImVec2 intermediate_pos_b = start_pos + (delta * 0.6f); - intermediate_pos_a += perp * ImSin(intermediate_pos_a.y * 0.1f) * position_offset_magnitude; - intermediate_pos_b += perp * ImCos(intermediate_pos_b.y * 0.1f) * position_offset_magnitude; - - // We manipulate Inputs->MousePosValue without reading back from g.IO.MousePos because the later is rounded. - // To handle high framerate it is easier to bypass this rounding. - float current_dist = 0.0f; // Our current distance along the line (in pixels) - while (true) - { - float move_speed = base_speed * g.IO.DeltaTime; - - //if (g.IO.KeyShift) - // move_speed *= 0.1f; - - current_dist += move_speed; // Move along the line - - // Calculate a parametric position on the direct line that we will use for the curve - float t = current_dist * inv_length; - t = ImClamp(t, 0.0f, 1.0f); - t = 1.0f - ((ImCos(t * IM_PI) + 1.0f) * 0.5f); // Generate a smooth curve with acceleration/deceleration - - //ImGui::GetOverlayDrawList()->AddCircle(target, 10.0f, IM_COL32(255, 255, 0, 255)); - - if (t >= 1.0f) - { - Inputs->MousePosValue = target; - ImGuiTestEngine_Yield(Engine); - ImGuiTestEngine_Yield(Engine); - return; - } - else - { - // Use a bezier curve through the wobble points - Inputs->MousePosValue = ImBezierCubicCalc(start_pos, intermediate_pos_a, intermediate_pos_b, target, t); - //ImGui::GetOverlayDrawList()->AddBezierCurve(start_pos, intermediate_pos_a, intermediate_pos_b, target, IM_COL32(255,0,0,255), 1.0f); - ImGuiTestEngine_Yield(Engine); - } - } -} - -// 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) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - 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); -} - -void ImGuiTestContext::MouseDown(ImGuiMouseButton button) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("MouseDown %d", button); - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Cinematic) - SleepStandard(); - - UiContext->IO.MouseClickedTime[button] = -FLT_MAX; // Prevent accidental double-click from happening ever - Inputs->MouseButtonsValue |= (1 << button); - Yield(); -} - -void ImGuiTestContext::MouseUp(ImGuiMouseButton button) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("MouseUp %d", button); - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Cinematic) - SleepShort(); - - Inputs->MouseButtonsValue &= ~(1 << button); - Yield(); -} - -// TODO: click time argument (seconds and/or frames) -void ImGuiTestContext::MouseClick(ImGuiMouseButton button) -{ - if (IsError()) - return; - MouseClickMulti(button, 1); -} - -// TODO: click time argument (seconds and/or frames) -void ImGuiTestContext::MouseClickMulti(ImGuiMouseButton button, int count) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - if (count > 1) - LogDebug("MouseClickMulti %d x%d", button, count); - else - LogDebug("MouseClick %d", button); - - // Make sure mouse buttons are released - IM_ASSERT(count >= 1); - IM_ASSERT(Inputs->MouseButtonsValue == 0); - Yield(); - - // Press - UiContext->IO.MouseClickedTime[button] = -FLT_MAX; // Prevent accidental double-click from happening ever - - for (int n = 0; n < count; n++) - { - Inputs->MouseButtonsValue = (1 << button); - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Cinematic) - SleepShort(); - else if (EngineIO->ConfigRunSpeed != ImGuiTestRunSpeed_Fast) - Yield(2); // Leave enough time for non-alive IDs to expire. (#5325) - else - Yield(); - Inputs->MouseButtonsValue = 0; - - if (EngineIO->ConfigRunSpeed != ImGuiTestRunSpeed_Fast) - Yield(2); // Not strictly necessary but covers more variant. - else - Yield(); - } - - // Now NewFrame() has seen the mouse release. - // Let the imgui frame finish, now e.g. Button() function will return true. Start a new frame. - Yield(); -} - -// TODO: click time argument (seconds and/or frames) -void ImGuiTestContext::MouseDoubleClick(ImGuiMouseButton button) -{ - MouseClickMulti(button, 2); -} - -void ImGuiTestContext::MouseLiftDragThreshold(ImGuiMouseButton button) -{ - if (IsError()) - return; - - ImGuiContext& g = *UiContext; - 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) -{ - ImGuiContext& g = *UiContext; - const ImVec2 padding_regular = g.Style.TouchExtraPadding; - const 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]; - if (!window->Active || window->Hidden) - continue; - if (window->Flags & ImGuiWindowFlags_NoMouseInputs) - continue; - - // Using the clipped AABB, a child window will typically be clipped by its parent (not always) - ImRect bb(window->OuterRectClipped); - if (window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) - bb.Expand(padding_regular); - else - bb.Expand(padding_for_resize); - if (!bb.Contains(pos)) - continue; - - // Support for one rectangular hole in any given window - // FIXME: Consider generalizing hit-testing override (with more generic data, callback, etc.) (#1512) - if (window->HitTestHoleSize.x != 0) - { - ImVec2 hole_pos(window->Pos.x + (float)window->HitTestHoleOffset.x, window->Pos.y + (float)window->HitTestHoleOffset.y); - ImVec2 hole_size((float)window->HitTestHoleSize.x, (float)window->HitTestHoleSize.y); - if (ImRect(hole_pos, hole_pos + hole_size).Contains(pos)) - continue; - } - - return window; - } - return NULL; -} - -static bool IsPosOnVoid(ImGuiContext& g, const ImVec2& pos) -{ - for (ImGuiWindow* window : g.Windows) -#ifdef IMGUI_HAS_DOCK - if (window->RootWindowDockTree == window && window->WasActive) -#else - if (window->RootWindow == window && window->WasActive) -#endif - { - ImRect r = window->Rect(); - r.Expand(g.WindowsHoverPadding); - if (r.Contains(pos)) - return false; - } - return true; -} - -// Sample viewport for an easy location with nothing on it. -// FIXME-OPT: If ever any problematic: -// - (1) could iterate g.WindowsFocusOrder[] now that we made the switch of it only containing root windows -// - (2) increase steps iteratively -// - (3) remember last answer and tries it first. -// - (4) shortpath to failure negative if a window covers the whole viewport? -bool ImGuiTestContext::FindExistingVoidPosOnViewport(ImGuiViewport* viewport, ImVec2* out) -{ - ImGuiContext& g = *UiContext; - if (IsError()) - return false; - - for (int yn = 0; yn < 20; yn++) - for (int xn = 0; xn < 20; xn++) - { - ImVec2 pos = viewport->Pos + viewport->Size * ImVec2(xn / 20.0f, yn / 20.0f); - if (!IsPosOnVoid(g, pos)) - continue; - *out = pos; - return true; - } - return false; -} - -ImVec2 ImGuiTestContext::GetPosOnVoid(ImGuiViewport* viewport) -{ - ImGuiContext& g = *UiContext; - if (IsError()) - return ImVec2(); - - ImVec2 void_pos; - bool found_existing_void_pos = FindExistingVoidPosOnViewport(viewport, &void_pos); - if (found_existing_void_pos) - return void_pos; - - // Move windows away - // FIXME: Should be optional and otherwise error. - void_pos = viewport->Pos + ImVec2(1, 1); - ImVec2 window_min_pos = void_pos + g.WindowsHoverPadding + ImVec2(1.0f, 1.0f); - for (ImGuiWindow* window : g.Windows) - { -#ifdef IMGUI_HAS_DOCK - if (window->Viewport != viewport) - continue; - if (window->RootWindowDockTree == window && window->WasActive) -#else - if (window->RootWindow == window && window->WasActive) -#endif - if (window->Rect().Contains(window_min_pos)) - WindowMove(window->Name, window_min_pos); - } - - return void_pos; -} - -ImVec2 ImGuiTestContext::GetWindowTitlebarPoint(ImGuiTestRef window_ref) -{ - // FIXME-TESTS: Need to find a -visible- click point. drag_pos may end up being outside of main viewport. - if (IsError()) - return ImVec2(); - - ImGuiWindow* window = GetWindowByRef(window_ref); - if (window == NULL) - { - IM_ERRORF_NOHDR("Unable to locate ref window: '%s'", window_ref.Path); - return ImVec2(); - } - - ImVec2 drag_pos; - for (int n = 0; n < 2; n++) - { -#ifdef IMGUI_HAS_DOCK - if (window->DockNode != NULL && window->DockNode->TabBar != NULL) - { - ImGuiTabBar* tab_bar = window->DockNode->TabBar; - ImGuiTabItem* tab = ImGui::TabBarFindTabByID(tab_bar, window->TabId); - IM_ASSERT(tab != NULL); - drag_pos = tab_bar->BarRect.Min + ImVec2(tab->Offset + tab->Width * 0.5f, tab_bar->BarRect.GetHeight() * 0.5f); - } - else -#endif - { - const float h = window->TitleBarHeight(); - drag_pos = ImFloor(window->Pos + ImVec2(window->Size.x, h) * 0.5f); - } - - // If we didn't have to teleport it means we can reach the position already - if (!WindowTeleportToMakePosVisible(window->ID, drag_pos)) - break; - } - return drag_pos; -} - -// Click position which should have no windows. -// Default to last mouse viewport if viewport not specified. -void ImGuiTestContext::MouseMoveToVoid(ImGuiViewport* viewport) -{ - ImGuiContext& g = *UiContext; - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("MouseMoveToVoid"); - -#ifdef IMGUI_HAS_VIEWPORT - if (viewport == NULL && g.MouseViewport && (g.MouseViewport->Flags & ImGuiViewportFlags_CanHostOtherWindows)) - viewport = g.MouseViewport; -#endif - if (viewport == NULL) - viewport = ImGui::GetMainViewport(); - - ImVec2 pos = GetPosOnVoid(viewport); // This may call WindowMove and alter mouse viewport. -#ifdef IMGUI_HAS_VIEWPORT - MouseSetViewportID(viewport->ID); -#endif - MouseMoveToPos(pos); - IM_CHECK(g.HoveredWindow == NULL); -} - -void ImGuiTestContext::MouseClickOnVoid(int mouse_button, ImGuiViewport* viewport) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("MouseClickOnVoid %d", mouse_button); - MouseMoveToVoid(viewport); - MouseClick(mouse_button); -} - -void ImGuiTestContext::MouseDragWithDelta(ImVec2 delta, ImGuiMouseButton button) -{ - ImGuiContext& g = *UiContext; - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("MouseDragWithDelta %d (%.1f, %.1f)", button, delta.x, delta.y); - - MouseDown(button); - MouseMoveToPos(g.IO.MousePos + delta); - MouseUp(button); -} - -// Important: always call MouseWheelX()/MouseWheelY() with an understand that holding Shift will swap axises. -// - On Windows/Linux, this swap is done in ImGui::NewFrame() -// - On OSX, this swap is generally done by the backends -// - In simulated test engine, always assume Windows/Linux behavior as we will swap in ImGuiTestEngine_ApplyInputToImGuiContext() -void ImGuiTestContext::MouseWheel(ImVec2 delta) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - - LogDebug("MouseWheel(%g, %g)", delta.x, delta.y); - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Cinematic) - SleepStandard(); - - float td = 0.0f; - const float scroll_speed = 15.0f; // Units per second. - while (delta.x != 0.0f || delta.y != 0.0f) - { - ImVec2 scroll; - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Fast) - { - scroll = delta; - } - else - { - td += UiContext->IO.DeltaTime; - scroll = ImFloor(delta * ImVec2(td, td) * scroll_speed); - } - - if (scroll.x != 0.0f || scroll.y != 0.0f) - { - scroll = ImClamp(scroll, ImVec2(ImMin(delta.x, 0.0f), ImMin(delta.y, 0.0f)), ImVec2(ImMax(delta.x, 0.0f), ImMax(delta.y, 0.0f))); - Inputs->MouseWheel = scroll; - delta -= scroll; - td = 0; - } - Yield(); - } -} - -void ImGuiTestContext::KeyDown(ImGuiKeyChord key_chord) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); -#if IMGUI_VERSION_NUM >= 19012 - const char* chord_desc = ImGui::GetKeyChordName(key_chord); -#else - char chord_desc[32]; - ImGui::GetKeyChordName(key_chord, chord_desc, IM_ARRAYSIZE(chord_desc)); -#endif - LogDebug("KeyDown(%s)", chord_desc); - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Cinematic) - SleepShort(); - - Inputs->Queue.push_back(ImGuiTestInput::ForKeyChord(key_chord, true)); - Yield(); - Yield(); -} - -void ImGuiTestContext::KeyUp(ImGuiKeyChord key_chord) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); -#if IMGUI_VERSION_NUM >= 19012 - const char* chord_desc = ImGui::GetKeyChordName(key_chord); -#else - char chord_desc[32]; - ImGui::GetKeyChordName(key_chord, chord_desc, IM_ARRAYSIZE(chord_desc)); -#endif - LogDebug("KeyUp(%s)", chord_desc); - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Cinematic) - SleepShort(); - - Inputs->Queue.push_back(ImGuiTestInput::ForKeyChord(key_chord, false)); - Yield(); - Yield(); -} - -void ImGuiTestContext::KeyPress(ImGuiKeyChord key_chord, int count) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); -#if IMGUI_VERSION_NUM >= 19012 - const char* chord_desc = ImGui::GetKeyChordName(key_chord); -#else - char chord_desc[32]; - ImGui::GetKeyChordName(key_chord, chord_desc, IM_ARRAYSIZE(chord_desc)); -#endif - LogDebug("KeyPress(%s, %d)", chord_desc, count); - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Cinematic) - SleepShort(); - - while (count > 0) - { - count--; - Inputs->Queue.push_back(ImGuiTestInput::ForKeyChord(key_chord, true)); - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Cinematic) - SleepShort(); - else - Yield(); - Inputs->Queue.push_back(ImGuiTestInput::ForKeyChord(key_chord, false)); - Yield(); - - // Give a frame for items to react - Yield(); - } -} - -void ImGuiTestContext::KeyHold(ImGuiKeyChord key_chord, float time) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); -#if IMGUI_VERSION_NUM >= 19012 - const char* chord_desc = ImGui::GetKeyChordName(key_chord); -#else - char chord_desc[32]; - ImGui::GetKeyChordName(key_chord, chord_desc, IM_ARRAYSIZE(chord_desc)); -#endif - LogDebug("KeyHold(%s, %.2f sec)", chord_desc, time); - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Cinematic) - SleepStandard(); - - Inputs->Queue.push_back(ImGuiTestInput::ForKeyChord(key_chord, true)); - SleepNoSkip(time, 1 / 100.0f); - Inputs->Queue.push_back(ImGuiTestInput::ForKeyChord(key_chord, false)); - Yield(); // Give a frame for items to react -} - -// No extra yield -void ImGuiTestContext::KeySetEx(ImGuiKeyChord key_chord, bool is_down, float time) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); -#if IMGUI_VERSION_NUM >= 19012 - const char* chord_desc = ImGui::GetKeyChordName(key_chord); -#else - char chord_desc[32]; - ImGui::GetKeyChordName(key_chord, chord_desc, IM_ARRAYSIZE(chord_desc)); -#endif - LogDebug("KeySetEx(%s, is_down=%d, time=%.f)", chord_desc, is_down, time); - Inputs->Queue.push_back(ImGuiTestInput::ForKeyChord(key_chord, is_down)); - if (time > 0.0f) - SleepNoSkip(time, 1.0f / 100.0f); -} - -void ImGuiTestContext::KeyChars(const char* chars) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("KeyChars('%s')", chars); - if (EngineIO->ConfigRunSpeed == ImGuiTestRunSpeed_Cinematic) - SleepStandard(); - - while (*chars) - { - unsigned int c = 0; - int bytes_count = ImTextCharFromUtf8(&c, chars, NULL); - chars += bytes_count; - if (c > 0 && c <= 0xFFFF) - Inputs->Queue.push_back(ImGuiTestInput::ForChar((ImWchar)c)); - - if (EngineIO->ConfigRunSpeed != ImGuiTestRunSpeed_Fast) - Sleep(1.0f / EngineIO->TypingSpeed); - } - Yield(); -} - -void ImGuiTestContext::KeyCharsAppend(const char* chars) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("KeyCharsAppend('%s')", chars); - KeyPress(ImGuiKey_End); - KeyChars(chars); -} - -void ImGuiTestContext::KeyCharsAppendEnter(const char* chars) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("KeyCharsAppendEnter('%s')", chars); - KeyPress(ImGuiKey_End); - KeyChars(chars); - KeyPress(ImGuiKey_Enter); -} - -void ImGuiTestContext::KeyCharsReplace(const char* chars) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("KeyCharsReplace('%s')", chars); - KeyPress(ImGuiKey_A | ImGuiMod_Shortcut); - if (chars[0]) - KeyChars(chars); - else - KeyPress(ImGuiKey_Delete); -} - -void ImGuiTestContext::KeyCharsReplaceEnter(const char* chars) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("KeyCharsReplaceEnter('%s')", chars); - KeyPress(ImGuiKey_A | ImGuiMod_Shortcut); - if (chars[0]) - KeyChars(chars); - else - KeyPress(ImGuiKey_Delete); - KeyPress(ImGuiKey_Enter); -} - -// depth = 1 -> immediate child of 'parent' in ID Stack -void ImGuiTestContext::GatherItems(ImGuiTestItemList* out_list, ImGuiTestRef parent, int depth) -{ - IM_ASSERT(out_list != NULL); - IM_ASSERT(depth > 0 || depth == -1); - - if (IsError()) - return; - - ImGuiTestGatherTask* task = &Engine->GatherTask; - IM_ASSERT(task->InParentID == 0); - IM_ASSERT(task->LastItemInfo == NULL); - - // Register gather tasks - if (depth == -1) - depth = 99; - if (parent.ID == 0) - parent.ID = GetID(parent); - task->InParentID = parent.ID; - task->InMaxDepth = depth; - task->InLayerMask = (1 << ImGuiNavLayer_Main); // FIXME: Configurable filter - task->OutList = out_list; - - // Keep running while gathering - // The corresponding hook is ItemAdd() -> ImGuiTestEngineHook_ItemAdd() -> ImGuiTestEngineHook_ItemAdd_GatherTask() - const int begin_gather_size = out_list->GetSize(); - while (true) - { - const int begin_gather_size_for_frame = out_list->GetSize(); - Yield(); - const int end_gather_size_for_frame = out_list->GetSize(); - if (begin_gather_size_for_frame == end_gather_size_for_frame) - break; - } - const int end_gather_size = out_list->GetSize(); - - // 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); - LogDebug("GatherItems from %s, %d deep: found %d items.", ImGuiTestRefDesc(parent, parent_item).c_str(), depth, end_gather_size - begin_gather_size); - - task->Clear(); -} - -// Supported values for ImGuiTestOpFlags: -// - ImGuiTestOpFlags_NoAutoOpenFullPath -// - ImGuiTestOpFlags_NoError -void ImGuiTestContext::ItemAction(ImGuiTestAction action, ImGuiTestRef ref, ImGuiTestOpFlags flags, void* action_arg) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - - // [DEBUG] Breakpoint - //if (ref.ID == 0x0d4af068) - // printf(""); - - // FIXME-TESTS: Fix that stuff - const bool is_wildcard = ref.Path != NULL && strstr(ref.Path, "**/") != 0; - if (is_wildcard) - { - // This is a fragile way to avoid some ambiguities, we're relying on expected action to further filter by status flags. - // These flags are not cleared by ItemInfo() because ItemAction() may call ItemInfo() again to get same item and thus it - // needs these flags to remain in place. - if (action == ImGuiTestAction_Check || action == ImGuiTestAction_Uncheck) - Engine->FindByLabelTask.InFilterItemStatusFlags = ImGuiItemStatusFlags_Checkable; - else if (action == ImGuiTestAction_Open || action == ImGuiTestAction_Close) - Engine->FindByLabelTask.InFilterItemStatusFlags = ImGuiItemStatusFlags_Openable; - } - - // Find item - ImGuiTestItemInfo* item; - if (flags & ImGuiTestOpFlags_NoAutoOpenFullPath) - item = ItemInfo(ref, (flags & ImGuiTestOpFlags_NoError)); - else - item = ItemInfoOpenFullPath(ref, (flags & ImGuiTestOpFlags_NoError)); - - ImGuiTestRefDesc desc(ref, item); - LogDebug("Item%s %s%s", GetActionName(action), desc.c_str(), (InputMode == ImGuiInputSource_Mouse) ? "" : " (w/ Nav)"); - if (item->ID == 0) - { - if (flags & ImGuiTestOpFlags_NoError) - LogDebug("Action skipped: Item doesn't exist + used ImGuiTestOpFlags_NoError."); - return; - } - - // Automatically uncollapse by default - if (item->Window && !(OpFlags & ImGuiTestOpFlags_NoAutoUncollapse)) - WindowCollapse(item->Window->ID, false); - - if (action == ImGuiTestAction_Hover) - { - MouseMove(ref, flags); - } - if (action == ImGuiTestAction_Click || action == ImGuiTestAction_DoubleClick) - { - if (InputMode == ImGuiInputSource_Mouse) - { - const int mouse_button = (int)(intptr_t)action_arg; - IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT); - MouseMove(ref, flags); - if (action == ImGuiTestAction_DoubleClick) - MouseDoubleClick(mouse_button); - else - MouseClick(mouse_button); - } - else - { - action = ImGuiTestAction_NavActivate; - } - } - - if (action == ImGuiTestAction_NavActivate) - { - IM_ASSERT(action_arg == NULL); // Unused - NavMoveTo(ref); - NavActivate(); - if (action == ImGuiTestAction_DoubleClick) - IM_ASSERT(0); - } - else if (action == ImGuiTestAction_Input) - { - IM_ASSERT(action_arg == NULL); // Unused - if (InputMode == ImGuiInputSource_Mouse) - { - MouseMove(ref, flags); - KeyDown(ImGuiMod_Ctrl); - MouseClick(0); - KeyUp(ImGuiMod_Ctrl); - } - else - { - NavMoveTo(ref); - NavInput(); - } - } - else if (action == ImGuiTestAction_Open) - { - IM_ASSERT(action_arg == NULL); // Unused - 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) - { - MouseClick(0); - 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->RefCount--; - //Yield(); - } - } - else if (action == ImGuiTestAction_Close) - { - IM_ASSERT(action_arg == NULL); // Unused - if ((item->StatusFlags & ImGuiItemStatusFlags_Opened) != 0) - { - item->RefCount++; - ItemClick(ref, 0, flags); - 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) - 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)) - { - ItemClick(ref, 0, flags); - } - ItemVerifyCheckedIfAlive(ref, true); // We can't just IM_ASSERT(ItemIsChecked()) because the item may disappear and never update its StatusFlags any more! - } - else if (action == ImGuiTestAction_Uncheck) - { - IM_ASSERT(action_arg == NULL); // Unused - if ((item->StatusFlags & ImGuiItemStatusFlags_Checkable) && (item->StatusFlags & ImGuiItemStatusFlags_Checked)) - { - ItemClick(ref, 0, flags); - } - ItemVerifyCheckedIfAlive(ref, false); // We can't just IM_ASSERT(ItemIsChecked()) because the item may disappear and never update its StatusFlags any more! - } - - //if (is_wildcard) - Engine->FindByLabelTask.InFilterItemStatusFlags = ImGuiItemStatusFlags_None; -} - -void ImGuiTestContext::ItemActionAll(ImGuiTestAction action, ImGuiTestRef ref_parent, const ImGuiTestActionFilter* filter) -{ - int max_depth = filter ? filter->MaxDepth : -1; - if (max_depth == -1) - max_depth = 99; - int max_passes = filter ? filter->MaxPasses : -1; - if (max_passes == -1) - max_passes = 99; - IM_ASSERT(max_depth > 0 && max_passes > 0); - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("ItemActionAll() %s", GetActionName(action)); - - if (!ref_parent.IsEmpty()) - { - // Open parent's parents - 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) - ItemOpen(ref_parent, ImGuiTestOpFlags_NoError); - } - } - - // Find child items - int actioned_total = 0; - for (int pass = 0; pass < max_passes; pass++) - { - ImGuiTestItemList items; - GatherItems(&items, ref_parent, max_depth); - //LogItemList(&items); - - // Find deep most items - int highest_depth = -1; - if (action == ImGuiTestAction_Close) - for (auto& item : items) - if ((item.StatusFlags & ImGuiItemStatusFlags_Openable) && (item.StatusFlags & ImGuiItemStatusFlags_Opened)) // Not checking Disabled state here - highest_depth = ImMax(highest_depth, item.Depth); - - const int actioned_total_at_beginning_of_pass = actioned_total; - - // Process top-to-bottom in most cases - int scan_start = 0; - int scan_end = items.GetSize(); - int scan_dir = +1; - if (action == ImGuiTestAction_Close) - { - // Close bottom-to-top because - // 1) it is more likely to handle same-depth parent/child relationship better (e.g. CollapsingHeader) - // 2) it gives a nicer sense of symmetry with the corresponding open operation. - scan_start = items.GetSize() - 1; - scan_end = -1; - scan_dir = -1; - } - - int processed_count_per_depth[8]; - memset(processed_count_per_depth, 0, sizeof(processed_count_per_depth)); - - for (int n = scan_start; n != scan_end; n += scan_dir) - { - if (IsError()) - break; - - const ImGuiTestItemInfo& item = *items[n]; - - if (filter && filter->RequireAllStatusFlags != 0) - if ((item.StatusFlags & filter->RequireAllStatusFlags) != filter->RequireAllStatusFlags) - continue; - - if (filter && filter->RequireAnyStatusFlags != 0) - if ((item.StatusFlags & filter->RequireAnyStatusFlags) != 0) - continue; - - if (filter && filter->MaxItemCountPerDepth != NULL) - { - if (item.Depth < IM_ARRAYSIZE(processed_count_per_depth)) - { - if (processed_count_per_depth[item.Depth] >= filter->MaxItemCountPerDepth[item.Depth]) - continue; - processed_count_per_depth[item.Depth]++; - } - } - - switch (action) - { - case ImGuiTestAction_Hover: - case ImGuiTestAction_Click: - ItemAction(action, item.ID); - actioned_total++; - break; - case ImGuiTestAction_Check: - if ((item.StatusFlags & ImGuiItemStatusFlags_Checkable) && !(item.StatusFlags & ImGuiItemStatusFlags_Checked)) - if ((item.InFlags & ImGuiItemFlags_Disabled) == 0) - { - ItemAction(action, item.ID); - actioned_total++; - } - break; - case ImGuiTestAction_Uncheck: - if ((item.StatusFlags & ImGuiItemStatusFlags_Checkable) && (item.StatusFlags & ImGuiItemStatusFlags_Checked)) - if ((item.InFlags & ImGuiItemFlags_Disabled) == 0) - { - ItemAction(action, item.ID); - actioned_total++; - } - break; - case ImGuiTestAction_Open: - if ((item.StatusFlags & ImGuiItemStatusFlags_Openable) && !(item.StatusFlags & ImGuiItemStatusFlags_Opened)) - if ((item.InFlags & ImGuiItemFlags_Disabled) == 0) - { - ItemAction(action, item.ID); - actioned_total++; - } - break; - case ImGuiTestAction_Close: - if (item.Depth == highest_depth && (item.StatusFlags & ImGuiItemStatusFlags_Openable) && (item.StatusFlags & ImGuiItemStatusFlags_Opened)) - if ((item.InFlags & ImGuiItemFlags_Disabled) == 0) - { - ItemClose(item.ID); - actioned_total++; - } - break; - default: - IM_ASSERT(0); - } - } - - if (IsError()) - break; - - if (action == ImGuiTestAction_Hover) - break; - if (actioned_total_at_beginning_of_pass == actioned_total) - break; - } - LogDebug("%s %d items in total!", GetActionVerb(action), actioned_total); -} - -void ImGuiTestContext::ItemOpenAll(ImGuiTestRef ref_parent, int max_depth, int max_passes) -{ - ImGuiTestActionFilter filter; - filter.MaxDepth = max_depth; - filter.MaxPasses = max_passes; - ItemActionAll(ImGuiTestAction_Open, ref_parent, &filter); -} - -void ImGuiTestContext::ItemCloseAll(ImGuiTestRef ref_parent, int max_depth, int max_passes) -{ - ImGuiTestActionFilter filter; - filter.MaxDepth = max_depth; - filter.MaxPasses = max_passes; - ItemActionAll(ImGuiTestAction_Close, ref_parent, &filter); -} - -void ImGuiTestContext::ItemInputValue(ImGuiTestRef ref, int value) -{ - char buf[32]; - ImFormatString(buf, IM_ARRAYSIZE(buf), "%d", value); - ItemInput(ref); - KeyCharsReplaceEnter(buf); -} -void ImGuiTestContext::ItemInputValue(ImGuiTestRef ref, float value) -{ - char buf[32]; - ImFormatString(buf, IM_ARRAYSIZE(buf), "%f", value); - ItemInput(ref); - KeyCharsReplaceEnter(buf); -} - -void ImGuiTestContext::ItemInputValue(ImGuiTestRef ref, const char* value) -{ - ItemInput(ref); - KeyCharsReplaceEnter(value); -} - -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); - - MouseMove(ref); - - Yield(); - Inputs->MouseButtonsValue = (1 << 0); - Sleep(time); - Inputs->MouseButtonsValue = 0; - Yield(); -} - -void ImGuiTestContext::ItemHoldForFrames(ImGuiTestRef ref, int frames) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("ItemHoldForFrames '%s' %08X", ref.Path ? ref.Path : "NULL", ref.ID); - - MouseMove(ref); - Yield(); - Inputs->MouseButtonsValue = (1 << 0); - Yield(frames); - Inputs->MouseButtonsValue = 0; - Yield(); -} - -// Used to test opening containers (TreeNode, Tabs) while dragging a payload -void ImGuiTestContext::ItemDragOverAndHold(ImGuiTestRef ref_src, ImGuiTestRef ref_dst) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - 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()); - - MouseMove(ref_src, ImGuiTestOpFlags_NoCheckHoveredId); - SleepStandard(); - MouseDown(0); - - // Enforce lifting drag threshold even if both item are exactly at the same location. - MouseLiftDragThreshold(); - - MouseMove(ref_dst, ImGuiTestOpFlags_NoCheckHoveredId); - SleepNoSkip(1.0f, 1.0f / 10.0f); - MouseUp(0); -} - -void ImGuiTestContext::ItemDragAndDrop(ImGuiTestRef ref_src, ImGuiTestRef ref_dst, ImGuiMouseButton button) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - 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()); - - // 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); - - // 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); - SleepStandard(); - MouseDown(button); - - // Enforce lifting drag threshold even if both item are exactly at the same location. - MouseLiftDragThreshold(); - - MouseMove(item_dst->ID, ImGuiTestOpFlags_NoCheckHoveredId | ImGuiTestOpFlags_NoFocusWindow); - SleepStandard(); - MouseUp(button); -} - -void ImGuiTestContext::ItemDragWithDelta(ImGuiTestRef ref_src, ImVec2 pos_delta) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - 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); - - MouseMove(ref_src, ImGuiTestOpFlags_NoCheckHoveredId); - SleepStandard(); - MouseDown(0); - - MouseMoveToPos(UiContext->IO.MousePos + pos_delta); - SleepStandard(); - MouseUp(0); -} - -bool ImGuiTestContext::ItemExists(ImGuiTestRef ref) -{ - 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; -} - -// 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; -} - -void ImGuiTestContext::ItemVerifyCheckedIfAlive(ImGuiTestRef ref, bool checked) -{ - // This is designed to deal with disappearing items which will not update their state, - // 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) - return; - 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()? -void ImGuiTestContext::TabClose(ImGuiTestRef ref) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("TabClose '%s' %08X", ref.Path ? ref.Path : "NULL", ref.ID); - - // Move into first, then click close button as it appears - MouseMove(ref); - ImGuiTestRef backup_ref = GetRef(); - SetRef(GetID(ref)); - ItemClick("#CLOSE"); - SetRef(backup_ref); -} - -bool ImGuiTestContext::TabBarCompareOrder(ImGuiTabBar* tab_bar, const char** tab_order) -{ - if (IsError()) - return false; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("TabBarCompareOrder"); - IM_CHECK_SILENT_RETV(tab_bar != NULL, false); - - // Display - char buf[256]; - char* buf_end = buf + IM_ARRAYSIZE(buf); - - char* p = buf; - for (int i = 0; i < tab_bar->Tabs.Size; i++) - p += ImFormatString(p, buf_end - p, "%s\"%s\"", i ? ", " : " ", ImGui::TabBarGetTabName(tab_bar, &tab_bar->Tabs[i])); - LogDebug(" Current {%s }", buf); - - p = buf; - for (int i = 0; tab_order[i] != NULL; i++) - p += ImFormatString(p, buf_end - p, "%s\"%s\"", i ? ", " : " ", tab_order[i]); - LogDebug(" Expected {%s }", buf); - - // Compare - for (int i = 0; tab_order[i] != NULL; i++) - { - if (i >= tab_bar->Tabs.Size) - return false; - const char* current = ImGui::TabBarGetTabName(tab_bar, &tab_bar->Tabs[i]); - const char* expected = tab_order[i]; - if (strcmp(current, expected) != 0) - return false; - } - return true; -} - - -void ImGuiTestContext::MenuAction(ImGuiTestAction action, ImGuiTestRef ref) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("MenuAction '%s' %08X", ref.Path ? ref.Path : "NULL", ref.ID); - - IM_ASSERT(ref.Path != NULL); - - // MenuAction() doesn't support **/ in most case it would be equivalent to opening all menus to "search". - // [01] Works: - // MenuClick("File/New"): - // [02] Works: - // MenuClick("File"); - // MenuClick("File/New"); - // [03] Works: - // MenuClick("File"); - // ItemClick("**/New"); - // [04] Doesn't work: (may work in the future) - // MenuClick("File"); - // MenuClick("**/New"); - // [05] Doesn't work: (unlikely to ever work) - // MenuClick("**/New"); - if (strncmp(ref.Path, "**/", 3) == 0) - { - LogError("\"**/\" is not yet supported by MenuAction()."); - return; - } - - int depth = 0; - const char* path = ref.Path; - const char* path_end = path + strlen(path); - - ImGuiWindow* ref_window = NULL; - if (path[0] == '/' && path[1] == '/') - { - const char* end = strstr(path + 2, "/"); - IM_CHECK_SILENT(end != NULL); // Menu interaction without any menus specified in ref. - Str64 window_name; - window_name.append(path, end); - ref_window = GetWindowByRef(GetID(window_name.c_str())); - path = end + 1; - if (ref_window == NULL) - LogError("MenuAction: missing ref window (invalid name \"//%s\" ?", window_name.c_str()); - } - else if (RefID) - { - ref_window = GetWindowByRef(RefID); - if (ref_window == NULL) - LogError("MenuAction: missing ref window (invalid SetRef value?)"); - } - IM_CHECK_SILENT(ref_window != NULL); // A ref window must always be set - - ImGuiWindow* current_window = ref_window; - Str128 buf; - while (path < path_end && !IsError()) - { - const char* p = ImStrchrRangeWithEscaping(path, path_end, '/'); - if (p == NULL) - p = path_end; - - const bool is_target_item = (p == path_end); - if (current_window->Flags & ImGuiWindowFlags_MenuBar) - buf.setf("//%s/##menubar/%.*s", current_window->Name, (int)(p - path), path); // Click menu in menu bar - else - buf.setf("//%s/%.*s", current_window->Name, (int)(p - path), path); // Click sub menu in its own window - -#if IMGUI_VERSION_NUM < 18520 - if (depth == 0 && (current_window->Flags & ImGuiWindowFlags_Popup)) - 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. - { - // 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--; - } - - if (is_target_item) - { - // Final item - ItemAction(action, buf.c_str()); - break; - } - else - { - // Then aim at the menu item. Menus may be navigated by holding mouse button down by hovering a menu. - ItemAction(Inputs->MouseButtonsValue ? ImGuiTestAction_Hover : ImGuiTestAction_Click, buf.c_str()); - } - } - current_window = GetWindowByRef(Str16f("##Menu_%02d", depth).c_str()); - IM_CHECK_SILENT(current_window != NULL); - - path = p + 1; - depth++; - } -} - -void ImGuiTestContext::MenuActionAll(ImGuiTestAction action, ImGuiTestRef ref_parent) -{ - ImGuiTestItemList items; - MenuAction(ImGuiTestAction_Open, ref_parent); - GatherItems(&items, "//$FOCUSED", 1); - //LogItemList(&items); - - for (auto item : items) - { - MenuAction(ImGuiTestAction_Open, ref_parent); // We assume that every interaction will close the menu again - - 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? - continue; - if ((info2->StatusFlags & ImGuiItemStatusFlags_Checkable) == 0) - continue; - } - - ItemAction(action, item.ID); - } -} - -static bool IsWindowACombo(ImGuiWindow* window) -{ - if ((window->Flags & ImGuiWindowFlags_Popup) == 0) - return false; - if (strncmp(window->Name, "##Combo_", strlen("##Combo_")) != 0) - return false; - return true; -} - -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); - - IM_ASSERT(ref.Path != NULL); - - const char* path = ref.Path; - const char* path_end = path + strlen(path); - - const char* p = ImStrchrRangeWithEscaping(path, path_end, '/'); - Str128f combo_popup_buf = Str128f("%.*s", (int)(p-path), path); - ItemClick(combo_popup_buf.c_str()); - - ImGuiWindow* popup = GetWindowByRef("//$FOCUSED"); - IM_CHECK_SILENT(popup && IsWindowACombo(popup)); - - Str128f combo_item_buf = Str128f("//%s/**/%s", popup->Name, p + 1); - ItemClick(combo_item_buf.c_str()); -} - -void ImGuiTestContext::ComboClickAll(ImGuiTestRef ref_parent) -{ - ItemClick(ref_parent); - - ImGuiWindow* popup = GetWindowByRef("//$FOCUSED"); - IM_CHECK_SILENT(popup && IsWindowACombo(popup)); - - ImGuiTestItemList items; - GatherItems(&items, "//$FOCUSED"); - for (auto item : items) - { - ItemClick(ref_parent); // We assume that every interaction will close the combo again - ItemClick(item.ID); - } -} - -static ImGuiTableColumn* HelperTableFindColumnByName(ImGuiTable* table, const char* name) -{ - for (int i = 0; i < table->Columns.size(); i++) - if (strcmp(ImGui::TableGetColumnName(table, i), name) == 0) - return &table->Columns[i]; - return NULL; -} - -void ImGuiTestContext::TableOpenContextMenu(ImGuiTestRef ref, int column_n) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("TableOpenContextMenu '%s' %08X", ref.Path ? ref.Path : "NULL", ref.ID); - - ImGuiTable* table = ImGui::TableFindByID(GetID(ref)); - IM_CHECK_SILENT(table != NULL); - - if (column_n == -1) - column_n = table->RightMostEnabledColumn; - ItemClick(TableGetHeaderID(table, column_n), ImGuiMouseButton_Right); - Yield(); -} - -ImGuiSortDirection ImGuiTestContext::TableClickHeader(ImGuiTestRef ref, const char* label, ImGuiKeyChord key_mods) -{ - IM_ASSERT((key_mods & ~ImGuiMod_Mask_) == 0); // Cannot pass keys only mods - - ImGuiTable* table = ImGui::TableFindByID(GetID(ref)); - IM_CHECK_SILENT_RETV(table != NULL, ImGuiSortDirection_None); - - ImGuiTableColumn* column = HelperTableFindColumnByName(table, label); - IM_CHECK_SILENT_RETV(column != NULL, ImGuiSortDirection_None); - - if (key_mods != ImGuiMod_None) - KeyDown(key_mods); - - ItemClick(TableGetHeaderID(table, label), ImGuiMouseButton_Left); - - if (key_mods != ImGuiMod_None) - KeyUp(key_mods); - return (ImGuiSortDirection_)column->SortDirection; -} - -void ImGuiTestContext::TableSetColumnEnabled(ImGuiTestRef ref, const char* label, bool enabled) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("TableSetColumnEnabled '%s' %08X = %d", ref.Path ? ref.Path : "NULL", ref.ID, enabled); - - TableOpenContextMenu(ref); - - ImGuiTestRef backup_ref = GetRef(); - SetRef("//$FOCUSED"); - if (enabled) - ItemCheck(label); - else - ItemUncheck(label); - PopupCloseOne(); - SetRef(backup_ref); -} - -void ImGuiTestContext::TableResizeColumn(ImGuiTestRef ref, int column_n, float width) -{ - if (IsError()) - 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); - - ImGuiTable* table = ImGui::TableFindByID(GetID(ref)); - IM_CHECK_SILENT(table != NULL); - - ImGuiID resize_id = ImGui::TableGetColumnResizeID(table, column_n); - float old_width = table->Columns[column_n].WidthGiven; - ItemDragWithDelta(resize_id, ImVec2(width - old_width, 0)); - - IM_CHECK_EQ(table->Columns[column_n].WidthRequest, width); -} - -const ImGuiTableSortSpecs* ImGuiTestContext::TableGetSortSpecs(ImGuiTestRef ref) -{ - ImGuiTable* table = ImGui::TableFindByID(GetID(ref)); - IM_CHECK_SILENT_RETV(table != NULL, NULL); - - ImGuiContext& g = *UiContext; - ImSwap(table, g.CurrentTable); - const ImGuiTableSortSpecs* sort_specs = ImGui::TableGetSortSpecs(); - ImSwap(table, g.CurrentTable); - return sort_specs; -} - -void ImGuiTestContext::WindowClose(ImGuiTestRef ref) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("WindowClose"); - ImGuiTestRef backup_ref = GetRef(); - SetRef(GetID(ref)); - -#ifdef IMGUI_HAS_DOCK - // When docked: first move to Tab to make Close Button appear. - if (ImGuiWindow* window = GetWindowByRef("")) - if (window->DockIsActive) - MouseMove(window->TabId); -#endif - - ItemClick("#CLOSE"); - SetRef(backup_ref); -} - -void ImGuiTestContext::WindowCollapse(ImGuiTestRef window_ref, bool collapsed) -{ - if (IsError()) - return; - ImGuiWindow* window = GetWindowByRef(window_ref); - if (window == NULL) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - if (window->Collapsed != collapsed) - { - LogDebug("WindowCollapse %d", collapsed); - ImGuiTestOpFlags backup_op_flags = OpFlags; - OpFlags |= ImGuiTestOpFlags_NoAutoUncollapse; - ImGuiTestRef backup_ref = GetRef(); - SetRef(window->ID); - ItemClick("#COLLAPSE"); - SetRef(backup_ref); - OpFlags = backup_op_flags; - Yield(); - IM_CHECK(window->Collapsed == collapsed); - } -} - -// Supported values for ImGuiTestOpFlags: -// - ImGuiTestOpFlags_NoError -void ImGuiTestContext::WindowFocus(ImGuiTestRef ref, ImGuiTestOpFlags flags) -{ - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - ImGuiTestRefDesc desc(ref, NULL); - LogDebug("WindowFocus('%s')", desc.c_str()); - - ImGuiWindow* window = GetWindowByRef(ref); - IM_CHECK_SILENT(window != NULL); - if (window) - { - ImGui::FocusWindow(window); // FIXME-TESTS-NOT_SAME_AS_END_USER: In theory should be replaced by click on title-bar or tab? - Yield(); - } - - // We cannot guarantee this will work 100% - // - Some modal inhibition may kick-in. - // - Because merely hovering an item may e.g. open a window or change focus. - // In particular this can be the case with MenuItem. So trying to Open a MenuItem may lead to its child opening while hovering, - // causing this function to seemingly fail (even if the end goal was reached). - ImGuiContext& g = *UiContext; - if ((window != g.NavWindow) && !(flags & ImGuiTestOpFlags_NoError)) - LogDebug("-- Expected focused window '%s', but '%s' got focus back.", window->Name, g.NavWindow ? g.NavWindow->Name : "<NULL>"); -} - -// Supported values for ImGuiTestOpFlags: -// - ImGuiTestOpFlags_NoError -// - ImGuiTestOpFlags_NoFocusWindow -// FIXME: In principle most calls to this could be replaced by WindowFocus()? -void ImGuiTestContext::WindowBringToFront(ImGuiTestRef ref, ImGuiTestOpFlags flags) -{ - ImGuiContext& g = *UiContext; - if (IsError()) - return; - - ImGuiWindow* window = GetWindowByRef(ref); - IM_CHECK_SILENT(window != NULL); - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - if (window != g.NavWindow && !(flags & ImGuiTestOpFlags_NoFocusWindow)) - { - LogDebug("WindowBringToFront()->FocusWindow('%s')", window->Name); - ImGui::FocusWindow(window); // FIXME-TESTS-NOT_SAME_AS_END_USER: In theory should be replaced by click on title-bar or tab? - Yield(2); - } - else if (window->RootWindow != g.Windows.back()->RootWindow) - { - LogDebug("BringWindowToDisplayFront('%s') (window.back=%s)", window->Name, g.Windows.back()->Name); - ImGui::BringWindowToDisplayFront(window); // FIXME-TESTS-NOT_SAME_AS_END_USER: This is not an actually possible action for end-user. - Yield(2); - } - - // Same as WindowFocus() - if ((window != g.NavWindow) && !(flags & ImGuiTestOpFlags_NoError)) - LogDebug("-- Expected focused window '%s', but '%s' got focus back.", window->Name, g.NavWindow ? g.NavWindow->Name : "<NULL>"); -} - -// Supported values for ImGuiTestOpFlags: -// - ImGuiTestOpFlags_NoFocusWindow -void ImGuiTestContext::WindowMove(ImGuiTestRef ref, ImVec2 input_pos, ImVec2 pivot, ImGuiTestOpFlags flags) -{ - if (IsError()) - return; - - ImGuiWindow* window = GetWindowByRef(ref); - IM_CHECK_SILENT(window != NULL); - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - 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) - { - //MouseMoveToPos(window->Pos); //?? - return; - } - - if ((flags & ImGuiTestOpFlags_NoFocusWindow) == 0) - WindowFocus(window->ID); - WindowCollapse(window->ID, false); - - MouseSetViewport(window); - MouseMoveToPos(GetWindowTitlebarPoint(ref)); - //IM_CHECK_SILENT(UiContext->HoveredWindow == window); - MouseDown(0); - - // Disable docking -#ifdef IMGUI_HAS_DOCK - if (UiContext->IO.ConfigDockingWithShift) - KeyUp(ImGuiMod_Shift); - else - KeyDown(ImGuiMod_Shift); -#endif - - ImVec2 delta = target_pos - window->Pos; - MouseMoveToPos(Inputs->MousePosValue + delta); - Yield(); - - MouseUp(); -#ifdef IMGUI_HAS_DOCK - KeyUp(ImGuiMod_Shift); -#endif - MouseSetViewport(window); // Update in case window has changed viewport -} - -void ImGuiTestContext::WindowResize(ImGuiTestRef ref, ImVec2 size) -{ - if (IsError()) - return; - - ImGuiWindow* window = GetWindowByRef(ref); - IM_CHECK_SILENT(window != NULL); - size = ImFloor(size); - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("WindowResize %s (%.1f,%.1f)", window->Name, size.x, size.y); - if (ImLengthSqr(size - window->Size) < 0.001f) - return; - - WindowFocus(window->ID); - WindowCollapse(window->ID, false); - - // 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); - MouseMove(id, ImGuiTestOpFlags_IsSecondAttempt); - - if (size.x <= 0.0f || size.y <= 0.0f) - { - IM_ASSERT(size.x <= 0.0f && size.y <= 0.0f); - MouseDoubleClick(0); - Yield(); - } - else - { - MouseDown(0); - ImVec2 delta = size - window->Size; - MouseMoveToPos(Inputs->MousePosValue + delta); - Yield(); // At this point we don't guarantee the final size! - MouseUp(); - } - MouseSetViewport(window); // Update in case window has changed viewport -} - -void ImGuiTestContext::PopupCloseOne() -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("PopupCloseOne"); - ImGuiContext& g = *UiContext; - if (g.OpenPopupStack.Size > 0) - ImGui::ClosePopupToLevel(g.OpenPopupStack.Size - 1, true); // FIXME-TESTS-NOT_SAME_AS_END_USER - Yield(); -} - -void ImGuiTestContext::PopupCloseAll() -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("PopupCloseAll"); - ImGuiContext& g = *UiContext; - if (g.OpenPopupStack.Size > 0) - ImGui::ClosePopupToLevel(0, true); // FIXME-TESTS-NOT_SAME_AS_END_USER - Yield(); -} - -// Match code in BeginPopupEx() -ImGuiID ImGuiTestContext::PopupGetWindowID(ImGuiTestRef ref) -{ - Str30f popup_name("//##Popup_%08x", GetID(ref)); - return GetID(popup_name.c_str()); -} - -#ifdef IMGUI_HAS_VIEWPORT -// Simulate a platform focus WITHOUT a click perceived by dear imgui. Similare to clicking on Platform title bar. -void ImGuiTestContext::ViewportPlatform_SetWindowFocus(ImGuiViewport* viewport) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("ViewportPlatform_SetWindowFocus(0x%08X)", viewport->ID); - Inputs->Queue.push_back(ImGuiTestInput::ForViewportFocus(viewport->ID)); // Queued since this will poke into backend, best to do in main thread. - Yield(); // Submit to Platform - Yield(); // Let Dear ImGui next frame see it -} - -// Simulate a platform window closure. -void ImGuiTestContext::ViewportPlatform_CloseWindow(ImGuiViewport* viewport) -{ - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("ViewportPlatform_CloseWindow(0x%08X)", viewport->ID); - Inputs->Queue.push_back(ImGuiTestInput::ForViewportClose(viewport->ID)); // Queued since this will poke into backend, best to do in main thread. - Yield(); // Submit to Platform - Yield(3); // Let Dear ImGui next frame see it -} - -#endif - -#ifdef IMGUI_HAS_DOCK -// Note: unlike DockBuilder functions, for _nodes_ this require the node to be visible. -// Supported values for ImGuiTestOpFlags: -// - ImGuiTestOpFlags_NoFocusWindow -// FIXME-TESTS: USING ImGuiTestOpFlags_NoFocusWindow leads to increase of ForeignWindowsHideOverPos(), best to avoid -void ImGuiTestContext::DockInto(ImGuiTestRef src_id, ImGuiTestRef dst_id, ImGuiDir split_dir, bool split_outer, ImGuiTestOpFlags flags) -{ - ImGuiContext& g = *UiContext; - if (IsError()) - return; - - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - - ImGuiWindow* window_src = GetWindowByRef(src_id); - ImGuiWindow* window_dst = GetWindowByRef(dst_id); - ImGuiDockNode* node_src = ImGui::DockBuilderGetNode(GetID(src_id)); - ImGuiDockNode* node_dst = ImGui::DockBuilderGetNode(GetID(dst_id)); - IM_CHECK_SILENT((window_src != NULL) != (node_src != NULL)); // Src must be either a window either a node - IM_CHECK_SILENT((window_dst != NULL) != (node_dst != NULL)); // Dst must be either a window either a node - - // Infer node from window. Not the opposite as docking a node would imply docking all of it. - if (node_src) - window_src = node_src->HostWindow; - if (node_dst) - window_dst = node_dst->HostWindow; - - Str128f log("DockInto() Src: %s '%s' (0x%08X), Dst: %s '%s' (0x%08X), SplitDir = %d", - node_src ? "node" : "window", node_src ? "" : window_src->Name, node_src ? node_src->ID : window_src->ID, - node_dst ? "node" : "window", node_dst ? "" : window_dst->Name, node_dst ? node_dst->ID : window_dst->ID, split_dir); - LogDebug("%s", log.c_str()); - - IM_CHECK_SILENT(window_src != NULL); - IM_CHECK_SILENT(window_dst != NULL); - IM_CHECK_SILENT(window_src->WasActive); - IM_CHECK_SILENT(window_dst->WasActive); - - // Avoid focusing if we don't need it (this facilitate avoiding focus flashing when recording animated gifs) - if (!(flags & ImGuiTestOpFlags_NoFocusWindow)) - { - if (g.Windows[g.Windows.Size - 2] != window_dst) - WindowFocus(window_dst->ID); - if (g.Windows[g.Windows.Size - 1] != window_src) - WindowFocus(window_src->ID); - } - - // Aim at title bar or tab or node grab - ImGuiTestRef ref_src; - if (node_src) - ref_src = ImGui::DockNodeGetWindowMenuButtonId(node_src); // Whole node grab - else - ref_src = (window_src->DockIsActive ? window_src->TabId : window_src->MoveId); // FIXME-TESTS FIXME-DOCKING: Identify tab - MouseMove(ref_src, ImGuiTestOpFlags_NoCheckHoveredId); - SleepStandard(); - - // Start dragging source, so it gets undocked already, because we calculate target position - // (Consider the possibility that dragging this out will move target position) - MouseDown(0); - if (g.IO.ConfigDockingWithShift) - KeyDown(ImGuiMod_Shift); - MouseLiftDragThreshold(); - if (window_src->DockIsActive) - MouseMoveToPos(g.IO.MousePos + ImVec2(0, ImGui::GetFrameHeight() * 2.0f)); - // (Button still held) - - // Locate target - ImVec2 drop_pos; - bool drop_is_valid = ImGui::DockContextCalcDropPosForDocking(window_dst, node_dst, window_src, node_src, split_dir, split_outer, &drop_pos); - IM_CHECK_SILENT(drop_is_valid); - if (!drop_is_valid) - { - if (g.IO.ConfigDockingWithShift) - KeyUp(ImGuiMod_Shift); - return; - } - - // Ensure we can reach target - WindowTeleportToMakePosVisible(window_dst->ID, drop_pos); - ImGuiWindow* friend_windows[] = { window_src, window_dst, NULL }; - ForeignWindowsHideOverPos(drop_pos, friend_windows); - - // Drag - drop_is_valid = ImGui::DockContextCalcDropPosForDocking(window_dst, node_dst, window_src, node_src, split_dir, split_outer, &drop_pos); - IM_CHECK_SILENT(drop_is_valid); - MouseSetViewport(window_dst); - MouseMoveToPos(drop_pos); - if (node_src) - window_src = node_src->HostWindow; // Dragging a menu button may detach a node and create a new window. - IM_CHECK_SILENT(g.MovingWindow == window_src); - - Yield(2); // Docking to dockspace over viewport (needs extra frame) or moving a dock node to another node (needs two extra frames) fails in fast mode without this. - IM_CHECK_SILENT(g.HoveredWindowUnderMovingWindow && g.HoveredWindowUnderMovingWindow->RootWindowDockTree == window_dst->RootWindowDockTree); - - // Docking will happen on the mouse-up - const ImGuiID prev_dock_id = window_src->DockId; - const ImGuiID prev_dock_parent_id = (window_src->DockNode && window_src->DockNode->ParentNode) ? window_src->DockNode->ParentNode->ID : 0; - const ImGuiID prev_dock_node_as_host_id = window_src->DockNodeAsHost ? window_src->DockNodeAsHost->ID : 0; - - MouseUp(0); - - // Cool down - if (g.IO.ConfigDockingWithShift) - KeyUp(ImGuiMod_Shift); - ForeignWindowsUnhideAll(); - Yield(); - Yield(); - - // Verify docking has succeeded! It's not easy to write a full fledged test, let's go for a simple one. - if (!(flags & ImGuiTestOpFlags_NoError)) - { - const ImGuiID curr_dock_id = window_src->DockId; - const ImGuiID curr_dock_parent_id = (window_src->DockNode && window_src->DockNode->ParentNode) ? window_src->DockNode->ParentNode->ID : 0; - const ImGuiID curr_dock_node_as_host_id = window_src->DockNodeAsHost ? window_src->DockNodeAsHost->ID : 0; - IM_CHECK_SILENT((prev_dock_id != curr_dock_id) || (prev_dock_parent_id != curr_dock_parent_id) || (prev_dock_node_as_host_id != curr_dock_node_as_host_id)); - } -} - -void ImGuiTestContext::DockClear(const char* window_name, ...) -{ - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("DockClear"); - - va_list args; - va_start(args, window_name); - while (window_name != NULL) - { - ImGui::DockBuilderDockWindow(window_name, 0); - window_name = va_arg(args, const char*); - } - va_end(args); - - if (ActiveFunc == ImGuiTestActiveFunc_TestFunc) - Yield(2); // Give time to rebuild dock in case io.ConfigDockingAlwaysTabBar is set -} - -bool ImGuiTestContext::WindowIsUndockedOrStandalone(ImGuiWindow* window) -{ - if (window->DockNode == NULL) - return true; - return DockIdIsUndockedOrStandalone(window->DockId); -} - -bool ImGuiTestContext::DockIdIsUndockedOrStandalone(ImGuiID dock_id) -{ - if (dock_id == 0) - return true; - if (ImGuiDockNode* node = ImGui::DockBuilderGetNode(dock_id)) - if (node->IsFloatingNode() && node->IsLeafNode() && node->Windows.Size == 1) - return true; - return false; -} - -void ImGuiTestContext::DockNodeHideTabBar(ImGuiDockNode* node, bool hidden) -{ - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("DockNodeHideTabBar %d", hidden); - - ImGuiTestRef backup_ref = GetRef(); - if (hidden) - { - SetRef(node->HostWindow); - ItemClick(ImGui::DockNodeGetWindowMenuButtonId(node)); - ImGuiID popup_id = PopupGetWindowID(GetID("#WindowMenu", node->ID)); - SetRef(popup_id); -#if IMGUI_VERSION_NUM >= 18910 - ItemClick("###HideTabBar"); -#else - ItemClick("Hide tab bar"); -#endif - IM_CHECK_SILENT(node->IsHiddenTabBar()); - } - else - { - IM_CHECK_SILENT(node->VisibleWindow != NULL); - SetRef(node->VisibleWindow); - ItemClick("#UNHIDE", 0, ImGuiTestOpFlags_MoveToEdgeD | ImGuiTestOpFlags_MoveToEdgeR); - IM_CHECK_SILENT(!node->IsHiddenTabBar()); - } - SetRef(backup_ref); -} - -void ImGuiTestContext::UndockNode(ImGuiID dock_id) -{ - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("UndockNode 0x%08X", dock_id); - - ImGuiDockNode* node = ImGui::DockBuilderGetNode(dock_id); - if (node == NULL) - return; - if (node->IsFloatingNode()) - return; - if (node->Windows.empty()) - return; - - const float h = node->Windows[0]->TitleBarHeight(); - if (!UiContext->IO.ConfigDockingWithShift) - KeyDown(ImGuiMod_Shift); // Disable docking - ItemDragWithDelta(ImGui::DockNodeGetWindowMenuButtonId(node), ImVec2(h, h) * -2); - if (!UiContext->IO.ConfigDockingWithShift) - KeyUp(ImGuiMod_Shift); - MouseUp(); -} - -void ImGuiTestContext::UndockWindow(const char* window_name) -{ - IM_ASSERT(window_name != NULL); - IMGUI_TEST_CONTEXT_REGISTER_DEPTH(this); - LogDebug("UndockWindow \"%s\"", window_name); - - ImGuiWindow* window = GetWindowByRef(window_name); - if (!window->DockIsActive) - return; - - const float h = window->TitleBarHeight(); - if (!UiContext->IO.ConfigDockingWithShift) - KeyDown(ImGuiMod_Shift); - ItemDragWithDelta(window->TabId, ImVec2(h, h) * -2); - if (!UiContext->IO.ConfigDockingWithShift) - KeyUp(ImGuiMod_Shift); - Yield(); -} - -#endif // #ifdef IMGUI_HAS_DOCK - -//------------------------------------------------------------------------- -// ImGuiTestContext - Performance Tools -//------------------------------------------------------------------------- - -// Calculate the reference DeltaTime, averaged over PerfIterations/500 frames, with GuiFunc disabled. -void ImGuiTestContext::PerfCalcRef() -{ - LogDebug("Measuring ref dt..."); - RunFlags |= ImGuiTestRunFlags_GuiFuncDisable; - - ImMovingAverage<double> delta_times; - delta_times.Init(PerfIterations); - for (int n = 0; n < PerfIterations && !Abort; n++) - { - Yield(); - delta_times.AddSample(UiContext->IO.DeltaTime); - } - - PerfRefDt = delta_times.GetAverage(); - RunFlags &= ~ImGuiTestRunFlags_GuiFuncDisable; -} - -void ImGuiTestContext::PerfCapture(const char* category, const char* test_name, const char* csv_file) -{ - if (IsError()) - return; - - // Calculate reference average DeltaTime if it wasn't explicitly called by TestFunc - if (PerfRefDt < 0.0) - PerfCalcRef(); - IM_ASSERT(PerfRefDt >= 0.0); - - // Yield for the average to stabilize - LogDebug("Measuring GUI dt..."); - ImMovingAverage<double> delta_times; - delta_times.Init(PerfIterations); - for (int n = 0; n < PerfIterations && !Abort; n++) - { - Yield(); - delta_times.AddSample(UiContext->IO.DeltaTime); - } - if (Abort) - return; - - double dt_curr = delta_times.GetAverage(); - double dt_ref_ms = PerfRefDt * 1000; - double dt_delta_ms = (dt_curr - PerfRefDt) * 1000; - - const ImBuildInfo* build_info = ImBuildGetCompilationInfo(); - - // Display results - // FIXME-TESTS: Would be nice if we could submit a custom marker (e.g. branch/feature name) - LogInfo("[PERF] Conditions: Stress x%d, %s, %s, %s, %s, %s", - PerfStressAmount, build_info->Type, build_info->Cpu, build_info->OS, build_info->Compiler, build_info->Date); - LogInfo("[PERF] Result: %+6.3f ms (from ref %+6.3f)", dt_delta_ms, dt_ref_ms); - - ImGuiPerfToolEntry entry; - entry.Timestamp = Engine->BatchStartTime; - entry.Category = category ? category : Test->Category; - entry.TestName = test_name ? test_name : Test->Name; - entry.DtDeltaMs = dt_delta_ms; - entry.PerfStressAmount = PerfStressAmount; - entry.GitBranchName = EngineIO->GitBranchName; - entry.BuildType = build_info->Type; - entry.Cpu = build_info->Cpu; - entry.OS = build_info->OS; - entry.Compiler = build_info->Compiler; - entry.Date = build_info->Date; - ImGuiTestEngine_PerfToolAppendToCSV(Engine->PerfTool, &entry, csv_file); - - // Disable the "Success" message - RunFlags |= ImGuiTestRunFlags_NoSuccessMsg; -} - -//------------------------------------------------------------------------- diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_context.h b/vendor/zgui/libs/imgui_test_engine/imgui_te_context.h deleted file mode 100644 index a469f60..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_context.h +++ /dev/null @@ -1,616 +0,0 @@ -// dear imgui test engine -// (context when a running test + end user automation API) -// This is the main (if not only) interface that your Tests will be using. - -#pragma once - -#include "imgui.h" -#include "imgui_internal.h" // ImGuiAxis, ImGuiItemStatusFlags, ImGuiInputSource, ImGuiWindow -#include "imgui_te_engine.h" // ImGuiTestStatus, ImGuiTestRunFlags, ImGuiTestActiveFunc, ImGuiTestItemInfo, ImGuiTestLogFlags - -/* - -Index of this file: -// [SECTION] Header mess, warnings -// [SECTION] Forward declarations -// [SECTION] ImGuiTestRef -// [SECTION] Helper keys -// [SECTION] ImGuiTestContext related Flags/Enumerations -// [SECTION] ImGuiTestGenericVars, ImGuiTestGenericItemStatus -// [SECTION] ImGuiTestContext -// [SECTION] Debugging macros: IM_SUSPEND_TESTFUNC() -// [SECTION] Testing/Checking macros: IM_CHECK(), IM_ERRORF() etc. - -*/ - -//------------------------------------------------------------------------- -// [SECTION] Header mess, warnings -//------------------------------------------------------------------------- - -// Undo some of the damage done by <windows.h> -#ifdef Yield -#undef Yield -#endif - -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' -#elif defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind -#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead -#endif - -//------------------------------------------------------------------------- -// [SECTION] Forward declarations -//------------------------------------------------------------------------- - -// This file -typedef int ImGuiTestOpFlags; // Flags: See ImGuiTestOpFlags_ - -// External: imgui -struct ImGuiDockNode; -struct ImGuiTabBar; -struct ImGuiWindow; - -// External: test engine -struct ImGuiTest; // A test registered with IM_REGISTER_TEST() -struct ImGuiTestEngine; // Test Engine Instance (opaque) -struct ImGuiTestEngineIO; // Test Engine IO structure (configuration flags, state) -struct ImGuiTestItemInfo; // Information gathered about an item: label, status, bounding box etc. -struct ImGuiTestItemList; // Result of an GatherItems() query -struct ImGuiTestInputs; // Test Engine Simulated Inputs structure (opaque) -struct ImGuiTestGatherTask; // Test Engine task for scanning/finding items -struct ImGuiCaptureArgs; // Parameters for ctx->CaptureXXX functions -enum ImGuiTestVerboseLevel : int; - -//------------------------------------------------------------------------- -// [SECTION] ImGuiTestRef -//------------------------------------------------------------------------- - -// Weak reference to an Item/Window given an hashed ID _or_ a string path ID. -// This is most often passed as argument to function and generally has a very short lifetime. -// Documentation: https://github.com/ocornut/imgui_test_engine/wiki/Named-References -// (SUGGESTION: add those constructors to "VA Step Filter" (Visual Assist) or a .natstepfilter file (Visual Studio) so they are skipped by F11 (StepInto) -struct IMGUI_API ImGuiTestRef -{ - ImGuiID ID; // Pre-hashed ID - const char* Path; // Relative or absolute path (string pointed to, not owned, as our lifetime is very short) - - ImGuiTestRef() { ID = 0; Path = NULL; } - ImGuiTestRef(ImGuiID id) { ID = id; Path = NULL; } - ImGuiTestRef(const char* path) { ID = 0; Path = path; } - bool IsEmpty() const { return ID == 0 && (Path == NULL || Path[0] == 0); } -}; - -// Debug helper to output a string showing the Path, ID or Debug Label based on what is available (some items only have ID as we couldn't find/store a Path) -// (The size is arbitrary, this is only used for logging info the user/debugger) -struct IMGUI_API ImGuiTestRefDesc -{ - char Buf[80]; - - const char* c_str() { return Buf; } - ImGuiTestRefDesc(const ImGuiTestRef& ref, const ImGuiTestItemInfo* item); -}; - -//------------------------------------------------------------------------- -// [SECTION] ImGuiTestContext related Flags/Enumerations -//------------------------------------------------------------------------- - -// Named actions. Generally you will call the named helpers e.g. ItemClick(). This is used by shared/low-level functions such as ItemAction(). -enum ImGuiTestAction -{ - ImGuiTestAction_Unknown = 0, - ImGuiTestAction_Hover, // Move mouse - ImGuiTestAction_Click, // Move mouse and click - ImGuiTestAction_DoubleClick, // Move mouse and double-click - ImGuiTestAction_Check, // Check item if unchecked (Checkbox, MenuItem or any widget reporting ImGuiItemStatusFlags_Checkable) - ImGuiTestAction_Uncheck, // Uncheck item if checked - ImGuiTestAction_Open, // Open item if closed (TreeNode, BeginMenu or any widget reporting ImGuiItemStatusFlags_Openable) - ImGuiTestAction_Close, // Close item if opened - ImGuiTestAction_Input, // Start text inputing into a field (e.g. CTRL+Click on Drags/Slider, click on InputText etc.) - ImGuiTestAction_NavActivate, // Activate item with navigation - ImGuiTestAction_COUNT -}; - -// Generic flags for many ImGuiTestContext functions -enum ImGuiTestOpFlags_ -{ - ImGuiTestOpFlags_None = 0, - ImGuiTestOpFlags_NoCheckHoveredId = 1 << 1, // Don't check for HoveredId after aiming for a widget. A few situations may want this: while e.g. dragging or another items prevents hovering, or for items that don't use ItemHoverable() - ImGuiTestOpFlags_NoError = 1 << 2, // Don't abort/error e.g. if the item cannot be found or the operation doesn't succeed. - 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, -}; - -// Advanced filtering for ItemActionAll() -struct IMGUI_API ImGuiTestActionFilter -{ - int MaxDepth; - int MaxPasses; - const int* MaxItemCountPerDepth; - ImGuiItemStatusFlags RequireAllStatusFlags; - ImGuiItemStatusFlags RequireAnyStatusFlags; - - ImGuiTestActionFilter() { MaxDepth = -1; MaxPasses = -1; MaxItemCountPerDepth = NULL; RequireAllStatusFlags = RequireAnyStatusFlags = 0; } -}; - -//------------------------------------------------------------------------- -// [SECTION] ImGuiTestGenericVars, ImGuiTestGenericItemStatus -//------------------------------------------------------------------------- - -// Helper struct to store various query-able state of an item. -// This facilitate interactions between GuiFunc and TestFunc, since those state are frequently used. -struct IMGUI_API ImGuiTestGenericItemStatus -{ - int RetValue; // return value - int Hovered; // result of IsItemHovered() - int Active; // result of IsItemActive() - int Focused; // result of IsItemFocused() - int Clicked; // result of IsItemClicked() - int Visible; // result of IsItemVisible() - int Edited; // result of IsItemEdited() - int Activated; // result of IsItemActivated() - int Deactivated; // result of IsItemDeactivated() - int DeactivatedAfterEdit; // result of IsItemDeactivatedAfterEdit() - - ImGuiTestGenericItemStatus() { Clear(); } - void Clear() { memset(this, 0, sizeof(*this)); } - void QuerySet(bool ret_val = false) { Clear(); QueryInc(ret_val); } - void QueryInc(bool ret_val = false) { RetValue += ret_val; Hovered += ImGui::IsItemHovered(); Active += ImGui::IsItemActive(); Focused += ImGui::IsItemFocused(); Clicked += ImGui::IsItemClicked(); Visible += ImGui::IsItemVisible(); Edited += ImGui::IsItemEdited(); Activated += ImGui::IsItemActivated(); Deactivated += ImGui::IsItemDeactivated(); DeactivatedAfterEdit += ImGui::IsItemDeactivatedAfterEdit(); } -}; - -// Generic structure with various storage fields. -// This is useful for tests to quickly share data between GuiFunc and TestFunc without creating custom data structure. -// If those fields are not enough: using test->SetVarsDataType<>() + ctx->GetVars<>() it is possible to store custom data. -struct IMGUI_API ImGuiTestGenericVars -{ - // Generic storage with a bit of semantic to make user/test code look neater - int Step; - int Count; - ImGuiID DockId; - ImGuiID OwnerId; - ImGuiWindowFlags WindowFlags; - ImGuiTableFlags TableFlags; - ImGuiPopupFlags PopupFlags; - ImGuiTestGenericItemStatus Status; - bool ShowWindow1, ShowWindow2; - bool UseClipper; - bool UseViewports; - float Width; - ImVec2 Pos; - ImVec2 Size; - ImVec2 Pivot; - ImVec4 Color1, Color2; - - // Generic unnamed storage - int Int1, Int2, IntArray[10]; - float Float1, Float2, FloatArray[10]; - bool Bool1, Bool2, BoolArray[10]; - ImGuiID Id, IdArray[10]; - char Str1[256], Str2[256]; - - ImGuiTestGenericVars() { Clear(); } - void Clear() { memset(this, 0, sizeof(*this)); } -}; - -//------------------------------------------------------------------------- -// [SECTION] ImGuiTestContext -// Context for a running ImGuiTest -// This is the interface that most tests will interact with. -//------------------------------------------------------------------------- - -struct IMGUI_API ImGuiTestContext -{ - // User variables - ImGuiTestGenericVars GenericVars; // Generic variables holder for convenience. - void* UserVars = NULL; // Access using ctx->GetVars<Type>(). Setup with test->SetVarsDataType<>(). - - // Public fields - ImGuiContext* UiContext = NULL; // UI context - ImGuiTestEngineIO* EngineIO = NULL; // Test Engine IO/settings - ImGuiTest* Test = NULL; // Test currently running - ImGuiTestOutput* TestOutput = NULL; // Test output (generally == &Test->Output) - ImGuiTestOpFlags OpFlags = ImGuiTestOpFlags_None; // Flags affecting all operation (supported: ImGuiTestOpFlags_NoAutoUncollapse) - int PerfStressAmount = 0; // Convenience copy of engine->IO.PerfStressAmount - int FrameCount = 0; // Test frame count (restarts from zero every time) - int FirstTestFrameCount = 0; // First frame where TestFunc is running (after warm-up frame). This is generally -1 or 0 depending on whether we have warm up enabled - bool FirstGuiFrame = false; - bool HasDock = false; // #ifdef IMGUI_HAS_DOCK expressed in an easier to test value - ImGuiCaptureArgs* CaptureArgs = NULL; // Capture settings used by ctx->Capture*() functions - - //------------------------------------------------------------------------- - // [Internal Fields] - //------------------------------------------------------------------------- - - ImGuiTestEngine* Engine = NULL; - ImGuiTestInputs* Inputs = NULL; - ImGuiTestRunFlags RunFlags = ImGuiTestRunFlags_None; - ImGuiTestActiveFunc ActiveFunc = ImGuiTestActiveFunc_None; // None/GuiFunc/TestFunc - double RunningTime = 0.0; // Amount of wall clock time the Test has been running. Used by safety watchdog. - int ActionDepth = 0; // Nested depth of ctx-> function calls (used to decorate log) - int CaptureCounter = 0; // Number of captures - int ErrorCounter = 0; // Number of errors (generally this maxxes at 1 as most functions will early out) - bool Abort = false; - double PerfRefDt = -1.0; - int PerfIterations = 400; // Number of frames for PerfCapture() measurements - char RefStr[256] = { 0 }; // Reference window/path over which all named references are based - ImGuiID RefID = 0; // Reference ID over which all named references are based - ImGuiID RefWindowID = 0; // ID of a window that contains RefID item - ImGuiInputSource InputMode = ImGuiInputSource_Mouse; // Prefer interacting with mouse/keyboard/gamepad - ImVector<char> Clipboard; // Private clipboard for the test instance - ImVector<ImGuiWindow*> ForeignWindowsToHide; - ImGuiTestItemInfo DummyItemInfoNull; // Storage for ItemInfoNull() - bool CachedLinesPrintedToTTY = false; - - //------------------------------------------------------------------------- - // Public API - //------------------------------------------------------------------------- - - // Main control - void RecoverFromUiContextErrors(); - void Finish(ImGuiTestStatus status = ImGuiTestStatus_Success); // Set test status and stop running. Usually called when running test logic from GuiFunc() only. - ImGuiTestStatus RunChildTest(const char* test_name, ImGuiTestRunFlags flags = 0); // [Experimental] Run another test from the current test. - template <typename T> T& GetVars() { IM_ASSERT(UserVars != NULL); return *(T*)(UserVars); }// Campanion to using t->SetVarsDataType<>(). FIXME: Assert to compare sizes - - // Main status queries - bool IsError() const { return TestOutput->Status == ImGuiTestStatus_Error || Abort; } - bool IsWarmUpGuiFrame() const { return FrameCount < FirstTestFrameCount; } // Unless test->Flags has ImGuiTestFlags_NoGuiWarmUp, we run GuiFunc() twice before running TestFunc(). Those frames are called "WarmUp" frames. - bool IsFirstGuiFrame() const { return FirstGuiFrame; } - bool IsFirstTestFrame() const { return FrameCount == FirstTestFrameCount; } // First frame where TestFunc is running (after warm-up frame). - bool IsGuiFuncOnly() const { return (RunFlags & ImGuiTestRunFlags_GuiFuncOnly) != 0; } - - // Debugging - bool SuspendTestFunc(const char* file = NULL, int line = 0); // [DEBUG] Generally called via IM_SUSPEND_TESTFUNC - - // Logging - void LogEx(ImGuiTestVerboseLevel level, ImGuiTestLogFlags flags, const char* fmt, ...) IM_FMTARGS(4); - void LogExV(ImGuiTestVerboseLevel level, ImGuiTestLogFlags flags, const char* fmt, va_list args) IM_FMTLIST(4); - void LogToTTY(ImGuiTestVerboseLevel level, const char* message, const char* message_end = NULL); - void LogToDebugger(ImGuiTestVerboseLevel level, const char* message); - void LogDebug(const char* fmt, ...) IM_FMTARGS(2); // ImGuiTestVerboseLevel_Debug or ImGuiTestVerboseLevel_Trace depending on context depth - void LogInfo(const char* fmt, ...) IM_FMTARGS(2); // ImGuiTestVerboseLevel_Info - void LogWarning(const char* fmt, ...) IM_FMTARGS(2); // ImGuiTestVerboseLevel_Warning - void LogError(const char* fmt, ...) IM_FMTARGS(2); // ImGuiTestVerboseLevel_Error - void LogBasicUiState(); - void LogItemList(ImGuiTestItemList* list); - - // 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. - void SleepNoSkip(float time_in_second, float framestep_in_second); - - // Base Reference - // - ItemClick("Window/Button") --> click "Window/Button" - // - SetRef("Window"), ItemClick("Button") --> click "Window/Button" - // - SetRef("Window"), ItemClick("/Button") --> click "Window/Button" - // - SetRef("Window"), ItemClick("//Button") --> click "/Button" - // - 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. - 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); - void WindowClose(ImGuiTestRef window_ref); - void WindowCollapse(ImGuiTestRef window_ref, bool collapsed); - void WindowFocus(ImGuiTestRef window_ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); - void WindowBringToFront(ImGuiTestRef window_ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); - void WindowMove(ImGuiTestRef window_ref, ImVec2 pos, ImVec2 pivot = ImVec2(0.0f, 0.0f), ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); - void WindowResize(ImGuiTestRef window_ref, ImVec2 sz); - bool WindowTeleportToMakePosVisible(ImGuiTestRef window_ref, ImVec2 pos_in_window); - ImGuiWindow*GetWindowByRef(ImGuiTestRef window_ref); - - // Popups - void PopupCloseOne(); - void PopupCloseAll(); - ImGuiID PopupGetWindowID(ImGuiTestRef ref); - - // Get hash for a decorated ID Path. - // Note: for windows you may use WindowInfo() - ImGuiID GetID(ImGuiTestRef ref); - ImGuiID GetID(ImGuiTestRef ref, ImGuiTestRef seed_ref); - - // Miscellaneous helpers - ImVec2 GetPosOnVoid(ImGuiViewport* viewport); // Find a point that has no windows // FIXME: This needs error return and flag to enable/disable forcefully finding void. - ImVec2 GetWindowTitlebarPoint(ImGuiTestRef window_ref); // Return a clickable point on window title-bar (window tab for docked windows). - ImVec2 GetMainMonitorWorkPos(); // Work pos and size of main viewport when viewports are disabled, or work pos and size of monitor containing main viewport when viewports are enabled. - ImVec2 GetMainMonitorWorkSize(); - - // Screenshot/Video Captures - void CaptureReset(); // Reset state (use when doing multiple captures) - void CaptureSetExtension(const char* ext); // Set capture file format (otherwise for video this default to EngineIO->VideoCaptureExtension) - bool CaptureAddWindow(ImGuiTestRef ref); // Add window to be captured (default to capture everything) - void CaptureScreenshotWindow(ImGuiTestRef ref, int capture_flags = 0); // Trigger a screen capture of a single window (== CaptureAddWindow() + CaptureScreenshot()) - bool CaptureScreenshot(int capture_flags = 0); // Trigger a screen capture - bool CaptureBeginVideo(); // Start a video capture - bool CaptureEndVideo(); - - // Mouse inputs - void MouseMove(ImGuiTestRef ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); - void MouseMoveToPos(ImVec2 pos); - void MouseTeleportToPos(ImVec2 pos); - void MouseClick(ImGuiMouseButton button = 0); - void MouseClickMulti(ImGuiMouseButton button, int count); - void MouseDoubleClick(ImGuiMouseButton button = 0); - void MouseDown(ImGuiMouseButton button = 0); - void MouseUp(ImGuiMouseButton button = 0); - void MouseLiftDragThreshold(ImGuiMouseButton button = 0); - void MouseDragWithDelta(ImVec2 delta, ImGuiMouseButton button = 0); - void MouseWheel(ImVec2 delta); - void MouseWheelX(float dx) { MouseWheel(ImVec2(dx, 0.0f)); } - void MouseWheelY(float dy) { MouseWheel(ImVec2(0.0f, dy)); } - void MouseMoveToVoid(ImGuiViewport* viewport = NULL); - void MouseClickOnVoid(ImGuiMouseButton button = 0, ImGuiViewport* viewport = NULL); - ImGuiWindow*FindHoveredWindowAtPos(const ImVec2& pos); - bool FindExistingVoidPosOnViewport(ImGuiViewport* viewport, ImVec2* out); - - // Mouse inputs: Viewports - // - This is automatically called by SetRef() and any mouse action taking an item reference (e.g. ItemClick("button"), MouseClick("button")) - // - But when using raw position directy e.g. MouseMoveToPos() / MouseTeleportToPos() without referring to the parent window before, this needs to be set. - void MouseSetViewport(ImGuiWindow* window); - void MouseSetViewportID(ImGuiID viewport_id); - - // Keyboard inputs - void KeyDown(ImGuiKeyChord key_chord); - void KeyUp(ImGuiKeyChord key_chord); - void KeyPress(ImGuiKeyChord key_chord, int count = 1); - void KeyHold(ImGuiKeyChord key_chord, float time); - void KeySetEx(ImGuiKeyChord key_chord, bool is_down, float time); - void KeyChars(const char* chars); // Input characters - void KeyCharsAppend(const char* chars); // Input characters at end of field - void KeyCharsAppendEnter(const char* chars); // Input characters at end of field, press Enter - void KeyCharsReplace(const char* chars); // Delete existing field then input characters - void KeyCharsReplaceEnter(const char* chars); // Delete existing field then input characters, press Enter - - // Navigation inputs - // FIXME: Need some redesign/refactoring: - // - This was initially intended to: replace mouse action with keyboard/gamepad - // - Abstract keyboard vs gamepad actions - // However this is widely inconsistent and unfinished at this point. - void SetInputMode(ImGuiInputSource input_mode); // Mouse or Keyboard or Gamepad. In Keyboard or Gamepad mode, actions such as ItemClick or ItemInput are using nav facilities instead of Mouse. - void NavMoveTo(ImGuiTestRef ref); - void NavActivate(); // Activate current selected item: activate button, tweak sliders/drags. Equivalent of pressing Space on keyboard, ImGuiKey_GamepadFaceUp on a gamepad. - void NavInput(); // Input into select item: input sliders/drags. Equivalent of pressing Enter on keyboard, ImGuiKey_GamepadFaceDown on a gamepad. - - // Scrolling - void ScrollTo(ImGuiTestRef ref, ImGuiAxis axis, float scroll_v, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); - void ScrollToX(ImGuiTestRef ref, float scroll_x) { ScrollTo(ref, ImGuiAxis_X, scroll_x); } - void ScrollToY(ImGuiTestRef ref, float scroll_y) { ScrollTo(ref, ImGuiAxis_Y, scroll_y); } - void ScrollToTop(ImGuiTestRef ref); - void ScrollToBottom(ImGuiTestRef ref); - void ScrollToItem(ImGuiTestRef ref, ImGuiAxis axis, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None); - void ScrollToItemX(ImGuiTestRef ref); - void ScrollToItemY(ImGuiTestRef ref); - void ScrollToTabItem(ImGuiTabBar* tab_bar, ImGuiID tab_id); - bool ScrollErrorCheck(ImGuiAxis axis, float expected, float actual, int* remaining_attempts); - void ScrollVerifyScrollMax(ImGuiTestRef ref); - - // 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); - ImGuiID ItemInfoHandleWildcardSearch(const char* wildcard_prefix_start, const char* wildcard_prefix_end, const char* wildcard_suffix_start); - ImGuiTestItemInfo* ItemInfoNull(); - void GatherItems(ImGuiTestItemList* out_list, ImGuiTestRef parent, int depth = -1); - - // Item/Widgets manipulation - void ItemAction(ImGuiTestAction action, ImGuiTestRef ref, ImGuiTestOpFlags flags = 0, void* action_arg = NULL); - void ItemClick(ImGuiTestRef ref, ImGuiMouseButton button = 0, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Click, ref, flags, (void*)(size_t)button); } - void ItemDoubleClick(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_DoubleClick, ref, flags); } - void ItemCheck(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Check, ref, flags); } - void ItemUncheck(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Uncheck, ref, flags); } - void ItemOpen(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Open, ref, flags); } - void ItemClose(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Close, ref, flags); } - void ItemInput(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Input, ref, flags); } - void ItemNavActivate(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_NavActivate, ref, flags); } - bool ItemOpenFullPath(ImGuiTestRef); - - // Item/Widgets: Batch actions over an entire scope - void ItemActionAll(ImGuiTestAction action, ImGuiTestRef ref_parent, const ImGuiTestActionFilter* filter = NULL); - void ItemOpenAll(ImGuiTestRef ref_parent, int depth = -1, int passes = -1); - void ItemCloseAll(ImGuiTestRef ref_parent, int depth = -1, int passes = -1); - - // Item/Widgets: Helpers to easily set a value - void ItemInputValue(ImGuiTestRef ref, int v); - 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: Status query - bool ItemExists(ImGuiTestRef ref); - bool ItemIsChecked(ImGuiTestRef ref); - bool ItemIsOpened(ImGuiTestRef ref); - void ItemVerifyCheckedIfAlive(ImGuiTestRef ref, bool checked); - - // Helpers for Tab Bars widgets - void TabClose(ImGuiTestRef ref); - bool TabBarCompareOrder(ImGuiTabBar* tab_bar, const char** tab_order); - - // Helpers for MenuBar and Menus widgets - // - e.g. MenuCheck("File/Options/Enable grid"); - void MenuAction(ImGuiTestAction action, ImGuiTestRef ref); - void MenuActionAll(ImGuiTestAction action, ImGuiTestRef ref_parent); - void MenuClick(ImGuiTestRef ref) { MenuAction(ImGuiTestAction_Click, ref); } - void MenuCheck(ImGuiTestRef ref) { MenuAction(ImGuiTestAction_Check, ref); } - void MenuUncheck(ImGuiTestRef ref) { MenuAction(ImGuiTestAction_Uncheck, ref); } - void MenuCheckAll(ImGuiTestRef ref_parent) { MenuActionAll(ImGuiTestAction_Check, ref_parent); } - void MenuUncheckAll(ImGuiTestRef ref_parent) { MenuActionAll(ImGuiTestAction_Uncheck, ref_parent); } - - // Helpers for Combo Boxes - void ComboClick(ImGuiTestRef ref); - void ComboClickAll(ImGuiTestRef ref); - - // Helpers for Tables - void TableOpenContextMenu(ImGuiTestRef ref, int column_n = -1); - ImGuiSortDirection TableClickHeader(ImGuiTestRef ref, const char* label, ImGuiKeyChord key_mods = 0); - void TableSetColumnEnabled(ImGuiTestRef ref, const char* label, bool enabled); - void TableResizeColumn(ImGuiTestRef ref, int column_n, float width); - const ImGuiTableSortSpecs* TableGetSortSpecs(ImGuiTestRef ref); - - // Viewports - // IMPORTANT: Those function may alter Platform state (unless using the "Mock Viewport" backend). Use carefully. - // Those are mostly useful to simulate OS actions and testing of viewport-specific features, may not be useful to most users. -#ifdef IMGUI_HAS_VIEWPORT - //void ViewportPlatform_SetWindowPos(ImGuiViewport* viewport, const ImVec2& pos); - //void ViewportPlatform_SetWindowSize(ImGuiViewport* viewport, const ImVec2& size); - void ViewportPlatform_SetWindowFocus(ImGuiViewport* viewport); - void ViewportPlatform_CloseWindow(ImGuiViewport* viewport); -#endif - - // Docking -#ifdef IMGUI_HAS_DOCK - void DockClear(const char* window_name, ...); - void DockInto(ImGuiTestRef src_id, ImGuiTestRef dst_id, ImGuiDir split_dir = ImGuiDir_None, bool is_outer_docking = false, ImGuiTestOpFlags flags = 0); - void UndockNode(ImGuiID dock_id); - void UndockWindow(const char* window_name); - bool WindowIsUndockedOrStandalone(ImGuiWindow* window); - bool DockIdIsUndockedOrStandalone(ImGuiID dock_id); - void DockNodeHideTabBar(ImGuiDockNode* node, bool hidden); -#endif - - // Performances Measurement (use along with Dear ImGui Perf Tool) - void PerfCalcRef(); - void PerfCapture(const char* category = NULL, const char* test_name = NULL, const char* csv_file = NULL); - - // Obsolete functions -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - // 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); } -#endif - - // [Internal] - // FIXME: Aim to remove this system... - void ForeignWindowsHideOverPos(ImVec2 pos, ImGuiWindow** ignore_list); - void ForeignWindowsUnhideAll(); -}; - -//------------------------------------------------------------------------- -// [SECTION] Debugging macros (IM_SUSPEND_TESTFUNC) -//------------------------------------------------------------------------- - -// Debug: Temporarily suspend TestFunc to let user interactively inspect the GUI state (user will need to press the "Continue" button to resume TestFunc execution) -#define IM_SUSPEND_TESTFUNC() do { if (ctx->SuspendTestFunc(__FILE__, __LINE__)) return; } while (0) - -//------------------------------------------------------------------------- -// [SECTION] Testing/Checking macros: IM_CHECK(), IM_ERRORF() etc. -//------------------------------------------------------------------------- - -// Helpers used by IM_CHECK_OP() macros. -// ImGuiTestEngine_GetTempStringBuilder() returns a shared instance of ImGuiTextBuffer to recycle memory allocations -template<typename T> void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, T v) { buf->append("???"); IM_UNUSED(v); } // FIXME-TESTS: Could improve with some template magic -template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, const char* v) { buf->appendf("\"%s\"", v); } -template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, bool v) { buf->append(v ? "true" : "false"); } -template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImS8 v) { buf->appendf("%d", v); } -template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImU8 v) { buf->appendf("%u", v); } -template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImS16 v) { buf->appendf("%hd", v); } -template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImU16 v) { buf->appendf("%hu", v); } -template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImS32 v) { buf->appendf("%d", v); } -template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImU32 v) { buf->appendf("0x%08X", v); } // Assuming ImGuiID -template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImS64 v) { buf->appendf("%lld", v); } -template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImU64 v) { buf->appendf("%llu", v); } -template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, float v) { buf->appendf("%.3f", v); } -template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, double v) { buf->appendf("%f", v); } -template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImVec2 v) { buf->appendf("(%.3f, %.3f)", v.x, v.y); } -template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, const void* v) { buf->appendf("%p", v); } -template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImGuiWindow* v){ if (v) buf->appendf("\"%s\"", v->Name); else buf->append("NULL"); } - -// We embed every macro in a do {} while(0) statement as a trick to allow using them as regular single statement, e.g. if (XXX) IM_CHECK(A); else IM_CHECK(B) -// We leave the IM_DEBUG_BREAK() outside of the check function to step out faster when using a debugger. It also has the benefit of being lighter than an IM_ASSERT(). -#define IM_CHECK(_EXPR) do { bool res = (bool)(_EXPR); if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, res, #_EXPR)) { IM_DEBUG_BREAK(); } if (!res) return; } while (0) -#define IM_CHECK_NO_RET(_EXPR) do { bool res = (bool)(_EXPR); if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, res, #_EXPR)) { IM_DEBUG_BREAK(); } } while (0) -#define IM_CHECK_SILENT(_EXPR) do { bool res = (bool)(_EXPR); if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_SilentSuccess, res, #_EXPR)) { IM_DEBUG_BREAK(); } if (!res) return; } while (0) -#define IM_CHECK_RETV(_EXPR,_RETV) do { bool res = (bool)(_EXPR); if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, res, #_EXPR)) { IM_DEBUG_BREAK(); } if (!res) return _RETV; } while (0) -#define IM_CHECK_SILENT_RETV(_EXPR,_RETV) do { bool res = (bool)(_EXPR); if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_SilentSuccess, res, #_EXPR)) { IM_DEBUG_BREAK(); } if (!res) return _RETV; } while (0) -#define IM_ERRORF(_FMT,...) do { if (ImGuiTestEngine_Error(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, _FMT, __VA_ARGS__)) { IM_DEBUG_BREAK(); } } while (0) -#define IM_ERRORF_NOHDR(_FMT,...) do { if (ImGuiTestEngine_Error(NULL, NULL, 0, ImGuiTestCheckFlags_None, _FMT, __VA_ARGS__)) { IM_DEBUG_BREAK(); } } while (0) - -// Those macros allow us to print out the values of both LHS and RHS expressions involved in a check. -#define IM_CHECK_OP(_LHS, _RHS, _OP, _RETURN) \ - do \ - { \ - auto __lhs = _LHS; /* Cache to avoid side effects */ \ - auto __rhs = _RHS; \ - bool __res = __lhs _OP __rhs; \ - ImGuiTextBuffer* expr_buf = ImGuiTestEngine_GetTempStringBuilder(); \ - expr_buf->append(#_LHS " ["); \ - ImGuiTestEngineUtil_appendf_auto(expr_buf, __lhs); \ - expr_buf->append("] " #_OP " " #_RHS " ["); \ - ImGuiTestEngineUtil_appendf_auto(expr_buf, __rhs); \ - expr_buf->append("]"); \ - if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, __res, expr_buf->c_str())) \ - IM_ASSERT(__res); \ - if (_RETURN && !__res) \ - return; \ - } while (0) - -#define IM_CHECK_STR_OP(_LHS, _RHS, _OP, _RETURN, _FLAGS) \ - do \ - { \ - bool __res; \ - if (ImGuiTestEngine_CheckStrOp(__FILE__, __func__, __LINE__, _FLAGS, #_OP, #_LHS, _LHS, #_RHS, _RHS, &__res)) \ - IM_ASSERT(__res); \ - if (_RETURN && !__res) \ - return; \ - } while (0) - -// Scalar compares -#define IM_CHECK_EQ(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, ==, true) // Equal -#define IM_CHECK_NE(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, !=, true) // Not Equal -#define IM_CHECK_LT(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, < , true) // Less Than -#define IM_CHECK_LE(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, <=, true) // Less or Equal -#define IM_CHECK_GT(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, > , true) // Greater Than -#define IM_CHECK_GE(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, >=, true) // Greater or Equal - -// Scalar compares, without return on failure -#define IM_CHECK_EQ_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, ==, false) // Equal -#define IM_CHECK_NE_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, !=, false) // Not Equal -#define IM_CHECK_LT_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, < , false) // Less Than -#define IM_CHECK_LE_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, <=, false) // Less or Equal -#define IM_CHECK_GT_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, > , false) // Greater Than -#define IM_CHECK_GE_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, >=, false) // Greater or Equal - -// String compares -#define IM_CHECK_STR_EQ(_LHS, _RHS) IM_CHECK_STR_OP(_LHS, _RHS, ==, true, ImGuiTestCheckFlags_None) -#define IM_CHECK_STR_NE(_LHS, _RHS) IM_CHECK_STR_OP(_LHS, _RHS, !=, true, ImGuiTestCheckFlags_None) -#define IM_CHECK_STR_EQ_NO_RET(_LHS, _RHS) IM_CHECK_STR_OP(_LHS, _RHS, ==, false, ImGuiTestCheckFlags_None) -#define IM_CHECK_STR_NE_NO_RET(_LHS, _RHS) IM_CHECK_STR_OP(_LHS, _RHS, !=, false, ImGuiTestCheckFlags_None) -#define IM_CHECK_STR_EQ_SILENT(_LHS, _RHS) IM_CHECK_STR_OP(_LHS, _RHS, ==, true, ImGuiTestCheckFlags_SilentSuccess) - -// 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_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) - -//------------------------------------------------------------------------- - -#if defined(__clang__) -#pragma clang diagnostic pop -#elif defined(__GNUC__) -#pragma GCC diagnostic pop -#endif diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_coroutine.cpp b/vendor/zgui/libs/imgui_test_engine/imgui_te_coroutine.cpp deleted file mode 100644 index 75845c2..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_coroutine.cpp +++ /dev/null @@ -1,167 +0,0 @@ -// dear imgui test engine -// (coroutine interface + optional implementation) -// Read https://github.com/ocornut/imgui_test_engine/wiki/Setting-Up - -#include "imgui_te_coroutine.h" -#include "imgui.h" - -#ifdef _MSC_VER -#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen -#endif - -//------------------------------------------------------------------------ -// Coroutine implementation using std::thread -// This implements a coroutine using std::thread, with a helper thread for each coroutine (with serialised execution, so threads never actually run concurrently) -//------------------------------------------------------------------------ - -#if IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL - -#include "imgui_te_utils.h" -#include "thirdparty/Str/Str.h" -#include <thread> -#include <mutex> -#include <condition_variable> - -struct Coroutine_ImplStdThreadData -{ - std::thread* Thread; // The thread this coroutine is using - std::condition_variable StateChange; // Condition variable notified when the coroutine state changes - std::mutex StateMutex; // Mutex to protect coroutine state - bool CoroutineRunning; // Is the coroutine currently running? Lock StateMutex before access and notify StateChange on change - bool CoroutineTerminated; // Has the coroutine terminated? Lock StateMutex before access and notify StateChange on change - Str64 Name; // The name of this coroutine -}; - -// The coroutine executing on the current thread (if it is a coroutine thread) -static thread_local Coroutine_ImplStdThreadData* GThreadCoroutine = NULL; - -// The main function for a coroutine thread -static void CoroutineThreadMain(Coroutine_ImplStdThreadData* data, ImGuiTestCoroutineMainFunc func, void* ctx) -{ - // Set our thread name - ImThreadSetCurrentThreadDescription(data->Name.c_str()); - - // Set the thread coroutine - GThreadCoroutine = data; - - // Wait for initial Run() - while (1) - { - std::unique_lock<std::mutex> lock(data->StateMutex); - if (data->CoroutineRunning) - break; - data->StateChange.wait(lock); - } - - // Run user code, which will then call Yield() when it wants to yield control - func(ctx); - - // Mark as terminated - { - std::lock_guard<std::mutex> lock(data->StateMutex); - - data->CoroutineTerminated = true; - data->CoroutineRunning = false; - data->StateChange.notify_all(); - } -} - - -static ImGuiTestCoroutineHandle Coroutine_ImplStdThread_Create(ImGuiTestCoroutineMainFunc* func, const char* name, void* ctx) -{ - Coroutine_ImplStdThreadData* data = new Coroutine_ImplStdThreadData(); - - data->Name = name; - data->CoroutineRunning = false; - data->CoroutineTerminated = false; - data->Thread = new std::thread(CoroutineThreadMain, data, func, ctx); - - return (ImGuiTestCoroutineHandle)data; -} - -static void Coroutine_ImplStdThread_Destroy(ImGuiTestCoroutineHandle handle) -{ - Coroutine_ImplStdThreadData* data = (Coroutine_ImplStdThreadData*)handle; - - IM_ASSERT(data->CoroutineTerminated); // The coroutine needs to run to termination otherwise it may leak all sorts of things and this will deadlock - if (data->Thread) - { - data->Thread->join(); - - delete data->Thread; - data->Thread = NULL; - } - - delete data; - data = NULL; -} - -// Run the coroutine until the next call to Yield(). Returns TRUE if the coroutine yielded, FALSE if it terminated (or had previously terminated) -static bool Coroutine_ImplStdThread_Run(ImGuiTestCoroutineHandle handle) -{ - Coroutine_ImplStdThreadData* data = (Coroutine_ImplStdThreadData*)handle; - - // Wake up coroutine thread - { - std::lock_guard<std::mutex> lock(data->StateMutex); - - if (data->CoroutineTerminated) - return false; // Coroutine has already finished - - data->CoroutineRunning = true; - data->StateChange.notify_all(); - } - - // Wait for coroutine to stop - while (1) - { - std::unique_lock<std::mutex> lock(data->StateMutex); - if (!data->CoroutineRunning) - { - // Breakpoint here to catch the point where we return from the coroutine - if (data->CoroutineTerminated) - return false; // Coroutine finished - break; - } - data->StateChange.wait(lock); - } - - return true; -} - -// Yield the current coroutine (can only be called from a coroutine) -static void Coroutine_ImplStdThread_Yield() -{ - IM_ASSERT(GThreadCoroutine); // This can only be called from a coroutine thread - - Coroutine_ImplStdThreadData* data = GThreadCoroutine; - - // Flag that we are not running any more - { - std::lock_guard<std::mutex> lock(data->StateMutex); - data->CoroutineRunning = false; - data->StateChange.notify_all(); - } - - // At this point the thread that called RunCoroutine() will leave the "Wait for coroutine to stop" loop - // Wait until we get started up again - while (1) - { - std::unique_lock<std::mutex> lock(data->StateMutex); - if (data->CoroutineRunning) - break; // Breakpoint here if you want to catch the point where execution of this coroutine resumes - data->StateChange.wait(lock); - } -} - -ImGuiTestCoroutineInterface* Coroutine_ImplStdThread_GetInterface() -{ - static ImGuiTestCoroutineInterface intf; - intf.CreateFunc = Coroutine_ImplStdThread_Create; - intf.DestroyFunc = Coroutine_ImplStdThread_Destroy; - intf.RunFunc = Coroutine_ImplStdThread_Run; - intf.YieldFunc = Coroutine_ImplStdThread_Yield; - return &intf; -} - -#endif // #if IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_coroutine.h b/vendor/zgui/libs/imgui_test_engine/imgui_te_coroutine.h deleted file mode 100644 index 5ed423b..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_coroutine.h +++ /dev/null @@ -1,56 +0,0 @@ -// dear imgui test engine -// (coroutine interface + optional implementation) -// Read https://github.com/ocornut/imgui_test_engine/wiki/Setting-Up - -#pragma once - -#ifndef IMGUI_VERSION -#include "imgui.h" -#endif - -//------------------------------------------------------------------------ -// Coroutine abstraction -//------------------------------------------------------------------------ -// Coroutines should be used like this: -// ImGuiTestCoroutineHandle handle = CoroutineCreate(<func>, <name>, <ctx>); // name being for debugging, and ctx being an arbitrary user context pointer -// while (CoroutineRun(handle)) { <do other stuff }; -// CoroutineDestroy(handle); -// The coroutine code itself should call CoroutineYieldFunc() whenever it wants to yield control back to the main thread. -//------------------------------------------------------------------------ - -// An arbitrary handle used internally to represent coroutines (NULL indicates no handle) -typedef void* ImGuiTestCoroutineHandle; - -// A coroutine main function -typedef void (ImGuiTestCoroutineMainFunc)(void* data); - -// Coroutine support interface -// Your app needs to return and implement this. -// You can '#define IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL 1' in your imconfig file to use a default implementation using std::thread -// Documentation: https://github.com/ocornut/imgui_test_engine/wiki/Setting-Up -struct IMGUI_API ImGuiTestCoroutineInterface -{ - // Create a new coroutine - ImGuiTestCoroutineHandle (*CreateFunc)(ImGuiTestCoroutineMainFunc* func, const char* name, void* data); - - // Destroy a coroutine (which must have completed first) - void (*DestroyFunc)(ImGuiTestCoroutineHandle handle); - - // Run a coroutine until it yields or finishes, returning false if finished - bool (*RunFunc)(ImGuiTestCoroutineHandle handle); - - // Yield from a coroutine back to the caller, preserving coroutine state - void (*YieldFunc)(); -}; - -//------------------------------------------------------------------------ -// Coroutine implementation using std::thread -// The "coroutine" thread and user's main thread will always block on each other (both threads will NEVER run in parallel) -// It is just an implementation convenience that we provide an implementation using std::thread as it is widely available/standard. -//------------------------------------------------------------------------ - -#if IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL - -IMGUI_API ImGuiTestCoroutineInterface* Coroutine_ImplStdThread_GetInterface(); - -#endif // #if IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_engine.cpp b/vendor/zgui/libs/imgui_test_engine/imgui_te_engine.cpp deleted file mode 100644 index f5cf37f..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_engine.cpp +++ /dev/null @@ -1,2371 +0,0 @@ -// dear imgui test engine -// (core) -// This is the interface that your initial setup (app init, main loop) will mostly be using. -// Actual tests will mostly use the interface of imgui_te_context.h - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -#define _CRT_SECURE_NO_WARNINGS -#endif - -#define IMGUI_DEFINE_MATH_OPERATORS -#include "imgui_te_engine.h" -#include "imgui.h" -#include "imgui_internal.h" -#include "imgui_te_utils.h" -#include "imgui_te_context.h" -#include "imgui_te_internal.h" -#include "imgui_te_perftool.h" -#include "imgui_te_exporters.h" -#include "thirdparty/Str/Str.h" -#if _WIN32 -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include <windows.h> // SetUnhandledExceptionFilter() -#undef Yield // Undo some of the damage done by <windows.h> -#else -#include <signal.h> // signal() -#include <unistd.h> // sleep() -#endif - -// Warnings -#ifdef _MSC_VER -#pragma warning (disable: 4127) // conditional expression is constant -#endif - -/* - -Index of this file: - -// [SECTION] TODO -// [SECTION] FORWARD DECLARATIONS -// [SECTION] DATA STRUCTURES -// [SECTION] TEST ENGINE FUNCTIONS -// [SECTION] CRASH HANDLING -// [SECTION] HOOKS FOR CORE LIBRARY -// [SECTION] CHECK/ERROR FUNCTIONS FOR TESTS -// [SECTION] SETTINGS -// [SECTION] ImGuiTestLog -// [SECTION] ImGuiTest - -*/ - -//------------------------------------------------------------------------- -// [SECTION] DATA -//------------------------------------------------------------------------- - -static ImGuiTestEngine* GImGuiTestEngine = NULL; - -//------------------------------------------------------------------------- -// [SECTION] FORWARD DECLARATIONS -//------------------------------------------------------------------------- - -// Private functions -static void ImGuiTestEngine_BindImGuiContext(ImGuiTestEngine* engine, ImGuiContext* ui_ctx); -static void ImGuiTestEngine_UnbindImGuiContext(ImGuiTestEngine* engine, ImGuiContext* ui_ctx); -static void ImGuiTestEngine_CoroutineStopAndJoin(ImGuiTestEngine* engine); -static void ImGuiTestEngine_StartCalcSourceLineEnds(ImGuiTestEngine* engine); -static void ImGuiTestEngine_ClearInput(ImGuiTestEngine* engine); -static void ImGuiTestEngine_ApplyInputToImGuiContext(ImGuiTestEngine* engine); -static void ImGuiTestEngine_ProcessTestQueue(ImGuiTestEngine* engine); -static void ImGuiTestEngine_ClearTests(ImGuiTestEngine* engine); -static void ImGuiTestEngine_PreNewFrame(ImGuiTestEngine* engine, ImGuiContext* ui_ctx); -static void ImGuiTestEngine_PostNewFrame(ImGuiTestEngine* engine, ImGuiContext* ui_ctx); -static void ImGuiTestEngine_PreRender(ImGuiTestEngine* engine, ImGuiContext* ui_ctx); -static void ImGuiTestEngine_PostRender(ImGuiTestEngine* engine, ImGuiContext* ui_ctx); -static void ImGuiTestEngine_UpdateHooks(ImGuiTestEngine* engine); -static void ImGuiTestEngine_RunGuiFunc(ImGuiTestEngine* engine); -static void ImGuiTestEngine_TestQueueCoroutineMain(void* engine_opaque); - -// Settings -static void* ImGuiTestEngine_SettingsReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name); -static void ImGuiTestEngine_SettingsReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line); -static void ImGuiTestEngine_SettingsWriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf); - -//------------------------------------------------------------------------- -// [SECTION] TEST ENGINE FUNCTIONS -//------------------------------------------------------------------------- -// Public -// - ImGuiTestEngine_CreateContext() -// - ImGuiTestEngine_DestroyContext() -// - ImGuiTestEngine_BindImGuiContext() -// - ImGuiTestEngine_UnbindImGuiContext() -// - ImGuiTestEngine_GetIO() -// - ImGuiTestEngine_Abort() -// - ImGuiTestEngine_QueueAllTests() -//------------------------------------------------------------------------- -// - ImGuiTestEngine_FindItemInfo() -// - ImGuiTestEngine_ClearTests() -// - ImGuiTestEngine_ApplyInputToImGuiContext() -// - ImGuiTestEngine_PreNewFrame() -// - ImGuiTestEngine_PostNewFrame() -// - ImGuiTestEngine_Yield() -// - ImGuiTestEngine_ProcessTestQueue() -// - ImGuiTestEngine_QueueTest() -// - ImGuiTestEngine_RunTest() -//------------------------------------------------------------------------- - -ImGuiTestEngine::ImGuiTestEngine() -{ - PerfRefDeltaTime = 0.0f; - PerfDeltaTime100.Init(100); - PerfDeltaTime500.Init(500); - PerfTool = IM_NEW(ImGuiPerfTool); - UiFilterTests = IM_NEW(Str256); // We bite the bullet of adding an extra alloc/indirection in order to avoid including Str.h in our header - UiFilterPerfs = IM_NEW(Str256); - - // Initialize std::thread based coroutine implementation if requested -#if IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL - IM_ASSERT(IO.CoroutineFuncs == NULL && "IO.CoroutineFuncs already setup elsewhere!"); - IO.CoroutineFuncs = Coroutine_ImplStdThread_GetInterface(); -#endif -} - -ImGuiTestEngine::~ImGuiTestEngine() -{ - IM_ASSERT(TestQueueCoroutine == NULL); - IM_DELETE(PerfTool); - IM_DELETE(UiFilterTests); - IM_DELETE(UiFilterPerfs); -} - -static void ImGuiTestEngine_BindImGuiContext(ImGuiTestEngine* engine, ImGuiContext* ui_ctx) -{ - IM_ASSERT(engine->UiContextTarget == ui_ctx); - - // Add .ini handle for ImGuiWindow type - if (engine->IO.ConfigSavedSettings) - { - ImGuiSettingsHandler ini_handler; - ini_handler.TypeName = "TestEngine"; - ini_handler.TypeHash = ImHashStr("TestEngine"); - ini_handler.ReadOpenFn = ImGuiTestEngine_SettingsReadOpen; - ini_handler.ReadLineFn = ImGuiTestEngine_SettingsReadLine; - ini_handler.WriteAllFn = ImGuiTestEngine_SettingsWriteAll; - ui_ctx->SettingsHandlers.push_back(ini_handler); - engine->PerfTool->_AddSettingsHandler(); - } - - // Install generic context hooks facility - ImGuiContextHook hook; - hook.Type = ImGuiContextHookType_Shutdown; - hook.Callback = [](ImGuiContext* ui_ctx, ImGuiContextHook* hook) { ImGuiTestEngine_UnbindImGuiContext((ImGuiTestEngine*)hook->UserData, ui_ctx); }; - hook.UserData = (void*)engine; - ImGui::AddContextHook(ui_ctx, &hook); - - hook.Type = ImGuiContextHookType_NewFramePre; - hook.Callback = [](ImGuiContext* ui_ctx, ImGuiContextHook* hook) { ImGuiTestEngine_PreNewFrame((ImGuiTestEngine*)hook->UserData, ui_ctx); }; - hook.UserData = (void*)engine; - ImGui::AddContextHook(ui_ctx, &hook); - - hook.Type = ImGuiContextHookType_NewFramePost; - hook.Callback = [](ImGuiContext* ui_ctx, ImGuiContextHook* hook) { ImGuiTestEngine_PostNewFrame((ImGuiTestEngine*)hook->UserData, ui_ctx); }; - hook.UserData = (void*)engine; - ImGui::AddContextHook(ui_ctx, &hook); - - hook.Type = ImGuiContextHookType_RenderPre; - hook.Callback = [](ImGuiContext* ui_ctx, ImGuiContextHook* hook) { ImGuiTestEngine_PreRender((ImGuiTestEngine*)hook->UserData, ui_ctx); }; - hook.UserData = (void*)engine; - ImGui::AddContextHook(ui_ctx, &hook); - - hook.Type = ImGuiContextHookType_RenderPost; - hook.Callback = [](ImGuiContext* ui_ctx, ImGuiContextHook* hook) { ImGuiTestEngine_PostRender((ImGuiTestEngine*)hook->UserData, ui_ctx); }; - hook.UserData = (void*)engine; - ImGui::AddContextHook(ui_ctx, &hook); - - // Install custom test engine hook data - if (GImGuiTestEngine == NULL) - GImGuiTestEngine = engine; - IM_ASSERT(ui_ctx->TestEngine == NULL); - ui_ctx->TestEngine = engine; -} - -static void ImGuiTestEngine_UnbindImGuiContext(ImGuiTestEngine* engine, ImGuiContext* ui_ctx) -{ - IM_ASSERT(engine->UiContextTarget == ui_ctx); - - // FIXME: Could use ImGui::RemoveContextHook() if we stored our hook ids - for (int hook_n = 0; hook_n < ui_ctx->Hooks.Size; hook_n++) - if (ui_ctx->Hooks[hook_n].UserData == engine) - ImGui::RemoveContextHook(ui_ctx, ui_ctx->Hooks[hook_n].HookId); - - ImGuiTestEngine_CoroutineStopAndJoin(engine); - - IM_ASSERT(ui_ctx->TestEngine == engine); - ui_ctx->TestEngine = NULL; - - // Remove .ini handler - IM_ASSERT(GImGui == ui_ctx); - if (engine->IO.ConfigSavedSettings) - { - ImGui::RemoveSettingsHandler("TestEngine"); - ImGui::RemoveSettingsHandler("TestEnginePerfTool"); - } - - // Remove hook - if (GImGuiTestEngine == engine) - GImGuiTestEngine = NULL; - engine->UiContextTarget = engine->UiContextActive = NULL; -} - -// Create test context (not bound to any dear imgui context yet) -ImGuiTestEngine* ImGuiTestEngine_CreateContext() -{ - ImGuiTestEngine* engine = IM_NEW(ImGuiTestEngine)(); - return engine; -} - -void ImGuiTestEngine_DestroyContext(ImGuiTestEngine* engine) -{ - // We require user to call DestroyContext() before ImGuiTestEngine_DestroyContext() in order to preserve ini data... - // In case of e.g. dynamically creating a TestEngine as runtime and not caring about its settings, you may set io.ConfigSavedSettings to false - // in order to allow earlier destruction of the context. - if (engine->IO.ConfigSavedSettings) - IM_ASSERT(engine->UiContextTarget == NULL && "You need to call ImGui::DestroyContext() BEFORE ImGuiTestEngine_DestroyContext()"); - - // Shutdown coroutine - ImGuiTestEngine_CoroutineStopAndJoin(engine); - if (engine->UiContextTarget != NULL) - ImGuiTestEngine_UnbindImGuiContext(engine, engine->UiContextTarget); - - ImGuiTestEngine_ClearTests(engine); - - for (int n = 0; n < engine->InfoTasks.Size; n++) - IM_DELETE(engine->InfoTasks[n]); - engine->InfoTasks.clear(); - - IM_DELETE(engine); - - // Release hook - if (GImGuiTestEngine == engine) - GImGuiTestEngine = NULL; -} - -void ImGuiTestEngine_Start(ImGuiTestEngine* engine, ImGuiContext* ui_ctx) -{ - IM_ASSERT(engine->Started == false); - IM_ASSERT(engine->UiContextTarget == NULL); - - engine->UiContextTarget = ui_ctx; - ImGuiTestEngine_BindImGuiContext(engine, engine->UiContextTarget); - ImGuiTestEngine_StartCalcSourceLineEnds(engine); - - // Create our coroutine - // (we include the word "Main" in the name to facilitate filtering for both this thread and the "Main Thread" in debuggers) - if (!engine->TestQueueCoroutine) - { - IM_ASSERT(engine->IO.CoroutineFuncs && "Missing CoroutineFuncs! Use '#define IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL 1' or define your own implementation!"); - engine->TestQueueCoroutine = engine->IO.CoroutineFuncs->CreateFunc(ImGuiTestEngine_TestQueueCoroutineMain, "Main Dear ImGui Test Thread", engine); - } - engine->Started = true; -} - -void ImGuiTestEngine_Stop(ImGuiTestEngine* engine) -{ - IM_ASSERT(engine->Started); - - engine->Abort = true; - ImGuiTestEngine_CoroutineStopAndJoin(engine); - ImGuiTestEngine_Export(engine); - engine->Started = false; -} - -static void ImGuiTestEngine_CoroutineStopRequest(ImGuiTestEngine* engine) -{ - if (engine->TestQueueCoroutine != NULL) - engine->TestQueueCoroutineShouldExit = true; -} - -static void ImGuiTestEngine_CoroutineStopAndJoin(ImGuiTestEngine* engine) -{ - if (engine->TestQueueCoroutine != NULL) - { - // Run until the coroutine exits - engine->TestQueueCoroutineShouldExit = true; - while (true) - { - if (!engine->IO.CoroutineFuncs->RunFunc(engine->TestQueueCoroutine)) - break; - } - engine->IO.CoroutineFuncs->DestroyFunc(engine->TestQueueCoroutine); - engine->TestQueueCoroutine = NULL; - } -} - -// [EXPERIMENTAL] Destroy and recreate ImGui context -// This potentially allow us to test issues related to handling new windows, restoring settings etc. -// This also gets us once inch closer to more dynamic management of context (e.g. jail tests in their own context) -// FIXME: This is currently called by ImGuiTestEngine_PreNewFrame() in hook but may end up needing to be called -// by main application loop in order to facilitate letting app know of the new pointers. For now none of our backends -// preserve the pointer so may be fine. -void ImGuiTestEngine_RebootUiContext(ImGuiTestEngine* engine) -{ - IM_ASSERT(engine->Started); - ImGuiContext* ctx = engine->UiContextTarget; - ImGuiTestEngine_Stop(engine); - ImGuiTestEngine_UnbindImGuiContext(engine, ctx); - - // Backup - bool backup_atlas_owned_by_context = ctx->FontAtlasOwnedByContext; - ImFontAtlas* backup_atlas = ctx->IO.Fonts; - ImGuiIO backup_io = ctx->IO; -#ifdef IMGUI_HAS_VIEWPORT - // FIXME: Break with multi-viewports as we don't preserve user windowing data properly. - // Backend tend to store e.g. HWND data in viewport 0. - if (ctx->IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - IM_ASSERT(0); - //ImGuiViewport backup_viewport0 = *(ImGuiViewport*)ctx->Viewports[0]; - //ImGuiPlatformIO backup_platform_io = ctx->PlatformIO; - //ImGui::DestroyPlatformWindows(); -#endif - - // Recreate - ctx->FontAtlasOwnedByContext = false; -#if 1 - ImGui::DestroyContext(); - ImGui::CreateContext(backup_atlas); -#else - // Preserve same context pointer, which is probably misleading and not even necessary. - ImGui::Shutdown(ctx); - ctx->~ImGuiContext(); - IM_PLACEMENT_NEW(ctx) ImGuiContext(backup_atlas); - ImGui::Initialize(ctx); -#endif - - // Restore - ctx->FontAtlasOwnedByContext = backup_atlas_owned_by_context; - ctx->IO = backup_io; -#ifdef IMGUI_HAS_VIEWPORT - //backup_platform_io.Viewports.swap(ctx->PlatformIO.Viewports); - //ctx->PlatformIO = backup_platform_io; - //ctx->Viewports[0]->RendererUserData = backup_viewport0.RendererUserData; - //ctx->Viewports[0]->PlatformUserData = backup_viewport0.PlatformUserData; - //ctx->Viewports[0]->PlatformHandle = backup_viewport0.PlatformHandle; - //ctx->Viewports[0]->PlatformHandleRaw = backup_viewport0.PlatformHandleRaw; - //memset(&backup_viewport0, 0, sizeof(backup_viewport0)); -#endif - - ImGuiTestEngine_Start(engine, ctx); -} - -void ImGuiTestEngine_PostSwap(ImGuiTestEngine* engine) -{ - engine->PostSwapCalled = true; - - if (engine->IO.ConfigFixedDeltaTime != 0.0f) - ImGuiTestEngine_SetDeltaTime(engine, engine->IO.ConfigFixedDeltaTime); - - // Sync capture tool configurations from engine IO. - engine->CaptureContext.ScreenCaptureFunc = engine->IO.ScreenCaptureFunc; - engine->CaptureContext.ScreenCaptureUserData = engine->IO.ScreenCaptureUserData; - engine->CaptureContext.VideoCaptureEncoderPath = engine->IO.VideoCaptureEncoderPath; - engine->CaptureContext.VideoCaptureEncoderPathSize = IM_ARRAYSIZE(engine->IO.VideoCaptureEncoderPath); - engine->CaptureContext.VideoCaptureEncoderParams = engine->IO.VideoCaptureEncoderParams; - engine->CaptureContext.VideoCaptureEncoderParamsSize = IM_ARRAYSIZE(engine->IO.VideoCaptureEncoderParams); - engine->CaptureContext.GifCaptureEncoderParams = engine->IO.GifCaptureEncoderParams; - engine->CaptureContext.GifCaptureEncoderParamsSize = IM_ARRAYSIZE(engine->IO.GifCaptureEncoderParams); - engine->CaptureTool.VideoCaptureExtension = engine->IO.VideoCaptureExtension; - engine->CaptureTool.VideoCaptureExtensionSize = IM_ARRAYSIZE(engine->IO.VideoCaptureExtension); - - // Capture a screenshot from main thread while coroutine waits - if (engine->CaptureCurrentArgs != NULL) - { - ImGuiCaptureStatus status = engine->CaptureContext.CaptureUpdate(engine->CaptureCurrentArgs); - if (status != ImGuiCaptureStatus_InProgress) - { - if (status == ImGuiCaptureStatus_Done) - ImStrncpy(engine->CaptureTool.OutputLastFilename, engine->CaptureCurrentArgs->InOutputFile, IM_ARRAYSIZE(engine->CaptureTool.OutputLastFilename)); - engine->CaptureCurrentArgs = NULL; - } - } -} - -ImGuiTestEngineIO& ImGuiTestEngine_GetIO(ImGuiTestEngine* engine) -{ - return engine->IO; -} - -void ImGuiTestEngine_AbortCurrentTest(ImGuiTestEngine* engine) -{ - engine->Abort = true; - if (ImGuiTestContext* test_context = engine->TestContext) - test_context->Abort = true; -} - -bool ImGuiTestEngine_TryAbortEngine(ImGuiTestEngine* engine) -{ - ImGuiTestEngine_AbortCurrentTest(engine); - ImGuiTestEngine_CoroutineStopRequest(engine); - if (ImGuiTestEngine_IsTestQueueEmpty(engine)) - return true; - return false; // Still running coroutine -} - -// FIXME-OPT -ImGuiTest* ImGuiTestEngine_FindTestByName(ImGuiTestEngine* engine, const char* category, const char* name) -{ - IM_ASSERT(category != NULL || name != NULL); - for (int n = 0; n < engine->TestsAll.Size; n++) - { - ImGuiTest* test = engine->TestsAll[n]; - if (name != NULL && strcmp(test->Name, name) != 0) - continue; - if (category != NULL && strcmp(test->Category, category) != 0) - continue; - return test; - } - return NULL; -} - -// FIXME-OPT -static ImGuiTestInfoTask* ImGuiTestEngine_FindInfoTask(ImGuiTestEngine* engine, ImGuiID id) -{ - for (int task_n = 0; task_n < engine->InfoTasks.Size; task_n++) - { - ImGuiTestInfoTask* task = engine->InfoTasks[task_n]; - if (task->ID == id) - return task; - } - return NULL; -} - -// Request information about one item. -// Will push a request for the test engine to process. -// Will return NULL when results are not ready (or not available). -ImGuiTestItemInfo* ImGuiTestEngine_FindItemInfo(ImGuiTestEngine* engine, ImGuiID id, const char* debug_id) -{ - IM_ASSERT(id != 0); - - if (ImGuiTestInfoTask* task = ImGuiTestEngine_FindInfoTask(engine, id)) - { - if (task->Result.TimestampMain + 2 >= engine->FrameCount) - { - task->FrameCount = engine->FrameCount; // Renew task - return &task->Result; - } - return NULL; - } - - // Create task - ImGuiTestInfoTask* task = IM_NEW(ImGuiTestInfoTask)(); - task->ID = id; - task->FrameCount = engine->FrameCount; - if (debug_id) - { - size_t debug_id_sz = strlen(debug_id); - if (debug_id_sz < IM_ARRAYSIZE(task->DebugName) - 1) - { - memcpy(task->DebugName, debug_id, debug_id_sz + 1); - } - else - { - size_t header_sz = (size_t)(IM_ARRAYSIZE(task->DebugName) * 0.30f); - size_t footer_sz = IM_ARRAYSIZE(task->DebugName) - 2 - header_sz; - IM_ASSERT(header_sz > 0 && footer_sz > 0); - ImFormatString(task->DebugName, IM_ARRAYSIZE(task->DebugName), "%.*s..%.*s", (int)header_sz, debug_id, (int)footer_sz, debug_id + debug_id_sz - footer_sz); - } - } - engine->InfoTasks.push_back(task); - - return NULL; -} - -static void ImGuiTestEngine_ClearTests(ImGuiTestEngine* engine) -{ - for (int n = 0; n < engine->TestsAll.Size; n++) - IM_DELETE(engine->TestsAll[n]); - engine->TestsAll.clear(); - engine->TestsQueue.clear(); -} - -// Called at the beginning of a test to ensure no previous inputs leak into the new test -// FIXME-TESTS: Would make sense to reset mouse position as well? -void ImGuiTestEngine_ClearInput(ImGuiTestEngine* engine) -{ - IM_ASSERT(engine->UiContextTarget != NULL); - ImGuiContext& g = *engine->UiContextTarget; - - engine->Inputs.MouseButtonsValue = 0; - engine->Inputs.Queue.clear(); - engine->Inputs.MouseWheel = ImVec2(0, 0); - - // FIXME: Necessary? -#if IMGUI_VERSION_NUM >= 18972 - g.IO.ClearEventsQueue(); -#else - g.InputEventsQueue.resize(0); - g.IO.ClearInputCharacters(); -#endif - g.IO.ClearInputKeys(); - - ImGuiTestEngine_ApplyInputToImGuiContext(engine); -} - -bool ImGuiTestEngine_IsUsingSimulatedInputs(ImGuiTestEngine* engine) -{ - if (engine->UiContextActive) - if (!ImGuiTestEngine_IsTestQueueEmpty(engine)) - if (!(engine->TestContext->RunFlags & ImGuiTestRunFlags_GuiFuncOnly)) - return true; - return false; -} - -// Setup inputs in the tested Dear ImGui context. Essentially we override the work of the backend here. -void ImGuiTestEngine_ApplyInputToImGuiContext(ImGuiTestEngine* engine) -{ - IM_ASSERT(engine->UiContextTarget != NULL); - ImGuiContext& g = *engine->UiContextTarget; - ImGuiIO& io = g.IO; - - const bool use_simulated_inputs = ImGuiTestEngine_IsUsingSimulatedInputs(engine); - if (!use_simulated_inputs) - return; - - // Erase events submitted by backend - for (int n = 0; n < g.InputEventsQueue.Size; n++) - if (g.InputEventsQueue[n].AddedByTestEngine == false) - g.InputEventsQueue.erase(&g.InputEventsQueue[n--]); - - // Special flags to stop submitting events - if (engine->TestContext->RunFlags & ImGuiTestRunFlags_EnableRawInputs) - return; - - // To support using ImGuiKey_NavXXXX shortcuts pointing to gamepad actions - // FIXME-TEST-ENGINE: Should restore - g.IO.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; - g.IO.BackendFlags |= ImGuiBackendFlags_HasGamepad; - - const int input_event_count_prev = g.InputEventsQueue.Size; - - // Apply mouse viewport -#ifdef IMGUI_HAS_VIEWPORT - ImGuiPlatformIO& platform_io = g.PlatformIO; - ImGuiViewport* mouse_hovered_viewport; - if (engine->Inputs.MouseHoveredViewport != 0) - mouse_hovered_viewport = ImGui::FindViewportByID(engine->Inputs.MouseHoveredViewport); // Common case - else - mouse_hovered_viewport = ImGui::FindHoveredViewportFromPlatformWindowStack(engine->Inputs.MousePosValue); // Rarely used, some tests rely on this (e.g. "docking_dockspace_passthru_hover") may make it a opt-in feature instead? - if (mouse_hovered_viewport && (mouse_hovered_viewport->Flags & ImGuiViewportFlags_NoInputs)) - mouse_hovered_viewport = NULL; - //if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - if (io.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport) - io.AddMouseViewportEvent(mouse_hovered_viewport ? mouse_hovered_viewport->ID : 0); - bool mouse_hovered_viewport_focused = mouse_hovered_viewport && (mouse_hovered_viewport->Flags & ImGuiViewportFlags_IsFocused) != 0; -#endif - - // Apply mouse - io.AddMousePosEvent(engine->Inputs.MousePosValue.x, engine->Inputs.MousePosValue.y); - for (int n = 0; n < ImGuiMouseButton_COUNT; n++) - { - bool down = (engine->Inputs.MouseButtonsValue & (1 << n)) != 0; - io.AddMouseButtonEvent(n, down); - - // A click simulate platform focus on the viewport. -#ifdef IMGUI_HAS_VIEWPORT - if (down && mouse_hovered_viewport && !mouse_hovered_viewport_focused) - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - mouse_hovered_viewport_focused = true; - engine->Inputs.Queue.push_back(ImGuiTestInput::ForViewportFocus(mouse_hovered_viewport->ID)); - } -#endif - } - - // Apply mouse wheel - // [OSX] Simulate OSX behavior of automatically swapping mouse wheel axis when SHIFT is held. - // This is working in conjonction with the fact that ImGuiTestContext::MouseWheel() assume Windows-style behavior. - ImVec2 wheel = engine->Inputs.MouseWheel; - if (io.ConfigMacOSXBehaviors && (io.KeyMods & ImGuiMod_Shift)) // FIXME!! - ImSwap(wheel.x, wheel.y); - if (wheel.x != 0.0f || wheel.y != 0.0f) - io.AddMouseWheelEvent(wheel.x, wheel.y); - engine->Inputs.MouseWheel = ImVec2(0, 0); - - // Process input requests/queues - if (engine->Inputs.Queue.Size > 0) - { - for (int n = 0; n < engine->Inputs.Queue.Size; n++) - { - const ImGuiTestInput& input = engine->Inputs.Queue[n]; - switch (input.Type) - { - case ImGuiTestInputType_Key: - { - ImGuiKeyChord key_chord = input.KeyChord; -#if IMGUI_VERSION_NUM >= 19016 - key_chord = ImGui::FixupKeyChord(&g, 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 (mods & ImGuiMod_Shortcut) - mods = (mods & ~ImGuiMod_Shortcut) | (g.IO.ConfigMacOSXBehaviors ? ImGuiMod_Super : ImGuiMod_Ctrl); -#endif - // Submitting a ImGuiMod_XXX without associated key needs to add at least one of the key. - if (mods & ImGuiMod_Ctrl) - { - io.AddKeyEvent(ImGuiMod_Ctrl, input.Down); - if (key != ImGuiKey_LeftCtrl && key != ImGuiKey_RightCtrl) - io.AddKeyEvent(ImGuiKey_LeftCtrl, input.Down); - } - if (mods & ImGuiMod_Shift) - { - io.AddKeyEvent(ImGuiMod_Shift, input.Down); - if (key != ImGuiKey_LeftShift && key != ImGuiKey_RightShift) - io.AddKeyEvent(ImGuiKey_LeftShift, input.Down); - } - if (mods & ImGuiMod_Alt) - { - io.AddKeyEvent(ImGuiMod_Alt, input.Down); - if (key != ImGuiKey_LeftAlt && key != ImGuiKey_RightAlt) - io.AddKeyEvent(ImGuiKey_LeftAlt, input.Down); - } - if (mods & ImGuiMod_Super) - { - io.AddKeyEvent(ImGuiMod_Super, input.Down); - if (key != ImGuiKey_LeftSuper && key != ImGuiKey_RightSuper) - io.AddKeyEvent(ImGuiKey_LeftSuper, input.Down); - } - } - - if (key != ImGuiKey_None) - io.AddKeyEvent(key, input.Down); - break; - } - case ImGuiTestInputType_Char: - { - IM_ASSERT(input.Char != 0); - io.AddInputCharacter(input.Char); - break; - } - case ImGuiTestInputType_ViewportFocus: - { -#ifdef IMGUI_HAS_VIEWPORT - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - IM_ASSERT(engine->TestContext != NULL); - ImGuiViewport* viewport = ImGui::FindViewportByID(input.ViewportId); - if (viewport == NULL) - engine->TestContext->LogError("ViewportPlatform_SetWindowFocus(%08X): cannot find viewport anymore!", input.ViewportId); - else if (platform_io.Platform_SetWindowSize == NULL) - engine->TestContext->LogError("ViewportPlatform_SetWindowFocus(%08X): backend's Platform_SetWindowSize() is not set", input.ViewportId); - else - platform_io.Platform_SetWindowFocus(viewport); - } -#endif - break; - } - case ImGuiTestInputType_ViewportClose: - { -#ifdef IMGUI_HAS_VIEWPORT - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - IM_ASSERT(engine->TestContext != NULL); - ImGuiViewport* viewport = ImGui::FindViewportByID(input.ViewportId); - if (viewport == NULL) - engine->TestContext->LogError("ViewportPlatform_CloseWindow(%08X): cannot find viewport anymore!", input.ViewportId); - else - viewport->PlatformRequestClose = true; - // FIXME: doesn't apply to actual backend - } -#endif - break; - } - case ImGuiTestInputType_None: - default: - break; - } - } - - engine->Inputs.Queue.resize(0); - } - - const int input_event_count_curr = g.InputEventsQueue.Size; - for (int n = input_event_count_prev; n < input_event_count_curr; n++) - g.InputEventsQueue[n].AddedByTestEngine = true; -} - -// FIXME: Trying to abort a running GUI test won't kill the app immediately. -static void ImGuiTestEngine_UpdateWatchdog(ImGuiTestEngine* engine, ImGuiContext* ui_ctx, double t0, double t1) -{ - IM_UNUSED(ui_ctx); - ImGuiTestContext* test_ctx = engine->TestContext; - - if (engine->IO.ConfigRunSpeed != ImGuiTestRunSpeed_Fast || ImOsIsDebuggerPresent()) - return; - - if (test_ctx->RunFlags & ImGuiTestRunFlags_RunFromGui) - return; - - const float timer_warn = engine->IO.ConfigWatchdogWarning; - const float timer_kill_test = engine->IO.ConfigWatchdogKillTest; - const float timer_kill_app = engine->IO.ConfigWatchdogKillApp; - - // Emit a warning and then fail the test after a given time. - if (t0 < timer_warn && t1 >= timer_warn) - { - test_ctx->LogWarning("[Watchdog] Running time for '%s' is >%.f seconds, may be excessive.", test_ctx->Test->Name, timer_warn); - } - if (t0 < timer_kill_test && t1 >= timer_kill_test) - { - test_ctx->LogError("[Watchdog] Running time for '%s' is >%.f seconds, aborting.", test_ctx->Test->Name, timer_kill_test); - IM_CHECK(false); - } - - // Final safety watchdog in case the TestFunc is calling Yield() but never returning. - // Note that we are not catching infinite loop cases where the TestFunc may be running but not yielding.. - if (t0 < timer_kill_app + 5.0f && t1 >= timer_kill_app + 5.0f) - { - test_ctx->LogError("[Watchdog] Emergency process exit as the test didn't return."); - exit(1); - } -} - -static void ImGuiTestEngine_PreNewFrame(ImGuiTestEngine* engine, ImGuiContext* ui_ctx) -{ - if (engine->UiContextTarget != ui_ctx) - return; - IM_ASSERT(ui_ctx == GImGui); - ImGuiContext& g = *ui_ctx; - - engine->CaptureContext.PreNewFrame(); - - if (engine->ToolDebugRebootUiContext) - { - ImGuiTestEngine_RebootUiContext(engine); - ui_ctx = engine->UiContextTarget; - engine->ToolDebugRebootUiContext = false; - } - - // Inject extra time into the Dear ImGui context - if (engine->OverrideDeltaTime >= 0.0f) - { - ui_ctx->IO.DeltaTime = engine->OverrideDeltaTime; - engine->OverrideDeltaTime = -1.0f; - } - - // NewFrame() will increase this so we are +1 ahead at the time of calling this - engine->FrameCount = g.FrameCount + 1; - if (ImGuiTestContext* test_ctx = engine->TestContext) - { - double t0 = test_ctx->RunningTime; - double t1 = t0 + ui_ctx->IO.DeltaTime; - test_ctx->FrameCount++; - test_ctx->RunningTime = t1; - ImGuiTestEngine_UpdateWatchdog(engine, ui_ctx, t0, t1); - } - - engine->PerfDeltaTime100.AddSample(g.IO.DeltaTime); - engine->PerfDeltaTime500.AddSample(g.IO.DeltaTime); - - if (!ImGuiTestEngine_IsTestQueueEmpty(engine) && !engine->Abort) - { - // Abort testing by holding ESC - // When running GuiFunc only main_io == simulated_io we test for a long hold. - ImGuiIO& main_io = g.IO; - for (auto& e : g.InputEventsQueue) - if (e.Type == ImGuiInputEventType_Key && e.Key.Key == ImGuiKey_Escape) - engine->Inputs.HostEscDown = e.Key.Down; - engine->Inputs.HostEscDownDuration = engine->Inputs.HostEscDown ? (ImMax(engine->Inputs.HostEscDownDuration, 0.0f) + main_io.DeltaTime) : -1.0f; - const bool abort = engine->Inputs.HostEscDownDuration >= 0.20f; - if (abort) - { - if (engine->TestContext) - engine->TestContext->LogWarning("User aborted (pressed ESC)"); - ImGuiTestEngine_AbortCurrentTest(engine); - } - } - else - { - engine->Inputs.HostEscDown = false; - engine->Inputs.HostEscDownDuration = -1.0f; - } - - ImGuiTestEngine_ApplyInputToImGuiContext(engine); - ImGuiTestEngine_UpdateHooks(engine); -} - -static void ImGuiTestEngine_PostNewFrame(ImGuiTestEngine* engine, ImGuiContext* ui_ctx) -{ - if (engine->UiContextTarget != ui_ctx) - return; - IM_ASSERT(ui_ctx == GImGui); - - // Set initial mouse position to a decent value on startup - if (engine->FrameCount == 1) - engine->Inputs.MousePosValue = ImGui::GetMainViewport()->Pos; - - engine->IO.IsCapturing = engine->CaptureContext.IsCapturing(); - - // Garbage collect unused tasks - const int LOCATION_TASK_ELAPSE_FRAMES = 20; - 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) - { - IM_DELETE(task); - engine->InfoTasks.erase(engine->InfoTasks.Data + task_n); - task_n--; - } - } - - // Slow down whole app - if (engine->ToolSlowDown) - ImThreadSleepInMilliseconds(engine->ToolSlowDownMs); - - // Call user GUI function - ImGuiTestEngine_RunGuiFunc(engine); - - // Process on-going queues in a coroutine - // Run the test coroutine. This will resume the test queue from either the last point the test called YieldFromCoroutine(), - // or the loop in ImGuiTestEngine_TestQueueCoroutineMain that does so if no test is running. - // If you want to breakpoint the point execution continues in the test code, breakpoint the exit condition in YieldFromCoroutine() - const int input_queue_size_before = ui_ctx->InputEventsQueue.Size; - engine->IO.CoroutineFuncs->RunFunc(engine->TestQueueCoroutine); - - // Events added by TestFunc() marked automaticaly to not be deleted - if (engine->TestContext && (engine->TestContext->RunFlags & ImGuiTestRunFlags_EnableRawInputs)) - for (int n = input_queue_size_before; n < ui_ctx->InputEventsQueue.Size; n++) - ui_ctx->InputEventsQueue[n].AddedByTestEngine = true; - - // Update hooks and output flags - ImGuiTestEngine_UpdateHooks(engine); - - // Disable vsync - engine->IO.IsRequestingMaxAppSpeed = engine->IO.ConfigNoThrottle; - if (engine->IO.ConfigRunSpeed == ImGuiTestRunSpeed_Fast && engine->IO.IsRunningTests) - if (engine->TestContext && (engine->TestContext->RunFlags & ImGuiTestRunFlags_GuiFuncOnly) == 0) - engine->IO.IsRequestingMaxAppSpeed = true; -} - -static void ImGuiTestEngine_PreRender(ImGuiTestEngine* engine, ImGuiContext* ui_ctx) -{ - if (engine->UiContextTarget != ui_ctx) - return; - IM_ASSERT(ui_ctx == GImGui); - - engine->CaptureContext.PreRender(); -} - -static void ImGuiTestEngine_PostRender(ImGuiTestEngine* engine, ImGuiContext* ui_ctx) -{ - if (engine->UiContextTarget != ui_ctx) - return; - IM_ASSERT(ui_ctx == GImGui); - - // When test are running make sure real backend doesn't pick mouse cursor shape from tests. - // (If were to instead set io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange in ImGuiTestEngine_RunTest() that would get us 99% of the way, - // but unfortunately backend wouldn't restore normal shape after modified by OS decoration such as resize, so not enough..) - ImGuiContext& g = *ui_ctx; - if (!engine->IO.ConfigMouseDrawCursor && !g.IO.MouseDrawCursor && ImGuiTestEngine_IsUsingSimulatedInputs(engine)) - g.MouseCursor = ImGuiMouseCursor_Arrow; - - - // Check ImDrawData integrity - // This is currently a very cheap operation but may later become slower we if e.g. check idx boundaries. -#ifdef IMGUI_HAS_DOCK - if (engine->IO.CheckDrawDataIntegrity) - for (ImGuiViewport* viewport : ImGui::GetPlatformIO().Viewports) - DrawDataVerifyMatchingBufferCount(viewport->DrawData); -#else - if (engine->IO.CheckDrawDataIntegrity) - DrawDataVerifyMatchingBufferCount(ImGui::GetDrawData()); -#endif - - engine->CaptureContext.PostRender(); -} - -static void ImGuiTestEngine_RunGuiFunc(ImGuiTestEngine* engine) -{ - ImGuiTestContext* ctx = engine->TestContext; - if (ctx && ctx->Test->GuiFunc) - { - if (!(ctx->RunFlags & ImGuiTestRunFlags_GuiFuncDisable)) - { - ImGuiTestActiveFunc backup_active_func = ctx->ActiveFunc; - ctx->ActiveFunc = ImGuiTestActiveFunc_GuiFunc; - engine->TestContext->Test->GuiFunc(engine->TestContext); - ctx->ActiveFunc = backup_active_func; - } - - // Safety net - //if (ctx->Test->Status == ImGuiTestStatus_Error) - ctx->RecoverFromUiContextErrors(); - } - if (ctx) - ctx->FirstGuiFrame = false; -} - -// Main function for the test coroutine -static void ImGuiTestEngine_TestQueueCoroutineMain(void* engine_opaque) -{ - ImGuiTestEngine* engine = (ImGuiTestEngine*)engine_opaque; - while (!engine->TestQueueCoroutineShouldExit) - { - ImGuiTestEngine_ProcessTestQueue(engine); - engine->IO.CoroutineFuncs->YieldFunc(); - } -} - -static void ImGuiTestEngine_DisableWindowInputs(ImGuiWindow* window) -{ - window->DisableInputsFrames = 1; - for (ImGuiWindow* child_window : window->DC.ChildWindows) - ImGuiTestEngine_DisableWindowInputs(child_window); -} - -// Yield control back from the TestFunc to the main update + GuiFunc, for one frame. -void ImGuiTestEngine_Yield(ImGuiTestEngine* engine) -{ - ImGuiTestContext* ctx = engine->TestContext; - - // Can only yield in the test func! - if (ctx) - { - IM_ASSERT(ctx->ActiveFunc == ImGuiTestActiveFunc_TestFunc && "Can only yield inside TestFunc()!"); - for (ImGuiWindow* window : ctx->ForeignWindowsToHide) - { - window->HiddenFramesForRenderOnly = 2; // Hide root window - ImGuiTestEngine_DisableWindowInputs(window); // Disable inputs for root window and all it's children recursively - } - } - - engine->IO.CoroutineFuncs->YieldFunc(); -} - -void ImGuiTestEngine_SetDeltaTime(ImGuiTestEngine* engine, float delta_time) -{ - IM_ASSERT(delta_time >= 0.0f); - engine->OverrideDeltaTime = delta_time; -} - -int ImGuiTestEngine_GetFrameCount(ImGuiTestEngine* engine) -{ - return engine->FrameCount; -} - -const char* ImGuiTestEngine_GetStatusName(ImGuiTestStatus v) -{ - static const char* names[ImGuiTestStatus_COUNT] = { "Success", "Queued", "Running", "Error", "Suspended" }; - IM_STATIC_ASSERT(IM_ARRAYSIZE(names) == ImGuiTestStatus_COUNT); - if (v >= 0 && v < IM_ARRAYSIZE(names)) - return names[v]; - return "N/A"; -} - -const char* ImGuiTestEngine_GetRunSpeedName(ImGuiTestRunSpeed v) -{ - static const char* names[ImGuiTestRunSpeed_COUNT] = { "Fast", "Normal", "Cinematic" }; - IM_STATIC_ASSERT(IM_ARRAYSIZE(names) == ImGuiTestRunSpeed_COUNT); - if (v >= 0 && v < IM_ARRAYSIZE(names)) - return names[v]; - return "N/A"; -} - -const char* ImGuiTestEngine_GetVerboseLevelName(ImGuiTestVerboseLevel v) -{ - static const char* names[ImGuiTestVerboseLevel_COUNT] = { "Silent", "Error", "Warning", "Info", "Debug", "Trace" }; - IM_STATIC_ASSERT(IM_ARRAYSIZE(names) == ImGuiTestVerboseLevel_COUNT); - if (v >= 0 && v < IM_ARRAYSIZE(names)) - return names[v]; - return "N/A"; -} - -bool ImGuiTestEngine_CaptureScreenshot(ImGuiTestEngine* engine, ImGuiCaptureArgs* args) -{ - if (engine->IO.ScreenCaptureFunc == NULL) - { - IM_ASSERT(0); - return false; - } - - IM_ASSERT(engine->CaptureCurrentArgs == NULL && "Nested captures are not supported."); - - // Graphics API must render a window so it can be captured - // FIXME: This should work without this, as long as Present vs Vsync are separated (we need a Present, we don't need Vsync) - const ImGuiTestRunSpeed backup_run_speed = engine->IO.ConfigRunSpeed; - engine->IO.ConfigRunSpeed = ImGuiTestRunSpeed_Fast; - - const int frame_count = engine->FrameCount; - - // Because we rely on window->ContentSize for stitching, let 1 extra frame elapse to make sure any - // windows which contents have changed in the last frame get a correct window->ContentSize value. - // FIXME: Can remove this yield if not stitching - if ((args->InFlags & ImGuiCaptureFlags_Instant) == 0) - ImGuiTestEngine_Yield(engine); - - // This will yield until ImGuiTestEngine_PostSwap() -> ImGuiCaptureContext::CaptureUpdate() return false. - // - CaptureUpdate() will call user provided test_io.ScreenCaptureFunc() function - // - Capturing is likely to take multiple frames depending on settings. - int frames_yielded = 0; - engine->CaptureCurrentArgs = args; - engine->PostSwapCalled = false; - while (engine->CaptureCurrentArgs != NULL) - { - ImGuiTestEngine_Yield(engine); - frames_yielded++; - if (frames_yielded > 4) - IM_ASSERT(engine->PostSwapCalled && "ImGuiTestEngine_PostSwap() is not being called by application! Must be called in order."); - } - - // Verify that the ImGuiCaptureFlags_Instant flag got honored - if (args->InFlags & ImGuiCaptureFlags_Instant) - IM_ASSERT(frame_count + 1 == engine->FrameCount); - - engine->IO.ConfigRunSpeed = backup_run_speed; - return true; -} - -bool ImGuiTestEngine_CaptureBeginVideo(ImGuiTestEngine* engine, ImGuiCaptureArgs* args) -{ - if (engine->IO.ScreenCaptureFunc == NULL) - { - IM_ASSERT(0); - return false; - } - - IM_ASSERT(engine->CaptureCurrentArgs == NULL && "Nested captures are not supported."); - - // RunSpeed set to Fast -> Switch to Cinematic, no throttle - // RunSpeed set to Normal -> No change - // RunSpeed set to Cinematic -> No change - engine->BackupConfigRunSpeed = engine->IO.ConfigRunSpeed; - engine->BackupConfigNoThrottle = engine->IO.ConfigNoThrottle; - if (engine->IO.ConfigRunSpeed == ImGuiTestRunSpeed_Fast) - { - engine->IO.ConfigRunSpeed = ImGuiTestRunSpeed_Cinematic; - engine->IO.ConfigNoThrottle = true; - engine->IO.ConfigFixedDeltaTime = 1.0f / 60.0f; - } - engine->CaptureCurrentArgs = args; - engine->CaptureContext.BeginVideoCapture(args); - return true; -} - -bool ImGuiTestEngine_CaptureEndVideo(ImGuiTestEngine* engine, ImGuiCaptureArgs* args) -{ - IM_UNUSED(args); - IM_ASSERT(engine->CaptureContext.IsCapturingVideo() && "No video capture is in progress."); - - engine->CaptureContext.EndVideoCapture(); - while (engine->CaptureCurrentArgs != NULL) // Wait until last frame is captured and gif is saved. - ImGuiTestEngine_Yield(engine); - engine->IO.ConfigRunSpeed = engine->BackupConfigRunSpeed; - engine->IO.ConfigNoThrottle = engine->BackupConfigNoThrottle; - engine->IO.ConfigFixedDeltaTime = 0; - engine->CaptureCurrentArgs = NULL; - return true; -} - -static void ImGuiTestEngine_ProcessTestQueue(ImGuiTestEngine* engine) -{ - // Avoid tracking scrolling in UI when running a single test - const bool track_scrolling = (engine->TestsQueue.Size > 1) || (engine->TestsQueue.Size == 1 && (engine->TestsQueue[0].RunFlags & ImGuiTestRunFlags_RunFromCommandLine)); - - // Backup some state - ImGuiIO& io = ImGui::GetIO(); - const char* backup_ini_filename = io.IniFilename; - ImGuiWindow* backup_nav_window = engine->UiContextTarget->NavWindow; - io.IniFilename = NULL; - - int ran_tests = 0; - engine->BatchStartTime = ImTimeGetInMicroseconds(); - engine->IO.IsRunningTests = true; - for (int n = 0; n < engine->TestsQueue.Size; n++) - { - ImGuiTestRunTask* run_task = &engine->TestsQueue[n]; - IM_ASSERT(run_task->Test->Output.Status == ImGuiTestStatus_Queued); - - // FIXME-TESTS: Blind mode not supported - IM_ASSERT(engine->UiContextTarget != NULL); - IM_ASSERT(engine->UiContextActive == NULL); - engine->UiContextActive = engine->UiContextTarget; - engine->UiSelectedTest = run_task->Test; - if (track_scrolling) - engine->UiSelectAndScrollToTest = run_task->Test; - - // Run test - ImGuiTestEngine_RunTest(engine, NULL, run_task->Test, run_task->RunFlags); - - // Cleanup - IM_ASSERT(engine->TestContext == NULL); - IM_ASSERT(engine->UiContextActive == engine->UiContextTarget); - engine->UiContextActive = NULL; - - // Auto select the first error test - //if (test->Status == ImGuiTestStatus_Error) - // if (engine->UiSelectedTest == NULL || engine->UiSelectedTest->Status != ImGuiTestStatus_Error) - // engine->UiSelectedTest = test; - - ran_tests++; - } - engine->IO.IsRunningTests = false; - engine->BatchEndTime = ImTimeGetInMicroseconds(); - - engine->Abort = false; - engine->TestsQueue.clear(); - - // Restore UI state (done after all ImGuiTestEngine_RunTest() are done) - if (ran_tests) - { - if (engine->IO.ConfigRestoreFocusAfterTests) - ImGui::FocusWindow(backup_nav_window); - } - io.IniFilename = backup_ini_filename; -} - -bool ImGuiTestEngine_IsTestQueueEmpty(ImGuiTestEngine* engine) -{ - return engine->TestsQueue.Size == 0; -} - -static bool ImGuiTestEngine_IsRunningTest(ImGuiTestEngine* engine, ImGuiTest* test) -{ - for (ImGuiTestRunTask& t : engine->TestsQueue) - if (t.Test == test) - return true; - return false; -} - -void ImGuiTestEngine_QueueTest(ImGuiTestEngine* engine, ImGuiTest* test, ImGuiTestRunFlags run_flags) -{ - if (ImGuiTestEngine_IsRunningTest(engine, test)) - return; - - // Detect lack of signal from imgui context, most likely not compiled with IMGUI_ENABLE_TEST_ENGINE=1 - // FIXME: Why is in this function? - if (engine->UiContextTarget && engine->FrameCount < engine->UiContextTarget->FrameCount - 2) - { - ImGuiTestEngine_AbortCurrentTest(engine); - IM_ASSERT(0 && "Not receiving signal from core library. Did you call ImGuiTestEngine_CreateContext() with the correct context? Did you compile imgui/ with IMGUI_ENABLE_TEST_ENGINE=1?"); - test->Output.Status = ImGuiTestStatus_Error; - return; - } - - test->Output.Status = ImGuiTestStatus_Queued; - - ImGuiTestRunTask run_task; - run_task.Test = test; - run_task.RunFlags = run_flags; - engine->TestsQueue.push_back(run_task); -} - -// Called by IM_REGISTER_TEST(). Prefer calling IM_REGISTER_TEST() in your code so src_file/src_line are automatically passed. -ImGuiTest* ImGuiTestEngine_RegisterTest(ImGuiTestEngine* engine, const char* category, const char* name, const char* src_file, int src_line) -{ - ImGuiTestGroup group = ImGuiTestGroup_Tests; - if (strcmp(category, "perf") == 0) - group = ImGuiTestGroup_Perfs; - - ImGuiTest* t = IM_NEW(ImGuiTest)(); - t->Group = group; - t->Category = category; - t->Name = name; - t->SourceFile = src_file; - t->SourceLine = t->SourceLineEnd = src_line; - engine->TestsAll.push_back(t); - - return t; -} - -ImGuiPerfTool* ImGuiTestEngine_GetPerfTool(ImGuiTestEngine* engine) -{ - return engine->PerfTool; -} - -// Filter tests by a specified query. Query is composed of one or more comma-separated filter terms optionally prefixed/suffixed with modifiers. -// Available modifiers: -// - '-' prefix excludes tests matched by the term. -// - '^' prefix anchors term matching to the start of the string. -// - '$' suffix anchors term matching to the end of the string. -// Special keywords: -// - "all" : all tests, no matter what group they are in. -// - "tests" : tests in ImGuiTestGroup_Tests group. -// - "perfs" : tests in ImGuiTestGroup_Perfs group. -// Example queries: -// - "" : empty query matches no tests. -// - "^nav_" : all tests with name starting with "nav_". -// - "_nav$" : all tests with name ending with "_nav". -// - "-xxx" : all tests and perfs that do not contain "xxx". -// - "tests,-scroll,-^nav_" : all tests (but no perfs) that do not contain "scroll" in their name and does not start with "nav_". -// Note: while we borrowed ^ and $ from regex conventions, we do not support actual regex syntax except for behavior of these two modifiers. -bool ImGuiTestEngine_PassFilter(ImGuiTest* test, const char* filter_specs) -{ - IM_ASSERT(filter_specs != NULL); - auto str_iequal = [](const char* s1, const char* s2, const char* s2_end) - { - size_t s2_len = (size_t)(s2_end - s2); - if (strlen(s1) != s2_len) return false; - return ImStrnicmp(s1, s2, s2_len) == 0; - }; - - auto str_iendswith = [&str_iequal](const char* s1, const char* s2, const char* s2_end) - { - size_t s1_len = strlen(s1); - size_t s2_len = (size_t)(s2_end - s2); - if (s1_len < s2_len) return false; - s1 = s1 + s1_len - s2_len; - return str_iequal(s1, s2, s2_end); - }; - - bool include = false; - const char* prefixes = "^-"; - - // When filter starts with exclude condition, we assume we have included all tests from the start. This enables - // writing "-window" instead of "all,-window". - for (int i = 0; filter_specs[i]; i++) - if (filter_specs[i] == '-') - include = true; // First filter is exclusion - else if (strchr(prefixes, filter_specs[i]) == NULL) - break; // End of prefixes - - for (const char* filter_start = filter_specs; filter_start[0];) - { - // Filter modifiers - bool is_exclude = false; - bool is_anchor_to_start = false; - bool is_anchor_to_end = false; - for (;;) - { - if (filter_start[0] == '-') - is_exclude = true; - else if (filter_start[0] == '^') - is_anchor_to_start = true; - else - break; - filter_start++; - } - - const char* filter_end = strstr(filter_start, ","); - filter_end = filter_end ? filter_end : filter_start + strlen(filter_start); - is_anchor_to_end = filter_end[-1] == '$'; - if (is_anchor_to_end) - filter_end--; - - if (str_iequal("all", filter_start, filter_end)) - include = !is_exclude; - else if (str_iequal("tests", filter_start, filter_end)) - include = (test->Group == ImGuiTestGroup_Tests) ? !is_exclude : include; - else if (str_iequal("perfs", filter_start, filter_end)) - include = (test->Group == ImGuiTestGroup_Perfs) ? !is_exclude : include; - else - { - // General filtering - for (int n = 0; n < 2; n++) - { - const char* name = (n == 0) ? test->Name : test->Category; - - bool match = true; - - // "foo" - match a substring. - if (!is_anchor_to_start && !is_anchor_to_end) - match = ImStristr(name, NULL, filter_start, filter_end) != NULL; - - // "^foo" - match start of the string. - // "foo$" - match end of the string. - // FIXME: (minor) '^aaa$' will incorrectly match 'aaabbbaaa'. - if (is_anchor_to_start) - match &= ImStrnicmp(name, filter_start, filter_end - filter_start) == 0; - if (is_anchor_to_end) - match &= str_iendswith(name, filter_start, filter_end); - - if (match) - { - include = is_exclude ? false : true; - break; - } - } - } - - while (filter_end[0] == ',' || filter_end[0] == '$') - filter_end++; - filter_start = filter_end; - } - return include; -} - -void ImGuiTestEngine_QueueTests(ImGuiTestEngine* engine, ImGuiTestGroup group, const char* filter_str, ImGuiTestRunFlags run_flags) -{ - IM_ASSERT(group >= ImGuiTestGroup_Unknown && group < ImGuiTestGroup_COUNT); - for (int n = 0; n < engine->TestsAll.Size; n++) - { - ImGuiTest* test = engine->TestsAll[n]; - if (group != ImGuiTestGroup_Unknown && test->Group != group) - continue; - - if (!ImGuiTestEngine_PassFilter(test, filter_str)) - continue; - - ImGuiTestEngine_QueueTest(engine, test, run_flags); - } -} - -static void ImGuiTestEngine_StartCalcSourceLineEnds(ImGuiTestEngine* engine) -{ - if (engine->TestsAll.empty()) - return; - - ImVector<int> line_starts; - line_starts.reserve(engine->TestsAll.Size); - for (int n = 0; n < engine->TestsAll.Size; n++) - line_starts.push_back(engine->TestsAll[n]->SourceLine); - ImQsort(line_starts.Data, (size_t)line_starts.Size, sizeof(int), [](const void* lhs, const void* rhs) { return (*(const int*)lhs) - *(const int*)rhs; }); - - for (int n = 0; n < engine->TestsAll.Size; n++) - { - ImGuiTest* test = engine->TestsAll[n]; - for (int m = 0; m < line_starts.Size - 1; m++) // FIXME-OPT - if (line_starts[m] == test->SourceLine) - test->SourceLineEnd = ImMax(test->SourceLine, line_starts[m + 1]); - } -} - -void ImGuiTestEngine_GetResult(ImGuiTestEngine* engine, int& count_tested, int& count_success) -{ - count_tested = 0; - count_success = 0; - for (int n = 0; n < engine->TestsAll.Size; n++) - { - ImGuiTest* test = engine->TestsAll[n]; - if (test->Output.Status == ImGuiTestStatus_Unknown) - continue; - IM_ASSERT(test->Output.Status != ImGuiTestStatus_Queued); - IM_ASSERT(test->Output.Status != ImGuiTestStatus_Running); - count_tested++; - if (test->Output.Status == ImGuiTestStatus_Success) - count_success++; - } -} - -// Get a copy of the test list -void ImGuiTestEngine_GetTestList(ImGuiTestEngine* engine, ImVector<ImGuiTest*>* out_tests) -{ - *out_tests = engine->TestsAll; -} - -// Get a copy of the test queue -void ImGuiTestEngine_GetTestQueue(ImGuiTestEngine* engine, ImVector<ImGuiTestRunTask>* out_tests) -{ - *out_tests = engine->TestsQueue; -} - -static void ImGuiTestEngine_UpdateHooks(ImGuiTestEngine* engine) -{ - ImGuiContext* ui_ctx = engine->UiContextTarget; - IM_ASSERT(ui_ctx->TestEngine == engine); - bool want_hooking = false; - - //if (engine->TestContext != NULL) - // want_hooking = true; - - if (engine->InfoTasks.Size > 0) - want_hooking = true; - if (engine->FindByLabelTask.InSuffix != NULL) - want_hooking = true; - if (engine->GatherTask.InParentID != 0) - want_hooking = true; - - // Update test engine specific hooks - ui_ctx->TestEngineHookItems = want_hooking; -} - -struct ImGuiTestContextUiContextBackup -{ - ImGuiIO IO; - ImGuiStyle Style; - ImGuiDebugLogFlags DebugLogFlags; - ImGuiKeyChord ConfigNavWindowingKeyNext; - ImGuiKeyChord ConfigNavWindowingKeyPrev; - - void Backup(ImGuiContext& g) - { - IO = g.IO; - Style = g.Style; - DebugLogFlags = g.DebugLogFlags; - ConfigNavWindowingKeyNext = g.ConfigNavWindowingKeyNext; - ConfigNavWindowingKeyPrev = g.ConfigNavWindowingKeyPrev; - memset(IO.MouseDown, 0, sizeof(IO.MouseDown)); - for (int n = 0; n < IM_ARRAYSIZE(IO.KeysData); n++) - IO.KeysData[n].Down = false; - } - void Restore(ImGuiContext& g) - { -#if IMGUI_VERSION_NUM < 18993 - IO.MetricsActiveAllocations = g.IO.MetricsActiveAllocations; -#endif - g.IO = IO; - g.Style = Style; - g.DebugLogFlags = DebugLogFlags; - g.ConfigNavWindowingKeyNext = ConfigNavWindowingKeyNext; - g.ConfigNavWindowingKeyPrev = ConfigNavWindowingKeyPrev; - } - void RestoreClipboardFuncs(ImGuiContext& g) - { - g.IO.GetClipboardTextFn = IO.GetClipboardTextFn; - g.IO.SetClipboardTextFn = IO.SetClipboardTextFn; - g.IO.ClipboardUserData = IO.ClipboardUserData; - } -}; - -// FIXME: Work toward simplifying this function? -void ImGuiTestEngine_RunTest(ImGuiTestEngine* engine, ImGuiTestContext* parent_ctx, ImGuiTest* test, ImGuiTestRunFlags run_flags) -{ - ImGuiTestContext stack_ctx; - ImGuiCaptureArgs stack_capture_args; - ImGuiTestContext* ctx; - - if (run_flags & ImGuiTestRunFlags_ShareTestContext) - { - // Reuse existing test context - IM_ASSERT(parent_ctx != NULL); - ctx = parent_ctx; - } - else - { - // Create a test context - ctx = &stack_ctx; - ctx->Engine = engine; - ctx->EngineIO = &engine->IO; - ctx->Inputs = &engine->Inputs; - ctx->CaptureArgs = &stack_capture_args; - ctx->UserVars = NULL; - ctx->PerfStressAmount = engine->IO.PerfStressAmount; -#ifdef IMGUI_HAS_DOCK - ctx->HasDock = true; -#else - ctx->HasDock = false; -#endif - } - - ImGuiTestOutput* test_output; - if (parent_ctx == NULL) - { - ctx->Test = test; - test_output = ctx->TestOutput = &test->Output; - test_output->StartTime = ImTimeGetInMicroseconds(); - } - else - { - ctx->Test = parent_ctx->Test; - test_output = ctx->TestOutput = parent_ctx->TestOutput; - } - - if (engine->Abort) - { - test_output->Status = ImGuiTestStatus_Unknown; - if (parent_ctx == NULL) - test_output->EndTime = test_output->StartTime; - ctx->Test = NULL; - ctx->TestOutput = NULL; - return; - } - - test_output->Status = ImGuiTestStatus_Running; - - ctx->RunFlags = run_flags; - ctx->UiContext = engine->UiContextActive; - - engine->TestContext = ctx; - ImGuiTestEngine_UpdateHooks(engine); - - void* backup_user_vars = NULL; - ImGuiTestGenericVars backup_generic_vars; - if (run_flags & ImGuiTestRunFlags_ShareVars) - { - // Share user vars and generic vars - IM_CHECK_SILENT(parent_ctx != NULL); - IM_CHECK_SILENT(test->VarsSize == parent_ctx->Test->VarsSize); - IM_CHECK_SILENT(test->VarsConstructor == parent_ctx->Test->VarsConstructor); - IM_CHECK_SILENT(test->VarsPostConstructor == parent_ctx->Test->VarsPostConstructor); - IM_CHECK_SILENT(test->VarsPostConstructorUserFn == parent_ctx->Test->VarsPostConstructorUserFn); - IM_CHECK_SILENT(test->VarsDestructor == parent_ctx->Test->VarsDestructor); - if ((run_flags & ImGuiTestRunFlags_ShareTestContext) == 0) - { - ctx->GenericVars = parent_ctx->GenericVars; - ctx->UserVars = parent_ctx->UserVars; - } - } - else - { - // Create user vars - if (run_flags & ImGuiTestRunFlags_ShareTestContext) - { - backup_user_vars = parent_ctx->UserVars; - backup_generic_vars = parent_ctx->GenericVars; - } - ctx->GenericVars.Clear(); - if (test->VarsConstructor != NULL) - { - ctx->UserVars = IM_ALLOC(test->VarsSize); - test->VarsConstructor(ctx->UserVars); - if (test->VarsPostConstructor != NULL && test->VarsPostConstructorUserFn != NULL) - test->VarsPostConstructor(ctx, ctx->UserVars, test->VarsPostConstructorUserFn); - } - } - - // Log header - if (parent_ctx == NULL) - { - ctx->LogEx(ImGuiTestVerboseLevel_Info, ImGuiTestLogFlags_NoHeader, "----------------------------------------------------------------------"); // Intentionally TTY only (just before clear: make it a flag?) - test_output->Log.Clear(); - ctx->LogWarning("Test: '%s' '%s'..", test->Category, test->Name); - } - 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); - } - - // 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); - - // Setup IO: software mouse cursor, viewport support - ImGuiIO& io = ctx->UiContext->IO; - if (engine->IO.ConfigMouseDrawCursor) - io.MouseDrawCursor = true; -#ifdef IMGUI_HAS_VIEWPORT - // We always fill io.MouseHoveredViewport manually (maintained in ImGuiTestInputs::SimulatedIO) - // so ensure we don't leave a chance to Dear ImGui to interpret things differently. - // FIXME: As written, this would prevent tests from toggling ImGuiConfigFlags_ViewportsEnable and have correct value for ImGuiBackendFlags_HasMouseHoveredViewport - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport; - else - io.BackendFlags &= ~ImGuiBackendFlags_HasMouseHoveredViewport; -#endif - - // Setup IO: override clipboard - if ((ctx->RunFlags & ImGuiTestRunFlags_GuiFuncOnly) == 0) - { - io.GetClipboardTextFn = [](void* user_data) -> const char* - { - ImGuiTestContext* ctx = (ImGuiTestContext*)user_data; - return ctx->Clipboard.empty() ? "" : ctx->Clipboard.Data; - }; - io.SetClipboardTextFn = [](void* user_data, const char* text) - { - ImGuiTestContext* ctx = (ImGuiTestContext*)user_data; - ctx->Clipboard.resize((int)strlen(text) + 1); - strcpy(ctx->Clipboard.Data, text); - }; - io.ClipboardUserData = ctx; - } - - // Mark as currently running the TestFunc (this is the only time when we are allowed to yield) - IM_ASSERT(ctx->ActiveFunc == ImGuiTestActiveFunc_None || ctx->ActiveFunc == ImGuiTestActiveFunc_TestFunc); - ImGuiTestActiveFunc backup_active_func = ctx->ActiveFunc; - ctx->ActiveFunc = ImGuiTestActiveFunc_TestFunc; - ctx->FirstGuiFrame = (test->GuiFunc != NULL) ? true : false; - - // Warm up GUI - // - We need one mandatory frame running GuiFunc before running TestFunc - // - We add a second frame, to avoid running tests while e.g. windows are typically appearing for the first time, hidden, - // measuring their initial size. Most tests are going to be more meaningful with this stabilized base. - if (!(test->Flags & ImGuiTestFlags_NoGuiWarmUp)) - { - ctx->FrameCount -= 2; - ctx->Yield(); - if (test_output->Status == ImGuiTestStatus_Running) // To allow GuiFunc calling Finish() in first frame - ctx->Yield(); - } - ctx->FirstTestFrameCount = ctx->FrameCount; - - // Call user test function (optional) - if (ctx->RunFlags & ImGuiTestRunFlags_GuiFuncOnly) - { - // No test function - while (!engine->Abort && test_output->Status == ImGuiTestStatus_Running) - ctx->Yield(); - } - else - { - if (test->TestFunc) - { - // Test function - test->TestFunc(ctx); - - // In case test failed without finishing gif capture - finish it here. This may trigger due to user error or - // due to IM_SUSPEND_TESTFUNC() terminating TestFunc() early. - if (engine->CaptureContext.IsCapturingVideo()) - { - ImGuiCaptureArgs* args = engine->CaptureCurrentArgs; - ImGuiTestEngine_CaptureEndVideo(engine, args); - //ImFileDelete(args->OutSavedFileName); - ctx->LogWarning("Recovered from missing CaptureEndVideo()"); - } - } - else - { - // No test function - if (test->Flags & ImGuiTestFlags_NoAutoFinish) - while (!engine->Abort && test_output->Status == ImGuiTestStatus_Running) - ctx->Yield(); - } - - // Capture failure screenshot. - if (ctx->IsError() && engine->IO.ConfigCaptureOnError) - { - // FIXME-VIEWPORT: Tested windows may be in their own viewport. This only captures everything in main viewport. Capture tool may be extended to capture viewport windows as well. This would leave out OS windows which may be a cause of failure. - ImGuiCaptureArgs args; - args.InFlags = ImGuiCaptureFlags_Instant; - args.InCaptureRect.Min = ImGui::GetMainViewport()->Pos; - args.InCaptureRect.Max = args.InCaptureRect.Min + ImGui::GetMainViewport()->Size; - ImFormatString(args.InOutputFile, IM_ARRAYSIZE(args.InOutputFile), "output/failures/%s_%04d.png", ctx->Test->Name, ctx->ErrorCounter); - if (ImGuiTestEngine_CaptureScreenshot(engine, &args)) - ctx->LogDebug("Saved '%s' (%d*%d pixels)", args.InOutputFile, (int)args.OutImageSize.x, (int)args.OutImageSize.y); - } - - // Recover missing End*/Pop* calls. - ctx->RecoverFromUiContextErrors(); - - if (engine->IO.ConfigRunSpeed != ImGuiTestRunSpeed_Fast) - ctx->SleepStandard(); - - // Stop in GuiFunc mode - if (engine->IO.ConfigKeepGuiFunc && ctx->IsError()) - { - // Position mouse cursor - ctx->UiContext->IO.WantSetMousePos = true; - ctx->UiContext->IO.MousePos = engine->Inputs.MousePosValue; - - // Restore backend clipboard functions - backup_ui_context.RestoreClipboardFuncs(*ctx->UiContext); - - // Unhide foreign windows (may be useful sometimes to inspect GuiFunc state... sometimes not) - //ctx->ForeignWindowsUnhideAll(); - } - - // Keep GuiFunc spinning - // FIXME-TESTS: after an error, this is not visible in the UI because status is not _Running anymore... - if (engine->IO.ConfigKeepGuiFunc) - { - if (engine->TestsQueue.Size == 1 || test_output->Status == ImGuiTestStatus_Error) - { -#if IMGUI_VERSION_NUM >= 18992 - ImGui::TeleportMousePos(engine->Inputs.MousePosValue); -#endif - while (engine->IO.ConfigKeepGuiFunc && !engine->Abort) - { - ctx->RunFlags |= ImGuiTestRunFlags_GuiFuncOnly; - ctx->Yield(); - } - } - } - } - - IM_ASSERT(engine->CaptureCurrentArgs == NULL && "Active capture was not terminated in the test code."); - - // Process and display result/status - test_output->EndTime = ImTimeGetInMicroseconds(); - if (test_output->Status == ImGuiTestStatus_Running) - test_output->Status = ImGuiTestStatus_Success; - if (engine->Abort && test_output->Status != ImGuiTestStatus_Error) - test_output->Status = ImGuiTestStatus_Unknown; - - // Log result - if (test_output->Status == ImGuiTestStatus_Success) - { - if ((ctx->RunFlags & ImGuiTestRunFlags_NoSuccessMsg) == 0) - ctx->LogInfo("Success."); - } - else if (engine->Abort) - ctx->LogWarning("Aborted."); - else if (test_output->Status == ImGuiTestStatus_Error) - ctx->LogError("%s test failed.", test->Name); - else - ctx->LogWarning("Unknown status."); - - // Additional yields to avoid consecutive tests who may share identifiers from missing their window/item activation. - ctx->RunFlags |= ImGuiTestRunFlags_GuiFuncDisable; - ctx->Yield(2); - - // Restore active func - ctx->ActiveFunc = backup_active_func; - if (parent_ctx) - parent_ctx->FrameCount = ctx->FrameCount; - - // Restore backed up IO and style - backup_ui_context.Restore(*ctx->UiContext); - - if (run_flags & ImGuiTestRunFlags_ShareVars) - { - // Share generic vars? - if ((run_flags & ImGuiTestRunFlags_ShareTestContext) == 0) - parent_ctx->GenericVars = ctx->GenericVars; - } - else - { - // Destruct user vars - if (test->VarsConstructor != NULL) - { - test->VarsDestructor(ctx->UserVars); - if (ctx->UserVars) - IM_FREE(ctx->UserVars); - ctx->UserVars = NULL; - } - if (run_flags & ImGuiTestRunFlags_ShareTestContext) - { - parent_ctx->UserVars = backup_user_vars; - parent_ctx->GenericVars = backup_generic_vars; - } - } - - IM_ASSERT(engine->TestContext == ctx); - engine->TestContext = parent_ctx; -} - -//------------------------------------------------------------------------- -// [SECTION] CRASH HANDLING -//------------------------------------------------------------------------- -// - ImGuiTestEngine_CrashHandler() -// - ImGuiTestEngine_InstallDefaultCrashHandler() -//------------------------------------------------------------------------- - -void ImGuiTestEngine_CrashHandler() -{ - static bool handled = false; - if (handled) - return; - handled = true; - - ImGuiContext& g = *GImGui; - ImGuiTestEngine* engine = (ImGuiTestEngine*)g.TestEngine; - - // Write stop times, because thread executing tests will no longer run. - engine->BatchEndTime = ImTimeGetInMicroseconds(); - for (int i = 0; i < engine->TestsAll.Size; i++) - { - if (engine->TestContext) - if (ImGuiTest* test = engine->TestContext->Test) - if (test->Output.Status == ImGuiTestStatus_Running) - { - test->Output.Status = ImGuiTestStatus_Error; - test->Output.EndTime = engine->BatchEndTime; - break; - } - } - - // Export test run results. - ImGuiTestEngine_Export(engine); -} - -#ifdef _WIN32 -static LONG WINAPI ImGuiTestEngine_CrashHandlerWin32(LPEXCEPTION_POINTERS) -{ - ImGuiTestEngine_CrashHandler(); - return EXCEPTION_EXECUTE_HANDLER; -} -#else -static void ImGuiTestEngine_CrashHandlerUnix(int signal) -{ - IM_UNUSED(signal); - ImGuiTestEngine_CrashHandler(); - abort(); -} -#endif - -void ImGuiTestEngine_InstallDefaultCrashHandler() -{ -#ifdef _WIN32 - SetUnhandledExceptionFilter(&ImGuiTestEngine_CrashHandlerWin32); -#else - // Install a crash handler to relevant signals. - struct sigaction action = {}; - action.sa_handler = ImGuiTestEngine_CrashHandlerUnix; - action.sa_flags = SA_SIGINFO; - sigaction(SIGILL, &action, NULL); - sigaction(SIGABRT, &action, NULL); - sigaction(SIGFPE, &action, NULL); - sigaction(SIGSEGV, &action, NULL); - sigaction(SIGPIPE, &action, NULL); - sigaction(SIGBUS, &action, NULL); -#endif -} - - -//------------------------------------------------------------------------- -// [SECTION] HOOKS FOR CORE LIBRARY -//------------------------------------------------------------------------- -// - ImGuiTestEngineHook_ItemAdd() -// - ImGuiTestEngineHook_ItemAdd_GatherTask() -// - ImGuiTestEngineHook_ItemInfo() -// - ImGuiTestEngineHook_ItemInfo_ResolveFindByLabel() -// - ImGuiTestEngineHook_Log() -// - ImGuiTestEngineHook_AssertFunc() -//------------------------------------------------------------------------- - -// This is rather slow at it runs on all items but only during a GatherItems() operations. -static void ImGuiTestEngineHook_ItemAdd_GatherTask(ImGuiContext* ui_ctx, ImGuiTestEngine* engine, ImGuiID id, const ImRect& bb, const ImGuiLastItemData* item_data) -{ - ImGuiContext& g = *ui_ctx; - ImGuiWindow* window = g.CurrentWindow; - ImGuiTestGatherTask* task = &engine->GatherTask; - - if ((task->InLayerMask & (1 << window->DC.NavLayerCurrent)) == 0) - return; - - const ImGuiID parent_id = window->IDStack.Size ? window->IDStack.back() : 0; - const ImGuiID gather_parent_id = task->InParentID; - int result_depth = -1; - if (gather_parent_id == parent_id) - { - result_depth = 0; - } - else - { - const int max_depth = task->InMaxDepth; - - // When using a 'PushID(label); Widget(""); PopID();` pattern flatten as 1 deep instead of 2 for simplicity. - // We do this by offsetting our depth level. - int curr_depth = (id == parent_id) ? -1 : 0; - - ImGuiWindow* curr_window = window; - while (result_depth == -1 && curr_window != NULL) - { - const int id_stack_size = curr_window->IDStack.Size; - for (ImGuiID* p_id_stack = curr_window->IDStack.Data + id_stack_size - 1; p_id_stack >= curr_window->IDStack.Data; p_id_stack--, curr_depth++) - { - if (curr_depth >= max_depth) - break; - if (*p_id_stack == gather_parent_id) - { - result_depth = curr_depth; - break; - } - } - - // Recurse in child (could be policy/option in GatherTask) - if (curr_window->Flags & ImGuiWindowFlags_ChildWindow) - curr_window = curr_window->ParentWindow; - else - curr_window = NULL; - } - } - - if (result_depth != -1) - { - ImGuiTestItemInfo* item = task->OutList->Pool.GetOrAddByKey(id); // Add - item->TimestampMain = engine->FrameCount; - item->ID = id; - item->ParentID = parent_id; - item->Window = window; - item->RectFull = item->RectClipped = bb; - item->RectClipped.ClipWithFull(window->ClipRect); // This two step clipping is important, we want RectClipped to stays within RectFull - item->RectClipped.ClipWithFull(item->RectFull); - item->NavLayer = window->DC.NavLayerCurrent; - item->Depth = result_depth; - item->InFlags = item_data ? item_data->InFlags : ImGuiItemFlags_None; - item->StatusFlags = item_data ? item_data->StatusFlags : ImGuiItemStatusFlags_None; - task->LastItemInfo = item; - } -} - -void ImGuiTestEngineHook_ItemAdd(ImGuiContext* ui_ctx, ImGuiID id, const ImRect& bb, const ImGuiLastItemData* item_data) -{ - ImGuiTestEngine* engine = (ImGuiTestEngine*)ui_ctx->TestEngine; - - IM_ASSERT(id != 0); - ImGuiContext& g = *ui_ctx; - ImGuiWindow* window = g.CurrentWindow; - - // FIXME-OPT: Early out if there are no active Info/Gather tasks. - - // Info Tasks - if (ImGuiTestInfoTask* task = ImGuiTestEngine_FindInfoTask(engine, id)) - { - ImGuiTestItemInfo* item = &task->Result; - item->TimestampMain = engine->FrameCount; - item->ID = id; - item->ParentID = window->IDStack.Size ? window->IDStack.back() : 0; - item->Window = window; - item->RectFull = item->RectClipped = bb; - item->RectClipped.ClipWithFull(window->ClipRect); // This two step clipping is important, we want RectClipped to stays within RectFull - item->RectClipped.ClipWithFull(item->RectFull); - item->NavLayer = window->DC.NavLayerCurrent; - item->Depth = 0; - item->InFlags = item_data ? item_data->InFlags : ImGuiItemFlags_None; - item->StatusFlags = item_data ? item_data->StatusFlags : ImGuiItemStatusFlags_None; - } - - // Gather Task (only 1 can be active) - if (engine->GatherTask.InParentID != 0) - ImGuiTestEngineHook_ItemAdd_GatherTask(ui_ctx, engine, id, bb, item_data); -} - -#if IMGUI_VERSION_NUM < 18934 -void ImGuiTestEngineHook_ItemAdd(ImGuiContext* ui_ctx, const ImRect& bb, ImGuiID id) -{ - ImGuiTestEngineHook_ItemAdd(ui_ctx, id, bb, NULL); -} -#endif - -// Task is submitted in TestFunc by ItemInfo() -> ItemInfoHandleWildcardSearch() -#ifdef IMGUI_HAS_IMSTR -static void ImGuiTestEngineHook_ItemInfo_ResolveFindByLabel(ImGuiContext* ui_ctx, ImGuiID id, const ImStrv label, ImGuiItemStatusFlags flags) -#else -static void ImGuiTestEngineHook_ItemInfo_ResolveFindByLabel(ImGuiContext* ui_ctx, ImGuiID id, const char* label, ImGuiItemStatusFlags flags) -#endif -{ - // At this point "label" is a match for the right-most name in user wildcard (e.g. the "bar" of "**/foo/bar" - ImGuiContext& g = *ui_ctx; - ImGuiTestEngine* engine = (ImGuiTestEngine*)ui_ctx->TestEngine; - IM_UNUSED(label); // Match ABI of caller function (faster call) - - // Test for matching status flags - ImGuiTestFindByLabelTask* label_task = &engine->FindByLabelTask; - if (ImGuiItemStatusFlags filter_flags = label_task->InFilterItemStatusFlags) - if (!(filter_flags & flags)) - return; - - // Test for matching PREFIX (the "window" of "window/**/foo/bar" or the "" of "/**/foo/bar") - // FIXME-TESTS: Stack depth limit? - // FIXME-TESTS: Recurse back into parent window limit? - bool match_prefix = false; - if (label_task->InPrefixId == 0) - { - match_prefix = true; - } - else - { - // Recurse back into parent, so from "WindowA" with SetRef("WindowA") it is possible to use "**/Button" to reach "WindowA/ChildXXXX/Button" - for (ImGuiWindow* window = g.CurrentWindow; window != NULL && !match_prefix; window = window->ParentWindow) - { - const int id_stack_size = window->IDStack.Size; - for (ImGuiID* p_id_stack = window->IDStack.Data + id_stack_size - 1; p_id_stack >= window->IDStack.Data; p_id_stack--) - if (*p_id_stack == label_task->InPrefixId) - { - match_prefix = true; - break; - } - } - } - if (!match_prefix) - return; - - // Test for full matching SUFFIX (the "foo/bar" or "window/**/foo/bar") - // Because at this point we have only compared the prefix and the right-most label (the "window" and "bar" or "window/**/foo/bar") - // FIXME-TESTS: The entire suffix must be inside the final window: - // - In theory, someone could craft a suffix that contains sub-window, e.g. "SomeWindow/**/SomeChild_XXXX/SomeItem" and this will fail. - // - Once we make child path easier to access we can fix that. - if (label_task->InSuffixDepth > 1) // This is merely an early out: for Depth==1 the compare has already been done in ImGuiTestEngineHook_ItemInfo() - { - ImGuiWindow* window = g.CurrentWindow; - const int id_stack_size = window->IDStack.Size; - int id_stack_pos = id_stack_size - label_task->InSuffixDepth; - - // At this point, IN MOST CASES (BUT NOT ALL) this should be the case: - // ImHashStr(label, 0, g.CurrentWindow->IDStack.back()) == id - // It's not always the case as we have situations where we call IMGUI_TEST_ENGINE_ITEM_INFO() outside of the right stack location: - // e.g. Begin(), or items using the PushID(label); SubItem(""); PopID(); idiom. - // If you are curious or need to understand this more in depth, uncomment this assert to detect them: - // ImGuiID tmp_id = ImHashStr(label, 0, g.CurrentWindow->IDStack.back()); - // IM_ASSERT(tmp_id == id); - // The "Try with parent" case is designed to handle that. May need further tuning. - - ImGuiID base_id = id_stack_pos >= 0 ? window->IDStack.Data[id_stack_pos] : 0; // base_id correspond to the "**" - ImGuiID find_id = ImHashDecoratedPath(label_task->InSuffix, NULL, base_id); // hash the whole suffix e.g. "foo/bar" over our base - if (id != find_id) - { - // Try with parent - base_id = id_stack_pos > 0 ? window->IDStack.Data[id_stack_pos - 1] : 0; - find_id = ImHashDecoratedPath(label_task->InSuffix, NULL, base_id); - if (id != find_id) - return; - } - } - - // Success - label_task->OutItemId = id; -} - -// label is optional -#ifdef IMGUI_HAS_IMSTR -void ImGuiTestEngineHook_ItemInfo(ImGuiContext* ui_ctx, ImGuiID id, ImStrv label, ImGuiItemStatusFlags flags) -#else -void ImGuiTestEngineHook_ItemInfo(ImGuiContext* ui_ctx, ImGuiID id, const char* label, ImGuiItemStatusFlags flags) -#endif -{ - ImGuiTestEngine* engine = (ImGuiTestEngine*)ui_ctx->TestEngine; - - IM_ASSERT(id != 0); - ImGuiContext& g = *ui_ctx; - //ImGuiWindow* window = g.CurrentWindow; - //IM_ASSERT(window->DC.LastItemId == id || window->DC.LastItemId == 0); // Need _ItemAdd() to be submitted before _ItemInfo() - - // Update Info Task status flags - if (ImGuiTestInfoTask* task = ImGuiTestEngine_FindInfoTask(engine, id)) - { - ImGuiTestItemInfo* item = &task->Result; - item->TimestampStatus = g.FrameCount; - item->StatusFlags = flags; - if (label) - ImStrncpy(item->DebugLabel, label, IM_ARRAYSIZE(item->DebugLabel)); - } - - // Update Gather Task status flags - if (engine->GatherTask.LastItemInfo && engine->GatherTask.LastItemInfo->ID == id) - { - ImGuiTestItemInfo* item = engine->GatherTask.LastItemInfo; - item->TimestampStatus = g.FrameCount; - item->StatusFlags = flags; - if (label) - ImStrncpy(item->DebugLabel, label, IM_ARRAYSIZE(item->DebugLabel)); - } - - // Update Find by Label Task - // FIXME-TESTS FIXME-OPT: Compare by hashes instead of strcmp to support "###" operator. - // Perhaps we could use strcmp() if we detect that ### is not used, that would be faster. - ImGuiTestFindByLabelTask* label_task = &engine->FindByLabelTask; - if (label && label_task->InSuffixLastItem && label_task->OutItemId == 0) -#ifdef IMGUI_HAS_IMSTR - if (label_task->InSuffixLastItemHash == ImHashStr(label)) -#else - if (label_task->InSuffixLastItemHash == ImHashStr(label, 0)) -#endif - ImGuiTestEngineHook_ItemInfo_ResolveFindByLabel(ui_ctx, id, label, flags); -} - -// Forward core/user-land text to test log -// This is called via the user-land IMGUI_TEST_ENGINE_LOG() macro. -void ImGuiTestEngineHook_Log(ImGuiContext* ui_ctx, const char* fmt, ...) -{ - ImGuiTestEngine* engine = (ImGuiTestEngine*)ui_ctx->TestEngine; - - va_list args; - va_start(args, fmt); - engine->TestContext->LogExV(ImGuiTestVerboseLevel_Debug, ImGuiTestLogFlags_None, fmt, args); - va_end(args); -} - -// Helper to output extra information (e.g. current test) during an assert. -// 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); - } -} - -// Used by IM_CHECK_OP() macros -ImGuiTextBuffer* ImGuiTestEngine_GetTempStringBuilder() -{ - static ImGuiTextBuffer builder; - builder.Buf.resize(1); - builder.Buf[0] = 0; - return &builder; -} - -// Out of convenience for main library we allow this to be called before TestEngine is initialized. -const char* ImGuiTestEngine_FindItemDebugLabel(ImGuiContext* ui_ctx, ImGuiID id) -{ - if (ui_ctx->TestEngine == NULL || id == 0) - return NULL; - if (ImGuiTestItemInfo* id_info = ImGuiTestEngine_FindItemInfo((ImGuiTestEngine*)ui_ctx->TestEngine, id, "")) - return id_info->DebugLabel; - return NULL; -} - -//------------------------------------------------------------------------- -// [SECTION] CHECK/ERROR FUNCTIONS FOR TESTS -//------------------------------------------------------------------------- -// - ImGuiTestEngine_Check() -// - ImGuiTestEngine_Error() -//------------------------------------------------------------------------- - -// Return true to request a debugger break -bool ImGuiTestEngine_Check(const char* file, const char* func, int line, ImGuiTestCheckFlags flags, bool result, const char* expr) -{ - ImGuiTestEngine* engine = GImGuiTestEngine; - (void)func; - - // Removed absolute path from output so we have deterministic output (otherwise __FILE__ gives us machine dending output) - const char* file_without_path = file ? ImPathFindFilename(file) : ""; - - if (ImGuiTestContext* ctx = engine->TestContext) - { - ImGuiTest* test = ctx->Test; - //ctx->LogDebug("IM_CHECK(%s)", expr); - if (!result) - { - if (!(ctx->RunFlags & ImGuiTestRunFlags_GuiFuncOnly)) - test->Output.Status = ImGuiTestStatus_Error; - - if (file) - ctx->LogError("Error %s:%d '%s'", file_without_path, line, expr); - else - ctx->LogError("Error '%s'", expr); - ctx->ErrorCounter++; - } - else if (!(flags & ImGuiTestCheckFlags_SilentSuccess)) - { - if (file) - ctx->LogInfo("OK %s:%d '%s'", file_without_path, line, expr); - else - ctx->LogInfo("OK '%s'", expr); - } - } - else - { - IM_ASSERT(0 && "No active tests!"); - } - - if (result == false && engine->IO.ConfigStopOnError && !engine->Abort) - engine->Abort = true; //ImGuiTestEngine_Abort(engine); - if (result == false && engine->IO.ConfigBreakOnError && !engine->Abort) - return true; - - return false; -} - -bool ImGuiTestEngine_CheckStrOp(const char* file, const char* func, int line, ImGuiTestCheckFlags flags, const char* op, const char* lhs_var, const char* lhs_value, const char* rhs_var, const char* rhs_value, bool* out_res) -{ - int res_strcmp = strcmp(lhs_value, rhs_value); - bool res = 0; - if (strcmp(op, "==") == 0) - res = (res_strcmp == 0); - else if (strcmp(op, "!=") == 0) - res = (res_strcmp != 0); - else - IM_ASSERT(0); - *out_res = res; - - ImGuiTextBuffer buf; // FIXME-OPT: Now we can probably remove that allocation - - bool lhs_is_literal = lhs_var[0] == '\"'; - bool rhs_is_literal = rhs_var[0] == '\"'; - if (strchr(lhs_value, '\n') != NULL || strchr(rhs_value, '\n') != NULL) - { - // Multi line strings - size_t lhs_value_len = strlen(lhs_value); - size_t rhs_value_len = strlen(rhs_value); - if (lhs_value_len > 0 && lhs_value[lhs_value_len - 1] == '\n') // Strip trailing carriage return as we are adding one ourselves - lhs_value_len--; - if (rhs_value_len > 0 && rhs_value[rhs_value_len - 1] == '\n') - rhs_value_len--; - buf.appendf( - "\n" - "---------------------------------------- // lhs: %s\n" - "%.*s\n" - "---------------------------------------- // rhs: %s, compare op: %s\n" - "%.*s\n" - "----------------------------------------\n", - lhs_is_literal ? "literal" : lhs_var, - (int)lhs_value_len, lhs_value, - rhs_is_literal ? "literal" : rhs_var, - op, - (int)rhs_value_len, rhs_value); - } - else - { - // Single line strings - buf.appendf( - "%s [\"%s\"] %s %s [\"%s\"]", - lhs_is_literal ? "" : lhs_var, lhs_value, - op, - rhs_is_literal ? "" : rhs_var, rhs_value); - } - - - return ImGuiTestEngine_Check(file, func, line, flags, res, buf.c_str()); -} - -bool ImGuiTestEngine_Error(const char* file, const char* func, int line, ImGuiTestCheckFlags flags, const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - Str256 buf; - buf.setfv(fmt, args); - bool ret = ImGuiTestEngine_Check(file, func, line, flags, false, buf.c_str()); - va_end(args); - - ImGuiTestEngine* engine = GImGuiTestEngine; - if (engine && engine->Abort) - return false; - return ret; -} - -//------------------------------------------------------------------------- -// [SECTION] SETTINGS -//------------------------------------------------------------------------- -// FIXME: In our wildest dreams we could provide a imgui_club/ serialization helper that would be -// easy to use in both the ReadLine and WriteAll functions. -//------------------------------------------------------------------------- - -static void* ImGuiTestEngine_SettingsReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) -{ - if (strcmp(name, "Data") != 0) - return NULL; - return (void*)1; -} - -static bool SettingsTryReadString(const char* line, const char* prefix, char* out_buf, size_t out_buf_size) -{ - // Could also use scanf() with "%[^\n]" but it won't bound check. - size_t prefix_len = strlen(prefix); - if (strncmp(line, prefix, prefix_len) != 0) - return false; - line += prefix_len; - IM_ASSERT(out_buf_size >= strlen(line) + 1); - ImFormatString(out_buf, out_buf_size, "%s", line); - return true; -} - -static bool SettingsTryReadString(const char* line, const char* prefix, Str* out_str) -{ - // Could also use scanf() with "%[^\n]" but it won't bound check. - size_t prefix_len = strlen(prefix); - if (strncmp(line, prefix, prefix_len) != 0) - return false; - line += prefix_len; - out_str->set(line); - return true; -} - -static void ImGuiTestEngine_SettingsReadLine(ImGuiContext* ui_ctx, ImGuiSettingsHandler*, void* entry, const char* line) -{ - ImGuiTestEngine* e = (ImGuiTestEngine*)ui_ctx->TestEngine; - IM_ASSERT(e != NULL); - IM_ASSERT(e->UiContextTarget == ui_ctx); - IM_UNUSED(entry); - - int n = 0; - /**/ if (SettingsTryReadString(line, "FilterTests=", e->UiFilterTests)) { } - else if (SettingsTryReadString(line, "FilterPerfs=", e->UiFilterPerfs)) { } - else if (sscanf(line, "LogHeight=%f", &e->UiLogHeight) == 1) { } - else if (sscanf(line, "CaptureTool=%d", &n) == 1) { e->UiCaptureToolOpen = (n != 0); } - else if (sscanf(line, "PerfTool=%d", &n) == 1) { e->UiPerfToolOpen = (n != 0); } - else if (sscanf(line, "StackTool=%d", &n) == 1) { e->UiStackToolOpen = (n != 0); } - else if (sscanf(line, "CaptureEnabled=%d", &n) == 1) { e->IO.ConfigCaptureEnabled = (n != 0); } - else if (sscanf(line, "CaptureOnError=%d", &n) == 1) { e->IO.ConfigCaptureOnError = (n != 0); } - else if (SettingsTryReadString(line, "VideoCapturePathToEncoder=", e->IO.VideoCaptureEncoderPath, IM_ARRAYSIZE(e->IO.VideoCaptureEncoderPath))) { } - else if (SettingsTryReadString(line, "VideoCaptureParamsToEncoder=", e->IO.VideoCaptureEncoderParams, IM_ARRAYSIZE(e->IO.VideoCaptureEncoderParams))) { } - else if (SettingsTryReadString(line, "GifCaptureParamsToEncoder=", e->IO.GifCaptureEncoderParams, IM_ARRAYSIZE(e->IO.GifCaptureEncoderParams))) { } - else if (SettingsTryReadString(line, "VideoCaptureExtension=", e->IO.VideoCaptureExtension, IM_ARRAYSIZE(e->IO.VideoCaptureExtension))) { } -} - -static void ImGuiTestEngine_SettingsWriteAll(ImGuiContext* ui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) -{ - ImGuiTestEngine* engine = (ImGuiTestEngine*)ui_ctx->TestEngine; - IM_ASSERT(engine != NULL); - IM_ASSERT(engine->UiContextTarget == ui_ctx); - - buf->appendf("[%s][Data]\n", handler->TypeName); - buf->appendf("FilterTests=%s\n", engine->UiFilterTests->c_str()); - buf->appendf("FilterPerfs=%s\n", engine->UiFilterPerfs->c_str()); - buf->appendf("LogHeight=%.0f\n", engine->UiLogHeight); - buf->appendf("CaptureTool=%d\n", engine->UiCaptureToolOpen); - buf->appendf("PerfTool=%d\n", engine->UiPerfToolOpen); - buf->appendf("StackTool=%d\n", engine->UiStackToolOpen); - buf->appendf("CaptureEnabled=%d\n", engine->IO.ConfigCaptureEnabled); - buf->appendf("CaptureOnError=%d\n", engine->IO.ConfigCaptureOnError); - buf->appendf("VideoCapturePathToEncoder=%s\n", engine->IO.VideoCaptureEncoderPath); - buf->appendf("VideoCaptureParamsToEncoder=%s\n", engine->IO.VideoCaptureEncoderParams); - buf->appendf("GifCaptureParamsToEncoder=%s\n", engine->IO.GifCaptureEncoderParams); - buf->appendf("VideoCaptureExtension=%s\n", engine->IO.VideoCaptureExtension); - buf->appendf("\n"); -} - -//------------------------------------------------------------------------- -// [SECTION] ImGuiTestLog -//------------------------------------------------------------------------- - -void ImGuiTestLog::Clear() -{ - Buffer.clear(); - LineInfo.clear(); - memset(&CountPerLevel, 0, sizeof(CountPerLevel)); -} - -// Output: -// - If 'buffer != NULL': all extracted lines are appended to 'buffer'. Use 'buffer->c_str()' on your side to obtain the text. -// - Return value: number of lines extracted (should be equivalent to number of '\n' inside buffer->c_str()). -// - You may call the function with buffer == NULL to only obtain a count without getting the data. -// Verbose levels are inclusive: -// - To get ONLY Error: Use level_min == ImGuiTestVerboseLevel_Error, level_max = ImGuiTestVerboseLevel_Error -// - To get ONLY Error and Warnings: Use level_min == ImGuiTestVerboseLevel_Error, level_max = ImGuiTestVerboseLevel_Warning -// - To get All Errors, Warnings, Debug... Use level_min == ImGuiTestVerboseLevel_Error, level_max = ImGuiTestVerboseLevel_Trace -int ImGuiTestLog::ExtractLinesForVerboseLevels(ImGuiTestVerboseLevel level_min, ImGuiTestVerboseLevel level_max, ImGuiTextBuffer* out_buffer) -{ - IM_ASSERT(level_min <= level_max); - - // Return count - int count = 0; - if (out_buffer == NULL) - { - for (int n = level_min; n <= level_max; n++) - count += CountPerLevel[n]; - return count; - } - - // Extract lines and return count - for (auto& line_info : LineInfo) - if (line_info.Level >= level_min && line_info.Level <= level_max) - { - const char* line_begin = Buffer.c_str() + line_info.LineOffset; - const char* line_end = strchr(line_begin, '\n'); - out_buffer->append(line_begin, line_end[0] == '\n' ? line_end + 1 : line_end); - count++; - } - return count; -} - -void ImGuiTestLog::UpdateLineOffsets(ImGuiTestEngineIO* engine_io, ImGuiTestVerboseLevel level, const char* start) -{ - IM_UNUSED(engine_io); - IM_ASSERT(Buffer.begin() <= start && start < Buffer.end()); - const char* p_begin = start; - const char* p_end = Buffer.end(); - const char* p = p_begin; - while (p < p_end) - { - const char* p_bol = p; - const char* p_eol = strchr(p, '\n'); - - bool last_empty_line = (p_bol + 1 == p_end); - if (!last_empty_line) - { - int offset = (int)(p_bol - Buffer.c_str()); - LineInfo.push_back({level, offset}); - CountPerLevel[level] += 1; - } - p = p_eol ? p_eol + 1 : NULL; - } -} - -//------------------------------------------------------------------------- -// [SECTION] ImGuiTest -//------------------------------------------------------------------------- - -ImGuiTest::~ImGuiTest() -{ - if (NameOwned) - ImGui::MemFree((char*)Name); -} - -void ImGuiTest::SetOwnedName(const char* name) -{ - IM_ASSERT(!NameOwned); - NameOwned = true; - Name = ImStrdup(name); -} - -//------------------------------------------------------------------------- diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_engine.h b/vendor/zgui/libs/imgui_test_engine/imgui_te_engine.h deleted file mode 100644 index c5a585d..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_engine.h +++ /dev/null @@ -1,429 +0,0 @@ -// dear imgui test engine -// (core) -// This is the interface that your initial setup (app init, main loop) will mostly be using. -// Actual tests will mostly use the interface of imgui_te_context.h - -#pragma once - -#include "imgui.h" -#include "imgui_internal.h" // ImPool<>, ImRect, ImGuiItemStatusFlags, ImFormatString -#include "imgui_te_utils.h" // ImFuncPtr -#include "imgui_capture_tool.h" // ImGuiScreenCaptureFunc - -//------------------------------------------------------------------------- -// Forward Declarations -//------------------------------------------------------------------------- - -struct ImGuiTest; // Data for a test registered with IM_REGISTER_TEST() -struct ImGuiTestContext; // Context while a test is running -struct ImGuiTestCoroutineInterface; // Interface to expose coroutine functions (imgui_te_coroutine provides a default implementation for C++11 using std::thread, but you may use your own) -struct ImGuiTestEngine; // Test engine instance -struct ImGuiTestEngineIO; // Test engine public I/O -struct ImGuiTestItemInfo; // Info queried from item (id, geometry, status flags, debug label) -struct ImGuiTestItemList; // A list of items -struct ImGuiTestInputs; // Simulated user inputs (will be fed into ImGuiIO by the test engine) -struct ImGuiTestRunTask; // A queued test (test + runflags) - -typedef int ImGuiTestFlags; // Flags: See ImGuiTestFlags_ -typedef int ImGuiTestCheckFlags; // Flags: See ImGuiTestCheckFlags_ -typedef int ImGuiTestLogFlags; // Flags: See ImGuiTestLogFlags_ -typedef int ImGuiTestRunFlags; // Flags: See ImGuiTestRunFlags_ - -enum ImGuiTestActiveFunc : int; -enum ImGuiTestGroup : int; -enum ImGuiTestRunSpeed : int; -enum ImGuiTestStatus : int; -enum ImGuiTestVerboseLevel : int; -enum ImGuiTestEngineExportFormat : int; - -//------------------------------------------------------------------------- -// Types -//------------------------------------------------------------------------- - -// Stored in ImGuiTestContext: where we are currently running GuiFunc or TestFunc -enum ImGuiTestActiveFunc : int -{ - ImGuiTestActiveFunc_None, - ImGuiTestActiveFunc_GuiFunc, - ImGuiTestActiveFunc_TestFunc -}; - -enum ImGuiTestRunSpeed : int -{ - ImGuiTestRunSpeed_Fast = 0, // Run tests as fast as possible (teleport mouse, skip delays, etc.) - ImGuiTestRunSpeed_Normal = 1, // Run tests at human watchable speed (for debugging) - ImGuiTestRunSpeed_Cinematic = 2, // Run tests with pauses between actions (for e.g. tutorials) - ImGuiTestRunSpeed_COUNT -}; - -enum ImGuiTestVerboseLevel : int -{ - ImGuiTestVerboseLevel_Silent = 0, // -v0 - ImGuiTestVerboseLevel_Error = 1, // -v1 - ImGuiTestVerboseLevel_Warning = 2, // -v2 - ImGuiTestVerboseLevel_Info = 3, // -v3 - ImGuiTestVerboseLevel_Debug = 4, // -v4 - ImGuiTestVerboseLevel_Trace = 5, - ImGuiTestVerboseLevel_COUNT -}; - -// 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_COUNT -}; - -// Test group: this is mostly used to categorize tests in our testing UI. (Stored in ImGuiTest) -enum ImGuiTestGroup : int -{ - ImGuiTestGroup_Unknown = -1, - ImGuiTestGroup_Tests = 0, - ImGuiTestGroup_Perfs = 1, - ImGuiTestGroup_COUNT -}; - -// Flags (stored in ImGuiTest) -enum ImGuiTestFlags_ -{ - ImGuiTestFlags_None = 0, - ImGuiTestFlags_NoGuiWarmUp = 1 << 0, // Disable running the GUI func for 2 frames before starting test code. For tests which absolutely need to start before GuiFunc. - ImGuiTestFlags_NoAutoFinish = 1 << 1, // By default, tests with no TestFunc (only a GuiFunc) will end after warmup. Setting this require test to call ctx->Finish(). - ImGuiTestFlags_NoRecoveryWarnings = 1 << 2 // Disable state recovery warnings (missing End/Pop calls etc.) for tests which may rely on those. - //ImGuiTestFlags_RequireViewports = 1 << 10 -}; - -// Flags for IM_CHECK* macros. -enum ImGuiTestCheckFlags_ -{ - ImGuiTestCheckFlags_None = 0, - ImGuiTestCheckFlags_SilentSuccess = 1 << 0 -}; - -// Flags for ImGuiTestContext::Log* functions. -enum ImGuiTestLogFlags_ -{ - ImGuiTestLogFlags_None = 0, - ImGuiTestLogFlags_NoHeader = 1 << 0 // Do not display frame count and depth padding -}; - -enum ImGuiTestRunFlags_ -{ - ImGuiTestRunFlags_None = 0, - ImGuiTestRunFlags_GuiFuncDisable = 1 << 0, // Used internally to temporarily disable the GUI func (at the end of a test, etc) - ImGuiTestRunFlags_GuiFuncOnly = 1 << 1, // Set when user selects "Run GUI func" - ImGuiTestRunFlags_NoSuccessMsg = 1 << 2, - ImGuiTestRunFlags_EnableRawInputs = 1 << 3, // Disable input submission to let test submission raw input event (in order to test e.g. IO queue) - ImGuiTestRunFlags_RunFromGui = 1 << 4, // Test ran manually from GUI, will disable watchdog. - ImGuiTestRunFlags_RunFromCommandLine= 1 << 5, // Test queued from command-line. - - // Flags for ImGuiTestContext::RunChildTest() - ImGuiTestRunFlags_NoError = 1 << 10, - ImGuiTestRunFlags_ShareVars = 1 << 11, // Share generic vars and custom vars between child and parent tests (custom vars need to be same type) - ImGuiTestRunFlags_ShareTestContext = 1 << 12, // Share ImGuiTestContext instead of creating a new one (unsure what purpose this may be useful for yet) - // TODO: Add GuiFunc options -}; - -//------------------------------------------------------------------------- -// Functions -//------------------------------------------------------------------------- - -// Hooks for core imgui/ library (generally called via macros) -extern void ImGuiTestEngineHook_ItemAdd(ImGuiContext* ui_ctx, ImGuiID id, const ImRect& bb, const ImGuiLastItemData* item_data); -#if IMGUI_VERSION_NUM < 18934 -extern void ImGuiTestEngineHook_ItemAdd(ImGuiContext* ui_ctx, const ImRect& bb, ImGuiID id); -#endif -#ifdef IMGUI_HAS_IMSTR -extern void ImGuiTestEngineHook_ItemInfo(ImGuiContext* ui_ctx, ImGuiID id, ImStrv label, ImGuiItemStatusFlags flags); -#else -extern void ImGuiTestEngineHook_ItemInfo(ImGuiContext* ui_ctx, ImGuiID id, const char* label, ImGuiItemStatusFlags flags); -#endif -extern void ImGuiTestEngineHook_Log(ImGuiContext* ui_ctx, const char* fmt, ...); -extern const char* ImGuiTestEngine_FindItemDebugLabel(ImGuiContext* ui_ctx, ImGuiID id); - -// Functions (generally called via IM_CHECK() macros) -IMGUI_API bool ImGuiTestEngine_Check(const char* file, const char* func, int line, ImGuiTestCheckFlags flags, bool result, const char* expr); -IMGUI_API bool ImGuiTestEngine_CheckStrOp(const char* file, const char* func, int line, ImGuiTestCheckFlags flags, const char* op, const char* lhs_var, const char* lhs_value, const char* rhs_var, const char* rhs_value, bool* out_result); -IMGUI_API bool ImGuiTestEngine_Error(const char* file, const char* func, int line, ImGuiTestCheckFlags flags, const char* fmt, ...); -IMGUI_API void ImGuiTestEngine_AssertLog(const char* expr, const char* file, const char* function, int line); -IMGUI_API ImGuiTextBuffer* ImGuiTestEngine_GetTempStringBuilder(); - -//------------------------------------------------------------------------- -// ImGuiTestEngine API -//------------------------------------------------------------------------- - -// Functions: Initialization -IMGUI_API ImGuiTestEngine* ImGuiTestEngine_CreateContext(); // Create test engine -IMGUI_API void ImGuiTestEngine_DestroyContext(ImGuiTestEngine* engine); // Destroy test engine. Call after ImGui::DestroyContext() so test engine specific ini data gets saved. -IMGUI_API void ImGuiTestEngine_Start(ImGuiTestEngine* engine, ImGuiContext* ui_ctx); // Bind to a dear imgui context. Start coroutine. -IMGUI_API void ImGuiTestEngine_Stop(ImGuiTestEngine* engine); // Stop coroutine and export if any. (Unbind will lazily happen on context shutdown) -IMGUI_API void ImGuiTestEngine_PostSwap(ImGuiTestEngine* engine); // Call every frame after framebuffer swap, will process screen capture and call test_io.ScreenCaptureFunc() -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() - -// Functions: Main -IMGUI_API void ImGuiTestEngine_QueueTest(ImGuiTestEngine* engine, ImGuiTest* test, ImGuiTestRunFlags run_flags = 0); -IMGUI_API void ImGuiTestEngine_QueueTests(ImGuiTestEngine* engine, ImGuiTestGroup group, const char* filter = NULL, ImGuiTestRunFlags run_flags = 0); -IMGUI_API bool ImGuiTestEngine_TryAbortEngine(ImGuiTestEngine* engine); -IMGUI_API void ImGuiTestEngine_AbortCurrentTest(ImGuiTestEngine* engine); -IMGUI_API ImGuiTest* ImGuiTestEngine_FindTestByName(ImGuiTestEngine* engine, const char* category, const char* name); - -// Functions: Status Queries -// FIXME: Clarify API to avoid function calls vs raw bools in ImGuiTestEngineIO -IMGUI_API bool ImGuiTestEngine_IsTestQueueEmpty(ImGuiTestEngine* engine); -IMGUI_API bool ImGuiTestEngine_IsUsingSimulatedInputs(ImGuiTestEngine* engine); -IMGUI_API void ImGuiTestEngine_GetResult(ImGuiTestEngine* engine, int& count_tested, int& success_count); -IMGUI_API void ImGuiTestEngine_GetTestList(ImGuiTestEngine* engine, ImVector<ImGuiTest*>* out_tests); -IMGUI_API void ImGuiTestEngine_GetTestQueue(ImGuiTestEngine* engine, ImVector<ImGuiTestRunTask>* out_tests); - -// Functions: Crash Handling -// Ensure past test results are properly exported even if application crash during a test. -IMGUI_API void ImGuiTestEngine_InstallDefaultCrashHandler(); // Install default crash handler (if you don't have one) -IMGUI_API void ImGuiTestEngine_CrashHandler(); // Default crash handler, should be called from a custom crash handler if such exists - -//----------------------------------------------------------------------------- -// IO structure to configure the test engine -//----------------------------------------------------------------------------- - -// Function bound to right-clicking on a test and selecting "Open source" in the UI -// - Easy: you can make this function call OS shell to "open" the file (e.g. ImOsOpenInShell() helper). -// - Better: bind this function to a custom setup which can pass line number to a text editor (e.g. see 'imgui_test_suite/tools/win32_open_with_sublime.cmd' example) -typedef void (ImGuiTestEngineSrcFileOpenFunc)(const char* filename, int line_no, void* user_data); - -struct IMGUI_API ImGuiTestEngineIO -{ - //------------------------------------------------------------------------- - // Functions - //------------------------------------------------------------------------- - - // Options: Functions - ImGuiTestCoroutineInterface* CoroutineFuncs = NULL; // (Required) Coroutine functions (see imgui_te_coroutines.h) - ImFuncPtr(ImGuiTestEngineSrcFileOpenFunc) SrcFileOpenFunc = NULL; // (Optional) To open source files from test engine UI - ImFuncPtr(ImGuiScreenCaptureFunc) ScreenCaptureFunc = NULL; // (Optional) To capture graphics output (application _MUST_ call ImGuiTestEngine_PostSwap() function after swapping is framebuffer) - void* SrcFileOpenUserData = NULL; // (Optional) User data for SrcFileOpenFunc - void* ScreenCaptureUserData = NULL; // (Optional) User data for ScreenCaptureFunc - - // Options: Main - bool ConfigSavedSettings = true; // Load/Save settings in main context .ini file. - ImGuiTestRunSpeed ConfigRunSpeed = ImGuiTestRunSpeed_Fast; // Run tests in fast/normal/cinematic mode - bool ConfigStopOnError = false; // Stop queued tests on test error - bool ConfigBreakOnError = false; // Break debugger on test error by calling IM_DEBUG_BREAK() - bool ConfigKeepGuiFunc = false; // Keep test GUI running at the end of the test - ImGuiTestVerboseLevel ConfigVerboseLevel = ImGuiTestVerboseLevel_Warning; - ImGuiTestVerboseLevel ConfigVerboseLevelOnError = ImGuiTestVerboseLevel_Info; - bool ConfigLogToTTY = false; - bool ConfigLogToDebugger = false; - bool ConfigRestoreFocusAfterTests = true;// Restore focus back after running tests - bool ConfigCaptureEnabled = true; // Master enable flags for capturing and saving captures. Disable to avoid e.g. lengthy saving of large PNG files. - bool ConfigCaptureOnError = false; - bool ConfigNoThrottle = false; // Disable vsync for performance measurement or fast test running - bool ConfigMouseDrawCursor = true; // Enable drawing of Dear ImGui software mouse cursor when running tests - float ConfigFixedDeltaTime = 0.0f; // Use fixed delta time instead of calculating it from wall clock - int PerfStressAmount = 1; // Integer to scale the amount of items submitted in test - char GitBranchName[64] = ""; // e.g. fill in branch name (e.g. recorded in perf samples .csv) - - // Options: Speed of user simulation - float MouseSpeed = 600.0f; // Mouse speed (pixel/second) when not running in fast mode - float MouseWobble = 0.25f; // (0.0f..1.0f) How much wobble to apply to the mouse (pixels per pixel of move distance) when not running in fast mode - float ScrollSpeed = 1400.0f; // Scroll speed (pixel/second) when not running in fast mode - float TypingSpeed = 20.0f; // Char input speed (characters/second) when not running in fast mode - float ActionDelayShort = 0.15f; // Time between short actions - float ActionDelayStandard = 0.40f; // Time between most actions - - // Options: Screen/video capture - char VideoCaptureEncoderPath[256] = ""; // Video encoder executable path, e.g. "path/to/ffmpeg.exe". - char VideoCaptureEncoderParams[256] = "";// Video encoder parameters for .MP4 captures, e.g. see IMGUI_CAPTURE_DEFAULT_VIDEO_PARAMS_FOR_FFMPEG - char GifCaptureEncoderParams[512] = ""; // Video encoder parameters for .GIF captures, e.g. see IMGUI_CAPTURE_DEFAULT_GIF_PARAMS_FOR_FFMPEG - char VideoCaptureExtension[8] = ".mp4"; // Video file extension (default, may be overridden by test). - - // Options: Watchdog. Set values to FLT_MAX to disable. - // Interactive GUI applications that may be slower tend to use higher values. - float ConfigWatchdogWarning = 30.0f; // Warn when a test exceed this time (in second) - float ConfigWatchdogKillTest = 60.0f; // Attempt to stop running a test when exceeding this time (in second) - float ConfigWatchdogKillApp = FLT_MAX; // Stop application when exceeding this time (in second) - - // Options: Export - // While you can manually call ImGuiTestEngine_Export(), registering filename/format here ensure the crash handler will always export if application crash. - const char* ExportResultsFilename = NULL; - ImGuiTestEngineExportFormat ExportResultsFormat = (ImGuiTestEngineExportFormat)0; - - // Options: Sanity Checks - bool CheckDrawDataIntegrity = false; // Check ImDrawData integrity (buffer count, etc.). Currently cheap but may become a slow operation. - - //------------------------------------------------------------------------- - // Output - //------------------------------------------------------------------------- - - // Output: State of test engine - bool IsRunningTests = false; - bool IsRequestingMaxAppSpeed = false; // When running in fast mode: request app to skip vsync or even skip rendering if it wants - bool IsCapturing = false; // Capture is in progress -}; - -//------------------------------------------------------------------------- -// ImGuiTestItemInfo -//------------------------------------------------------------------------- - -// 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. - 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 - 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; } -}; - -// Result of an GatherItems() query -struct IMGUI_API ImGuiTestItemList -{ - ImPool<ImGuiTestItemInfo> Pool; - - void Clear() { Pool.Clear(); } - void Reserve(int capacity) { Pool.Reserve(capacity); } - int GetSize() const { return Pool.GetMapSize(); } - const ImGuiTestItemInfo* GetByIndex(int n) { return Pool.GetByIndex(n); } - const ImGuiTestItemInfo* GetByID(ImGuiID id) { return Pool.GetByKey(id); } - - // For range-for - size_t size() const { return (size_t)Pool.GetMapSize(); } - const ImGuiTestItemInfo* begin() const { return Pool.Buf.begin(); } - const ImGuiTestItemInfo* end() const { return Pool.Buf.end(); } - const ImGuiTestItemInfo* operator[] (size_t n) { return &Pool.Buf[(int)n]; } -}; - -//------------------------------------------------------------------------- -// ImGuiTestLog: store textual output of one given Test. -//------------------------------------------------------------------------- - -struct IMGUI_API ImGuiTestLogLineInfo -{ - ImGuiTestVerboseLevel Level; - int LineOffset; -}; - -struct IMGUI_API ImGuiTestLog -{ - ImGuiTextBuffer Buffer; - ImVector<ImGuiTestLogLineInfo> LineInfo; - int CountPerLevel[ImGuiTestVerboseLevel_COUNT] = {}; - - // Functions - ImGuiTestLog() {} - bool IsEmpty() const { return Buffer.empty(); } - void Clear(); - - // Extract log contents filtered per log-level. - // Output: - // - If 'buffer != NULL': all extracted lines are appended to 'buffer'. Use 'buffer->c_str()' on your side to obtain the text. - // - Return value: number of lines extracted (should be equivalent to number of '\n' inside buffer->c_str()). - // - You may call the function with buffer == NULL to only obtain a count without getting the data. - // Verbose levels are inclusive: - // - To get ONLY Error: Use level_min == ImGuiTestVerboseLevel_Error, level_max = ImGuiTestVerboseLevel_Error - // - To get ONLY Error and Warnings: Use level_min == ImGuiTestVerboseLevel_Error, level_max = ImGuiTestVerboseLevel_Warning - // - To get All Errors, Warnings, Debug... Use level_min == ImGuiTestVerboseLevel_Error, level_max = ImGuiTestVerboseLevel_Trace - int ExtractLinesForVerboseLevels(ImGuiTestVerboseLevel level_min, ImGuiTestVerboseLevel level_max, ImGuiTextBuffer* out_buffer); - - // [Internal] - void UpdateLineOffsets(ImGuiTestEngineIO* engine_io, ImGuiTestVerboseLevel level, const char* start); -}; - -//------------------------------------------------------------------------- -// ImGuiTest -//------------------------------------------------------------------------- - -typedef void (ImGuiTestGuiFunc)(ImGuiTestContext* ctx); -typedef void (ImGuiTestTestFunc)(ImGuiTestContext* ctx); - -// Wraps a placement new of a given type (where 'buffer' is the allocated memory) -typedef void (ImGuiTestVarsConstructor)(void* buffer); -typedef void (ImGuiTestVarsPostConstructor)(ImGuiTestContext* ctx, void* ptr, void* fn); -typedef void (ImGuiTestVarsDestructor)(void* ptr); - -// Storage for the output of a test run -struct IMGUI_API ImGuiTestOutput -{ - ImGuiTestStatus Status = ImGuiTestStatus_Unknown; - ImGuiTestLog Log; - ImU64 StartTime = 0; - ImU64 EndTime = 0; -}; - -// Storage for one test -struct IMGUI_API ImGuiTest -{ - // Test Definition - const char* Category = NULL; // Literal, not owned - const char* Name = NULL; // Literal, generally not owned unless NameOwned=true - ImGuiTestGroup Group = ImGuiTestGroup_Unknown; // Coarse groups: 'Tests' or 'Perf' - bool NameOwned = false; // - const char* SourceFile = NULL; // __FILE__ - int SourceLine = 0; // __LINE__ - int SourceLineEnd = 0; // Calculated by ImGuiTestEngine_StartCalcSourceLineEnds() - int ArgVariant = 0; // User parameter. Generally we use it to run variations of a same test by sharing GuiFunc/TestFunc - ImGuiTestFlags Flags = ImGuiTestFlags_None; // See ImGuiTestFlags_ - ImFuncPtr(ImGuiTestGuiFunc) GuiFunc = NULL; // GUI function (optional if your test are running over an existing GUI application) - ImFuncPtr(ImGuiTestTestFunc) TestFunc = NULL; // Test function - void* UserData = NULL; // General purpose user data (if assigning capturing lambdas on GuiFunc/TestFunc you may not need to use this) - //ImVector<ImGuiTestRunTask> Dependencies; // Registered via AddDependencyTest(), ran automatically before our test. This is a simpler wrapper to calling ctx->RunChildTest() - - // Last Test Output/Status - // (this is the only part that may change after registration) - ImGuiTestOutput Output; - - // User variables (which are instantiated when running the test) - // Setup after test registration with SetVarsDataType<>(), access instance during test with GetVars<>(). - // This is mostly useful to communicate between GuiFunc and TestFunc. If you don't use both you may not want to use it! - size_t VarsSize = 0; - ImGuiTestVarsConstructor* VarsConstructor = NULL; - ImGuiTestVarsPostConstructor* VarsPostConstructor = NULL; // To override constructor default (in case the default are problematic on the first GuiFunc frame) - void* VarsPostConstructorUserFn = NULL; - ImGuiTestVarsDestructor* VarsDestructor = NULL; - - // Functions - ImGuiTest() {} - ~ImGuiTest(); - - void SetOwnedName(const char* name); - - template <typename T> - void SetVarsDataType(void(*post_initialize)(ImGuiTestContext* ctx, T& vars) = NULL) - { - VarsSize = sizeof(T); - VarsConstructor = [](void* ptr) { IM_PLACEMENT_NEW(ptr) T; }; - VarsDestructor = [](void* ptr) { IM_UNUSED(ptr); reinterpret_cast<T*>(ptr)->~T(); }; - if (post_initialize != NULL) - { - VarsPostConstructorUserFn = (void*)post_initialize; - VarsPostConstructor = [](ImGuiTestContext* ctx, void* ptr, void* fn) { ((void (*)(ImGuiTestContext*, T&))(fn))(ctx, *(T*)ptr); }; - } - } -}; - -// Stored in test queue -struct IMGUI_API ImGuiTestRunTask -{ - ImGuiTest* Test = NULL; - ImGuiTestRunFlags RunFlags = ImGuiTestRunFlags_None; -}; - -//------------------------------------------------------------------------- diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_exporters.cpp b/vendor/zgui/libs/imgui_test_engine/imgui_te_exporters.cpp deleted file mode 100644 index 8290c52..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_exporters.cpp +++ /dev/null @@ -1,303 +0,0 @@ -// dear imgui test engine -// (result exporters) -// Read https://github.com/ocornut/imgui_test_engine/wiki/Exporting-Results - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -#define _CRT_SECURE_NO_WARNINGS -#endif -#include "imgui_te_exporters.h" -#include "imgui_te_engine.h" -#include "imgui_te_internal.h" -#include "thirdparty/Str/Str.h" - -//------------------------------------------------------------------------- -// [SECTION] FORWARD DECLARATIONS -//------------------------------------------------------------------------- - -static void ImGuiTestEngine_ExportJUnitXml(ImGuiTestEngine* engine, const char* output_file); - -//------------------------------------------------------------------------- -// [SECTION] TEST ENGINE EXPORTER FUNCTIONS -//------------------------------------------------------------------------- -// - ImGuiTestEngine_PrintResultSummary() -// - ImGuiTestEngine_Export() -// - ImGuiTestEngine_ExportEx() -// - ImGuiTestEngine_ExportJUnitXml() -//------------------------------------------------------------------------- - -void ImGuiTestEngine_PrintResultSummary(ImGuiTestEngine* engine) -{ - int count_tested = 0; - int count_success = 0; - ImGuiTestEngine_GetResult(engine, count_tested, count_success); - - if (count_success < count_tested) - { - printf("\nFailing tests:\n"); - for (ImGuiTest* test : engine->TestsAll) - if (test->Output.Status == ImGuiTestStatus_Error) - printf("- %s\n", test->Name); - } - - ImOsConsoleSetTextColor(ImOsConsoleStream_StandardOutput, (count_success == count_tested) ? ImOsConsoleTextColor_BrightGreen : ImOsConsoleTextColor_BrightRed); - printf("\nTests Result: %s\n", (count_success == count_tested) ? "OK" : "Errors"); - printf("(%d/%d tests passed)\n", count_success, count_tested); - ImOsConsoleSetTextColor(ImOsConsoleStream_StandardOutput, ImOsConsoleTextColor_White); -} - -// This is mostly a copy of ImGuiTestEngine_PrintResultSummary with few additions. -static void ImGuiTestEngine_ExportResultSummary(ImGuiTestEngine* engine, FILE* fp, int indent_count, ImGuiTestGroup group) -{ - int count_tested = 0; - int count_success = 0; - - for (ImGuiTest* test : engine->TestsAll) - { - if (test->Group != group) - continue; - if (test->Output.Status != ImGuiTestStatus_Unknown) - count_tested++; - if (test->Output.Status == ImGuiTestStatus_Success) - count_success++; - } - - Str64 indent_str; - indent_str.reserve(indent_count + 1); - memset(indent_str.c_str(), ' ', indent_count); - indent_str[indent_count] = 0; - const char* indent = indent_str.c_str(); - - if (count_success < count_tested) - { - fprintf(fp, "\n%sFailing tests:\n", indent); - for (ImGuiTest* test : engine->TestsAll) - { - if (test->Group != group) - continue; - if (test->Output.Status == ImGuiTestStatus_Error) - fprintf(fp, "%s- %s\n", indent, test->Name); - } - fprintf(fp, "\n"); - } - - fprintf(fp, "%sTests Result: %s\n", indent, (count_success == count_tested) ? "OK" : "Errors"); - fprintf(fp, "%s(%d/%d tests passed)\n", indent, count_success, count_tested); -} - -static bool ImGuiTestEngine_HasAnyLogLines(ImGuiTestLog* test_log, ImGuiTestVerboseLevel level) -{ - for (auto& line_info : test_log->LineInfo) - if (line_info.Level <= level) - return true; - return false; -} - -static void ImGuiTestEngine_PrintLogLines(FILE* fp, ImGuiTestLog* test_log, int indent, ImGuiTestVerboseLevel level) -{ - Str128 log_line; - for (auto& line_info : test_log->LineInfo) - { - if (line_info.Level > level) - continue; - const char* line_start = test_log->Buffer.c_str() + line_info.LineOffset; - const char* line_end = strstr(line_start, "\n"); // FIXME: Incorrect. - log_line.set(line_start, line_end); - ImStrXmlEscape(&log_line); // FIXME: Should not be here considering the function name. - - // Some users may want to disable indenting? - fprintf(fp, "%*s%s\n", indent, "", log_line.c_str()); - } -} - -// Export using settings stored in ImGuiTestEngineIO -// This is called by ImGuiTestEngine_CrashHandler(). -void ImGuiTestEngine_Export(ImGuiTestEngine* engine) -{ - ImGuiTestEngineIO& io = engine->IO; - ImGuiTestEngine_ExportEx(engine, io.ExportResultsFormat, io.ExportResultsFilename); -} - -// Export using custom settings. -void ImGuiTestEngine_ExportEx(ImGuiTestEngine* engine, ImGuiTestEngineExportFormat format, const char* filename) -{ - if (format == ImGuiTestEngineExportFormat_None) - return; - IM_ASSERT(filename != NULL); - - if (format == ImGuiTestEngineExportFormat_JUnitXml) - ImGuiTestEngine_ExportJUnitXml(engine, filename); - else - IM_ASSERT(0); -} - -void ImGuiTestEngine_ExportJUnitXml(ImGuiTestEngine* engine, const char* output_file) -{ - IM_ASSERT(engine != NULL); - IM_ASSERT(output_file != NULL); - - FILE* fp = fopen(output_file, "w+b"); - if (fp == NULL) - { - fprintf(stderr, "Writing '%s' failed.\n", output_file); - return; - } - - // Per-testsuite test statistics. - struct - { - const char* Name = NULL; - int Tests = 0; - int Failures = 0; - int Disabled = 0; - } testsuites[ImGuiTestGroup_COUNT]; - testsuites[ImGuiTestGroup_Tests].Name = "tests"; - testsuites[ImGuiTestGroup_Perfs].Name = "perfs"; - - for (int n = 0; n < engine->TestsAll.Size; n++) - { - ImGuiTest* test = engine->TestsAll[n]; - auto* stats = &testsuites[test->Group]; - stats->Tests += 1; - if (test->Output.Status == ImGuiTestStatus_Error) - stats->Failures += 1; - else if (test->Output.Status == ImGuiTestStatus_Unknown) - stats->Disabled += 1; - } - - // Attributes for <testsuites> tag. - const char* testsuites_name = "Dear ImGui"; - int testsuites_failures = 0; - int testsuites_tests = 0; - int testsuites_disabled = 0; - float testsuites_time = (float)((double)(engine->BatchEndTime - engine->BatchStartTime) / 1000000.0); - for (int testsuite_id = ImGuiTestGroup_Tests; testsuite_id < ImGuiTestGroup_COUNT; testsuite_id++) - { - testsuites_tests += testsuites[testsuite_id].Tests; - testsuites_failures += testsuites[testsuite_id].Failures; - testsuites_disabled += testsuites[testsuite_id].Disabled; - } - - // FIXME: "errors" attribute and <error> tag in <testcase> may be supported if we have means to catch unexpected errors like assertions. - fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - "<testsuites disabled=\"%d\" errors=\"0\" failures=\"%d\" name=\"%s\" tests=\"%d\" time=\"%.3f\">\n", - testsuites_disabled, testsuites_failures, testsuites_name, testsuites_tests, testsuites_time); - - for (int testsuite_id = ImGuiTestGroup_Tests; testsuite_id < ImGuiTestGroup_COUNT; testsuite_id++) - { - // Attributes for <testsuite> tag. - auto* testsuite = &testsuites[testsuite_id]; - float testsuite_time = testsuites_time; // FIXME: We do not differentiate between tests and perfs, they are executed in one big batch. - Str30 testsuite_timestamp = ""; - ImTimestampToISO8601(engine->BatchStartTime, &testsuite_timestamp); - fprintf(fp, " <testsuite name=\"%s\" tests=\"%d\" disabled=\"%d\" errors=\"0\" failures=\"%d\" hostname=\"\" id=\"%d\" package=\"\" skipped=\"0\" time=\"%.3f\" timestamp=\"%s\">\n", - testsuite->Name, testsuite->Tests, testsuite->Disabled, testsuite->Failures, testsuite_id, testsuite_time, testsuite_timestamp.c_str()); - - for (int n = 0; n < engine->TestsAll.Size; n++) - { - ImGuiTest* test = engine->TestsAll[n]; - if (test->Group != testsuite_id) - continue; - - ImGuiTestOutput* test_output = &test->Output; - ImGuiTestLog* test_log = &test_output->Log; - - // Attributes for <testcase> tag. - const char* testcase_name = test->Name; - const char* testcase_classname = test->Category; - const char* testcase_status = ImGuiTestEngine_GetStatusName(test_output->Status); - const float testcase_time = (float)((double)(test_output->EndTime - test_output->StartTime) / 1000000.0); - - fprintf(fp, " <testcase name=\"%s\" assertions=\"0\" classname=\"%s\" status=\"%s\" time=\"%.3f\">\n", - testcase_name, testcase_classname, testcase_status, testcase_time); - - if (test_output->Status == ImGuiTestStatus_Error) - { - // Skip last error message because it is generic information that test failed. - Str128 log_line; - for (int i = test_log->LineInfo.Size - 2; i >= 0; i--) - { - ImGuiTestLogLineInfo* line_info = &test_log->LineInfo[i]; - if (line_info->Level > engine->IO.ConfigVerboseLevelOnError) - continue; - if (line_info->Level == ImGuiTestVerboseLevel_Error) - { - const char* line_start = test_log->Buffer.c_str() + line_info->LineOffset; - const char* line_end = strstr(line_start, "\n"); - log_line.set(line_start, line_end); - ImStrXmlEscape(&log_line); - break; - } - } - - // Failing tests save their "on error" log output in text element of <failure> tag. - fprintf(fp, " <failure message=\"%s\" type=\"error\">\n", log_line.c_str()); - ImGuiTestEngine_PrintLogLines(fp, test_log, 8, engine->IO.ConfigVerboseLevelOnError); - fprintf(fp, " </failure>\n"); - } - - if (test_output->Status == ImGuiTestStatus_Unknown) - { - fprintf(fp, " <skipped message=\"Skipped\" />\n"); - } - else - { - // Succeeding tests save their default log output output as "stdout". - if (ImGuiTestEngine_HasAnyLogLines(test_log, engine->IO.ConfigVerboseLevel)) - { - fprintf(fp, " <system-out>\n"); - ImGuiTestEngine_PrintLogLines(fp, test_log, 8, engine->IO.ConfigVerboseLevel); - fprintf(fp, " </system-out>\n"); - } - - // Save error messages as "stderr". - if (ImGuiTestEngine_HasAnyLogLines(test_log, ImGuiTestVerboseLevel_Error)) - { - fprintf(fp, " <system-err>\n"); - ImGuiTestEngine_PrintLogLines(fp, test_log, 8, ImGuiTestVerboseLevel_Error); - fprintf(fp, " </system-err>\n"); - } - } - fprintf(fp, " </testcase>\n"); - } - - if (testsuites[testsuite_id].Disabled < testsuites[testsuite_id].Tests) // Any tests executed - { - // Log all log messages as "stdout". - fprintf(fp, " <system-out>\n"); - for (int n = 0; n < engine->TestsAll.Size; n++) - { - ImGuiTest* test = engine->TestsAll[n]; - ImGuiTestOutput* test_output = &test->Output; - if (test->Group != testsuite_id) - continue; - if (test_output->Status == ImGuiTestStatus_Unknown) - continue; - fprintf(fp, " [0000] Test: '%s' '%s'..\n", test->Category, test->Name); - ImGuiTestVerboseLevel level = test_output->Status == ImGuiTestStatus_Error ? engine->IO.ConfigVerboseLevelOnError : engine->IO.ConfigVerboseLevel; - ImGuiTestEngine_PrintLogLines(fp, &test_output->Log, 6, level); - } - ImGuiTestEngine_ExportResultSummary(engine, fp, 6, (ImGuiTestGroup)testsuite_id); - fprintf(fp, " </system-out>\n"); - - // Log all warning and error messages as "stderr". - fprintf(fp, " <system-err>\n"); - for (int n = 0; n < engine->TestsAll.Size; n++) - { - ImGuiTest* test = engine->TestsAll[n]; - ImGuiTestOutput* test_output = &test->Output; - if (test->Group != testsuite_id) - continue; - if (test_output->Status == ImGuiTestStatus_Unknown) - continue; - fprintf(fp, " [0000] Test: '%s' '%s'..\n", test->Category, test->Name); - ImGuiTestEngine_PrintLogLines(fp, &test_output->Log, 6, ImGuiTestVerboseLevel_Warning); - } - ImGuiTestEngine_ExportResultSummary(engine, fp, 6, (ImGuiTestGroup)testsuite_id); - fprintf(fp, " </system-err>\n"); - } - fprintf(fp, " </testsuite>\n"); - } - fprintf(fp, "</testsuites>\n"); - fclose(fp); - fprintf(stdout, "Saved test results to '%s' successfully.\n", output_file); -} diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_exporters.h b/vendor/zgui/libs/imgui_test_engine/imgui_te_exporters.h deleted file mode 100644 index 49392a8..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_exporters.h +++ /dev/null @@ -1,59 +0,0 @@ -// dear imgui test engine -// (result exporters) -// Read https://github.com/ocornut/imgui_test_engine/wiki/Exporting-Results - -#pragma once - -//------------------------------------------------------------------------- -// Description -//------------------------------------------------------------------------- -// -// Test results may be exported in one of supported formats. -// To enable result exporting please configure test engine as follows: -// -// ImGuiTestEngineIO& test_io = ImGuiTestEngine_GetIO(engine); -// test_io.ExportResultsFile = "output_file.xml"; -// test_io.ExportResultsFormat = ImGuiTestEngineExportFormat_<...>; -// -// JUnit XML format -//------------------ -// JUnit XML format described at https://llg.cubic.org/docs/junit/. Many -// third party applications support consumption of this format. Some of -// of them are listed here: -// - Jenkins -// - Installation guide: https://www.jenkins.io/doc/book/installing/docker/ -// - JUnit plugin: https://plugins.jenkins.io/junit/ -// - xunit-viewer -// - Project: https://github.com/lukejpreston/xunit-viewer -// - Install npm: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm -// - Install viewer and view test results: -// npm install xunit-viewer -// imgui_test_suite -nopause -v2 -ve4 -nogui -export-file junit.xml tests -// node_modules/xunit-viewer/bin/xunit-viewer -r junit.xml -o junit.html -// - Open junit.html -// - -//------------------------------------------------------------------------- -// Forward Declarations -//------------------------------------------------------------------------- - -struct ImGuiTestEngine; - -//------------------------------------------------------------------------- -// Types -//------------------------------------------------------------------------- - -enum ImGuiTestEngineExportFormat : int -{ - ImGuiTestEngineExportFormat_None = 0, - ImGuiTestEngineExportFormat_JUnitXml, -}; - -//------------------------------------------------------------------------- -// Functions -//------------------------------------------------------------------------- - -void ImGuiTestEngine_PrintResultSummary(ImGuiTestEngine* engine); - -void ImGuiTestEngine_Export(ImGuiTestEngine* engine); -void ImGuiTestEngine_ExportEx(ImGuiTestEngine* engine, ImGuiTestEngineExportFormat format, const char* filename); diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_imconfig.h b/vendor/zgui/libs/imgui_test_engine/imgui_te_imconfig.h deleted file mode 100644 index e68b7d9..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_imconfig.h +++ /dev/null @@ -1,57 +0,0 @@ -// dear imgui test engine -// (template for compile-time configuration) -// Replicate or #include this file in your imconfig.h to enable test engine. - -// Compile Dear ImGui with test engine hooks -// (Important: This is a value-less define, to be consistent with other defines used in core dear imgui.) -#define IMGUI_ENABLE_TEST_ENGINE - -// [Optional, default 0] Enable plotting of perflog data for comparing performance of different runs. -// This feature requires ImPlot to be linked in the application. -#ifndef IMGUI_TEST_ENGINE_ENABLE_IMPLOT -#define IMGUI_TEST_ENGINE_ENABLE_IMPLOT 0 -#endif - -// [Optional, default 1] Enable screen capture and PNG/GIF saving functionalities -// There's not much point to disable this but we provide it to reassure user that the dependencies on imstb_image_write.h and ffmpeg are technically optional. -#ifndef IMGUI_TEST_ENGINE_ENABLE_CAPTURE -#define IMGUI_TEST_ENGINE_ENABLE_CAPTURE 1 -#endif - -// [Optional, default 0] Using std::function and <functional> for function pointers such as ImGuiTest::TestFunc and ImGuiTest::GuiFunc -#ifndef IMGUI_TEST_ENGINE_ENABLE_STD_FUNCTION -#define IMGUI_TEST_ENGINE_ENABLE_STD_FUNCTION 0 -#endif - -// [Optional, default 0] Automatically fill ImGuiTestEngineIO::CoroutineFuncs with a default implementation using std::thread -#ifndef IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL -#define IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL 0 -#endif - -// Define IM_DEBUG_BREAK macros so it is accessible in imgui.h -// (this is a conveniance for app using test engine may define an IM_ASSERT() that uses this instead of an actual assert) -// (this is a copy of the block in imgui_internal.h. if the one in imgui_internal.h were to be defined at the top of imgui.h we wouldn't need this) -#ifndef IM_DEBUG_BREAK -#if defined (_MSC_VER) -#define IM_DEBUG_BREAK() __debugbreak() -#elif defined(__clang__) -#define IM_DEBUG_BREAK() __builtin_debugtrap() -#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) -#define IM_DEBUG_BREAK() __asm__ volatile("int $0x03") -#elif defined(__GNUC__) && defined(__thumb__) -#define IM_DEBUG_BREAK() __asm__ volatile(".inst 0xde01") -#elif defined(__GNUC__) && defined(__arm__) && !defined(__thumb__) -#define IM_DEBUG_BREAK() __asm__ volatile(".inst 0xe7f001f0"); -#else -#define IM_DEBUG_BREAK() IM_ASSERT(0) // It is expected that you define IM_DEBUG_BREAK() into something that will break nicely in a debugger! -#endif -#endif // #ifndef IMGUI_DEBUG_BREAK - -// [Options] We provide custom assert macro used by our our test suite, which you may use: -// - Calling IM_DEBUG_BREAK() instead of an actual assert, so we can easily recover and step over (compared to many assert implementations). -// - If a test is running, test name will be included in the log. -// - Macro is calling IM_DEBUG_BREAK() inline to get debugger to break in the calling function (instead of a deeper callstack level). -// - Macro is using comma operator instead of an if() to avoid "conditional expression is constant" warnings. -extern void ImGuiTestEngine_AssertLog(const char* expr, const char* file, const char* func, int line); -#define IM_TEST_ENGINE_ASSERT(_EXPR) do { if ((void)0, !(_EXPR)) { ImGuiTestEngine_AssertLog(#_EXPR, __FILE__, __func__, __LINE__); IM_DEBUG_BREAK(); } } while (0) -// V_ASSERT_CONTRACT, assertMacro:IM_ASSERT diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_internal.h b/vendor/zgui/libs/imgui_test_engine/imgui_te_internal.h deleted file mode 100644 index 4b20438..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_internal.h +++ /dev/null @@ -1,213 +0,0 @@ -// dear imgui test engine -// (internal api) - -#pragma once - -#include "imgui_te_coroutine.h" -#include "imgui_te_utils.h" // ImMovingAverage -#include "imgui_capture_tool.h" // ImGuiCaptureTool // FIXME - -//------------------------------------------------------------------------- -// FORWARD DECLARATIONS -//------------------------------------------------------------------------- - -class Str; // Str<> from thirdparty/Str/Str.h -struct ImGuiPerfTool; - -//------------------------------------------------------------------------- -// DATA STRUCTURES -//------------------------------------------------------------------------- - -// Query item position/window/state given ID. -struct ImGuiTestInfoTask -{ - // Input - ImGuiID ID = 0; - int FrameCount = -1; // Timestamp of request - char DebugName[64] = ""; // Debug string representing the queried ID - - // Output - ImGuiTestItemInfo Result; -}; - -// Gather item list in given parent ID. -struct ImGuiTestGatherTask -{ - // Input - ImGuiID InParentID = 0; - int InMaxDepth = 0; - short InLayerMask = 0; - - // Output/Temp - ImGuiTestItemList* OutList = NULL; - ImGuiTestItemInfo* LastItemInfo = NULL; - - void Clear() { memset(this, 0, sizeof(*this)); } -}; - -// Find item ID given a label and a parent id -// Usually used by queries with wildcards such as ItemInfo("hello/**/foo/bar") -struct ImGuiTestFindByLabelTask -{ - // Input - ImGuiID InPrefixId = 0; // A known base ID which appears BEFORE the wildcard ID (for "hello/**/foo/bar" it would be hash of "hello") - int InSuffixDepth = 0; // Number of labels in a path, after unknown base ID (for "hello/**/foo/bar" it would be 2) - const char* InSuffix = NULL; // A label string which appears on ID stack after unknown base ID (for "hello/**/foo/bar" it would be "foo/bar") - const char* InSuffixLastItem = NULL; // A last label string (for "hello/**/foo/bar" it would be "bar") - ImGuiID InSuffixLastItemHash = 0; - ImGuiItemStatusFlags InFilterItemStatusFlags = 0; // Flags required for item to be returned - - // Output - ImGuiID OutItemId = 0; // Result item ID -}; - -enum ImGuiTestInputType -{ - ImGuiTestInputType_None, - ImGuiTestInputType_Key, - ImGuiTestInputType_Char, - ImGuiTestInputType_ViewportFocus, - ImGuiTestInputType_ViewportClose -}; - -// FIXME: May want to strip further now that core imgui is using its own input queue -struct ImGuiTestInput -{ - ImGuiTestInputType Type = ImGuiTestInputType_None; - ImGuiKeyChord KeyChord = ImGuiKey_None; - ImWchar Char = 0; - bool Down = false; - ImGuiID ViewportId = 0; - - static ImGuiTestInput ForKeyChord(ImGuiKeyChord key_chord, bool down) - { - ImGuiTestInput inp; - inp.Type = ImGuiTestInputType_Key; - inp.KeyChord = key_chord; - inp.Down = down; - return inp; - } - - static ImGuiTestInput ForChar(ImWchar v) - { - ImGuiTestInput inp; - inp.Type = ImGuiTestInputType_Char; - inp.Char = v; - return inp; - } - - static ImGuiTestInput ForViewportFocus(ImGuiID viewport_id) - { - ImGuiTestInput inp; - inp.Type = ImGuiTestInputType_ViewportFocus; - inp.ViewportId = viewport_id; - return inp; - } - - static ImGuiTestInput ForViewportClose(ImGuiID viewport_id) - { - ImGuiTestInput inp; - inp.Type = ImGuiTestInputType_ViewportClose; - inp.ViewportId = viewport_id; - return inp; - } -}; - -struct ImGuiTestInputs -{ - ImVec2 MousePosValue; // Own non-rounded copy of MousePos in order facilitate simulating mouse movement very slow speed and high-framerate - ImVec2 MouseWheel; - ImGuiID MouseHoveredViewport = 0; - int MouseButtonsValue = 0x00; // FIXME-TESTS: Use simulated_io.MouseDown[] ? - ImVector<ImGuiTestInput> Queue; - bool HostEscDown = false; - float HostEscDownDuration = -1.0f; // Maintain our own DownDuration for host/backend ESC key so we can abort. -}; - -// [Internal] Test Engine Context -struct ImGuiTestEngine -{ - ImGuiTestEngineIO IO; - ImGuiContext* UiContextTarget = NULL; // imgui context for testing - ImGuiContext* UiContextActive = NULL; // imgui context for testing == UiContextTarget or NULL - - bool Started = false; - ImU64 BatchStartTime = 0; - ImU64 BatchEndTime = 0; - int FrameCount = 0; - float OverrideDeltaTime = -1.0f; // Inject custom delta time into imgui context to simulate clock passing faster than wall clock time. - ImVector<ImGuiTest*> TestsAll; - ImVector<ImGuiTestRunTask> TestsQueue; - ImGuiTestContext* TestContext = NULL; - ImVector<ImGuiTestInfoTask*>InfoTasks; - ImGuiTestGatherTask GatherTask; - ImGuiTestFindByLabelTask FindByLabelTask; - ImGuiTestCoroutineHandle TestQueueCoroutine = NULL; // Coroutine to run the test queue - bool TestQueueCoroutineShouldExit = false; // Flag to indicate that we are shutting down and the test queue coroutine should stop - - // Inputs - ImGuiTestInputs Inputs; - - // UI support - bool Abort = false; - ImGuiTest* UiSelectAndScrollToTest = NULL; - ImGuiTest* UiSelectedTest = NULL; - Str* UiFilterTests; - Str* UiFilterPerfs; - ImU32 UiFilterByStatusMask = ~0u; - bool UiMetricsOpen = false; - bool UiDebugLogOpen = false; - bool UiCaptureToolOpen = false; - bool UiStackToolOpen = false; - bool UiPerfToolOpen = false; - float UiLogHeight = 150.0f; - - // Performance Monitor - double PerfRefDeltaTime; - ImMovingAverage<double> PerfDeltaTime100; - ImMovingAverage<double> PerfDeltaTime500; - ImGuiPerfTool* PerfTool = NULL; - - // Screen/Video Capturing - ImGuiCaptureToolUI CaptureTool; // Capture tool UI - ImGuiCaptureContext CaptureContext; // Capture context used in tests - ImGuiCaptureArgs* CaptureCurrentArgs = NULL; - - // Tools - bool PostSwapCalled = false; - bool ToolDebugRebootUiContext = false; // Completely shutdown and recreate the dear imgui context in place - bool ToolSlowDown = false; - int ToolSlowDownMs = 100; - ImGuiTestRunSpeed BackupConfigRunSpeed = ImGuiTestRunSpeed_Fast; - bool BackupConfigNoThrottle = false; - - // Functions - ImGuiTestEngine(); - ~ImGuiTestEngine(); -}; - -//------------------------------------------------------------------------- -// INTERNAL FUNCTIONS -//------------------------------------------------------------------------- - -ImGuiTestItemInfo* ImGuiTestEngine_FindItemInfo(ImGuiTestEngine* engine, ImGuiID id, const char* debug_id); -void ImGuiTestEngine_Yield(ImGuiTestEngine* engine); -void ImGuiTestEngine_SetDeltaTime(ImGuiTestEngine* engine, float delta_time); -int ImGuiTestEngine_GetFrameCount(ImGuiTestEngine* engine); -bool ImGuiTestEngine_PassFilter(ImGuiTest* test, const char* filter); -void ImGuiTestEngine_RunTest(ImGuiTestEngine* engine, ImGuiTestContext* ctx, ImGuiTest* test, ImGuiTestRunFlags run_flags); - -void ImGuiTestEngine_RebootUiContext(ImGuiTestEngine* engine); -ImGuiPerfTool* ImGuiTestEngine_GetPerfTool(ImGuiTestEngine* engine); - -// Screen/Video Capturing -bool ImGuiTestEngine_CaptureScreenshot(ImGuiTestEngine* engine, ImGuiCaptureArgs* args); -bool ImGuiTestEngine_CaptureBeginVideo(ImGuiTestEngine* engine, ImGuiCaptureArgs* args); -bool ImGuiTestEngine_CaptureEndVideo(ImGuiTestEngine* engine, ImGuiCaptureArgs* args); - -// Helper functions -const char* ImGuiTestEngine_GetStatusName(ImGuiTestStatus v); -const char* ImGuiTestEngine_GetRunSpeedName(ImGuiTestRunSpeed v); -const char* ImGuiTestEngine_GetVerboseLevelName(ImGuiTestVerboseLevel v); - -//------------------------------------------------------------------------- diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_perftool.cpp b/vendor/zgui/libs/imgui_test_engine/imgui_te_perftool.cpp deleted file mode 100644 index 65b853a..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_perftool.cpp +++ /dev/null @@ -1,1949 +0,0 @@ -// dear imgui test engine -// (performance tool) -// Browse and visualize samples recorded by ctx->PerfCapture() calls. -// User access via 'Test Engine UI -> Tools -> Perf Tool' - -/* - -Index of this file: -// [SECTION] Header mess -// [SECTION] ImGuiPerflogEntry -// [SECTION] Types & everything else -// [SECTION] USER INTERFACE -// [SECTION] SETTINGS -// [SECTION] TESTS - -*/ - -// Terminology: -// * Entry: information about execution of a single perf test. This corresponds to one line in CSV file. -// * Batch: a group of entries that were created together during a single execution. A new batch is created each time -// one or more perf tests are executed. All entries in a single batch will have a matching ImGuiPerflogEntry::Timestamp. -// * Build: A group of batches that have matching BuildType, OS, Cpu, Compiler, GitBranchName. -// * Baseline: A batch that we are comparing against. Baselines are identified by batch timestamp and build id. - -//------------------------------------------------------------------------- -// [SECTION] Header mess -//------------------------------------------------------------------------- - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -#define _CRT_SECURE_NO_WARNINGS -#endif - -#define IMGUI_DEFINE_MATH_OPERATORS -#include "imgui_te_perftool.h" -#include "imgui.h" -#include "imgui_internal.h" -#include "imgui_te_utils.h" -#include "thirdparty/Str/Str.h" -#include <time.h> // time(), localtime() -#if IMGUI_TEST_ENGINE_ENABLE_IMPLOT -#include "implot.h" -#include "implot_internal.h" -#endif - -// For tests -#include "imgui_te_engine.h" -#include "imgui_te_context.h" -#include "imgui_te_internal.h" // ImGuiTestEngine_GetPerfTool() -#include "imgui_capture_tool.h" - -//------------------------------------------------------------------------- -// [SECTION] ImGuiPerflogEntry -//------------------------------------------------------------------------- - -void ImGuiPerfToolEntry::Set(const ImGuiPerfToolEntry& other) -{ - Timestamp = other.Timestamp; - Category = other.Category; - TestName = other.TestName; - DtDeltaMs = other.DtDeltaMs; - DtDeltaMsMin = other.DtDeltaMsMin; - DtDeltaMsMax = other.DtDeltaMsMax; - NumSamples = other.NumSamples; - PerfStressAmount = other.PerfStressAmount; - GitBranchName = other.GitBranchName; - BuildType = other.BuildType; - Cpu = other.Cpu; - OS = other.OS; - Compiler = other.Compiler; - Date = other.Date; - //DateMax = ... - VsBaseline = other.VsBaseline; - LabelIndex = other.LabelIndex; -} - -//------------------------------------------------------------------------- -// [SECTION] Types & everything else -//------------------------------------------------------------------------- - -typedef ImGuiID(*HashEntryFn)(ImGuiPerfToolEntry* entry); -typedef void(*FormatEntryLabelFn)(ImGuiPerfTool* perftool, Str* result, ImGuiPerfToolEntry* entry); - -struct ImGuiPerfToolColumnInfo -{ - const char* Title; - int Offset; - ImGuiDataType Type; - bool ShowAlways; - ImGuiTableFlags Flags; - - template<typename T> - T GetValue(const ImGuiPerfToolEntry* entry) const { return *(T*)((const char*)entry + Offset); } -}; - -// Update _ShowEntriesTable() and SaveHtmlReport() when adding new entries. -static const ImGuiPerfToolColumnInfo PerfToolColumnInfo[] = -{ - { /* 00 */ "Date", offsetof(ImGuiPerfToolEntry, Timestamp), ImGuiDataType_U64, true, ImGuiTableColumnFlags_DefaultHide }, - { /* 01 */ "Test Name", offsetof(ImGuiPerfToolEntry, TestName), ImGuiDataType_COUNT, true, 0 }, - { /* 02 */ "Branch", offsetof(ImGuiPerfToolEntry, GitBranchName), ImGuiDataType_COUNT, true, 0 }, - { /* 03 */ "Compiler", offsetof(ImGuiPerfToolEntry, Compiler), ImGuiDataType_COUNT, true, 0 }, - { /* 04 */ "OS", offsetof(ImGuiPerfToolEntry, OS), ImGuiDataType_COUNT, true, 0 }, - { /* 05 */ "CPU", offsetof(ImGuiPerfToolEntry, Cpu), ImGuiDataType_COUNT, true, 0 }, - { /* 06 */ "Build", offsetof(ImGuiPerfToolEntry, BuildType), ImGuiDataType_COUNT, true, 0 }, - { /* 07 */ "Stress", offsetof(ImGuiPerfToolEntry, PerfStressAmount), ImGuiDataType_S32, true, 0 }, - { /* 08 */ "Avg ms", offsetof(ImGuiPerfToolEntry, DtDeltaMs), ImGuiDataType_Double, true, 0 }, - { /* 09 */ "Min ms", offsetof(ImGuiPerfToolEntry, DtDeltaMsMin), ImGuiDataType_Double, false, 0 }, - { /* 00 */ "Max ms", offsetof(ImGuiPerfToolEntry, DtDeltaMsMax), ImGuiDataType_Double, false, 0 }, - { /* 11 */ "Samples", offsetof(ImGuiPerfToolEntry, NumSamples), ImGuiDataType_S32, false, 0 }, - { /* 12 */ "VS Baseline", offsetof(ImGuiPerfToolEntry, VsBaseline), ImGuiDataType_Float, true, 0 }, -}; - -static const char* PerfToolReportDefaultOutputPath = "./output/capture_perf_report.html"; - -// This is declared as a standalone function in order to run without a PerfTool instance -void ImGuiTestEngine_PerfToolAppendToCSV(ImGuiPerfTool* perf_log, ImGuiPerfToolEntry* entry, const char* filename) -{ - if (filename == NULL) - filename = IMGUI_PERFLOG_DEFAULT_FILENAME; - - if (!ImFileCreateDirectoryChain(filename, ImPathFindFilename(filename))) - { - fprintf(stderr, "Unable to create missing directory '%*s', perftool entry was not saved.\n", (int)(ImPathFindFilename(filename) - filename), filename); - return; - } - - // Appends to .csv - FILE* f = fopen(filename, "a+b"); - if (f == NULL) - { - fprintf(stderr, "Unable to open '%s', perftool entry was not saved.\n", filename); - return; - } - fprintf(f, "%llu,%s,%s,%.3f,x%d,%s,%s,%s,%s,%s,%s\n", entry->Timestamp, entry->Category, entry->TestName, - entry->DtDeltaMs, entry->PerfStressAmount, entry->GitBranchName, entry->BuildType, entry->Cpu, entry->OS, - entry->Compiler, entry->Date); - fflush(f); - fclose(f); - - // Register to runtime perf tool if any - if (perf_log != NULL) - perf_log->AddEntry(entry); -} - -// Tri-state button. Copied and modified ButtonEx(). -static bool Button3(const char* label, int* value) -{ - ImGuiWindow* window = ImGui::GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); - float dot_radius2 = g.FontSize; - ImVec2 btn_size(dot_radius2 * 2, dot_radius2); - - ImVec2 pos = window->DC.CursorPos; - ImVec2 size = ImGui::CalcItemSize(ImVec2(), btn_size.x + label_size.x + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x, label_size.y + style.FramePadding.y * 2.0f); - - const ImRect bb(pos, pos + size); - ImGui::ItemSize(size, style.FramePadding.y); - if (!ImGui::ItemAdd(bb, id)) - return false; - - bool hovered, held; - bool pressed = ImGui::ButtonBehavior(ImRect(pos, pos + style.FramePadding + btn_size), id, &hovered, &held, 0); - - // Render - const ImU32 col = ImGui::GetColorU32(ImGuiCol_FrameBg); - ImGui::RenderNavHighlight(bb, id); - ImGui::RenderFrame(bb.Min + style.FramePadding, bb.Min + style.FramePadding + btn_size, col, true, /*style.FrameRounding*/ 5.0f); - - ImColor btn_col; - if (held) - btn_col = style.Colors[ImGuiCol_SliderGrabActive]; - else if (hovered) - btn_col = style.Colors[ImGuiCol_ButtonHovered]; - else - btn_col = style.Colors[ImGuiCol_SliderGrab]; - ImVec2 center = bb.Min + ImVec2(dot_radius2 + (dot_radius2 * (float)*value), dot_radius2) * 0.5f + style.FramePadding; - window->DrawList->AddCircleFilled(center, dot_radius2 * 0.5f, btn_col); - - ImRect text_bb; - text_bb.Min = bb.Min + style.FramePadding + ImVec2(btn_size.x + style.ItemInnerSpacing.x, 0); - text_bb.Max = text_bb.Min + label_size; - ImGui::RenderTextClipped(text_bb.Min, text_bb.Max, label, NULL, &label_size, style.ButtonTextAlign, &bb); - - *value = (*value + pressed) % 3; - return pressed; -} - -static ImGuiID GetBuildID(const ImGuiPerfToolEntry* entry) -{ - IM_ASSERT(entry != NULL); - ImGuiID build_id = ImHashStr(entry->BuildType); - build_id = ImHashStr(entry->OS, 0, build_id); - build_id = ImHashStr(entry->Cpu, 0, build_id); - build_id = ImHashStr(entry->Compiler, 0, build_id); - build_id = ImHashStr(entry->GitBranchName, 0, build_id); - return build_id; -} - -static ImGuiID GetBuildID(const ImGuiPerfToolBatch* batch) -{ - IM_ASSERT(batch != NULL); - IM_ASSERT(!batch->Entries.empty()); - return GetBuildID(&batch->Entries.Data[0]); -} - -// Batch ID depends on display type. It is either a build ID (when combinding by build type) or batch timestamp otherwise. -static ImGuiID GetBatchID(const ImGuiPerfTool* perftool, const ImGuiPerfToolEntry* entry) -{ - IM_ASSERT(perftool != NULL); - IM_ASSERT(entry != NULL); - if (perftool->_DisplayType == ImGuiPerfToolDisplayType_CombineByBuildInfo) - return GetBuildID(entry); - else - return (ImU32)entry->Timestamp; -} - -static int PerfToolComparerStr(const void* a, const void* b) -{ - return strcmp(*(const char**)b, *(const char**)a); -} - -static int IMGUI_CDECL PerfToolComparerByEntryInfo(const void* lhs, const void* rhs) -{ - const ImGuiPerfToolEntry* a = (const ImGuiPerfToolEntry*)lhs; - const ImGuiPerfToolEntry* b = (const ImGuiPerfToolEntry*)rhs; - - // While build ID does include git branch it wont ensure branches are grouped together, therefore we do branch - // sorting manually. - int result = strcmp(a->GitBranchName, b->GitBranchName); - - // Now that we have groups of branches - sort individual builds within those groups. - if (result == 0) - result = ImClamp<int>((int)((ImS64)GetBuildID(a) - (ImS64)GetBuildID(b)), -1, +1); - - // Group individual runs together within build groups. - if (result == 0) - result = (int)ImClamp<ImS64>((ImS64)b->Timestamp - (ImS64)a->Timestamp, -1, +1); - - // And finally sort individual runs by perf name so we can have a predictable order (used to optimize in _Rebuild()). - if (result == 0) - result = (int)strcmp(a->TestName, b->TestName); - - return result; -} - -static ImGuiPerfTool* PerfToolInstance = NULL; -static int IMGUI_CDECL CompareWithSortSpecs(const void* lhs, const void* rhs) -{ - IM_ASSERT(PerfToolInstance != NULL); - ImGuiPerfTool* tool = PerfToolInstance; - const ImGuiTableSortSpecs* sort_specs = PerfToolInstance->_InfoTableSortSpecs; - int batch_index_a, entry_index_a, mono_index_a, batch_index_b, entry_index_b, mono_index_b; - tool->_UnpackSortedKey(*(ImU64*)lhs, &batch_index_a, &entry_index_a, &mono_index_a); - tool->_UnpackSortedKey(*(ImU64*)rhs, &batch_index_b, &entry_index_b, &mono_index_b); - for (int i = 0; i < sort_specs->SpecsCount; i++) - { - const ImGuiTableColumnSortSpecs* specs = &sort_specs->Specs[i]; - const ImGuiPerfToolColumnInfo& col_info = PerfToolColumnInfo[specs->ColumnIndex]; - const ImGuiPerfToolBatch* batch_a = &tool->_Batches[batch_index_a]; - const ImGuiPerfToolBatch* batch_b = &tool->_Batches[batch_index_b]; - ImGuiPerfToolEntry* a = &batch_a->Entries.Data[entry_index_a]; - ImGuiPerfToolEntry* b = &batch_b->Entries.Data[entry_index_b]; - if (specs->SortDirection == ImGuiSortDirection_Ascending) - ImSwap(a, b); - - int result = 0; - switch (col_info.Type) - { - case ImGuiDataType_S32: - result = col_info.GetValue<int>(a) - col_info.GetValue<int>(b); - break; - case ImGuiDataType_U64: - result = (int)(col_info.GetValue<ImU64>(a) - col_info.GetValue<ImU64>(b)); - break; - case ImGuiDataType_Float: - result = (int)((col_info.GetValue<float>(a) - col_info.GetValue<float>(b)) * 1000.0f); - break; - case ImGuiDataType_Double: - result = (int)((col_info.GetValue<double>(a) - col_info.GetValue<double>(b)) * 1000.0); - break; - case ImGuiDataType_COUNT: - result = strcmp(col_info.GetValue<const char*>(a), col_info.GetValue<const char*>(b)); - break; - default: - IM_ASSERT(false); - } - if (result != 0) - return result; - } - return mono_index_a - mono_index_b; -} - -// Dates are in format "YYYY-MM-DD" -static bool IsDateValid(const char* date) -{ - if (date[4] != '-' || date[7] != '-') - return false; - for (int i = 0; i < 10; i++) - { - if (i == 4 || i == 7) - continue; - if (date[i] < '0' || date[i] > '9') - return false; - } - return true; -} - -static float FormatVsBaseline(ImGuiPerfToolEntry* entry, ImGuiPerfToolEntry* baseline_entry, Str& out_label) -{ - if (baseline_entry == NULL) - { - out_label.appendf("--"); - return FLT_MAX; - } - - if (entry == baseline_entry) - { - out_label.append("baseline"); - return FLT_MAX; - } - - double percent_vs_first = 100.0 / baseline_entry->DtDeltaMs * entry->DtDeltaMs; - double dt_change = -(100.0 - percent_vs_first); - if (dt_change == INFINITY) - out_label.appendf("--"); - else if (ImAbs(dt_change) > 0.001f) - out_label.appendf("%+.2lf%% (%s)", dt_change, dt_change < 0.0f ? "faster" : "slower"); - else - out_label.appendf("=="); - return (float)dt_change; -} - -#if IMGUI_TEST_ENGINE_ENABLE_IMPLOT -static void PerfToolFormatBuildInfo(ImGuiPerfTool* perftool, Str* result, ImGuiPerfToolBatch* batch) -{ - IM_ASSERT(perftool != NULL); - IM_ASSERT(result != NULL); - IM_ASSERT(batch != NULL); - IM_ASSERT(batch->Entries.Size > 0); - ImGuiPerfToolEntry* entry = &batch->Entries.Data[0]; - Str64f legend_format("x%%-%dd %%-%ds %%-%ds %%-%ds %%-%ds %%-%ds %%s%%s%%s%%s(%%-%dd sample%%s)%%s", - perftool->_AlignStress, perftool->_AlignType, perftool->_AlignCpu, perftool->_AlignOs, perftool->_AlignCompiler, - perftool->_AlignBranch, perftool->_AlignSamples); - result->appendf(legend_format.c_str(), entry->PerfStressAmount, entry->BuildType, entry->Cpu, entry->OS, - entry->Compiler, entry->GitBranchName, entry->Date, -#if 0 - // Show min-max dates. - perftool->_CombineByBuildInfo ? " - " : "", - entry->DateMax ? entry->DateMax : "", -#else - "", "", -#endif - *entry->Date ? " " : "", - batch->NumSamples, - batch->NumSamples > 1 ? "s" : "", // Singular/plural form of "sample(s)" - batch->NumSamples > 1 || perftool->_AlignSamples == 1 ? "" : " " // Space after legend entry to separate * marking baseline - ); -} -#endif - -static int PerfToolCountBuilds(ImGuiPerfTool* perftool, bool only_visible) -{ - int num_builds = 0; - ImU64 build_id = 0; - for (ImGuiPerfToolEntry& entry : perftool->_SrcData) - { - if (build_id != GetBuildID(&entry)) - { - if (!only_visible || perftool->_IsVisibleBuild(&entry)) - num_builds++; - build_id = GetBuildID(&entry); - } - } - return num_builds; -} - -static bool InputDate(const char* label, char* date, int date_len, bool valid) -{ - ImGui::SetNextItemWidth(ImGui::CalcTextSize("YYYY-MM-DD").x + ImGui::GetStyle().FramePadding.x * 2.0f); - const bool date_valid = date[0] == 0 || (IsDateValid(date) && valid); - if (!date_valid) - { - ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(255, 0, 0, 255)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1); - } - bool date_changed = ImGui::InputTextWithHint(label, "YYYY-MM-DD", date, date_len); - if (!date_valid) - { - ImGui::PopStyleVar(); - ImGui::PopStyleColor(); - } - return date_changed; -} - -static void FormatDate(ImU64 microseconds, char* buf, size_t buf_size) -{ - time_t timestamp = (time_t)(microseconds / 1000000); - tm* time = localtime(×tamp); - ImFormatString(buf, buf_size, "%04d-%02d-%02d", time->tm_year + 1900, time->tm_mon + 1, time->tm_mday); -} - -static void FormatDateAndTime(ImU64 microseconds, char* buf, size_t buf_size) -{ - time_t timestamp = (time_t)(microseconds / 1000000); - tm* time = localtime(×tamp); - ImFormatString(buf, buf_size, "%04d-%02d-%02d %02d:%02d:%02d", time->tm_year + 1900, time->tm_mon + 1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec); -} - -static void RenderFilterInput(ImGuiPerfTool* perf, const char* hint, float width = -FLT_MIN) -{ - if (ImGui::IsWindowAppearing()) - strcpy(perf->_Filter, ""); - ImGui::SetNextItemWidth(width); - ImGui::InputTextWithHint("##filter", hint, perf->_Filter, IM_ARRAYSIZE(perf->_Filter)); - if (ImGui::IsWindowAppearing()) - ImGui::SetKeyboardFocusHere(); -} - -static bool RenderMultiSelectFilter(ImGuiPerfTool* perf, const char* filter_hint, ImVector<const char*>* labels) -{ - ImGuiContext& g = *ImGui::GetCurrentContext(); - ImGuiIO& io = ImGui::GetIO(); - ImGuiStorage& visibility = perf->_Visibility; - bool modified = false; - RenderFilterInput(perf, filter_hint, -(ImGui::CalcTextSize("(?)").x + g.Style.ItemSpacing.x)); - ImGui::SameLine(); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) - 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 (!io.KeyShift) - ImGui::PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true); - - if (ImGui::MenuItem("Show All")) - { - for (const char* label : *labels) - if (strstr(label, perf->_Filter) != NULL) - visibility.SetBool(ImHashStr(label), true); - modified = true; - } - - if (ImGui::MenuItem("Hide All")) - { - for (const char* label : *labels) - if (strstr(label, perf->_Filter) != NULL) - visibility.SetBool(ImHashStr(label), false); - modified = true; - } - - // Render perf labels in reversed order. Labels are sorted, but stored in reversed order to render them on the plot - // from top down (ImPlot renders stuff from bottom up). - int filtered_entries = 0; - for (int i = labels->Size - 1; i >= 0; i--) - { - const char* label = (*labels)[i]; - if (strstr(label, perf->_Filter) == NULL) // Filter out entries not matching a filter query - continue; - - if (filtered_entries == 0) - ImGui::Separator(); - - ImGuiID build_id = ImHashStr(label); - bool visible = visibility.GetBool(build_id, true); - if (ImGui::MenuItem(label, NULL, &visible)) - { - modified = true; - if (io.KeyCtrl) - { - for (const char* label2 : *labels) - { - ImGuiID build_id2 = ImHashStr(label2); - visibility.SetBool(build_id2, !visibility.GetBool(build_id2, true)); - } - } - else - { - visibility.SetBool(build_id, !visibility.GetBool(build_id, true)); - } - } - filtered_entries++; - } - - if (!io.KeyShift) - ImGui::PopItemFlag(); - - return modified; -} - -// Based on ImPlot::SetupFinish(). -#if IMGUI_TEST_ENGINE_ENABLE_IMPLOT -static ImRect ImPlotGetYTickRect(int t, int y = 0) -{ - ImPlotContext& gp = *GImPlot; - ImPlotPlot& plot = *gp.CurrentPlot; - ImPlotAxis& ax = plot.YAxis(y); - const ImPlotTicker& tkc = ax.Ticker; - const bool opp = ax.IsOpposite(); - ImRect result(1.0f, 1.0f, -1.0f, -1.0f); - if (ax.HasTickLabels()) - { - const ImPlotTick& tk = tkc.Ticks[t]; - 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); - result.Min = start; - result.Max = start + tk.LabelSize; - } - } - return result; -} -#endif // #if IMGUI_TEST_ENGINE_ENABLE_IMPLOT - -ImGuiPerfTool::ImGuiPerfTool() -{ - _CsvParser = IM_NEW(ImGuiCsvParser)(); - Clear(); -} - -ImGuiPerfTool::~ImGuiPerfTool() -{ - _SrcData.clear_destruct(); - _Batches.clear_destruct(); - IM_DELETE(_CsvParser); -} - -void ImGuiPerfTool::AddEntry(ImGuiPerfToolEntry* entry) -{ - if (strcmp(_FilterDateFrom, entry->Date) > 0) - ImStrncpy(_FilterDateFrom, entry->Date, IM_ARRAYSIZE(_FilterDateFrom)); - if (strcmp(_FilterDateTo, entry->Date) < 0) - ImStrncpy(_FilterDateTo, entry->Date, IM_ARRAYSIZE(_FilterDateTo)); - - _SrcData.push_back(*entry); - _Batches.clear_destruct(); -} - -void ImGuiPerfTool::_Rebuild() -{ - if (_SrcData.empty()) - return; - - ImGuiStorage& temp_set = _TempSet; - _Labels.resize(0); - _LabelsVisible.resize(0); - _InfoTableSort.resize(0); - _Batches.clear_destruct(); - _InfoTableSortDirty = true; - - // Gather all visible labels. Legend batches will store data in this order. - temp_set.Data.resize(0); // name_id:IsLabelSeen - for (ImGuiPerfToolEntry& entry : _SrcData) - { - ImGuiID name_id = ImHashStr(entry.TestName); - if (!temp_set.GetBool(name_id)) - { - temp_set.SetBool(name_id, true); - _Labels.push_back(entry.TestName); - if (_IsVisibleTest(entry.TestName)) - _LabelsVisible.push_front(entry.TestName); - } - } - int num_visible_labels = _LabelsVisible.Size; - - // Labels are sorted in reverse order so they appear to be oredered from top down. - ImQsort(_Labels.Data, _Labels.Size, sizeof(const char*), &PerfToolComparerStr); - ImQsort(_LabelsVisible.Data, num_visible_labels, sizeof(const char*), &PerfToolComparerStr); - - // _SrcData vector stores sorted raw entries of imgui_perflog.csv. Sorting is very important, - // algorithm depends on data being correctly sorted. Sorting _SrcData is OK, because it is only - // ever appended to and never written out to disk. Entries are sorted by multiple criteria, - // in specified order: - // 1. By branch name - // 2. By build ID - // 3. By run timestamp - // 4. By test name - // This results in a neatly partitioned dataset where similar data is grouped together and where perf test order - // is consistent in all batches. Sorting by build ID _before_ timestamp is also important as we will be aggregating - // entries by build ID instead of timestamp, when appropriate display mode is enabled. - ImQsort(_SrcData.Data, _SrcData.Size, sizeof(ImGuiPerfToolEntry), &PerfToolComparerByEntryInfo); - - // Sort groups of entries into batches. - const bool combine_by_build_info = _DisplayType == ImGuiPerfToolDisplayType_CombineByBuildInfo; - _LabelBarCounts.Data.resize(0); - - // Process all batches. `entry` is always a first batch element (guaranteed by _SrcData being sorted by timestamp). - // At the end of this loop we fast-forward until next batch (first entry having different batch id (which is a - // timestamp or build info)). - for (ImGuiPerfToolEntry* entry = _SrcData.begin(); entry < _SrcData.end();) - { - // Filtered out entries can be safely ignored. Note that entry++ does not follow logic of fast-forwarding to the - // next batch, as found at the end of this loop. This is OK, because all entries belonging to a same batch will - // also have same date. - if ((_FilterDateFrom[0] && strcmp(entry->Date, _FilterDateFrom) < 0) || (_FilterDateTo[0] && strcmp(entry->Date, _FilterDateTo) > 0)) - { - entry++; - continue; - } - - _Batches.push_back(ImGuiPerfToolBatch()); - ImGuiPerfToolBatch& batch = _Batches.back(); - batch.BatchID = GetBatchID(this, entry); - batch.Entries.resize(num_visible_labels); - - // Fill in defaults. Done once before data aggregation loop, because same entry may be touched multiple times in - // the following loop when entries are being combined by build info. - for (int i = 0; i < num_visible_labels; i++) - { - ImGuiPerfToolEntry* e = &batch.Entries.Data[i]; - *e = *entry; - e->DtDeltaMs = 0; - e->NumSamples = 0; - e->LabelIndex = i; - e->TestName = _LabelsVisible.Data[i]; - } - - // Find perf test runs for this particular batch and accumulate them. - for (int i = 0; i < num_visible_labels; i++) - { - // This inner loop walks all entries that belong to current batch. Due to sorting we are sure that batch - // always starts with `entry`, and all entries that belong to a batch (whether we combine by build info or not) - // will be grouped in _SrcData. - ImGuiPerfToolEntry* aggregate = &batch.Entries.Data[i]; - for (ImGuiPerfToolEntry* e = entry; e < _SrcData.end() && GetBatchID(this, e) == batch.BatchID; e++) - { - if (strcmp(e->TestName, aggregate->TestName) != 0) - continue; - aggregate->DtDeltaMs += e->DtDeltaMs; - aggregate->NumSamples++; - aggregate->DtDeltaMsMin = ImMin(aggregate->DtDeltaMsMin, e->DtDeltaMs); - aggregate->DtDeltaMsMax = ImMax(aggregate->DtDeltaMsMax, e->DtDeltaMs); - } - } - - // In case data is combined by build info, DtDeltaMs will be a sum of all combined entries. Average it out. - if (combine_by_build_info) - for (int i = 0; i < num_visible_labels; i++) - { - ImGuiPerfToolEntry* aggregate = &batch.Entries.Data[i]; - if (aggregate->NumSamples > 0) - aggregate->DtDeltaMs /= aggregate->NumSamples; - } - - // Advance to the next batch. - batch.NumSamples = 1; - if (combine_by_build_info) - { - ImU64 last_timestamp = entry->Timestamp; - for (ImGuiID build_id = GetBuildID(entry); entry < _SrcData.end() && build_id == GetBuildID(entry);) - { - // Also count how many unique batches participate in this aggregated batch. - if (entry->Timestamp != last_timestamp) - { - batch.NumSamples++; - last_timestamp = entry->Timestamp; - } - entry++; - } - } - else - { - for (ImU64 timestamp = entry->Timestamp; entry < _SrcData.end() && timestamp == entry->Timestamp;) - entry++; - } - } - - // Create man entries for every batch. - // Pushed after sorting so they are always at the start of the chart. - const char* mean_labels[] = { "harmonic mean", "arithmetic mean", "geometric mean" }; - int num_visible_mean_labels = 0; - for (const char* label : mean_labels) - { - _Labels.push_back(label); - if (_IsVisibleTest(label)) - { - _LabelsVisible.push_back(label); - num_visible_mean_labels++; - } - } - for (ImGuiPerfToolBatch& batch : _Batches) - { - double delta_sum = 0.0; - double delta_prd = 1.0; - double delta_rec = 0.0; - for (int i = 0; i < batch.Entries.Size; i++) - { - ImGuiPerfToolEntry* entry = &batch.Entries.Data[i]; - delta_sum += entry->DtDeltaMs; - delta_prd *= entry->DtDeltaMs; - delta_rec += 1 / entry->DtDeltaMs; - } - - int visible_label_i = 0; - for (int i = 0; i < IM_ARRAYSIZE(mean_labels); i++) - { - if (!_IsVisibleTest(mean_labels[i])) - continue; - - batch.Entries.push_back(ImGuiPerfToolEntry()); - ImGuiPerfToolEntry* mean_entry = &batch.Entries.back(); - *mean_entry = batch.Entries.Data[0]; - mean_entry->LabelIndex = _LabelsVisible.Size - num_visible_mean_labels + visible_label_i; - mean_entry->TestName = _LabelsVisible.Data[mean_entry->LabelIndex]; - mean_entry->GitBranchName = ""; - mean_entry->BuildType = ""; - mean_entry->Compiler = ""; - mean_entry->OS = ""; - mean_entry->Cpu = ""; - mean_entry->Date = ""; - visible_label_i++; - if (i == 0) - mean_entry->DtDeltaMs = num_visible_labels / delta_rec; - else if (i == 1) - mean_entry->DtDeltaMs = delta_sum / num_visible_labels; - else if (i == 2) - mean_entry->DtDeltaMs = pow(delta_prd, 1.0 / num_visible_labels); - else - IM_ASSERT(0); - } - IM_ASSERT(batch.Entries.Size == _LabelsVisible.Size); - } - - // Find number of bars (batches) each label will render. - for (ImGuiPerfToolBatch& batch : _Batches) - { - if (!_IsVisibleBuild(&batch)) - continue; - - for (ImGuiPerfToolEntry& entry : batch.Entries) - { - ImGuiID label_id = ImHashStr(entry.TestName); - int num_bars = _LabelBarCounts.GetInt(label_id) + 1; - _LabelBarCounts.SetInt(label_id, num_bars); - } - } - - // Index branches, used for per-branch colors. - temp_set.Data.resize(0); // ImHashStr(branch_name):linear_index - int branch_index_last = 0; - _BaselineBatchIndex = -1; - for (ImGuiPerfToolBatch& batch : _Batches) - { - if (batch.Entries.empty()) - continue; - ImGuiPerfToolEntry* entry = &batch.Entries.Data[0]; - ImGuiID branch_hash = ImHashStr(entry->GitBranchName); - batch.BranchIndex = temp_set.GetInt(branch_hash, -1); - if (batch.BranchIndex < 0) - { - batch.BranchIndex = branch_index_last++; - temp_set.SetInt(branch_hash, batch.BranchIndex); - } - - if (_BaselineBatchIndex < 0) - if ((combine_by_build_info && GetBuildID(entry) == _BaselineBuildId) || _BaselineTimestamp == entry->Timestamp) - _BaselineBatchIndex = _Batches.index_from_ptr(&batch); - } - - // When per-branch colors are enabled we aggregate sample counts and set them to all batches with identical build info. - temp_set.Data.resize(0); // build_id:TotalSamples - if (_DisplayType == ImGuiPerfToolDisplayType_PerBranchColors) - { - // Aggregate totals to temp_set. - for (ImGuiPerfToolBatch& batch : _Batches) - { - ImGuiID build_id = GetBuildID(&batch); - temp_set.SetInt(build_id, temp_set.GetInt(build_id, 0) + batch.NumSamples); - } - - // Fill in batch sample counts. - for (ImGuiPerfToolBatch& batch : _Batches) - { - ImGuiID build_id = GetBuildID(&batch); - batch.NumSamples = temp_set.GetInt(build_id, 1); - } - } - - _NumVisibleBuilds = PerfToolCountBuilds(this, true); - _NumUniqueBuilds = PerfToolCountBuilds(this, false); - - _CalculateLegendAlignment(); - temp_set.Data.resize(0); -} - -void ImGuiPerfTool::Clear() -{ - _Labels.clear(); - _LabelsVisible.clear(); - _Batches.clear_destruct(); - _Visibility.Clear(); - _SrcData.clear_destruct(); - _CsvParser->Clear(); - - ImStrncpy(_FilterDateFrom, "9999-99-99", IM_ARRAYSIZE(_FilterDateFrom)); - ImStrncpy(_FilterDateTo, "0000-00-00", IM_ARRAYSIZE(_FilterDateFrom)); -} - -bool ImGuiPerfTool::LoadCSV(const char* filename) -{ - if (filename == NULL) - filename = IMGUI_PERFLOG_DEFAULT_FILENAME; - - Clear(); - - ImGuiCsvParser* parser = _CsvParser; - parser->Columns = 11; - if (!parser->Load(filename)) - return false; - - // Read perf test entries from CSV - for (int row = 0; row < parser->Rows; row++) - { - ImGuiPerfToolEntry entry; - int col = 0; - sscanf(parser->GetCell(row, col++), "%llu", &entry.Timestamp); - entry.Category = parser->GetCell(row, col++); - entry.TestName = parser->GetCell(row, col++); - sscanf(parser->GetCell(row, col++), "%lf", &entry.DtDeltaMs); - sscanf(parser->GetCell(row, col++), "x%d", &entry.PerfStressAmount); - entry.GitBranchName = parser->GetCell(row, col++); - entry.BuildType = parser->GetCell(row, col++); - entry.Cpu = parser->GetCell(row, col++); - entry.OS = parser->GetCell(row, col++); - entry.Compiler = parser->GetCell(row, col++); - entry.Date = parser->GetCell(row, col++); - AddEntry(&entry); - } - - return true; -} - -void ImGuiPerfTool::ViewOnly(const char** perf_names) -{ - // Data would not be built if we tried to view perftool of a particular test without first opening perftool via button. We need data to be built to hide perf tests. - if (_Batches.empty()) - _Rebuild(); - - // Hide other perf tests. - for (const char* label : _Labels) - { - bool visible = false; - for (const char** p_name = perf_names; !visible && *p_name; p_name++) - visible |= strcmp(label, *p_name) == 0; - _Visibility.SetBool(ImHashStr(label), visible); - } -} - -void ImGuiPerfTool::ViewOnly(const char* perf_name) -{ - const char* names[] = { perf_name, NULL }; - ViewOnly(names); -} - -ImGuiPerfToolEntry* ImGuiPerfTool::GetEntryByBatchIdx(int idx, const char* perf_name) -{ - if (idx < 0) - return NULL; - IM_ASSERT(idx < _Batches.Size); - ImGuiPerfToolBatch& batch = _Batches.Data[idx]; - for (int i = 0; i < batch.Entries.Size; i++) - if (ImGuiPerfToolEntry* entry = &batch.Entries.Data[i]) - if (strcmp(entry->TestName, perf_name) == 0) - return entry; - return NULL; -} - -bool ImGuiPerfTool::_IsVisibleBuild(ImGuiPerfToolBatch* batch) -{ - IM_ASSERT(batch != NULL); - if (batch->Entries.empty()) - return false; // All entries are hidden. - return _IsVisibleBuild(&batch->Entries.Data[0]); -} - -bool ImGuiPerfTool::_IsVisibleBuild(ImGuiPerfToolEntry* entry) -{ - return _Visibility.GetBool(ImHashStr(entry->GitBranchName), true) && - _Visibility.GetBool(ImHashStr(entry->Compiler), true) && - _Visibility.GetBool(ImHashStr(entry->Cpu), true) && - _Visibility.GetBool(ImHashStr(entry->OS), true) && - _Visibility.GetBool(ImHashStr(entry->BuildType), true); -} - -bool ImGuiPerfTool::_IsVisibleTest(const char* test_name) -{ - return _Visibility.GetBool(ImHashStr(test_name), true); -} - -void ImGuiPerfTool::_CalculateLegendAlignment() -{ - // Estimate paddings for legend format so it looks nice and aligned - // FIXME: Rely on font being monospace. May need to recalculate every frame on a per-need basis based on font? - _AlignStress = _AlignType = _AlignCpu = _AlignOs = _AlignCompiler = _AlignBranch = _AlignSamples = 0; - for (ImGuiPerfToolBatch& batch : _Batches) - { - if (batch.Entries.empty()) - continue; - ImGuiPerfToolEntry* entry = &batch.Entries.Data[0]; - if (!_IsVisibleBuild(entry)) - continue; - _AlignStress = ImMax(_AlignStress, (int)ceil(log10(entry->PerfStressAmount))); - _AlignType = ImMax(_AlignType, (int)strlen(entry->BuildType)); - _AlignCpu = ImMax(_AlignCpu, (int)strlen(entry->Cpu)); - _AlignOs = ImMax(_AlignOs, (int)strlen(entry->OS)); - _AlignCompiler = ImMax(_AlignCompiler, (int)strlen(entry->Compiler)); - _AlignBranch = ImMax(_AlignBranch, (int)strlen(entry->GitBranchName)); - _AlignSamples = ImMax(_AlignSamples, (int)Str16f("%d", entry->NumSamples).length()); - } -} - -bool ImGuiPerfTool::SaveHtmlReport(const char* file_name, const char* image_file) -{ - if (!ImFileCreateDirectoryChain(file_name, ImPathFindFilename(file_name))) - return false; - - FILE* fp = fopen(file_name, "w+"); - if (fp == NULL) - return false; - - fprintf(fp, "<!doctype html>\n" - "<html>\n" - "<head>\n" - " <meta charset=\"utf-8\"/>\n" - " <title>Dear ImGui perf report</title>\n" - "</head>\n" - "<body>\n" - " <pre id=\"content\">\n"); - - // Embed performance chart. - fprintf(fp, "## Dear ImGui perf report\n\n"); - - if (image_file != NULL) - { - FILE* fp_img = fopen(image_file, "rb"); - if (fp_img != NULL) - { - ImVector<char> image_buffer; - ImVector<char> base64_buffer; - fseek(fp_img, 0, SEEK_END); - image_buffer.resize((int)ftell(fp_img)); - base64_buffer.resize(((image_buffer.Size / 3) + 1) * 4 + 1); - rewind(fp_img); - fread(image_buffer.Data, 1, image_buffer.Size, fp_img); - fclose(fp_img); - int len = ImStrBase64Encode((unsigned char*)image_buffer.Data, base64_buffer.Data, image_buffer.Size); - base64_buffer.Data[len] = 0; - fprintf(fp, "\n\n", base64_buffer.Data); - } - } - - // Print info table. - const bool combine_by_build_info = _DisplayType == ImGuiPerfToolDisplayType_CombineByBuildInfo; - for (const auto& column_info : PerfToolColumnInfo) - if (column_info.ShowAlways || combine_by_build_info) - fprintf(fp, "| %s ", column_info.Title); - fprintf(fp, "|\n"); - for (const auto& column_info : PerfToolColumnInfo) - if (column_info.ShowAlways || combine_by_build_info) - fprintf(fp, "| -- "); - fprintf(fp, "|\n"); - - for (int row_index = _InfoTableSort.Size - 1; row_index >= 0; row_index--) - { - int batch_index_sorted, entry_index_sorted; - _UnpackSortedKey(_InfoTableSort[row_index], &batch_index_sorted, &entry_index_sorted); - ImGuiPerfToolBatch* batch = &_Batches[batch_index_sorted]; - ImGuiPerfToolEntry* entry = &batch->Entries[entry_index_sorted]; - const char* test_name = entry->TestName; - if (!_IsVisibleBuild(entry) || entry->NumSamples == 0) - continue; - - ImGuiPerfToolEntry* baseline_entry = GetEntryByBatchIdx(_BaselineBatchIndex, test_name); - for (int i = 0; i < IM_ARRAYSIZE(PerfToolColumnInfo); i++) - { - Str30f label(""); - const ImGuiPerfToolColumnInfo& column_info = PerfToolColumnInfo[i]; - if (column_info.ShowAlways || combine_by_build_info) - { - switch (i) - { - case 0: - { - char date[64]; - FormatDateAndTime(entry->Timestamp, date, IM_ARRAYSIZE(date)); - fprintf(fp, "| %s ", date); - break; - } - case 1: fprintf(fp, "| %s ", entry->TestName); break; - case 2: fprintf(fp, "| %s ", entry->GitBranchName); break; - case 3: fprintf(fp, "| %s ", entry->Compiler); break; - case 4: fprintf(fp, "| %s ", entry->OS); break; - case 5: fprintf(fp, "| %s ", entry->Cpu); break; - case 6: fprintf(fp, "| %s ", entry->BuildType); break; - case 7: fprintf(fp, "| x%d ", entry->PerfStressAmount); break; - case 8: fprintf(fp, "| %.2f ", entry->DtDeltaMs); break; - case 9: fprintf(fp, "| %.2f ", entry->DtDeltaMsMin); break; - case 10: fprintf(fp, "| %.2f ", entry->DtDeltaMsMax); break; - case 11: fprintf(fp, "| %d ", entry->NumSamples); break; - case 12: FormatVsBaseline(entry, baseline_entry, label); fprintf(fp, "| %s ", label.c_str()); break; - default: IM_ASSERT(0); break; - } - } - } - fprintf(fp, "|\n"); - } - - fprintf(fp, "</pre>\n" - " <script src=\"https://cdn.jsdelivr.net/npm/marked@4.0.0/marked.min.js\"></script>\n" - " <script>\n" - " var content = document.getElementById('content');\n" - " content.innerHTML = marked.parse(content.innerText);\n" - " </script>\n" - "</body>\n" - "</html>\n"); - - fclose(fp); - return true; -} - -void ImGuiPerfTool::_SetBaseline(int batch_index) -{ - IM_ASSERT(batch_index < _Batches.Size); - _BaselineBatchIndex = batch_index; - if (batch_index >= 0) - { - _BaselineTimestamp = _Batches.Data[batch_index].Entries.Data[0].Timestamp; - _BaselineBuildId = GetBuildID(&_Batches.Data[batch_index]); - } -} - -//------------------------------------------------------------------------- -// [SECTION] USER INTERFACE -//------------------------------------------------------------------------- - -void ImGuiPerfTool::ShowPerfToolWindow(ImGuiTestEngine* engine, bool* p_open) -{ - if (!ImGui::Begin("Dear ImGui Perf Tool", p_open)) - { - ImGui::End(); - return; - } - - if (ImGui::IsWindowAppearing() && Empty()) - LoadCSV(); - - ImGuiStyle& style = ImGui::GetStyle(); - - // ----------------------------------------------------------------------------------------------------------------- - // Render utility buttons - // ----------------------------------------------------------------------------------------------------------------- - - // Date filter - ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted("Date Range:"); - ImGui::SameLine(); - - bool dirty = _Batches.empty(); - bool date_changed = InputDate("##date-from", _FilterDateFrom, IM_ARRAYSIZE(_FilterDateFrom), - (strcmp(_FilterDateFrom, _FilterDateTo) <= 0 || !*_FilterDateTo)); - if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) - ImGui::OpenPopup("InputDate From Menu"); - ImGui::SameLine(0, 0.0f); - ImGui::TextUnformatted(".."); - ImGui::SameLine(0, 0.0f); - date_changed |= InputDate("##date-to", _FilterDateTo, IM_ARRAYSIZE(_FilterDateTo), - (strcmp(_FilterDateFrom, _FilterDateTo) <= 0 || !*_FilterDateFrom)); - if (date_changed) - { - dirty = (!_FilterDateFrom[0] || IsDateValid(_FilterDateFrom)) && (!_FilterDateTo[0] || IsDateValid(_FilterDateTo)); - if (_FilterDateFrom[0] && _FilterDateTo[0]) - dirty &= strcmp(_FilterDateFrom, _FilterDateTo) <= 0; - } - if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) - ImGui::OpenPopup("InputDate To Menu"); - ImGui::SameLine(); - - for (int i = 0; i < 2; i++) - { - if (ImGui::BeginPopup(i == 0 ? "InputDate From Menu" : "InputDate To Menu")) - { - char* date = i == 0 ? _FilterDateFrom : _FilterDateTo; - int date_size = i == 0 ? IM_ARRAYSIZE(_FilterDateFrom) : IM_ARRAYSIZE(_FilterDateTo); - if (i == 0 && ImGui::MenuItem("Set Min")) - { - for (ImGuiPerfToolEntry& entry : _SrcData) - if (strcmp(date, entry.Date) > 0) - { - ImStrncpy(date, entry.Date, date_size); - dirty = true; - } - } - if (ImGui::MenuItem("Set Max")) - { - for (ImGuiPerfToolEntry& entry : _SrcData) - if (strcmp(date, entry.Date) < 0) - { - ImStrncpy(date, entry.Date, date_size); - dirty = true; - } - } - if (ImGui::MenuItem("Set Today")) - { - time_t now = time(NULL); - FormatDate((ImU64)now * 1000000, date, date_size); - dirty = true; - } - ImGui::EndPopup(); - } - } - - if (ImGui::Button(Str64f("Filter builds (%d/%d)###Filter builds", _NumVisibleBuilds, _NumUniqueBuilds).c_str())) - ImGui::OpenPopup("Filter builds"); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Hide or show individual builds."); - ImGui::SameLine(); - if (ImGui::Button(Str64f("Filter tests (%d/%d)###Filter tests", _LabelsVisible.Size, _Labels.Size).c_str())) - ImGui::OpenPopup("Filter perfs"); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Hide or show individual tests."); - ImGui::SameLine(); - - dirty |= Button3("Combine", (int*)&_DisplayType); - if (ImGui::IsItemHovered()) - { - ImGui::BeginTooltip(); - ImGui::RadioButton("Display each run separately", _DisplayType == ImGuiPerfToolDisplayType_Simple); - ImGui::RadioButton("Use one color per branch. Disables baseline comparisons!", _DisplayType == ImGuiPerfToolDisplayType_PerBranchColors); - ImGui::RadioButton("Combine multiple runs with same build info into one averaged build entry.", _DisplayType == ImGuiPerfToolDisplayType_CombineByBuildInfo); - ImGui::EndTooltip(); - } - - ImGui::SameLine(); - if (_ReportGenerating && ImGuiTestEngine_IsTestQueueEmpty(engine)) - { - _ReportGenerating = false; - ImOsOpenInShell(PerfToolReportDefaultOutputPath); - } - if (_Batches.empty()) - ImGui::BeginDisabled(); - if (ImGui::Button("Html Export")) - { - // In order to capture a screenshot Report is saved by executing a "capture_perf_report" test. - _ReportGenerating = true; - ImGuiTestEngine_QueueTests(engine, ImGuiTestGroup_Tests, "capture_perf_report"); - } - if (_Batches.empty()) - ImGui::EndDisabled(); - ImGui::SameLine(); - if (ImGui::IsItemHovered()) - 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::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) - { - ImGui::BeginTooltip(); - ImGui::BulletText("To change baseline build, double-click desired build in the legend."); - ImGui::BulletText("Extra information is displayed when hovering bars of a particular perf test and holding SHIFT."); - ImGui::BulletText("Double-click plot to fit plot into available area."); - ImGui::EndTooltip(); - } - - if (ImGui::BeginPopup("Filter builds")) - { - ImGuiStorage& temp_set = _TempSet; - temp_set.Data.resize(0); // ImHashStr(BuildProperty):seen - - static const char* columns[] = { "Branch", "Build", "CPU", "OS", "Compiler" }; - bool show_all = ImGui::Button("Show All"); - ImGui::SameLine(); - bool hide_all = ImGui::Button("Hide All"); - if (ImGui::BeginTable("Builds", IM_ARRAYSIZE(columns), ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) - { - for (int i = 0; i < IM_ARRAYSIZE(columns); i++) - ImGui::TableSetupColumn(columns[i]); - ImGui::TableHeadersRow(); - - // Find columns with nothing checked. - bool checked_any[] = { false, false, false, false, false }; - for (ImGuiPerfToolEntry& entry : _SrcData) - { - const char* properties[] = { entry.GitBranchName, entry.BuildType, entry.Cpu, entry.OS, entry.Compiler }; - for (int i = 0; i < IM_ARRAYSIZE(properties); i++) - { - ImGuiID hash = ImHashStr(properties[i]); - checked_any[i] |= _Visibility.GetBool(hash, true); - } - } - - int property_offsets[] = - { - offsetof(ImGuiPerfToolEntry, GitBranchName), - offsetof(ImGuiPerfToolEntry, BuildType), - offsetof(ImGuiPerfToolEntry, Cpu), - offsetof(ImGuiPerfToolEntry, OS), - offsetof(ImGuiPerfToolEntry, Compiler), - }; - - ImGui::TableNextRow(); - for (int i = 0; i < IM_ARRAYSIZE(property_offsets); i++) - { - ImGui::TableSetColumnIndex(i); - for (ImGuiPerfToolEntry& entry : _SrcData) - { - const char* property = *(const char**)((const char*)&entry + property_offsets[i]); - ImGuiID hash = ImHashStr(property); - if (temp_set.GetBool(hash)) - continue; - temp_set.SetBool(hash, true); - - bool visible = _Visibility.GetBool(hash, true) || show_all; - if (hide_all) - visible = false; - bool modified = ImGui::Checkbox(property, &visible) || show_all || hide_all; - _Visibility.SetBool(hash, visible); - if (modified) - { - _CalculateLegendAlignment(); - _NumVisibleBuilds = PerfToolCountBuilds(this, true); - dirty = true; - } - if (!checked_any[i]) - { - ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ImColor(1.0f, 0.0f, 0.0f, 0.2f)); - if (ImGui::TableGetColumnFlags() & ImGuiTableColumnFlags_IsHovered) - ImGui::SetTooltip("Check at least one item in each column to see any data."); - } - } - } - ImGui::EndTable(); - } - ImGui::EndPopup(); - } - - if (ImGui::BeginPopup("Filter perfs")) - { - dirty |= RenderMultiSelectFilter(this, "Filter by perf test", &_Labels); - if (ImGui::IsKeyPressed(ImGuiKey_Escape)) - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - } - - if (dirty) - _Rebuild(); - - // Rendering a plot of empty dataset is not possible. - if (_Batches.empty() || _LabelsVisible.Size == 0 || _NumVisibleBuilds == 0) - { - ImGui::TextUnformatted("No data is available. Run some perf tests or adjust filter settings."); - } - else - { -#if IMGUI_TEST_ENGINE_ENABLE_IMPLOT - // Splitter between two following child windows is rendered first. - float plot_height = 0.0f; - float& table_height = _InfoTableHeight; - ImGui::Splitter("splitter", &plot_height, &table_height, ImGuiAxis_Y, +1); - - // Double-click to move splitter to bottom - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) - { - table_height = 0; - plot_height = ImGui::GetContentRegionAvail().y - style.ItemSpacing.y; - ImGui::ClearActiveID(); - } - - // Render entries plot - if (ImGui::BeginChild(ImGui::GetID("plot"), ImVec2(0, plot_height))) - _ShowEntriesPlot(); - ImGui::EndChild(); - - // Render entries tables - if (table_height > 0.0f) - { - if (ImGui::BeginChild(ImGui::GetID("info-table"), ImVec2(0, table_height))) - _ShowEntriesTable(); - ImGui::EndChild(); - } -#else - _ShowEntriesTable(); -#endif - } - ImGui::End(); -} - -#if IMGUI_TEST_ENGINE_ENABLE_IMPLOT -static double GetLabelVerticalOffset(double occupy_h, int max_visible_builds, int now_visible_builds) -{ - const double h = occupy_h / (float)max_visible_builds; - double offset = -h * ((max_visible_builds - 1) * 0.5); - return (double)now_visible_builds * h + offset; -} -#endif - -void ImGuiPerfTool::_ShowEntriesPlot() -{ -#if IMGUI_TEST_ENGINE_ENABLE_IMPLOT - ImGuiIO& io = ImGui::GetIO(); - ImGuiStyle& style = ImGui::GetStyle(); - Str256 label; - Str256 display_label; - - ImPlot::PushStyleColor(ImPlotCol_AxisBgHovered, IM_COL32(0, 0, 0, 0)); - ImPlot::PushStyleColor(ImPlotCol_AxisBgActive, IM_COL32(0, 0, 0, 0)); - if (!ImPlot::BeginPlot("PerfTool", ImVec2(-1, -1), ImPlotFlags_NoTitle)) - return; - - ImPlot::SetupAxis(ImAxis_X1, NULL, ImPlotAxisFlags_NoTickLabels); - if (_LabelsVisible.Size > 1) - { - ImPlot::SetupAxisTicks(ImAxis_Y1, 0, _LabelsVisible.Size, _LabelsVisible.Size, _LabelsVisible.Data); - } - else if (_LabelsVisible.Size == 1) - { - const char* labels[] = { _LabelsVisible[0], "" }; - ImPlot::SetupAxisTicks(ImAxis_Y1, 0, _LabelsVisible.Size, 2, labels); - } - ImPlot::SetupLegend(ImPlotLocation_NorthEast); - - // Amount of vertical space bars of one label will occupy. 1.0 would leave no space between bars of adjacent labels. - const float occupy_h = 0.8f; - - // Plot bars - bool legend_hovered = false; - ImGuiStorage& temp_set = _TempSet; - temp_set.Data.resize(0); // ImHashStr(TestName):now_visible_builds_i - int current_baseline_batch_index = _BaselineBatchIndex; // Cache this value before loop, so toggling it does not create flicker. - for (int batch_index = 0; batch_index < _Batches.Size; batch_index++) - { - ImGuiPerfToolBatch& batch = _Batches[batch_index]; - if (!_IsVisibleBuild(&batch.Entries.Data[0])) - continue; - - // Plot bars. - label.clear(); - display_label.clear(); - PerfToolFormatBuildInfo(this, &label, &batch); - display_label.append(label.c_str()); - ImGuiID batch_label_id; - bool baseline_match = false; - if (_DisplayType == ImGuiPerfToolDisplayType_PerBranchColors) - { - // No "vs baseline" comparison for per-branch colors, because runs are combined in the legend, but not in the info table. - batch_label_id = GetBuildID(&batch); - } - else - { - batch_label_id = ImHashData(&batch.BatchID, sizeof(batch.BatchID)); - baseline_match = current_baseline_batch_index == batch_index; - } - display_label.appendf("%s###%08X", baseline_match ? " *" : "", batch_label_id); - - // Plot all bars one by one, so batches with varying number of bars would not contain empty holes. - for (ImGuiPerfToolEntry& entry : batch.Entries) - { - if (entry.NumSamples == 0) - continue; // Dummy entry, perf did not run for this test in this batch. - ImGuiID label_id = ImHashStr(entry.TestName); - const int max_visible_builds = _LabelBarCounts.GetInt(label_id); - const int now_visible_builds = temp_set.GetInt(label_id); - temp_set.SetInt(label_id, now_visible_builds + 1); - double y_pos = (double)entry.LabelIndex + GetLabelVerticalOffset(occupy_h, max_visible_builds, now_visible_builds); - ImPlot::SetNextFillStyle(ImPlot::GetColormapColor(_DisplayType == ImGuiPerfToolDisplayType_PerBranchColors ? batch.BranchIndex : batch_index)); - ImPlot::PlotBars<double>(display_label.c_str(), &entry.DtDeltaMs, &y_pos, 1, occupy_h / (double)max_visible_builds, ImPlotBarsFlags_Horizontal); - } - legend_hovered |= ImPlot::IsLegendEntryHovered(display_label.c_str()); - - // Set baseline. - if (ImPlot::IsLegendEntryHovered(display_label.c_str())) - { - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) - _SetBaseline(batch_index); - } - } - - // Plot highlights. - ImPlotContext& gp = *GImPlot; - ImPlotPlot& plot = *gp.CurrentPlot; - _PlotHoverTest = -1; - _PlotHoverBatch = -1; - _PlotHoverTestLabel = false; - bool can_highlight = !legend_hovered && (ImPlot::IsPlotHovered() || ImPlot::IsAxisHovered(ImAxis_Y1)); - ImDrawList* plot_draw_list = ImPlot::GetPlotDrawList(); - - // Highlight bars when hovering a label. - int hovered_label_index = -1; - for (int i = 0; i < _LabelsVisible.Size && can_highlight; i++) - { - ImRect label_rect_loose = ImPlotGetYTickRect(i); // Rect around test label - ImRect label_rect_tight; // Rect around test label, covering bar height and label area width - label_rect_tight.Min.y = ImPlot::PlotToPixels(0, (float)i + 0.5f).y; - label_rect_tight.Max.y = ImPlot::PlotToPixels(0, (float)i - 0.5f).y; - label_rect_tight.Min.x = plot.CanvasRect.Min.x; - label_rect_tight.Max.x = plot.PlotRect.Min.x; - - ImRect rect_bars; // Rect around bars only - rect_bars.Min.x = plot.PlotRect.Min.x; - rect_bars.Max.x = plot.PlotRect.Max.x; - rect_bars.Min.y = ImPlot::PlotToPixels(0, (float)i + 0.5f).y; - rect_bars.Max.y = ImPlot::PlotToPixels(0, (float)i - 0.5f).y; - - // Render underline signaling it is clickable. Clicks are handled when rendering info table. - if (label_rect_loose.Contains(io.MousePos)) - { - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - plot_draw_list->AddLine(ImFloor(label_rect_loose.GetBL()), ImFloor(label_rect_loose.GetBR()), - ImColor(style.Colors[ImGuiCol_Text])); - } - - // Highlight bars belonging to hovered label. - if (label_rect_tight.Contains(io.MousePos)) - { - plot_draw_list->AddRectFilled(rect_bars.Min, rect_bars.Max, ImColor(style.Colors[ImGuiCol_TextSelectedBg])); - _PlotHoverTestLabel = true; - _PlotHoverTest = i; - } - - if (rect_bars.Contains(io.MousePos)) - hovered_label_index = i; - } - - // Highlight individual bars when hovering them on the plot or info table. - temp_set.Data.resize(0); // ImHashStr(hovered_label):now_visible_builds_i - if (hovered_label_index < 0) - hovered_label_index = _TableHoveredTest; - if (hovered_label_index >= 0) - { - const char* hovered_label = _LabelsVisible.Data[hovered_label_index]; - ImGuiID label_id = ImHashStr(hovered_label); - for (ImGuiPerfToolBatch& batch : _Batches) - { - int batch_index = _Batches.index_from_ptr(&batch); - if (!_IsVisibleBuild(&batch)) - continue; - - ImGuiPerfToolEntry* entry = &batch.Entries.Data[hovered_label_index]; - if (entry->NumSamples == 0) - continue; // Dummy entry, perf did not run for this test in this batch. - - int max_visible_builds = _LabelBarCounts.GetInt(label_id); - const int now_visible_builds = temp_set.GetInt(label_id); - temp_set.SetInt(label_id, now_visible_builds + 1); - float h = occupy_h / (float)max_visible_builds; - float y_pos = (float)entry->LabelIndex; - y_pos += (float)GetLabelVerticalOffset(occupy_h, max_visible_builds, now_visible_builds); - ImRect rect_bar; // Rect around hovered bar only - rect_bar.Min.x = plot.PlotRect.Min.x; - rect_bar.Max.x = plot.PlotRect.Max.x; - rect_bar.Min.y = ImPlot::PlotToPixels(0, y_pos - h * 0.5f + h).y; // ImPlot y_pos is for bar center, therefore we adjust positions by half-height to get a bounding box. - rect_bar.Max.y = ImPlot::PlotToPixels(0, y_pos - h * 0.5f).y; - - // Mouse is hovering label or bars of a perf test - highlight them in info table. - if (_PlotHoverTest < 0 && rect_bar.Min.y <= io.MousePos.y && io.MousePos.y < rect_bar.Max.y && io.MousePos.x > plot.PlotRect.Min.x) - { - // _LabelsVisible is inverted to make perf test order match info table order. Revert it back. - _PlotHoverTest = hovered_label_index; - _PlotHoverBatch = batch_index; - plot_draw_list->AddRectFilled(rect_bar.Min, rect_bar.Max, ImColor(style.Colors[ImGuiCol_TextSelectedBg])); - } - - // Mouse is hovering a row in info table - highlight relevant bars on the plot. - if (_TableHoveredBatch == batch_index && _TableHoveredTest == hovered_label_index) - plot_draw_list->AddRectFilled(rect_bar.Min, rect_bar.Max, ImColor(style.Colors[ImGuiCol_TextSelectedBg])); - } - } - - if (io.KeyShift && _PlotHoverTest >= 0) - { - // Info tooltip with delta times of each batch for a hovered test. - const char* test_name = _LabelsVisible.Data[_PlotHoverTest]; - ImGui::BeginTooltip(); - float w = ImGui::CalcTextSize(test_name).x; - float total_w = ImGui::GetContentRegionAvail().x; - if (total_w > w) - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (total_w - w) * 0.5f); - ImGui::TextUnformatted(test_name); - - for (int i = 0; i < _Batches.Size; i++) - { - if (ImGuiPerfToolEntry* hovered_entry = GetEntryByBatchIdx(i, test_name)) - ImGui::Text("%s %.3fms", label.c_str(), hovered_entry->DtDeltaMs); - else - ImGui::Text("%s --", label.c_str()); - } - ImGui::EndTooltip(); - } - - ImPlot::EndPlot(); - ImPlot::PopStyleColor(2); -#else - ImGui::TextUnformatted("Not enabled because ImPlot is not available (IMGUI_TEST_ENGINE_ENABLE_IMPLOT=0)."); -#endif -} - -void ImGuiPerfTool::_ShowEntriesTable() -{ - ImGuiTableFlags table_flags = ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_Sortable | - ImGuiTableFlags_SortMulti | ImGuiTableFlags_SortTristate | ImGuiTableFlags_Resizable | - ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY; - if (!ImGui::BeginTable("PerfInfo", IM_ARRAYSIZE(PerfToolColumnInfo), table_flags)) - return; - - ImGuiStyle& style = ImGui::GetStyle(); - int num_visible_labels = _LabelsVisible.Size; - - // Test name column is not sorted because we do sorting only within perf runs of a particular tests, - // so as far as sorting function is concerned all items in first column are identical. - for (int i = 0; i < IM_ARRAYSIZE(PerfToolColumnInfo); i++) - { - const ImGuiPerfToolColumnInfo& info = PerfToolColumnInfo[i]; - ImGuiTableColumnFlags column_flags = info.Flags; - if (i == 0 && _DisplayType != ImGuiPerfToolDisplayType_Simple) - column_flags |= ImGuiTableColumnFlags_Disabled; // Date only visible in non-combining mode. - if (!info.ShowAlways && _DisplayType != ImGuiPerfToolDisplayType_CombineByBuildInfo) - column_flags |= ImGuiTableColumnFlags_Disabled; - ImGui::TableSetupColumn(info.Title, column_flags); - } - ImGui::TableSetupScrollFreeze(0, 1); - - if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) - if (sorts_specs->SpecsDirty || _InfoTableSortDirty) - { - // Fill sort table with unsorted indices. - sorts_specs->SpecsDirty = _InfoTableSortDirty = false; - - // Reinitialize sorting table to unsorted state. - _InfoTableSort.resize(num_visible_labels * _Batches.Size); - for (int entry_index = 0, i = 0; entry_index < num_visible_labels; entry_index++) - for (int batch_index = 0; batch_index < _Batches.Size; batch_index++, i++) - _InfoTableSort.Data[i] = (((ImU64)batch_index * num_visible_labels + entry_index) << 24) | i; - - // Sort batches of each label. - if (sorts_specs->SpecsCount > 0) - { - _InfoTableSortSpecs = sorts_specs; - PerfToolInstance = this; - ImQsort(_InfoTableSort.Data, (size_t)_InfoTableSort.Size, sizeof(_InfoTableSort.Data[0]), CompareWithSortSpecs); - _InfoTableSortSpecs = NULL; - PerfToolInstance = NULL; - } - } - - ImGui::TableHeadersRow(); - - // ImPlot renders bars from bottom to the top. We want bars to render from top to the bottom, therefore we loop - // labels and batches in reverse order. - _TableHoveredTest = -1; - _TableHoveredBatch = -1; - const bool scroll_into_view = _PlotHoverTestLabel && ImGui::IsMouseClicked(ImGuiMouseButton_Left); - const float header_row_height = ImGui::TableGetCellBgRect(ImGui::GetCurrentTable(), 0).GetHeight(); - ImRect scroll_into_view_rect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); - - for (int row_index = _InfoTableSort.Size - 1; row_index >= 0; row_index--) - { - int batch_index_sorted, entry_index_sorted; - _UnpackSortedKey(_InfoTableSort[row_index], &batch_index_sorted, &entry_index_sorted); - ImGuiPerfToolBatch* batch = &_Batches[batch_index_sorted]; - ImGuiPerfToolEntry* entry = &batch->Entries[entry_index_sorted]; - const char* test_name = entry->TestName; - - if (!_IsVisibleBuild(entry) || !_IsVisibleTest(entry->TestName) || entry->NumSamples == 0) - continue; - - ImGui::PushID(entry); - ImGui::TableNextRow(); - if (row_index & 1) - ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, ImGui::GetColorU32(ImGuiCol_TableRowBgAlt, 0.5f)); - else - ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, ImGui::GetColorU32(ImGuiCol_TableRowBg, 0.5f)); - - if (_PlotHoverTest == entry_index_sorted) - { - // Highlight a row that corresponds to hovered bar, or all rows that correspond to hovered perf test label. - if (_PlotHoverBatch == batch_index_sorted || _PlotHoverTestLabel) - ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, ImColor(style.Colors[ImGuiCol_TextSelectedBg])); - } - - ImGuiPerfToolEntry* baseline_entry = GetEntryByBatchIdx(_BaselineBatchIndex, test_name); - - // Date - if (ImGui::TableNextColumn()) - { - char date[64]; - FormatDateAndTime(entry->Timestamp, date, IM_ARRAYSIZE(date)); - ImGui::TextUnformatted(date); - } - - // Build info - if (ImGui::TableNextColumn()) - { - // ImGuiSelectableFlags_Disabled + changing ImGuiCol_TextDisabled color prevents selectable from overriding table highlight behavior. - ImGui::PushStyleColor(ImGuiCol_Header, style.Colors[ImGuiCol_Text]); - ImGui::PushStyleColor(ImGuiCol_HeaderHovered, style.Colors[ImGuiCol_TextSelectedBg]); - ImGui::PushStyleColor(ImGuiCol_HeaderActive, style.Colors[ImGuiCol_TextSelectedBg]); - ImGui::Selectable(entry->TestName, false, ImGuiSelectableFlags_SpanAllColumns); - ImGui::PopStyleColor(3); - if (ImGui::IsItemHovered()) - { - _TableHoveredTest = entry_index_sorted; - _TableHoveredBatch = batch_index_sorted; - } - - if (ImGui::BeginPopupContextItem()) - { - if (entry == baseline_entry) - ImGui::BeginDisabled(); - if (ImGui::MenuItem("Set as baseline")) - _SetBaseline(batch_index_sorted); - if (entry == baseline_entry) - ImGui::EndDisabled(); - ImGui::EndPopup(); - } - } - if (ImGui::TableNextColumn()) - ImGui::TextUnformatted(entry->GitBranchName); - if (ImGui::TableNextColumn()) - ImGui::TextUnformatted(entry->Compiler); - if (ImGui::TableNextColumn()) - ImGui::TextUnformatted(entry->OS); - if (ImGui::TableNextColumn()) - ImGui::TextUnformatted(entry->Cpu); - if (ImGui::TableNextColumn()) - ImGui::TextUnformatted(entry->BuildType); - if (ImGui::TableNextColumn()) - ImGui::Text("x%d", entry->PerfStressAmount); - - // Avg ms - if (ImGui::TableNextColumn()) - ImGui::Text("%.3lf", entry->DtDeltaMs); - - // Min ms - if (ImGui::TableNextColumn()) - ImGui::Text("%.3lf", entry->DtDeltaMsMin); - - // Max ms - if (ImGui::TableNextColumn()) - ImGui::Text("%.3lf", entry->DtDeltaMsMax); - - // Num samples - if (ImGui::TableNextColumn()) - ImGui::Text("%d", entry->NumSamples); - - // VS Baseline - if (ImGui::TableNextColumn()) - { - float dt_change = (float)entry->VsBaseline; - if (_DisplayType == ImGuiPerfToolDisplayType_PerBranchColors) - { - ImGui::TextUnformatted("--"); - } - else - { - Str30 label; - dt_change = FormatVsBaseline(entry, baseline_entry, label); - ImGui::TextUnformatted(label.c_str()); - if (dt_change != entry->VsBaseline) - { - entry->VsBaseline = dt_change; - _InfoTableSortDirty = true; // Force re-sorting. - } - } - } - - if (_PlotHoverTest == entry_index_sorted && scroll_into_view) - { - ImGuiTable* table = ImGui::GetCurrentTable(); - scroll_into_view_rect.Add(ImGui::TableGetCellBgRect(table, 0)); - } - - ImGui::PopID(); - } - - if (scroll_into_view) - { - scroll_into_view_rect.Min.y -= header_row_height; // FIXME-TABLE: Compensate for frozen header row covering a first content row scrolled into view. - ImGui::ScrollToRect(ImGui::GetCurrentWindow(), scroll_into_view_rect, ImGuiScrollFlags_NoScrollParent); - } - - ImGui::EndTable(); -} - -//------------------------------------------------------------------------- -// [SECTION] SETTINGS -//------------------------------------------------------------------------- - -static void PerflogSettingsHandler_ClearAll(ImGuiContext*, ImGuiSettingsHandler* ini_handler) -{ - ImGuiPerfTool* perftool = (ImGuiPerfTool*)ini_handler->UserData; - perftool->_Visibility.Clear(); -} - -static void* PerflogSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char*) -{ - return (void*)1; -} - -static void PerflogSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler* ini_handler, void*, const char* line) -{ - ImGuiPerfTool* perftool = (ImGuiPerfTool*)ini_handler->UserData; - char buf[128]; - int visible = -1, display_type = -1; - /**/ if (sscanf(line, "DateFrom=%10s", perftool->_FilterDateFrom)) {} - else if (sscanf(line, "DateTo=%10s", perftool->_FilterDateTo)) {} - else if (sscanf(line, "DisplayType=%d", &display_type)) { perftool->_DisplayType = (ImGuiPerfToolDisplayType)display_type; } - else if (sscanf(line, "BaselineBuildId=%llu", &perftool->_BaselineBuildId)) {} - else if (sscanf(line, "BaselineTimestamp=%llu", &perftool->_BaselineTimestamp)) {} - else if (sscanf(line, "TestVisibility=%[^,],%d", buf, &visible) == 2) { perftool->_Visibility.SetBool(ImHashStr(buf), !!visible); } - else if (sscanf(line, "BuildVisibility=%[^,],%d", buf, &visible) == 2) { perftool->_Visibility.SetBool(ImHashStr(buf), !!visible); } -} - -static void PerflogSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSettingsHandler* ini_handler) -{ - ImGuiPerfTool* perftool = (ImGuiPerfTool*)ini_handler->UserData; - perftool->_Batches.clear_destruct(); - perftool->_SetBaseline(-1); -} - -static void PerflogSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler* ini_handler, ImGuiTextBuffer* buf) -{ - ImGuiPerfTool* perftool = (ImGuiPerfTool*)ini_handler->UserData; - if (perftool->_Batches.empty()) - return; - buf->appendf("[%s][Data]\n", ini_handler->TypeName); - buf->appendf("DateFrom=%s\n", perftool->_FilterDateFrom); - buf->appendf("DateTo=%s\n", perftool->_FilterDateTo); - buf->appendf("DisplayType=%d\n", perftool->_DisplayType); - buf->appendf("BaselineBuildId=%llu\n", perftool->_BaselineBuildId); - buf->appendf("BaselineTimestamp=%llu\n", perftool->_BaselineTimestamp); - for (const char* label : perftool->_Labels) - buf->appendf("TestVisibility=%s,%d\n", label, perftool->_Visibility.GetBool(ImHashStr(label), true)); - - ImGuiStorage& temp_set = perftool->_TempSet; - temp_set.Data.clear(); - for (ImGuiPerfToolEntry& entry : perftool->_SrcData) - { - const char* properties[] = { entry.GitBranchName, entry.BuildType, entry.Cpu, entry.OS, entry.Compiler }; - for (int i = 0; i < IM_ARRAYSIZE(properties); i++) - { - ImGuiID hash = ImHashStr(properties[i]); - if (!temp_set.GetBool(hash)) - { - temp_set.SetBool(hash, true); - buf->appendf("BuildVisibility=%s,%d\n", properties[i], perftool->_Visibility.GetBool(hash, true)); - } - } - } - buf->append("\n"); -} - -void ImGuiPerfTool::_AddSettingsHandler() -{ - ImGuiSettingsHandler ini_handler; - ini_handler.TypeName = "TestEnginePerfTool"; - ini_handler.TypeHash = ImHashStr("TestEnginePerfTool"); - ini_handler.ClearAllFn = PerflogSettingsHandler_ClearAll; - ini_handler.ReadOpenFn = PerflogSettingsHandler_ReadOpen; - ini_handler.ReadLineFn = PerflogSettingsHandler_ReadLine; - ini_handler.ApplyAllFn = PerflogSettingsHandler_ApplyAll; - ini_handler.WriteAllFn = PerflogSettingsHandler_WriteAll; - ini_handler.UserData = this; - ImGui::AddSettingsHandler(&ini_handler); -} - -void ImGuiPerfTool::_UnpackSortedKey(ImU64 key, int* batch_index, int* entry_index, int* monotonic_index) -{ - IM_ASSERT(batch_index != NULL); - IM_ASSERT(entry_index != NULL); - const int num_visible_labels = _LabelsVisible.Size; - *batch_index = (int)((key >> 24) / num_visible_labels); - *entry_index = (int)((key >> 24) % num_visible_labels); - if (monotonic_index) - *monotonic_index = (int)(key & 0xFFFFFF); -} - -//------------------------------------------------------------------------- -// [SECTION] TESTS -//------------------------------------------------------------------------- - -static bool SetPerfToolWindowOpen(ImGuiTestContext* ctx, bool is_open) -{ - ctx->MenuClick("//Dear ImGui Test Engine/Tools"); - bool was_open = ctx->ItemIsChecked("//##Menu_00/Perf Tool"); - ctx->MenuAction(is_open ? ImGuiTestAction_Check : ImGuiTestAction_Uncheck, "//Dear ImGui Test Engine/Tools/Perf Tool"); - return was_open; -} - -void RegisterTests_TestEnginePerfTool(ImGuiTestEngine* e) -{ - ImGuiTest* t = NULL; - - // ## Flex perf tool code. - t = IM_REGISTER_TEST(e, "testengine", "testengine_cov_perftool"); - t->GuiFunc = [](ImGuiTestContext* ctx) - { - IM_UNUSED(ctx); - ImGui::Begin("Test Func", NULL, ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize); - int loop_count = 1000; - bool v1 = false, v2 = true; - for (int n = 0; n < loop_count / 2; n++) - { - ImGui::PushID(n); - ImGui::Checkbox("Hello, world", &v1); - ImGui::Checkbox("Hello, world", &v2); - ImGui::PopID(); - } - ImGui::End(); - }; - t->TestFunc = [](ImGuiTestContext* ctx) - { - ImGuiPerfTool* perftool = ImGuiTestEngine_GetPerfTool(ctx->Engine); - const char* temp_perf_csv = "output/misc_cov_perf_tool.csv"; - - Str16f min_date_bkp = perftool->_FilterDateFrom; - Str16f max_date_bkp = perftool->_FilterDateTo; - - // Execute few perf tests, serialize them to temporary csv file. - ctx->PerfIterations = 50; // Make faster - ctx->PerfCapture("perf", "misc_cov_perf_tool_1", temp_perf_csv); - ctx->PerfCapture("perf", "misc_cov_perf_tool_2", temp_perf_csv); - - // Load perf data from csv file and open perf tool. - perftool->Clear(); - perftool->LoadCSV(temp_perf_csv); - bool perf_was_open = SetPerfToolWindowOpen(ctx, true); - ctx->Yield(); - - ImGuiWindow* window = ctx->GetWindowByRef("Dear ImGui Perf Tool"); - IM_CHECK(window != NULL); - ImVec2 pos_bkp = window->Pos; - ImVec2 size_bkp = window->Size; - ctx->SetRef(window); - 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 - IM_CHECK(plot_child != NULL); - - // Move legend to right side. - ctx->MouseMoveToPos(plot_child->Rect().GetCenter()); - ctx->MouseDoubleClick(ImGuiMouseButton_Left); // Auto-size plots while at it - ctx->MouseClick(ImGuiMouseButton_Right); - ctx->MenuClick("//$FOCUSED/Legend/NE"); - - // Click some stuff for more coverage. - ctx->MouseMoveToPos(plot_child->Rect().GetCenter()); - ctx->KeyPress(ImGuiMod_Shift); -#endif - ctx->ItemClick("##date-from", ImGuiMouseButton_Right); - ctx->ItemClick(ctx->GetID("//$FOCUSED/Set Min")); - ctx->ItemClick("##date-to", ImGuiMouseButton_Right); - ctx->ItemClick(ctx->GetID("//$FOCUSED/Set Max")); - ctx->ItemClick("###Filter builds"); - ctx->ItemClick("###Filter tests"); - ctx->ItemClick("Combine", 0, ImGuiTestOpFlags_MoveToEdgeL); // Toggle thrice to leave state unchanged - ctx->ItemClick("Combine", 0, ImGuiTestOpFlags_MoveToEdgeL); - ctx->ItemClick("Combine", 0, ImGuiTestOpFlags_MoveToEdgeL); - - // Restore original state. - perftool->Clear(); // Clear test data and load original data - ImFileDelete(temp_perf_csv); - perftool->LoadCSV(); - ctx->Yield(); -#if IMGUI_TEST_ENGINE_ENABLE_IMPLOT - ctx->MouseMoveToPos(plot_child->Rect().GetCenter()); - ctx->MouseDoubleClick(ImGuiMouseButton_Left); // Fit plot to original data -#endif - ImStrncpy(perftool->_FilterDateFrom, min_date_bkp.c_str(), IM_ARRAYSIZE(perftool->_FilterDateFrom)); - ImStrncpy(perftool->_FilterDateTo, max_date_bkp.c_str(), IM_ARRAYSIZE(perftool->_FilterDateTo)); - ImGui::SetWindowPos(window, pos_bkp); - ImGui::SetWindowSize(window, size_bkp); - SetPerfToolWindowOpen(ctx, perf_was_open); // Restore window visibility - }; - - // ## Capture perf tool graph. - t = IM_REGISTER_TEST(e, "capture", "capture_perf_report"); - t->TestFunc = [](ImGuiTestContext* ctx) - { - ImGuiPerfTool* perftool = ImGuiTestEngine_GetPerfTool(ctx->Engine); - const char* perf_report_image = NULL; - if (!ImFileExist(IMGUI_PERFLOG_DEFAULT_FILENAME)) - { - ctx->LogWarning("Perf tool has no data. Perf report generation was aborted."); - return; - } - - char min_date_bkp[sizeof(perftool->_FilterDateFrom)], max_date_bkp[sizeof(perftool->_FilterDateTo)]; - ImStrncpy(min_date_bkp, perftool->_FilterDateFrom, IM_ARRAYSIZE(min_date_bkp)); - ImStrncpy(max_date_bkp, perftool->_FilterDateTo, IM_ARRAYSIZE(max_date_bkp)); - bool perf_was_open = SetPerfToolWindowOpen(ctx, true); - ctx->Yield(); - - ImGuiWindow* window = ctx->GetWindowByRef("Dear ImGui Perf Tool"); - IM_CHECK_SILENT(window != NULL); - ImVec2 pos_bkp = window->Pos; - ImVec2 size_bkp = window->Size; - ctx->SetRef(window); - ctx->WindowMove("", ImVec2(50, 50)); - ctx->WindowResize("", ImVec2(1400, 900)); -#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 - IM_CHECK(plot_child != NULL); - - // Move legend to right side. - ctx->MouseMoveToPos(plot_child->Rect().GetCenter()); - ctx->MouseDoubleClick(ImGuiMouseButton_Left); // Auto-size plots while at it - ctx->MouseClick(ImGuiMouseButton_Right); - ctx->MenuClick("//$FOCUSED/Legend/NE"); -#endif - // Click some stuff for more coverage. - ctx->ItemClick("##date-from", ImGuiMouseButton_Right); - ctx->ItemClick(ctx->GetID("//$FOCUSED/Set Min")); - ctx->ItemClick("##date-to", ImGuiMouseButton_Right); - ctx->ItemClick(ctx->GetID("//$FOCUSED/Set Max")); -#if IMGUI_TEST_ENGINE_ENABLE_IMPLOT - // Take a screenshot. - ImGuiCaptureArgs* args = ctx->CaptureArgs; - args->InCaptureRect = plot_child->Rect(); - ctx->CaptureAddWindow(window->Name); - ctx->CaptureScreenshot(ImGuiCaptureFlags_HideMouseCursor); - ctx->ItemDragWithDelta("splitter", ImVec2(0, -180)); // Show info table - perf_report_image = args->InOutputFile; -#endif - ImStrncpy(perftool->_FilterDateFrom, min_date_bkp, IM_ARRAYSIZE(min_date_bkp)); - ImStrncpy(perftool->_FilterDateTo, max_date_bkp, IM_ARRAYSIZE(max_date_bkp)); - ImGui::SetWindowPos(window, pos_bkp); - ImGui::SetWindowSize(window, size_bkp); - SetPerfToolWindowOpen(ctx, perf_was_open); // Restore window visibility - - const char* perf_report_output = getenv("CAPTURE_PERF_REPORT_OUTPUT"); - if (perf_report_output == NULL) - perf_report_output = PerfToolReportDefaultOutputPath; - perftool->SaveHtmlReport(perf_report_output, perf_report_image); - }; -} - -//------------------------------------------------------------------------- diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_perftool.h b/vendor/zgui/libs/imgui_test_engine/imgui_te_perftool.h deleted file mode 100644 index d75e667..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_perftool.h +++ /dev/null @@ -1,131 +0,0 @@ -// dear imgui test engine -// (performance tool) -// Browse and visualize samples recorded by ctx->PerfCapture() calls. -// User access via 'Test Engine UI -> Tools -> Perf Tool' - -#pragma once - -#include "imgui.h" - -// Forward Declaration -struct ImGuiPerfToolColumnInfo; -struct ImGuiTestEngine; -struct ImGuiCsvParser; - -// Configuration -#define IMGUI_PERFLOG_DEFAULT_FILENAME "output/imgui_perflog.csv" - -// [Internal] Perf log entry. Changes to this struct should be reflected in ImGuiTestContext::PerfCapture() and ImGuiTestEngine_Start(). -// This struct assumes strings stored here will be available until next ImGuiPerfTool::Clear() call. Fortunately we do not have to actively -// manage lifetime of these strings. New entries are created only in two cases: -// 1. ImGuiTestEngine_PerfToolAppendToCSV() call after perf test has run. This call receives ImGuiPerfToolEntry with const strings stored indefinitely by application. -// 2. As a consequence of ImGuiPerfTool::LoadCSV() call, we persist the ImGuiCSVParser instance, which keeps parsed CSV text, from which strings are referenced. -// As a result our solution also doesn't make many allocations. -struct IMGUI_API ImGuiPerfToolEntry -{ - ImU64 Timestamp = 0; // Title of a particular batch of perftool entries. - const char* Category = NULL; // Name of category perf test is in. - const char* TestName = NULL; // Name of perf test. - double DtDeltaMs = 0.0; // Result of perf test. - double DtDeltaMsMin = +FLT_MAX; // May be used by perftool. - double DtDeltaMsMax = -FLT_MAX; // May be used by perftool. - int NumSamples = 1; // Number aggregated samples. - int PerfStressAmount = 0; // - const char* GitBranchName = NULL; // Build information. - const char* BuildType = NULL; // - const char* Cpu = NULL; // - const char* OS = NULL; // - const char* Compiler = NULL; // - const char* Date = NULL; // Date of this entry or min date of combined entries. - //const char* DateMax = NULL; // Max date of combined entries, or NULL. - double VsBaseline = 0.0; // Percent difference vs baseline. - int LabelIndex = 0; // Index of TestName in ImGuiPerfTool::_LabelsVisible. - - ImGuiPerfToolEntry() { } - ImGuiPerfToolEntry(const ImGuiPerfToolEntry& rhs) { Set(rhs); } - ImGuiPerfToolEntry& operator=(const ImGuiPerfToolEntry& rhs){ Set(rhs); return *this; } - void Set(const ImGuiPerfToolEntry& rhs); -}; - -// [Internal] Perf log batch. -struct ImGuiPerfToolBatch -{ - ImU64 BatchID = 0; // Timestamp of the batch, or unique ID of the build in combined mode. - int NumSamples = 0; // A number of unique batches aggregated. - int BranchIndex = 0; // For per-branch color mapping. - ImVector<ImGuiPerfToolEntry> Entries; // Aggregated perf test entries. Order follows ImGuiPerfTool::_LabelsVisible order. - ~ImGuiPerfToolBatch() { Entries.clear_destruct(); } // FIXME: Misleading: nothing to destruct in that struct? -}; - -enum ImGuiPerfToolDisplayType : int -{ - ImGuiPerfToolDisplayType_Simple, // Each run will be displayed individually. - ImGuiPerfToolDisplayType_PerBranchColors, // Use one bar color per branch. - ImGuiPerfToolDisplayType_CombineByBuildInfo, // Entries with same build information will be averaged. -}; - -// -struct IMGUI_API ImGuiPerfTool -{ - ImVector<ImGuiPerfToolEntry> _SrcData; // Raw entries from CSV file (with string pointer into CSV data). - ImVector<const char*> _Labels; - ImVector<const char*> _LabelsVisible; // ImPlot requires a pointer of all labels beforehand. Always contains a dummy "" entry at the end! - ImVector<ImGuiPerfToolBatch> _Batches; - ImGuiStorage _LabelBarCounts; // Number bars each label will render. - int _NumVisibleBuilds = 0; // Cached number of visible builds. - int _NumUniqueBuilds = 0; // Cached number of unique builds. - ImGuiPerfToolDisplayType _DisplayType = ImGuiPerfToolDisplayType_CombineByBuildInfo; - int _BaselineBatchIndex = 0; // Index of baseline build. - ImU64 _BaselineTimestamp = 0; - ImU64 _BaselineBuildId = 0; - char _Filter[128]; // Context menu filtering substring. - char _FilterDateFrom[11] = {}; - char _FilterDateTo[11] = {}; - float _InfoTableHeight = 180.0f; - int _AlignStress = 0; // Alignment values for build info components, so they look aligned in the legend. - int _AlignType = 0; - int _AlignOs = 0; - int _AlignCpu = 0; - int _AlignCompiler = 0; - int _AlignBranch = 0; - int _AlignSamples = 0; - bool _InfoTableSortDirty = false; - ImVector<ImU64> _InfoTableSort; // _InfoTableSort[_LabelsVisible.Size * _Batches.Size]. Contains sorted batch indices for each label. - const ImGuiTableSortSpecs* _InfoTableSortSpecs = NULL; // Current table sort specs. - ImGuiStorage _TempSet; // Used as a set - int _TableHoveredTest = -1; // Index within _VisibleLabelPointers array. - int _TableHoveredBatch = -1; - int _PlotHoverTest = -1; - int _PlotHoverBatch = -1; - bool _PlotHoverTestLabel = false; - bool _ReportGenerating = false; - ImGuiStorage _Visibility; - ImGuiCsvParser* _CsvParser = NULL; // We keep this around and point to its fields - - ImGuiPerfTool(); - ~ImGuiPerfTool(); - - void Clear(); - bool LoadCSV(const char* filename = NULL); - void AddEntry(ImGuiPerfToolEntry* entry); - - void ShowPerfToolWindow(ImGuiTestEngine* engine, bool* p_open); - void ViewOnly(const char* perf_name); - void ViewOnly(const char** perf_names); - ImGuiPerfToolEntry* GetEntryByBatchIdx(int idx, const char* perf_name = NULL); - bool SaveHtmlReport(const char* file_name, const char* image_file = NULL); - inline bool Empty() { return _SrcData.empty(); } - - void _Rebuild(); - bool _IsVisibleBuild(ImGuiPerfToolBatch* batch); - bool _IsVisibleBuild(ImGuiPerfToolEntry* batch); - bool _IsVisibleTest(const char* test_name); - void _CalculateLegendAlignment(); - void _ShowEntriesPlot(); - void _ShowEntriesTable(); - void _SetBaseline(int batch_index); - void _AddSettingsHandler(); - void _UnpackSortedKey(ImU64 key, int* batch_index, int* entry_index, int* monotonic_index = NULL); -}; - -IMGUI_API void ImGuiTestEngine_PerfToolAppendToCSV(ImGuiPerfTool* perf_log, ImGuiPerfToolEntry* entry, const char* filename = NULL); diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_ui.cpp b/vendor/zgui/libs/imgui_test_engine/imgui_te_ui.cpp deleted file mode 100644 index 2d42bde..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_ui.cpp +++ /dev/null @@ -1,842 +0,0 @@ -// dear imgui test engine -// (ui) -// If you run tests in an interactive or visible application, you may want to call ImGuiTestEngine_ShowTestEngineWindows() - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -#define _CRT_SECURE_NO_WARNINGS -#endif - -#define IMGUI_DEFINE_MATH_OPERATORS -#include "imgui_te_ui.h" -#include "imgui.h" -#include "imgui_internal.h" -#include "imgui_te_engine.h" -#include "imgui_te_context.h" -#include "imgui_te_internal.h" -#include "imgui_te_perftool.h" -#include "thirdparty/Str/Str.h" - -//------------------------------------------------------------------------- -// TEST ENGINE: USER INTERFACE -//------------------------------------------------------------------------- -// - DrawTestLog() [internal] -// - GetVerboseLevelName() [internal] -// - ShowTestGroup() [internal] -// - ImGuiTestEngine_ShowTestWindows() -//------------------------------------------------------------------------- - -// Look for " filename:number " in the string and add menu option to open source. -static bool ParseLineAndDrawFileOpenItemForSourceFile(ImGuiTestEngine* e, ImGuiTest* test, const char* line_start, const char* line_end) -{ - const char* separator = ImStrchrRange(line_start, line_end, ':'); - if (separator == NULL) - return false; - - const char* path_end = separator; - const char* path_begin = separator - 1; - while (path_begin > line_start&& path_begin[-1] != ' ') - path_begin--; - if (path_begin == path_end) - return false; - - int line_no = -1; - sscanf(separator + 1, "%d ", &line_no); - if (line_no == -1) - return false; - - Str256f buf("Open '%.*s' at line %d", (int)(path_end - path_begin), path_begin, line_no); - if (ImGui::MenuItem(buf.c_str())) - { - // FIXME-TESTS: Assume folder is same as folder of test->SourceFile! - const char* src_path = test->SourceFile; - const char* src_name = ImPathFindFilename(src_path); - buf.setf("%.*s%.*s", (int)(src_name - src_path), src_path, (int)(path_end - path_begin), path_begin); - - ImGuiTestEngineIO& e_io = ImGuiTestEngine_GetIO(e); - e_io.SrcFileOpenFunc(buf.c_str(), line_no, e_io.SrcFileOpenUserData); - } - - return true; -} - -// Look for "[ ,"]filename.png" in the string and add menu option to open image. -static bool ParseLineAndDrawFileOpenItemForImageFile(ImGuiTestEngine* e, ImGuiTest* test, const char* line_start, const char* line_end, const char* file_ext) -{ - IM_UNUSED(e); - IM_UNUSED(test); - - const char* extension = ImStristr(line_start, line_end, file_ext, NULL); - if (extension == NULL) - return false; - - const char* path_end = extension + strlen(file_ext); - const char* path_begin = extension - 1; - while (path_begin > line_start && path_begin[-1] != ' ' && path_begin[-1] != '\'' && path_begin[-1] != '\"') - path_begin--; - if (path_begin == path_end) - return false; - - Str256 buf; - - // Open file - buf.setf("Open file: %.*s", (int)(path_end - path_begin), path_begin); - if (ImGui::MenuItem(buf.c_str())) - { - buf.setf("%.*s", (int)(path_end - path_begin), path_begin); - ImPathFixSeparatorsForCurrentOS(buf.c_str()); - ImOsOpenInShell(buf.c_str()); - } - - // Open folder - const char* folder_begin = path_begin; - const char* folder_end = ImPathFindFilename(path_begin, path_end); - buf.setf("Open folder: %.*s", (int)(folder_end - folder_begin), path_begin); - if (ImGui::MenuItem(buf.c_str())) - { - buf.setf("%.*s", (int)(folder_end - folder_begin), folder_begin); - ImPathFixSeparatorsForCurrentOS(buf.c_str()); - ImOsOpenInShell(buf.c_str()); - } - - return true; -} - -static bool ParseLineAndDrawFileOpenItem(ImGuiTestEngine* e, ImGuiTest* test, const char* line_start, const char* line_end) -{ - if (ParseLineAndDrawFileOpenItemForSourceFile(e, test, line_start, line_end)) - return true; - if (ParseLineAndDrawFileOpenItemForImageFile(e, test, line_start, line_end, ".png")) - return true; - if (ParseLineAndDrawFileOpenItemForImageFile(e, test, line_start, line_end, ".gif")) - return true; - if (ParseLineAndDrawFileOpenItemForImageFile(e, test, line_start, line_end, ".mp4")) - return true; - return false; -} - -static float GetDpiScale() -{ -#ifdef IMGUI_HAS_VIEWPORT - return ImGui::GetWindowViewport()->DpiScale; -#else - return 1.0f; -#endif -} - -static void DrawTestLog(ImGuiTestEngine* e, ImGuiTest* test) -{ - const ImU32 error_col = IM_COL32(255, 150, 150, 255); - const ImU32 warning_col = IM_COL32(240, 240, 150, 255); - const ImU32 unimportant_col = IM_COL32(190, 190, 190, 255); - const float dpi_scale = GetDpiScale(); - - ImGuiTestOutput* test_output = &test->Output; - - ImGuiTestLog* log = &test_output->Log; - const char* text = log->Buffer.begin(); - const char* text_end = log->Buffer.end(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 2.0f) * dpi_scale); - ImGuiListClipper clipper; - ImGuiTestVerboseLevel max_log_level = test_output->Status == ImGuiTestStatus_Error ? e->IO.ConfigVerboseLevelOnError : e->IO.ConfigVerboseLevel; - int line_count = log->ExtractLinesForVerboseLevels(ImGuiTestVerboseLevel_Silent, max_log_level, NULL); - int current_index_clipped = -1; - int current_index_abs = 0; - clipper.Begin(line_count); - while (clipper.Step()) - { - for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) - { - // Advance index_by_log_level to find log entry indicated by line_no. - ImGuiTestLogLineInfo* line_info = NULL; - while (current_index_clipped < line_no) - { - line_info = &log->LineInfo[current_index_abs]; - if (line_info->Level <= max_log_level) - current_index_clipped++; - current_index_abs++; - } - - const char* line_start = text + line_info->LineOffset; - const char* line_end = strchr(line_start, '\n'); - if (line_end == NULL) - line_end = text_end; - - switch (line_info->Level) - { - case ImGuiTestVerboseLevel_Error: - ImGui::PushStyleColor(ImGuiCol_Text, error_col); - break; - case ImGuiTestVerboseLevel_Warning: - ImGui::PushStyleColor(ImGuiCol_Text, warning_col); - break; - case ImGuiTestVerboseLevel_Debug: - case ImGuiTestVerboseLevel_Trace: - ImGui::PushStyleColor(ImGuiCol_Text, unimportant_col); - break; - default: - ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32_WHITE); - break; - } - ImGui::TextUnformatted(line_start, line_end); - ImGui::PopStyleColor(); - - ImGui::PushID(line_no); - if (ImGui::BeginPopupContextItem("Context", 1)) - { - if (!ParseLineAndDrawFileOpenItem(e, test, line_start, line_end)) - ImGui::MenuItem("No options", NULL, false, false); - ImGui::EndPopup(); - } - ImGui::PopID(); - } - } - ImGui::PopStyleVar(); -} - -#if IMGUI_VERSION_NUM <= 18963 -namespace ImGui -{ - void SetItemTooltip(const char* fmt, ...) - { - if (ImGui::IsItemHovered()) - { - va_list args; - va_start(args, fmt); - ImGui::SetTooltipV(fmt, args); - va_end(args); - } - } -} // namespace ImGui -#endif - -static bool ShowTestGroupFilterTest(ImGuiTestEngine* e, ImGuiTestGroup group, const char* filter, ImGuiTest* test) -{ - if (test->Group != group) - return false; - if (!ImGuiTestEngine_PassFilter(test, *filter ? filter : "all")) - return false; - if ((e->UiFilterByStatusMask & (1 << test->Output.Status)) == 0) - return false; - return true; -} - -static void GetFailingTestsAsString(ImGuiTestEngine* e, ImGuiTestGroup group, char separator, Str* out_string) -{ - IM_ASSERT(out_string != NULL); - bool first = true; - for (int i = 0; i < e->TestsAll.Size; i++) - { - ImGuiTest* failing_test = e->TestsAll[i]; - Str* filter = (group == ImGuiTestGroup_Tests) ? e->UiFilterTests : e->UiFilterPerfs; - if (failing_test->Group != group) - continue; - if (failing_test->Output.Status != ImGuiTestStatus_Error) - continue; - if (!ImGuiTestEngine_PassFilter(failing_test, filter->empty() ? "all" : filter->c_str())) - continue; - if (!first) - out_string->append(separator); - out_string->append(failing_test->Name); - first = false; - } -} - -static void TestStatusButton(const char* id, const ImVec4& color, bool running, int display_counter) -{ - ImGuiContext& g = *GImGui; - ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop, true); - ImGui::ColorButton(id, color, ImGuiColorEditFlags_NoTooltip); - ImGui::PopItemFlag(); - if (running) - { - //ImRect r = g.LastItemData.Rect; - ImVec2 center = g.LastItemData.Rect.GetCenter(); - float radius = ImFloor(ImMin(g.LastItemData.Rect.GetWidth(), g.LastItemData.Rect.GetHeight()) * 0.40f); - float t = (float)(ImGui::GetTime() * 20.0f); - ImVec2 off(ImCos(t) * radius, ImSin(t) * radius); - ImGui::GetWindowDrawList()->AddLine(center - off, center + off, ImGui::GetColorU32(ImGuiCol_Text), 1.5f); - //ImGui::RenderText(r.Min + style.FramePadding + ImVec2(0, 0), &"|\0/\0-\0\\"[(((ImGui::GetFrameCount() / 5) & 3) << 1)], NULL); - } - else if (display_counter >= 0) - { - ImVec2 center = g.LastItemData.Rect.GetCenter(); - Str30f buf("%d", display_counter); - ImGui::GetWindowDrawList()->AddText(center - ImGui::CalcTextSize(buf.c_str()) * 0.5f, ImGui::GetColorU32(ImGuiCol_Text), buf.c_str()); - } -} - -static void ShowTestGroup(ImGuiTestEngine* e, ImGuiTestGroup group, Str* filter) -{ - ImGuiStyle& style = ImGui::GetStyle(); - ImGuiIO& io = ImGui::GetIO(); - const float dpi_scale = GetDpiScale(); - - // Colored Status button: will be displayed later below - // - Save position of test run status button and make space for it. - const ImVec2 status_button_pos = ImGui::GetCursorPos(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetFrameHeight() + style.ItemInnerSpacing.x); - - //ImGui::Text("TESTS (%d)", engine->TestsAll.Size); -#if 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 - if (run) - { - for (int n = 0; n < e->TestsAll.Size; n++) - { - ImGuiTest* test = e->TestsAll[n]; - if (!ShowTestGroupFilterTest(e, group, filter->c_str(), test)) - continue; - ImGuiTestEngine_QueueTest(e, test, ImGuiTestRunFlags_None); - } - } - ImGui::SameLine(); - - { - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6.0f); - const char* filter_by_status_desc = ""; - if (e->UiFilterByStatusMask == ~0u) - filter_by_status_desc = "All"; - else if (e->UiFilterByStatusMask == ~(1u << ImGuiTestStatus_Success)) - filter_by_status_desc = "Not OK"; - else if (e->UiFilterByStatusMask == (1u << ImGuiTestStatus_Error)) - filter_by_status_desc = "Errors"; - if (ImGui::BeginCombo("##filterbystatus", filter_by_status_desc)) - { - if (ImGui::Selectable("All", e->UiFilterByStatusMask == ~0u)) - e->UiFilterByStatusMask = (ImU32)~0u; - if (ImGui::Selectable("Not OK", e->UiFilterByStatusMask == ~(1u << ImGuiTestStatus_Success))) - e->UiFilterByStatusMask = (ImU32)~(1u << ImGuiTestStatus_Success); - if (ImGui::Selectable("Errors", e->UiFilterByStatusMask == (1u << ImGuiTestStatus_Error))) - e->UiFilterByStatusMask = (ImU32)(1u << ImGuiTestStatus_Error); - ImGui::EndCombo(); - } - } - - ImGui::SameLine(); - const char* perflog_label = "Perf Tool"; - float filter_width = ImGui::GetWindowContentRegionMax().x - ImGui::GetCursorPos().x; - float perf_stress_factor_width = (30 * dpi_scale); - if (group == ImGuiTestGroup_Perfs) - { - filter_width -= style.ItemSpacing.x + perf_stress_factor_width; - filter_width -= style.ItemSpacing.x + style.FramePadding.x * 2 + ImGui::CalcTextSize(perflog_label).x; - } - filter_width -= ImGui::CalcTextSize("(?)").x + style.ItemSpacing.x; - ImGui::SetNextItemWidth(ImMax(20.0f, filter_width)); - ImGui::InputText("##filter", filter); - ImGui::SameLine(); - ImGui::TextDisabled("(?)"); - ImGui::SetItemTooltip("Query is composed of one or more comma-separated filter terms with optional modifiers.\n" - "Available modifiers:\n" - "- '-' prefix excludes tests matched by the term.\n" - "- '^' prefix anchors term matching to the start of the string.\n" - "- '$' suffix anchors term matching to the end of the string."); - if (group == ImGuiTestGroup_Perfs) - { - ImGui::SameLine(); - ImGui::SetNextItemWidth(perf_stress_factor_width); - ImGui::DragInt("##PerfStress", &e->IO.PerfStressAmount, 0.1f, 1, 20, "x%d"); - ImGui::SetItemTooltip("Increase workload of performance tests (higher means longer run)."); // FIXME: Move? - ImGui::SameLine(); - if (ImGui::Button(perflog_label)) - { - e->UiPerfToolOpen = true; - ImGui::FocusWindow(ImGui::FindWindowByName("Dear ImGui Perf Tool")); - } - } - - int tests_completed = 0; - int tests_succeeded = 0; - int tests_failed = 0; - if (ImGui::BeginTable("Tests", 3, ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_SizingFixedFit)) - { - ImGui::TableSetupScrollFreeze(0, 1); - ImGui::TableSetupColumn("Status"); - ImGui::TableSetupColumn("Category"); - ImGui::TableSetupColumn("Test", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableHeadersRow(); - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6, 4) * dpi_scale); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 0) * dpi_scale); - //ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(100, 10) * dpi_scale); - for (int test_n = 0; test_n < e->TestsAll.Size; test_n++) - { - ImGuiTest* test = e->TestsAll[test_n]; - if (!ShowTestGroupFilterTest(e, group, filter->c_str(), test)) - continue; - - ImGuiTestOutput* test_output = &test->Output; - ImGuiTestContext* test_context = (e->TestContext && e->TestContext->Test == test) ? e->TestContext : NULL; // Running context, if any - - ImGui::TableNextRow(); - ImGui::PushID(test_n); - - // Colors match general test status colors defined below. - ImVec4 status_color; - switch (test_output->Status) - { - case ImGuiTestStatus_Error: - status_color = ImVec4(0.9f, 0.1f, 0.1f, 1.0f); - tests_completed++; - tests_failed++; - break; - case ImGuiTestStatus_Success: - status_color = ImVec4(0.1f, 0.9f, 0.1f, 1.0f); - tests_completed++; - tests_succeeded++; - break; - case ImGuiTestStatus_Queued: - case ImGuiTestStatus_Running: - case ImGuiTestStatus_Suspended: - if (test_context && (test_context->RunFlags & ImGuiTestRunFlags_GuiFuncOnly)) - status_color = ImVec4(0.8f, 0.0f, 0.8f, 1.0f); - else - status_color = ImVec4(0.8f, 0.4f, 0.1f, 1.0f); - break; - default: - status_color = ImVec4(0.4f, 0.4f, 0.4f, 1.0f); - break; - } - - ImGui::TableNextColumn(); - TestStatusButton("status", status_color, test_output->Status == ImGuiTestStatus_Running || test_output->Status == ImGuiTestStatus_Suspended, -1); - ImGui::SameLine(); - - bool queue_test = false; - bool queue_gui_func_toggle = false; - bool select_test = false; - - if (test_output->Status == ImGuiTestStatus_Suspended) - { - // Resume IM_SUSPEND_TESTFUNC - // FIXME: Terrible user experience to have this here. - if (ImGui::Button("Con###Run")) - test_output->Status = ImGuiTestStatus_Running; - ImGui::SetItemTooltip("CTRL+Space to continue."); - if (ImGui::IsKeyPressed(ImGuiKey_Space) && io.KeyCtrl) - test_output->Status = ImGuiTestStatus_Running; - } - else - { - if (ImGui::Button("Run###Run")) - queue_test = select_test = true; - } - - ImGui::TableNextColumn(); - if (ImGui::Selectable(test->Category, test == e->UiSelectedTest, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SelectOnNav)) - select_test = true; - - // Double-click to run test, CTRL+Double-click to run GUI function - const bool is_running_gui_func = (test_context && (test_context->RunFlags & ImGuiTestRunFlags_GuiFuncOnly)); - const bool has_gui_func = (test->GuiFunc != NULL); - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) - { - if (ImGui::GetIO().KeyCtrl) - queue_gui_func_toggle = true; - else - queue_test = true; - } - - /*if (ImGui::IsItemHovered() && test->TestLog.size() > 0) - { - ImGui::BeginTooltip(); - DrawTestLog(engine, test, false); - ImGui::EndTooltip(); - }*/ - - if (e->UiSelectAndScrollToTest == test) - ImGui::SetScrollHereY(); - - bool view_source = false; - if (ImGui::BeginPopupContextItem()) - { - select_test = true; - - if (ImGui::MenuItem("Run test")) - queue_test = true; - if (ImGui::MenuItem("Run GUI func", "Ctrl+DblClick", is_running_gui_func, has_gui_func)) - queue_gui_func_toggle = true; - - ImGui::Separator(); - - const bool open_source_available = (test->SourceFile != NULL) && (e->IO.SrcFileOpenFunc != NULL); - - Str128 buf; - if (test->SourceFile != NULL) // This is normally set by IM_REGISTER_TEST() but custom registration may omit it. - buf.setf("Open source (%s:%d)", ImPathFindFilename(test->SourceFile), test->SourceLine); - else - buf.set("Open source"); - if (ImGui::MenuItem(buf.c_str(), NULL, false, open_source_available)) - e->IO.SrcFileOpenFunc(test->SourceFile, test->SourceLine, e->IO.SrcFileOpenUserData); - if (ImGui::MenuItem("View source...", NULL, false, test->SourceFile != NULL)) - view_source = true; - - if (group == ImGuiTestGroup_Perfs && ImGui::MenuItem("View perflog")) - { - e->PerfTool->ViewOnly(test->Name); - e->UiPerfToolOpen = true; - } - - ImGui::Separator(); - if (ImGui::MenuItem("Copy name", NULL, false)) - ImGui::SetClipboardText(test->Name); - - if (test_output->Status == ImGuiTestStatus_Error) - if (ImGui::MenuItem("Copy names of all failing tests")) - { - Str256 failing_tests; - GetFailingTestsAsString(e, group, ',', &failing_tests); - ImGui::SetClipboardText(failing_tests.c_str()); - } - - ImGuiTestLog* test_log = &test_output->Log; - if (ImGui::BeginMenu("Copy log", !test_log->IsEmpty())) - { - for (int level_n = ImGuiTestVerboseLevel_Error; level_n < ImGuiTestVerboseLevel_COUNT; level_n++) - { - ImGuiTestVerboseLevel level = (ImGuiTestVerboseLevel)level_n; - int count = test_log->ExtractLinesForVerboseLevels((ImGuiTestVerboseLevel)0, level, NULL); - if (ImGui::MenuItem(Str64f("%s (%d lines)", ImGuiTestEngine_GetVerboseLevelName(level), count).c_str(), NULL, false, count > 0)) - { - ImGuiTextBuffer buffer; - test_log->ExtractLinesForVerboseLevels((ImGuiTestVerboseLevel)0, level, &buffer); - ImGui::SetClipboardText(buffer.c_str()); - } - } - ImGui::EndMenu(); - } - - if (ImGui::MenuItem("Clear log", NULL, false, !test_log->IsEmpty())) - test_log->Clear(); - - ImGui::EndPopup(); - } - - // Process source popup - static ImGuiTextBuffer source_blurb; - static int goto_line = -1; - if (view_source) - { - source_blurb.clear(); - size_t file_size = 0; - char* file_data = (char*)ImFileLoadToMemory(test->SourceFile, "rb", &file_size); - if (file_data) - source_blurb.append(file_data, file_data + file_size); - else - source_blurb.append("<Error loading sources>"); - goto_line = (test->SourceLine + test->SourceLineEnd) / 2; - ImGui::OpenPopup("Source"); - } - if (ImGui::BeginPopup("Source")) - { - // FIXME: Local vs screen pos too messy :( - const ImVec2 start_pos = ImGui::GetCursorStartPos(); - const float line_height = ImGui::GetTextLineHeight(); - if (goto_line != -1) - ImGui::SetScrollFromPosY(start_pos.y + (goto_line - 1) * line_height, 0.5f); - goto_line = -1; - - ImRect r(0.0f, test->SourceLine * line_height, ImGui::GetWindowWidth(), (test->SourceLine + 1) * line_height); // SourceLineEnd is too flaky - ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetWindowPos() + start_pos + r.Min, ImGui::GetWindowPos() + start_pos + r.Max, IM_COL32(80, 80, 150, 150)); - - ImGui::TextUnformatted(source_blurb.c_str(), source_blurb.end()); - ImGui::EndPopup(); - } - - ImGui::TableNextColumn(); - ImGui::TextUnformatted(test->Name); - - // Process selection - if (select_test) - e->UiSelectedTest = test; - - // Process queuing - if (queue_gui_func_toggle && is_running_gui_func) - ImGuiTestEngine_AbortCurrentTest(e); - else if (queue_gui_func_toggle && !e->IO.IsRunningTests) - ImGuiTestEngine_QueueTest(e, test, ImGuiTestRunFlags_RunFromGui | ImGuiTestRunFlags_GuiFuncOnly); - if (queue_test && !e->IO.IsRunningTests) - ImGuiTestEngine_QueueTest(e, test, ImGuiTestRunFlags_RunFromGui); - - ImGui::PopID(); - } - ImGui::Spacing(); - ImGui::PopStyleVar(2); - ImGui::EndTable(); - } - - // Display test status recap (colors match per-test run button colors defined above) - { - ImVec4 status_color; - if (tests_failed > 0) - status_color = ImVec4(0.9f, 0.1f, 0.1f, 1.0f); // Red - else if (e->IO.IsRunningTests) - status_color = ImVec4(0.8f, 0.4f, 0.1f, 1.0f); - else if (tests_succeeded > 0 && tests_completed == tests_succeeded) - status_color = ImVec4(0.1f, 0.9f, 0.1f, 1.0f); - else - status_color = ImVec4(0.4f, 0.4f, 0.4f, 1.0f); - //ImVec2 cursor_pos_bkp = ImGui::GetCursorPos(); - ImGui::SetCursorPos(status_button_pos); - TestStatusButton("status", status_color, false, tests_failed > 0 ? tests_failed : -1);// e->IO.IsRunningTests); - ImGui::SetItemTooltip("Filtered: %d\n- OK: %d\n- Errors: %d", tests_completed, tests_succeeded, tests_failed); - //ImGui::SetCursorPos(cursor_pos_bkp); // Restore cursor position for rendering further widgets - } -} - -static void ImGuiTestEngine_ShowLogAndTools(ImGuiTestEngine* engine) -{ - ImGuiContext& g = *GImGui; - const float dpi_scale = GetDpiScale(); - - if (!ImGui::BeginTabBar("##tools")) - return; - - if (ImGui::BeginTabItem("LOG")) - { - ImGuiTest* selected_test = engine->UiSelectedTest; - - if (selected_test != NULL) - ImGui::Text("Log for '%s' '%s'", selected_test->Category, selected_test->Name); - else - ImGui::Text("N/A"); - if (ImGui::SmallButton("Clear")) - if (selected_test) - selected_test->Output.Log.Clear(); - ImGui::SameLine(); - if (ImGui::SmallButton("Copy to clipboard")) - if (engine->UiSelectedTest) - ImGui::SetClipboardText(selected_test->Output.Log.Buffer.c_str()); - ImGui::Separator(); - - ImGui::BeginChild("Log"); - if (engine->UiSelectedTest) - { - DrawTestLog(engine, engine->UiSelectedTest); - if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) - ImGui::SetScrollHereY(); - } - ImGui::EndChild(); - ImGui::EndTabItem(); - } - - // Options - if (ImGui::BeginTabItem("OPTIONS")) - { - ImGuiIO& io = ImGui::GetIO(); - ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); - ImGui::Text("TestEngine: HookItems: %d, HookPushId: %d, InfoTasks: %d", g.TestEngineHookItems, g.DebugHookIdInfo != 0, engine->InfoTasks.Size); - ImGui::Separator(); - - if (ImGui::Button("Reboot UI context")) - engine->ToolDebugRebootUiContext = true; - - const ImGuiInputTextCallback filter_callback = [](ImGuiInputTextCallbackData* data) { return (data->EventChar == ',' || data->EventChar == ';') ? 1 : 0; }; - ImGui::InputText("Branch/Annotation", engine->IO.GitBranchName, IM_ARRAYSIZE(engine->IO.GitBranchName), ImGuiInputTextFlags_CallbackCharFilter, filter_callback, NULL); - ImGui::SetItemTooltip("This will be stored in the CSV file for performance tools."); - - ImGui::Separator(); - - if (ImGui::TreeNode("Screen/video capture")) - { - ImGui::Checkbox("Capture when requested by API", &engine->IO.ConfigCaptureEnabled); - ImGui::SetItemTooltip("Enable or disable screen capture API completely."); - ImGui::Checkbox("Capture screen on error", &engine->IO.ConfigCaptureOnError); - ImGui::SetItemTooltip("Capture a screenshot on test failure."); - - // Fields modified by in this call will be synced to engine->CaptureContext. - engine->CaptureTool._ShowEncoderConfigFields(&engine->CaptureContext); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Performances")) - { - ImGui::Checkbox("Slow down whole app", &engine->ToolSlowDown); - ImGui::SameLine(); ImGui::SetNextItemWidth(70 * dpi_scale); - ImGui::SliderInt("##ms", &engine->ToolSlowDownMs, 0, 400, "%d ms"); - - // FIXME-TESTS: Need to be visualizing the samples/spikes. - double dt_1 = 1.0 / ImGui::GetIO().Framerate; - double fps_now = 1.0 / dt_1; - double dt_100 = engine->PerfDeltaTime100.GetAverage(); - double dt_500 = engine->PerfDeltaTime500.GetAverage(); - - //if (engine->PerfRefDeltaTime <= 0.0 && engine->PerfRefDeltaTime.IsFull()) - // engine->PerfRefDeltaTime = dt_2000; - - ImGui::Checkbox("Unthrolled", &engine->IO.ConfigNoThrottle); - ImGui::SameLine(); - if (ImGui::Button("Pick ref dt")) - engine->PerfRefDeltaTime = dt_500; - - double dt_ref = engine->PerfRefDeltaTime; - ImGui::Text("[ref dt] %6.3f ms", engine->PerfRefDeltaTime * 1000); - ImGui::Text("[last 001] %6.3f ms (%.1f FPS) ++ %6.3f ms", dt_1 * 1000.0, 1.0 / dt_1, (dt_1 - dt_ref) * 1000); - ImGui::Text("[last 100] %6.3f ms (%.1f FPS) ++ %6.3f ms ~ converging in %.1f secs", dt_100 * 1000.0, 1.0 / dt_100, (dt_1 - dt_ref) * 1000, 100.0 / fps_now); - ImGui::Text("[last 500] %6.3f ms (%.1f FPS) ++ %6.3f ms ~ converging in %.1f secs", dt_500 * 1000.0, 1.0 / dt_500, (dt_1 - dt_ref) * 1000, 500.0 / fps_now); - - //ImGui::PlotLines("Last 100", &engine->PerfDeltaTime100.Samples.Data, engine->PerfDeltaTime100.Samples.Size, engine->PerfDeltaTime100.Idx, NULL, 0.0f, dt_1000 * 1.10f, ImVec2(0.0f, ImGui::GetFontSize())); - ImVec2 plot_size(0.0f, ImGui::GetFrameHeight() * 3); - ImMovingAverage<double>* ma = &engine->PerfDeltaTime500; - ImGui::PlotLines("Last 500", - [](void* data, int n) { ImMovingAverage<double>* ma = (ImMovingAverage<double>*)data; return (float)(ma->Samples[n] * 1000); }, - ma, ma->Samples.Size, 0 * ma->Idx, NULL, 0.0f, (float)(ImMax(dt_100, dt_500) * 1000.0 * 1.2f), plot_size); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Dear ImGui Configuration Flags")) - { - ImGui::CheckboxFlags("io.ConfigFlags: NavEnableKeyboard", &io.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard); - ImGui::CheckboxFlags("io.ConfigFlags: NavEnableGamepad", &io.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad); -#ifdef IMGUI_HAS_DOCK - ImGui::Checkbox("io.ConfigDockingAlwaysTabBar", &io.ConfigDockingAlwaysTabBar); -#endif - ImGui::TreePop(); - } - - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); -} - -static void ImGuiTestEngine_ShowTestTool(ImGuiTestEngine* engine, bool* p_open) -{ - const float dpi_scale = GetDpiScale(); - - ImGui::SetNextWindowSize(ImVec2(ImGui::GetFontSize() * 50, ImGui::GetFontSize() * 40), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Dear ImGui Test Engine", p_open, ImGuiWindowFlags_MenuBar)) - { - ImGui::End(); - return; - } - - if (ImGui::BeginMenuBar()) - { - if (ImGui::BeginMenu("Tools")) - { - ImGuiContext& g = *GImGui; - ImGui::MenuItem("Metrics/Debugger", "", &engine->UiMetricsOpen); - ImGui::MenuItem("Debug Log", "", &engine->UiDebugLogOpen); - ImGui::MenuItem("Stack Tool", "", &engine->UiStackToolOpen); - ImGui::MenuItem("Item Picker", "", &g.DebugItemPickerActive); - ImGui::Separator(); - ImGui::MenuItem("Capture Tool", "", &engine->UiCaptureToolOpen); - ImGui::MenuItem("Perf Tool", "", &engine->UiPerfToolOpen); - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - } - - ImGui::SetNextItemWidth(90 * dpi_scale); - if (ImGui::BeginCombo("##RunSpeed", ImGuiTestEngine_GetRunSpeedName(engine->IO.ConfigRunSpeed), ImGuiComboFlags_None)) - { - for (ImGuiTestRunSpeed level = (ImGuiTestRunSpeed)0; level < ImGuiTestRunSpeed_COUNT; level = (ImGuiTestRunSpeed)(level + 1)) - if (ImGui::Selectable(ImGuiTestEngine_GetRunSpeedName(level), engine->IO.ConfigRunSpeed == level)) - engine->IO.ConfigRunSpeed = level; - ImGui::EndCombo(); - } - ImGui::SetItemTooltip( - "Running speed\n" - "- Fast: Run tests as fast as possible (no delay/vsync, teleport mouse, etc.).\n" - "- Normal: Run tests at human watchable speed (for debugging).\n" - "- Cinematic: Run tests with pauses between actions (for e.g. tutorials)." - ); - ImGui::SameLine(); - //ImGui::Checkbox("Fast", &engine->IO.ConfigRunFast); - //ImGui::SameLine(); - ImGui::Checkbox("Stop", &engine->IO.ConfigStopOnError); - ImGui::SetItemTooltip("Stop running tests when hitting an error."); - ImGui::SameLine(); - ImGui::Checkbox("DbgBrk", &engine->IO.ConfigBreakOnError); - ImGui::SetItemTooltip("Break in debugger when hitting an error."); - 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::SameLine(); - ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); - ImGui::SameLine(); - ImGui::SetNextItemWidth(70 * dpi_scale); - if (ImGui::BeginCombo("##Verbose", ImGuiTestEngine_GetVerboseLevelName(engine->IO.ConfigVerboseLevel), ImGuiComboFlags_None)) - { - for (ImGuiTestVerboseLevel level = (ImGuiTestVerboseLevel)0; level < ImGuiTestVerboseLevel_COUNT; level = (ImGuiTestVerboseLevel)(level + 1)) - if (ImGui::Selectable(ImGuiTestEngine_GetVerboseLevelName(level), engine->IO.ConfigVerboseLevel == level)) - engine->IO.ConfigVerboseLevel = engine->IO.ConfigVerboseLevelOnError = level; - ImGui::EndCombo(); - } - ImGui::SetItemTooltip("Verbose level."); - //ImGui::PopStyleVar(); - ImGui::Separator(); - - // SPLITTER - // FIXME-OPT: A better splitter API supporting arbitrary number of splits would be useful. - float list_height = 0.0f; - float& log_height = engine->UiLogHeight; - ImGui::Splitter("splitter", &list_height, &log_height, ImGuiAxis_Y, +1); - - // TESTS - ImGui::BeginChild("List", ImVec2(0, list_height), false, ImGuiWindowFlags_NoScrollbar); - if (ImGui::BeginTabBar("##Tests", ImGuiTabBarFlags_NoTooltip)) // Add _NoPushId flag in TabBar? - { - if (ImGui::BeginTabItem("TESTS", NULL, ImGuiTabItemFlags_NoPushId)) - { - ShowTestGroup(engine, ImGuiTestGroup_Tests, engine->UiFilterTests); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("PERFS", NULL, ImGuiTabItemFlags_NoPushId)) - { - ShowTestGroup(engine, ImGuiTestGroup_Perfs, engine->UiFilterPerfs); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); - } - ImGui::EndChild(); - engine->UiSelectAndScrollToTest = NULL; - - // LOG & TOOLS - ImGui::BeginChild("Log", ImVec2(0, log_height)); - ImGuiTestEngine_ShowLogAndTools(engine); - ImGui::EndChild(); - - ImGui::End(); -} - -void ImGuiTestEngine_ShowTestEngineWindows(ImGuiTestEngine* e, bool* p_open) -{ - // Test Tool - ImGuiTestEngine_ShowTestTool(e, p_open); - - // Stack Tool -#if IMGUI_VERSION_NUM < 18993 - if (e->UiStackToolOpen) - ImGui::ShowStackToolWindow(&e->UiStackToolOpen); -#else - if (e->UiStackToolOpen) - ImGui::ShowIDStackToolWindow(&e->UiStackToolOpen); -#endif - - // Capture Tool - if (e->UiCaptureToolOpen) - e->CaptureTool.ShowCaptureToolWindow(&e->CaptureContext, &e->UiCaptureToolOpen); - - // Performance tool - if (e->UiPerfToolOpen) - e->PerfTool->ShowPerfToolWindow(e, &e->UiPerfToolOpen);; - - // Show Dear ImGui windows - // (we cannot show demo window here because it could lead to duplicate display, which demo windows isn't guarded for) - if (e->UiMetricsOpen) - ImGui::ShowMetricsWindow(&e->UiMetricsOpen); - if (e->UiDebugLogOpen) - ImGui::ShowDebugLogWindow(&e->UiDebugLogOpen); -} diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_ui.h b/vendor/zgui/libs/imgui_test_engine/imgui_te_ui.h deleted file mode 100644 index 4527fb7..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_ui.h +++ /dev/null @@ -1,21 +0,0 @@ -// dear imgui test engine -// (ui) -// If you run tests in an interactive or visible application, you may want to call ImGuiTestEngine_ShowTestEngineWindows() - -// Provide access to: -// - "Dear ImGui Test Engine" main interface -// - "Dear ImGui Capture Tool" -// - "Dear ImGui Perf Tool" -// - other core debug functions: Metrics, Debug Log - -#pragma once - -#ifndef IMGUI_VERSION -#include "imgui.h" // IMGUI_API -#endif - -// Forward declarations -struct ImGuiTestEngine; - -// Functions -IMGUI_API void ImGuiTestEngine_ShowTestEngineWindows(ImGuiTestEngine* engine, bool* p_open); diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_utils.cpp b/vendor/zgui/libs/imgui_test_engine/imgui_te_utils.cpp deleted file mode 100644 index ad473a7..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_utils.cpp +++ /dev/null @@ -1,1306 +0,0 @@ -// dear imgui test engine -// (helpers/utilities. do NOT use this as a general purpose library) - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -#define _CRT_SECURE_NO_WARNINGS -#endif - -#include "imgui_te_utils.h" -#include "imgui.h" -#include "imgui_internal.h" -#define STR_IMPLEMENTATION -#include "thirdparty/Str/Str.h" - -#if defined(_WIN32) -#if !defined(_WINDOWS_) -#define WIN32_LEAN_AND_MEAN -#include <windows.h> -#endif -#include <shellapi.h> // ShellExecuteA() -#include <stdio.h> -#else -#include <errno.h> -#include <unistd.h> -#endif -#ifndef _MSC_VER -#include <sys/types.h> -#include <sys/stat.h> // stat() -#endif - -#if defined(__linux) || defined(__linux__) || defined(__MACH__) || defined(__MSL__) || defined(__MINGW32__) -#include <pthread.h> // pthread_setname_np() -#endif -#include <chrono> // high_resolution_clock::now() -#include <thread> // this_thread::sleep_for() - -//----------------------------------------------------------------------------- -// Hashing Helpers -//----------------------------------------------------------------------------- -// - ImHashDecoratedPathParseLiteral() [internal] -// - ImHashDecoratedPath() -// - ImFindNextDecoratedPartInPath() -//----------------------------------------------------------------------------- - -// - Parse literals encoded as "$$xxxx/" and incorporate into our hash based on type. -// - $$ not passed by caller. -static ImGuiID ImHashDecoratedPathParseLiteral(ImGuiID crc, const unsigned char* str, const unsigned char* str_end, const unsigned char** out_str_remaining) -{ - // Parse type (default to int) - ImGuiDataType type = ImGuiDataType_S32; - if (*str == '(') - { - // "$$(int)????" where ???? is s32 or u32 - if (str + 5 < str_end && memcmp(str, "(int)", 5) == 0) - { - type = ImGuiDataType_S32; - str += 5; - } - // "$$(ptr)0x????" where ???? is ptr size - else if (str + 7 < str_end && memcmp(str, "(ptr)0x", 7) == 0) - { - type = ImGuiDataType_Pointer; - str += 7; - } - } - - // Parse value - switch (type) - { - case ImGuiDataType_S32: - { - // e.g. "$$(int)123" for s32/u32/ImGuiID, same as PushID(int) - int v = 0; - { - int negative = 0; - if (str < str_end && *str == '-') { negative = 1; str++; } - if (str < str_end && *str == '+') { str++; } - for (char c = *str; str < str_end; c = *(++str)) - { - if (c >= '0' && c <= '9') { v = (v * 10) + (c - '0'); } - else break; - } - if (negative) - v = -v; - } - crc = ~ImHashData(&v, sizeof(int), ~crc); - break; - } - case ImGuiDataType_Pointer: - { - // e.g. "$$(ptr)0x1234FFFF" for pointers, same as PushID(void*) - intptr_t v = 0; - { - for (char c = *str; str < str_end; c = *(++str)) - { - if (c >= '0' && c <= '9') { v = (v << 4) + (c - '0'); } - else if (c >= 'A' && c <= 'F') { v = (v << 4) + 10 + (c - 'A'); } - else if (c >= 'a' && c <= 'f') { v = (v << 4) + 10 + (c - 'a'); } - else break; - } - } - crc = ~ImHashData(&v, sizeof(void*), ~crc); - break; - } - } - - // "$$xxxx" must always be either end of string, either leading to a next section e.g. "$$xxxx/" - IM_ASSERT(str == str_end || *str == '/'); - - *out_str_remaining = str; - return crc; -} - -// Hash "hello/world" as if it was "helloworld" -// To hash a forward slash we need to use "hello\\/world" -// IM_ASSERT(ImHashDecoratedPath("Hello/world") == ImHashStr("Helloworld", 0)); -// IM_ASSERT(ImHashDecoratedPath("Hello\\/world") == ImHashStr("Hello/world", 0)); -// IM_ASSERT(ImHashDecoratedPath("$$1") == (n = 1, ImHashData(&n, sizeof(int)))); -// Adapted from ImHash(). Not particularly fast! -ImGuiID ImHashDecoratedPath(const char* str, const char* str_end, ImGuiID seed) -{ - static ImU32 crc32_lut[256] = { 0 }; - if (!crc32_lut[1]) - { - const ImU32 polynomial = 0xEDB88320; - for (ImU32 i = 0; i < 256; i++) - { - ImU32 crc = i; - for (ImU32 j = 0; j < 8; j++) - crc = (crc >> 1) ^ (ImU32(-int(crc & 1)) & polynomial); - crc32_lut[i] = crc; - } - } - - // Prefixing the string with / ignore the seed - if (str != str_end && str[0] == '/') - seed = 0; - - seed = ~seed; - ImU32 crc = seed; - - // Focus for non-zero terminated string for consistency - if (str_end == NULL) - str_end = str + strlen(str); - - bool inhibit_one = false; - bool new_section = true; - const unsigned char* current = (const unsigned char*)str; - while (current < (const unsigned char*)str_end) - { - const unsigned char c = *current++; - - // Backslash to inhibit special behavior of following character - if (c == '\\' && !inhibit_one) - { - inhibit_one = true; - continue; - } - - // Forward slashes are ignored unless prefixed with a backward slash - if (c == '/' && !inhibit_one) - { - inhibit_one = false; - new_section = true; - seed = crc; // Set seed to the new path - continue; - } - - // $$ at the beginning of a section to encode literals. - // - Currently: "$$????" = hash of 1 as int - // - May add pointers and other types. - if (c == '$' && current[0] == '$' && !inhibit_one && new_section) - { - crc = ImHashDecoratedPathParseLiteral(crc, current + 1, (const unsigned char*)str_end, ¤t); - continue; - } - - // Reset the hash when encountering ### - if (c == '#' && current[0] == '#' && current[1] == '#') - crc = seed; - - // Hash byte - crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; - - inhibit_one = new_section = false; - } - return ~crc; -} - -// Returns a next element of decorated hash path. -// "//hello/world/child" --> "world/child" -// "world/child" --> "child" -// This is a helper for code needing to do some parsing of individual nodes in a path. -// Note: we need the (unsigned char*) stuff in order to keep code similar to ImHashDecoratedPath(). They are not really necessary in this function tho. -const char* ImFindNextDecoratedPartInPath(const char* str, const char* str_end) -{ - const unsigned char* current = (const unsigned char*)str; - while (*current == '/') - current++; - - bool inhibit_one = false; - while (true) - { - if (str_end != NULL && current == (const unsigned char*)str_end) - break; - - const unsigned char c = *current++; - if (c == 0) - break; - if (c == '\\' && !inhibit_one) - { - inhibit_one = true; - continue; - } - - // Forward slashes are ignored unless prefixed with a backward slash - if (c == '/' && !inhibit_one) - return (const char*)current; - - inhibit_one = false; - } - return NULL; -} - -//----------------------------------------------------------------------------- -// File/Directory Helpers -//----------------------------------------------------------------------------- -// - ImFileExist() -// - ImFileCreateDirectoryChain() -// - ImFileFindInParents() -// - ImFileLoadSourceBlurb() -//----------------------------------------------------------------------------- - -#if _WIN32 -static const char IM_DIR_SEPARATOR = '\\'; - -static void ImUtf8ToWideChar(const char* multi_byte, ImVector<wchar_t>* buf) -{ - const int wsize = ::MultiByteToWideChar(CP_UTF8, 0, multi_byte, -1, NULL, 0); - buf->resize(wsize); - ::MultiByteToWideChar(CP_UTF8, 0, multi_byte, -1, (wchar_t*)buf->Data, wsize); -} -#else -static const char IM_DIR_SEPARATOR = '/'; -#endif - -bool ImFileExist(const char* filename) -{ - struct stat dir_stat; - int ret = stat(filename, &dir_stat); - return (ret == 0); -} - -bool ImFileDelete(const char* filename) -{ -#if _WIN32 - ImVector<wchar_t> buf; - ImUtf8ToWideChar(filename, &buf); - return ::DeleteFileW(&buf[0]) == TRUE; -#else - unlink(filename); -#endif - return false; -} - -// Create directories for specified path. Slashes will be replaced with platform directory separators. -// e.g. ImFileCreateDirectoryChain("aaaa/bbbb/cccc.png") -// will try to create "aaaa/" then "aaaa/bbbb/". -bool ImFileCreateDirectoryChain(const char* path, const char* path_end) -{ - IM_ASSERT(path != NULL); - IM_ASSERT(path[0] != 0); - - if (path_end == NULL) - path_end = path + strlen(path); - - // Copy in a local, zero-terminated buffer - size_t path_len = (size_t)(path_end - path); - char* path_local = (char*)IM_ALLOC(path_len + 1); - memcpy(path_local, path, path_len); - path_local[path_len] = 0; - -#if defined(_WIN32) - ImVector<ImWchar> 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. - for (char* token = strtok(path_local, "\\/"); token != NULL; token = strtok(NULL, "\\/")) - { - // strtok() replaces slashes with NULLs. Overwrite removed slashes here with the type of slashes the OS needs (win32 functions need backslashes). - if (token != path_local) - *(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; - buf.resize(filename_wsize); - ImTextStrFromUtf8(&buf[0], filename_wsize, path_local, NULL); - if (!::CreateDirectoryW((wchar_t*)&buf[0], NULL) && GetLastError() != ERROR_ALREADY_EXISTS) -#else - if (mkdir(path_local, S_IRWXU) != 0 && errno != EEXIST) -#endif - { - IM_FREE(path_local); - return false; - } - } - IM_FREE(path_local); - return true; -} - -bool ImFileFindInParents(const char* sub_path, int max_parent_count, Str* output) -{ - IM_ASSERT(sub_path != NULL); - IM_ASSERT(output != NULL); - for (int parent_level = 0; parent_level < max_parent_count; parent_level++) - { - output->clear(); - for (int j = 0; j < parent_level; j++) - output->append("../"); - output->append(sub_path); - if (ImFileExist(output->c_str())) - return true; - } - output->clear(); - return false; -} - -bool ImFileLoadSourceBlurb(const char* file_name, int line_no_start, int line_no_end, ImGuiTextBuffer* out_buf) -{ - size_t file_size = 0; - char* file_begin = (char*)ImFileLoadToMemory(file_name, "rb", &file_size, 1); - if (file_begin == NULL) - return false; - - char* file_end = file_begin + file_size; - int line_no = 0; - const char* test_src_begin = NULL; - const char* test_src_end = NULL; - for (const char* p = file_begin; p < file_end; ) - { - line_no++; - const char* line_begin = p; - const char* line_end = ImStrchrRange(line_begin + 1, file_end, '\n'); - if (line_end == NULL) - line_end = file_end; - if (line_no >= line_no_start && line_no <= line_no_end) - { - if (test_src_begin == NULL) - test_src_begin = line_begin; - test_src_end = ImMax(test_src_end, line_end); - } - p = line_end + 1; - } - - if (test_src_begin != NULL) - out_buf->append(test_src_begin, test_src_end); - else - out_buf->clear(); - - ImGui::MemFree(file_begin); - return true; -} - -//----------------------------------------------------------------------------- -// Path Helpers -//----------------------------------------------------------------------------- -// - ImPathFindFilename() -// - ImPathFindFileExt() -// - ImPathFixSeparatorsForCurrentOS() -//----------------------------------------------------------------------------- - -const char* ImPathFindFilename(const char* path, const char* path_end) -{ - IM_ASSERT(path != NULL); - if (!path_end) - path_end = path + strlen(path); - const char* p = path_end; - while (p > path) - { - if (p[-1] == '/' || p[-1] == '\\') - break; - p--; - } - return p; -} - -// "folder/filename" -> return pointer to "" (end of string) -// "folder/filename.png" -> return pointer to ".png" -// "folder/filename.png.bak" -> return pointer to ".png.bak" -const char* ImPathFindExtension(const char* path, const char* path_end) -{ - if (!path_end) - path_end = path + strlen(path); - const char* filename = ImPathFindFilename(path, path_end); - const char* p = filename; - while (p < path_end) - { - if (p[0] == '.') - break; - p++; - } - return p; -} - -void ImPathFixSeparatorsForCurrentOS(char* buf) -{ -#ifdef _WIN32 - for (char* p = buf; *p != 0; p++) - if (*p == '/') - *p = '\\'; -#else - for (char* p = buf; *p != 0; p++) - if (*p == '\\') - *p = '/'; -#endif -} - -//----------------------------------------------------------------------------- -// String Helpers -//----------------------------------------------------------------------------- - -static const char* ImStrStr(const char* haystack, size_t hlen, const char* needle, int nlen) -{ - const char* end = haystack + hlen; - const char* p = haystack; - while ((p = (const char*)memchr(p, *needle, end - p)) != NULL) - { - if (end - p < nlen) - return NULL; - if (memcmp(p, needle, nlen) == 0) - return p; - p++; - } - return NULL; -} - -void ImStrReplace(Str* s, const char* find, const char* repl) -{ - IM_ASSERT(find != NULL && *find); - IM_ASSERT(repl != NULL); - int find_len = (int)strlen(find); - int repl_len = (int)strlen(repl); - int repl_diff = repl_len - find_len; - - // Estimate required length of new buffer if string size increases. - int need_capacity = s->capacity(); - int num_matches = INT_MAX; - if (repl_diff > 0) - { - num_matches = 0; - need_capacity = s->length() + 1; - for (char* p = s->c_str(), *end = s->c_str() + s->length(); p != NULL && p < end;) - { - p = (char*)ImStrStr(p, end - p, find, find_len); - if (p) - { - need_capacity += repl_diff; - p += find_len; - num_matches++; - } - } - } - - if (num_matches == 0) - return; - - const char* not_owned_data = s->owned() ? NULL : s->c_str(); - if (!s->owned() || need_capacity > s->capacity()) - s->reserve(need_capacity); - if (not_owned_data != NULL) - s->set(not_owned_data); - - // Replace data. - for (char* p = s->c_str(), *end = s->c_str() + s->length(); p != NULL && p < end && num_matches--;) - { - p = (char*)ImStrStr(p, end - p, find, find_len); - if (p) - { - memmove(p + repl_len, p + find_len, end - p - find_len + 1); - memcpy(p, repl, repl_len); - p += repl_len; - end += repl_diff; - } - } -} - -const char* ImStrchrRangeWithEscaping(const char* str, const char* str_end, char find_c) -{ - while (str < str_end) - { - const char c = *str; - if (c == '\\') - { - str += 2; - continue; - } - if (c == find_c) - return str; - str++; - } - return NULL; -} - -// Suboptimal but ok for the data size we are dealing with (see commit on 2022/08/22 for a faster and more complicated version) -void ImStrXmlEscape(Str* s) -{ - ImStrReplace(s, "&", "&"); - ImStrReplace(s, "<", "<"); - ImStrReplace(s, ">", ">"); - ImStrReplace(s, "\"", """); - ImStrReplace(s, "\'", "'"); -} - -// Based on code from https://github.com/EddieBreeg/C_b64 by @EddieBreeg. -int ImStrBase64Encode(const unsigned char* src, char* dst, int length) -{ - static const char* b64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - int i, j, k, l, encoded_len = 0; - - while (length > 0) - { - switch (length) - { - case 1: - i = src[0] >> 2; - j = (src[0] & 3) << 4; - k = 64; - l = 64; - break; - case 2: - i = src[0] >> 2; - j = ((src[0] & 3) << 4) | (src[1] >> 4); - k = (src[1] & 15) << 2; - l = 64; - break; - default: - i = src[0] >> 2; - j = ((src[0] & 3) << 4) | (src[1] >> 4); - k = ((src[1] & 0xf) << 2) | (src[2] >> 6 & 3); - l = src[2] & 0x3f; - break; - } - dst[0] = b64Table[i]; - dst[1] = b64Table[j]; - dst[2] = b64Table[k]; - dst[3] = b64Table[l]; - src += 3; - dst += 4; - length -= 3; - encoded_len += 4; - } - return encoded_len; -} - -//----------------------------------------------------------------------------- -// Parsing Helpers -//----------------------------------------------------------------------------- -// - ImParseSplitCommandLine() -// - ImParseFindIniSection() -//----------------------------------------------------------------------------- - -void ImParseExtractArgcArgvFromCommandLine(int* out_argc, char const*** out_argv, const char* cmd_line) -{ - size_t cmd_line_len = strlen(cmd_line); - - int n = 1; - { - const char* p = cmd_line; - while (*p != 0) - { - const char* arg = p; - while (*arg == ' ') - arg++; - const char* arg_end = strchr(arg, ' '); - if (arg_end == NULL) - p = arg_end = cmd_line + cmd_line_len; - else - p = arg_end + 1; - n++; - } - } - - int argc = n; - char const** argv = (char const**)malloc(sizeof(char*) * ((size_t)argc + 1) + (cmd_line_len + 1)); - IM_ASSERT(argv != NULL); - char* cmd_line_dup = (char*)argv + sizeof(char*) * ((size_t)argc + 1); - strcpy(cmd_line_dup, cmd_line); - - { - argv[0] = "main.exe"; - argv[argc] = NULL; - - char* p = cmd_line_dup; - for (n = 1; n < argc; n++) - { - char* arg = p; - char* arg_end = strchr(arg, ' '); - if (arg_end == NULL) - p = arg_end = cmd_line_dup + cmd_line_len; - else - p = arg_end + 1; - argv[n] = arg; - arg_end[0] = 0; - } - } - - *out_argc = argc; - *out_argv = argv; -} - -bool ImParseFindIniSection(const char* ini_config, const char* header, ImVector<char>* result) -{ - IM_ASSERT(ini_config != NULL); - IM_ASSERT(header != NULL); - IM_ASSERT(result != NULL); - - size_t ini_len = strlen(ini_config); - size_t header_len = strlen(header); - - IM_ASSERT(header_len > 0); - - if (ini_len == 0) - return false; - - const char* section_start = strstr(ini_config, header); - if (section_start == NULL) - return false; - - const char* section_end = strstr(section_start + header_len, "\n["); - if (section_end == NULL) - section_end = section_start + ini_len; - - // "\n[" matches next header start on all platforms, but it cuts new line marker in half on windows. - if (*(section_end - 1) == '\r') - --section_end; - - size_t section_len = (size_t)(section_end - section_start); - result->resize((int)section_len + 1); - ImStrncpy(result->Data, section_start, section_len); - - return true; -} - -//----------------------------------------------------------------------------- -// Time Helpers -//----------------------------------------------------------------------------- -// - ImTimeGetInMicroseconds() -// - ImTimestampToISO8601() -//----------------------------------------------------------------------------- - -uint64_t ImTimeGetInMicroseconds() -{ - // Trying std::chrono out of unfettered optimism that it may actually work.. - using namespace std; - chrono::microseconds ms = chrono::duration_cast<chrono::microseconds>(chrono::high_resolution_clock::now().time_since_epoch()); - return (uint64_t)ms.count(); -} - -void ImTimestampToISO8601(uint64_t timestamp, Str* out_date) -{ - time_t unix_time = (time_t)(timestamp / 1000000); // Convert to seconds. - tm* time = gmtime(&unix_time); - const char* time_format = "%Y-%m-%dT%H:%M:%S"; - size_t size_req = strftime(out_date->c_str(), out_date->capacity(), time_format, time); - if (size_req >= (size_t)out_date->capacity()) - { - out_date->reserve((int)size_req); - strftime(out_date->c_str(), out_date->capacity(), time_format, time); - } -} - -//----------------------------------------------------------------------------- -// Threading Helpers -//----------------------------------------------------------------------------- -// - ImThreadSleepInMilliseconds() -// - ImThreadSetCurrentThreadDescription() -//----------------------------------------------------------------------------- - -void ImThreadSleepInMilliseconds(int ms) -{ - using namespace std; - this_thread::sleep_for(chrono::milliseconds(ms)); -} - -#if defined(_MSC_VER) -// Helper function for setting thread name on Win32 -// This is a separate function because __try cannot coexist with local objects that need destructors called on stack unwind -static void ImThreadSetCurrentThreadDescriptionWin32OldStyle(const char* description) -{ - // Old-style Win32 thread name setting method - // See https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-set-a-thread-name-in-native-code - const DWORD MS_VC_EXCEPTION = 0x406D1388; -#pragma pack(push,8) - typedef struct tagTHREADNAME_INFO - { - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. - } THREADNAME_INFO; -#pragma pack(pop) - - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = description; - info.dwThreadID = (DWORD)-1; - info.dwFlags = 0; -#pragma warning(push) -#pragma warning(disable: 6320 6322) - __try - { - RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); - } - __except (EXCEPTION_EXECUTE_HANDLER) - { - } -#pragma warning(pop) -} -#endif // #ifdef _WIN32 - -// Set the description (name) of the current thread for debugging purposes -void ImThreadSetCurrentThreadDescription(const char* description) -{ -#if defined(_MSC_VER) // Windows + Visual Studio - // New-style thread name setting - // Only supported from Win 10 version 1607/Server 2016 onwards, hence the need for dynamic linking - - typedef HRESULT(WINAPI* SetThreadDescriptionFunc)(HANDLE hThread, PCWSTR lpThreadDescription); - - SetThreadDescriptionFunc set_thread_description = (SetThreadDescriptionFunc)::GetProcAddress(GetModuleHandleA("Kernel32.dll"), "SetThreadDescription"); - if (set_thread_description) - { - ImVector<ImWchar> buf; - const int description_wsize = ImTextCountCharsFromUtf8(description, NULL) + 1; - buf.resize(description_wsize); - ImTextStrFromUtf8(&buf[0], description_wsize, description, NULL); - set_thread_description(::GetCurrentThread(), (wchar_t*)&buf[0]); - } - - // Also do the old-style method too even if the new-style one worked, as the two work in slightly different sets of circumstances - ImThreadSetCurrentThreadDescriptionWin32OldStyle(description); -#elif defined(__linux) || defined(__linux__) || defined(__MINGW32__) // Linux or MingW - pthread_setname_np(pthread_self(), description); -#elif defined(__MACH__) || defined(__MSL__) // OSX - pthread_setname_np(description); -#else - // This is a nice-to-have rather than critical functionality, so fail silently if we don't support this platform -#endif -} - -//----------------------------------------------------------------------------- -// Build info helpers -//----------------------------------------------------------------------------- -// - ImBuildGetCompilationInfo() -// - ImBuildGetGitBranchName() -//----------------------------------------------------------------------------- - -// Turn __DATE__ "Jan 10 2019" into "2019-01-10" -static void ImBuildParseDateFromCompilerIntoYMD(const char* in_date, char* out_buf, size_t out_buf_size) -{ - char month_str[5]; - int year, month, day; - sscanf(in_date, "%3s %d %d", month_str, &day, &year); - const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; - const char* p = strstr(month_names, month_str); - month = p ? (int)(1 + (p - month_names) / 3) : 0; - ImFormatString(out_buf, out_buf_size, "%04d-%02d-%02d", year, month, day); -} - -// Those strings are used to output easily identifiable markers in compare logs. We only need to support what we use for testing. -// We can probably grab info in eaplatform.h/eacompiler.h etc. in EASTL -const ImBuildInfo* ImBuildGetCompilationInfo() -{ - static ImBuildInfo build_info; - - if (build_info.Type[0] == '\0') - { - // Build Type -#if defined(DEBUG) || defined(_DEBUG) - build_info.Type = "Debug"; -#else - build_info.Type = "Release"; -#endif - - // 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__) - build_info.Cpu = "ARM64"; -#else -#error - build_info.Cpu = (sizeof(size_t) == 4) ? "Unknown32" : "Unknown64"; -#endif - - // Platform/OS -#if defined(_WIN32) - build_info.OS = "Windows"; -#elif defined(__linux) || defined(__linux__) - build_info.OS = "Linux"; -#elif defined(__MACH__) || defined(__MSL__) - build_info.OS = "OSX"; -#elif defined(__ORBIS__) - build_info.OS = "PS4"; -#elif defined(_DURANGO) - build_info.OS = "XboxOne"; -#else - build_info.OS = "Unknown"; -#endif - - // Compiler -#if defined(_MSC_VER) - build_info.Compiler = "MSVC"; -#elif defined(__clang__) - build_info.Compiler = "Clang"; -#elif defined(__GNUC__) - build_info.Compiler = "GCC"; -#else - build_info.Compiler = "Unknown"; -#endif - - // Date/Time - ImBuildParseDateFromCompilerIntoYMD(__DATE__, build_info.Date, IM_ARRAYSIZE(build_info.Date)); - build_info.Time = __TIME__; - } - - return &build_info; -} - -bool ImBuildFindGitBranchName(const char* git_repo_path, Str* branch_name) -{ - IM_ASSERT(git_repo_path != NULL); - IM_ASSERT(branch_name != NULL); - Str256f head_path("%s/.git/HEAD", git_repo_path); - size_t head_size = 0; - bool result = false; - if (char* git_head = (char*)ImFileLoadToMemory(head_path.c_str(), "r", &head_size, 1)) - { - const char prefix[] = "ref: refs/heads/"; // Branch name is prefixed with this in HEAD file. - const int prefix_length = IM_ARRAYSIZE(prefix) - 1; - strtok(git_head, "\r\n"); // Trim new line - if (head_size > prefix_length && strncmp(git_head, prefix, prefix_length) == 0) - { - // "ref: refs/heads/master" -> "master" - branch_name->set(git_head + prefix_length); - } - else - { - // Should be git hash, keep first 8 characters (see #42) - branch_name->setf("%.8s", git_head); - } - result = true; - IM_FREE(git_head); - } - return result; -} - -//----------------------------------------------------------------------------- -// Operating System Helpers -//----------------------------------------------------------------------------- -// - ImOsCreateProcess() -// - ImOsPOpen() -// - ImOsPClose() -// - ImOsOpenInShell() -// - ImOsConsoleSetTextColor() -// - ImOsIsDebuggerPresent() -//----------------------------------------------------------------------------- - -bool ImOsCreateProcess(const char* cmd_line) -{ -#ifdef _WIN32 - STARTUPINFOA siStartInfo; - PROCESS_INFORMATION piProcInfo; - ZeroMemory(&siStartInfo, sizeof(STARTUPINFOA)); - char* cmd_line_copy = ImStrdup(cmd_line); - BOOL ret = ::CreateProcessA(NULL, cmd_line_copy, NULL, NULL, FALSE, 0, NULL, NULL, &siStartInfo, &piProcInfo); - free(cmd_line_copy); - ::CloseHandle(siStartInfo.hStdInput); - ::CloseHandle(siStartInfo.hStdOutput); - ::CloseHandle(siStartInfo.hStdError); - ::CloseHandle(piProcInfo.hProcess); - ::CloseHandle(piProcInfo.hThread); - return ret != 0; -#else - IM_UNUSED(cmd_line); - return false; -#endif -} - -FILE* ImOsPOpen(const char* cmd_line, const char* mode) -{ - IM_ASSERT(cmd_line != NULL && *cmd_line); - IM_ASSERT(mode != NULL && *mode); -#if _WIN32 - ImVector<wchar_t> w_cmd_line; - ImVector<wchar_t> w_mode; - ImUtf8ToWideChar(cmd_line, &w_cmd_line); - ImUtf8ToWideChar(mode, &w_mode); - w_mode.resize(w_mode.Size + 1); - wcscat(w_mode.Data, L"b"); // Windows requires 'b' mode while unixes do not support it and default to binary. - return _wpopen(w_cmd_line.Data, w_mode.Data); -#else - return popen(cmd_line, mode); -#endif -} - -void ImOsPClose(FILE* fp) -{ - IM_ASSERT(fp != NULL); -#if _WIN32 - _pclose(fp); -#else - pclose(fp); -#endif -} - -void ImOsOpenInShell(const char* path) -{ - Str256 command(path); -#ifdef _WIN32 - ImPathFixSeparatorsForCurrentOS(command.c_str()); - ::ShellExecuteA(NULL, "open", command.c_str(), NULL, NULL, SW_SHOWDEFAULT); -#else -#if __APPLE__ - const char* open_executable = "open"; -#else - const char* open_executable = "xdg-open"; -#endif - command.setf("%s \"%s\"", open_executable, path); - ImPathFixSeparatorsForCurrentOS(command.c_str()); - system(command.c_str()); -#endif -} - -void ImOsConsoleSetTextColor(ImOsConsoleStream stream, ImOsConsoleTextColor color) -{ -#ifdef _WIN32 - HANDLE hConsole = 0; - switch (stream) - { - case ImOsConsoleStream_StandardOutput: hConsole = ::GetStdHandle(STD_OUTPUT_HANDLE); break; - case ImOsConsoleStream_StandardError: hConsole = ::GetStdHandle(STD_ERROR_HANDLE); break; - } - WORD wAttributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; - switch (color) - { - case ImOsConsoleTextColor_Black: wAttributes = 0x00; break; - case ImOsConsoleTextColor_White: wAttributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break; - case ImOsConsoleTextColor_BrightWhite: wAttributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break; - case ImOsConsoleTextColor_BrightRed: wAttributes = FOREGROUND_RED | FOREGROUND_INTENSITY; break; - case ImOsConsoleTextColor_BrightGreen: wAttributes = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; - case ImOsConsoleTextColor_BrightBlue: wAttributes = FOREGROUND_BLUE | FOREGROUND_INTENSITY; break; - case ImOsConsoleTextColor_BrightYellow: wAttributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; - default: IM_ASSERT(0); - } - ::SetConsoleTextAttribute(hConsole, wAttributes); -#elif defined(__linux) || defined(__linux__) || defined(__MACH__) || defined(__MSL__) - // FIXME: check system capabilities (with environment variable TERM) - FILE* handle = 0; - switch (stream) - { - case ImOsConsoleStream_StandardOutput: handle = stdout; break; - case ImOsConsoleStream_StandardError: handle = stderr; break; - } - - const char* modifier = ""; - switch (color) - { - case ImOsConsoleTextColor_Black: modifier = "\033[30m"; break; - case ImOsConsoleTextColor_White: modifier = "\033[0m"; break; - case ImOsConsoleTextColor_BrightWhite: modifier = "\033[1;37m"; break; - case ImOsConsoleTextColor_BrightRed: modifier = "\033[1;31m"; break; - case ImOsConsoleTextColor_BrightGreen: modifier = "\033[1;32m"; break; - case ImOsConsoleTextColor_BrightBlue: modifier = "\033[1;34m"; break; - case ImOsConsoleTextColor_BrightYellow: modifier = "\033[1;33m"; break; - default: IM_ASSERT(0); - } - - fprintf(handle, "%s", modifier); -#endif -} - -bool ImOsIsDebuggerPresent() -{ -#ifdef _WIN32 - return ::IsDebuggerPresent() != 0; -#elif defined(__linux__) - int debugger_pid = 0; - char buf[2048]; // TracerPid is located near the start of the file. If end of the buffer gets cut off thats fine. - FILE* fp = fopen("/proc/self/status", "rb"); // Can not use ImFileLoadToMemory because size detection of /proc/self/status would fail. - if (fp == NULL) - return false; - fread(buf, 1, IM_ARRAYSIZE(buf), fp); - fclose(fp); - buf[IM_ARRAYSIZE(buf) - 1] = 0; - if (char* tracer_pid = strstr(buf, "TracerPid:")) - { - tracer_pid += 10; // Skip label - while (isspace(*tracer_pid)) - tracer_pid++; - debugger_pid = atoi(tracer_pid); - } - return debugger_pid != 0; -#else - // FIXME - return false; -#endif -} - -void ImOsOutputDebugString(const char* message) -{ -#ifdef _WIN32 - OutputDebugStringA(message); -#else - IM_UNUSED(message); -#endif -} - -//----------------------------------------------------------------------------- -// Str.h + InputText bindings -//----------------------------------------------------------------------------- - -struct InputTextCallbackStr_UserData -{ - Str* StrObj; - ImGuiInputTextCallback ChainCallback; - void* ChainCallbackUserData; -}; - -static int InputTextCallbackStr(ImGuiInputTextCallbackData* data) -{ - InputTextCallbackStr_UserData* user_data = (InputTextCallbackStr_UserData*)data->UserData; - if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) - { - // Resize string callback - // If for some reason we refuse the new length (BufTextLen) and/or capacity (BufSize) we need to set them back to what we want. - Str* str = user_data->StrObj; - IM_ASSERT(data->Buf == str->c_str()); - str->reserve(data->BufTextLen + 1); - data->Buf = (char*)str->c_str(); - } - else if (user_data->ChainCallback) - { - // Forward to user callback, if any - data->UserData = user_data->ChainCallbackUserData; - return user_data->ChainCallback(data); - } - return 0; -} - -// Draw an extra colored frame over the previous item -// Similar to DebugDrawItemRect() but use Max(1.0f, FrameBorderSize) -void ImGui::ItemErrorFrame(ImU32 col) -{ - ImGuiContext& g = *GetCurrentContext(); - ImDrawList* drawlist = GetWindowDrawList(); - ImGuiStyle& style = GetStyle(); - // FIXME: GetItemRectMin() / GetItemRectMax() will include label. NavRect is not probably defined :( - drawlist->AddRect(g.LastItemData.NavRect.Min, g.LastItemData.NavRect.Max, GetColorU32(col), style.FrameRounding, ImDrawFlags_None, ImMax(1.0f, style.FrameBorderSize)); -} - -bool ImGui::InputText(const char* label, Str* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) -{ - IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); - flags |= ImGuiInputTextFlags_CallbackResize; - - InputTextCallbackStr_UserData cb_user_data; - cb_user_data.StrObj = str; - cb_user_data.ChainCallback = callback; - cb_user_data.ChainCallbackUserData = user_data; - return InputText(label, (char*)str->c_str(), (size_t)str->capacity() + 1, flags, InputTextCallbackStr, &cb_user_data); -} - -bool ImGui::InputTextWithHint(const char* label, const char* hint, Str* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) -{ - IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); - flags |= ImGuiInputTextFlags_CallbackResize; - - InputTextCallbackStr_UserData cb_user_data; - cb_user_data.StrObj = str; - cb_user_data.ChainCallback = callback; - cb_user_data.ChainCallbackUserData = user_data; - return InputTextWithHint(label, hint, (char*)str->c_str(), (size_t)str->capacity() + 1, flags, InputTextCallbackStr, &cb_user_data); -} - -bool ImGui::InputTextMultiline(const char* label, Str* str, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) -{ - IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); - flags |= ImGuiInputTextFlags_CallbackResize; - - InputTextCallbackStr_UserData cb_user_data; - cb_user_data.StrObj = str; - cb_user_data.ChainCallback = callback; - cb_user_data.ChainCallbackUserData = user_data; - return InputTextMultiline(label, (char*)str->c_str(), (size_t)str->capacity() + 1, size, flags, InputTextCallbackStr, &cb_user_data); -} - -// anchor parameter indicates which split would retain it's constant size. -// anchor = 0 - both splits resize when parent container size changes. Both value_1 and value_2 should be persistent. -// anchor = -1 - top/left split would have a constant size. bottom/right split would resize when parent container size changes. value_1 should be persistent, value_2 will always be recalculated from value_1. -// anchor = +1 - bottom/right split would have a constant size. top/left split would resize when parent container size changes. value_2 should be persistent, value_1 will always be recalculated from value_2. -bool ImGui::Splitter(const char* id, float* value_1, float* value_2, int axis, int anchor, float min_size_0, float min_size_1) -{ - // FIXME-DOGFOODING: This needs further refining. - // FIXME-SCROLL: When resizing either we'd like to keep scroll focus on something (e.g. last clicked item for list, bottom for log) - // See https://github.com/ocornut/imgui/issues/319 - ImGuiContext& g = *GImGui; - ImGuiStyle& style = g.Style; - ImGuiWindow* window = ImGui::GetCurrentWindow(); - if (min_size_0 < 0) - min_size_0 = ImGui::GetFrameHeight(); - if (min_size_1) - min_size_1 = ImGui::GetFrameHeight(); - - IM_ASSERT(axis == ImGuiAxis_X || axis == ImGuiAxis_Y); - - float& v_1 = *value_1; - float& v_2 = *value_2; - ImRect splitter_bb; - const float avail = axis == ImGuiAxis_X ? ImGui::GetContentRegionAvail().x - style.ItemSpacing.x : ImGui::GetContentRegionAvail().y - style.ItemSpacing.y; - if (anchor < 0) - { - v_2 = ImMax(avail - v_1, min_size_1); // First split is constant size. - } - else if (anchor > 0) - { - v_1 = ImMax(avail - v_2, min_size_0); // Second split is constant size. - } - else - { - float r = v_1 / (v_1 + v_2); // Both splits maintain same relative size to parent. - v_1 = IM_ROUND(avail * r) - 1; - v_2 = IM_ROUND(avail * (1.0f - r)) - 1; - } - if (axis == ImGuiAxis_X) - { - float x = window->DC.CursorPos.x + v_1 + IM_ROUND(style.ItemSpacing.x * 0.5f); - splitter_bb = ImRect(x - 1, window->WorkRect.Min.y, x + 1, window->WorkRect.Max.y); - } - else if (axis == ImGuiAxis_Y) - { - float y = window->DC.CursorPos.y + v_1 + IM_ROUND(style.ItemSpacing.y * 0.5f); - splitter_bb = ImRect(window->WorkRect.Min.x, y - 1, window->WorkRect.Max.x, y + 1); - } - return ImGui::SplitterBehavior(splitter_bb, ImGui::GetID(id), (ImGuiAxis)axis, &v_1, &v_2, min_size_0, min_size_1, 3.0f); -} - -// FIXME-TESTS: Should eventually remove. -ImFont* ImGui::FindFontByPrefix(const char* prefix) -{ - ImGuiContext& g = *GImGui; - for (ImFont* font : g.IO.Fonts->Fonts) - if (strncmp(font->ConfigData->Name, prefix, strlen(prefix)) == 0) - return font; - return NULL; -} - -// Legacy version support -#if IMGUI_VERSION_NUM < 18924 -const char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) -{ - return tab_bar->GetTabName(tab); -} -#endif - -#if IMGUI_VERSION_NUM < 18927 -ImGuiID ImGui::TableGetInstanceID(ImGuiTable* table, int instance_no) -{ - // Changed in #6140 - return table->ID + instance_no; -} -#endif - -ImGuiID TableGetHeaderID(ImGuiTable* table, const char* column, int instance_no) -{ - IM_ASSERT(table != NULL); - int column_n = -1; - for (int n = 0; n < table->Columns.size() && column_n < 0; n++) - if (strcmp(ImGui::TableGetColumnName(table, n), column) == 0) - column_n = n; - IM_ASSERT(column_n != -1); - return TableGetHeaderID(table, column_n, instance_no); -} - -ImGuiID TableGetHeaderID(ImGuiTable* table, int column_n, int instance_no) -{ - IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount); - const ImGuiID table_instance_id = ImGui::TableGetInstanceID(table, instance_no); - const char* column_name = ImGui::TableGetColumnName(table, column_n); -#if IMGUI_VERSION_NUM >= 18927 - const int column_id_differencier = column_n; -#else - const int column_id_differencier = instance_no * table->ColumnsCount + column_n; -#endif - const int column_id = ImHashData(&column_id_differencier, sizeof(column_id_differencier), table_instance_id); - return ImHashData(column_name, strlen(column_name), column_id); -} - -// FIXME: Could be moved to core as an internal function? -void TableDiscardInstanceAndSettings(ImGuiID table_id) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(g.CurrentTable == NULL); - if (ImGuiTableSettings* settings = ImGui::TableSettingsFindByID(table_id)) - settings->ID = 0; - - if (ImGuiTable* table = ImGui::TableFindByID(table_id)) - ImGui::TableRemove(table); - // FIXME-TABLE: We should be able to use TableResetSettings() instead of TableRemove()! Maybe less of a clean slate but would be good to check that it does the job - //ImGui::TableResetSettings(table); -} - -// Helper to verify ImDrawData integrity of buffer count (broke before e.g. #6716) -void DrawDataVerifyMatchingBufferCount(ImDrawData* draw_data) -{ - int total_vtx_count = 0; - int total_idx_count = 0; - for (ImDrawList* draw_list : draw_data->CmdLists) - { - total_vtx_count += draw_list->VtxBuffer.Size; - total_idx_count += draw_list->IdxBuffer.Size; - } - IM_UNUSED(total_vtx_count); - IM_UNUSED(total_idx_count); - IM_ASSERT(total_vtx_count == draw_data->TotalVtxCount); - IM_ASSERT(total_idx_count == draw_data->TotalIdxCount); -} - -//----------------------------------------------------------------------------- -// Simple CSV parser -//----------------------------------------------------------------------------- - -void ImGuiCsvParser::Clear() -{ - Rows = Columns = 0; - if (_Data != NULL) - IM_FREE(_Data); - _Data = NULL; - _Index.clear(); -} - -bool ImGuiCsvParser::Load(const char* filename) -{ - size_t len = 0; - _Data = (char*)ImFileLoadToMemory(filename, "rb", &len, 1); - if (_Data == NULL) - return false; - - int columns = 1; - if (Columns > 0) - { - columns = Columns; // User-provided expected column count. - } - else - { - for (const char* c = _Data; *c != '\n' && *c != '\0'; c++) // Count columns. Quoted columns with commas are not supported. - if (*c == ',') - columns++; - } - - // Count rows. Extra new lines anywhere in the file are ignored. - int max_rows = 0; - for (const char* c = _Data, *end = c + len; c < end; c++) - if ((*c == '\n' && c[1] != '\r' && c[1] != '\n') || *c == '\0') - max_rows++; - - if (columns == 0 || max_rows == 0) - return false; - - // Create index - _Index.resize(columns * max_rows); - - int col = 0; - char* col_data = _Data; - for (char* c = _Data; *c != '\0'; c++) - { - const bool is_comma = (*c == ','); - const bool is_eol = (*c == '\n' || *c == '\r'); - const bool is_eof = (*c == '\0'); - if (is_comma || is_eol || is_eof) - { - _Index[Rows * columns + col] = col_data; - col_data = c + 1; - if (is_comma) - { - col++; - } - else - { - if (col + 1 == columns) - Rows++; - else - fprintf(stderr, "%s: Unexpected number of columns on line %d, ignoring.\n", filename, Rows + 1); // FIXME - col = 0; - } - *c = 0; - if (is_eol) - while (c[1] == '\r' || c[1] == '\n') - c++; - } - } - - Columns = columns; - return true; -} - -//----------------------------------------------------------------------------- diff --git a/vendor/zgui/libs/imgui_test_engine/imgui_te_utils.h b/vendor/zgui/libs/imgui_test_engine/imgui_te_utils.h deleted file mode 100644 index 1eef55c..0000000 --- a/vendor/zgui/libs/imgui_test_engine/imgui_te_utils.h +++ /dev/null @@ -1,221 +0,0 @@ -// dear imgui test engine -// (helpers/utilities. do NOT use this as a general purpose library) - -#pragma once - -//----------------------------------------------------------------------------- -// Includes -//----------------------------------------------------------------------------- - -#include <math.h> // fabsf -#include <stdint.h> // uint64_t -#include <stdio.h> // FILE* -#include "imgui.h" // ImGuiID, ImGuiKey -class Str; // Str<> from thirdparty/Str/Str.h - -//----------------------------------------------------------------------------- -// Function Pointers -//----------------------------------------------------------------------------- - -#if IMGUI_TEST_ENGINE_ENABLE_STD_FUNCTION -#include <functional> -#define ImFuncPtr(FUNC_TYPE) std::function<FUNC_TYPE> -#else -#define ImFuncPtr(FUNC_TYPE) FUNC_TYPE* -#endif - -//----------------------------------------------------------------------------- -// Hashing Helpers -//----------------------------------------------------------------------------- - -ImGuiID ImHashDecoratedPath(const char* str, const char* str_end = NULL, ImGuiID seed = 0); -const char* ImFindNextDecoratedPartInPath(const char* str, const char* str_end = NULL); - -//----------------------------------------------------------------------------- -// File/Directory Helpers -//----------------------------------------------------------------------------- - -bool ImFileExist(const char* filename); -bool ImFileDelete(const char* filename); -bool ImFileCreateDirectoryChain(const char* path, const char* path_end = NULL); -bool ImFileFindInParents(const char* sub_path, int max_parent_count, Str* output); -bool ImFileLoadSourceBlurb(const char* filename, int line_no_start, int line_no_end, ImGuiTextBuffer* out_buf); - -//----------------------------------------------------------------------------- -// Path Helpers -//----------------------------------------------------------------------------- - -// Those are strictly string manipulation functions -const char* ImPathFindFilename(const char* path, const char* path_end = NULL); // Return value always between path and path_end -const char* ImPathFindExtension(const char* path, const char* path_end = NULL); // Return value always between path and path_end -void ImPathFixSeparatorsForCurrentOS(char* buf); - -//----------------------------------------------------------------------------- -// String Helpers -//----------------------------------------------------------------------------- - -void ImStrReplace(Str* s, const char* find, const char* repl); -const char* ImStrchrRangeWithEscaping(const char* str, const char* str_end, char find_c); -void ImStrXmlEscape(Str* s); -int ImStrBase64Encode(const unsigned char* src, char* dst, int length); - -//----------------------------------------------------------------------------- -// Parsing Helpers -//----------------------------------------------------------------------------- - -void ImParseExtractArgcArgvFromCommandLine(int* out_argc, char const*** out_argv, const char* cmd_line); -bool ImParseFindIniSection(const char* ini_config, const char* header, ImVector<char>* result); - -//----------------------------------------------------------------------------- -// Time Helpers -//----------------------------------------------------------------------------- - -uint64_t ImTimeGetInMicroseconds(); -void ImTimestampToISO8601(uint64_t timestamp, Str* out_date); - -//----------------------------------------------------------------------------- -// Threading Helpers -//----------------------------------------------------------------------------- - -void ImThreadSleepInMilliseconds(int ms); -void ImThreadSetCurrentThreadDescription(const char* description); - -//----------------------------------------------------------------------------- -// Build Info helpers -//----------------------------------------------------------------------------- - -// All the pointers are expect to be literals/persistent -struct ImBuildInfo -{ - const char* Type = ""; - const char* Cpu = ""; - const char* OS = ""; - const char* Compiler = ""; - char Date[32]; // "YYYY-MM-DD" - const char* Time = ""; -}; - -const ImBuildInfo* ImBuildGetCompilationInfo(); -bool ImBuildFindGitBranchName(const char* git_repo_path, Str* branch_name); - -//----------------------------------------------------------------------------- -// Operating System Helpers -//----------------------------------------------------------------------------- - -enum ImOsConsoleStream -{ - ImOsConsoleStream_StandardOutput, - ImOsConsoleStream_StandardError, -}; - -enum ImOsConsoleTextColor -{ - ImOsConsoleTextColor_Black, - ImOsConsoleTextColor_White, - ImOsConsoleTextColor_BrightWhite, - ImOsConsoleTextColor_BrightRed, - ImOsConsoleTextColor_BrightGreen, - ImOsConsoleTextColor_BrightBlue, - ImOsConsoleTextColor_BrightYellow, -}; - -bool ImOsCreateProcess(const char* cmd_line); -FILE* ImOsPOpen(const char* cmd_line, const char* mode); -void ImOsPClose(FILE* fp); -void ImOsOpenInShell(const char* path); -bool ImOsIsDebuggerPresent(); -void ImOsOutputDebugString(const char* message); -void ImOsConsoleSetTextColor(ImOsConsoleStream stream, ImOsConsoleTextColor color); - -//----------------------------------------------------------------------------- -// Miscellaneous functions -//----------------------------------------------------------------------------- - -// Tables functions -struct ImGuiTable; -ImGuiID TableGetHeaderID(ImGuiTable* table, const char* column, int instance_no = 0); -ImGuiID TableGetHeaderID(ImGuiTable* table, int column_n, int instance_no = 0); -void TableDiscardInstanceAndSettings(ImGuiID table_id); - -// DrawData functions -void DrawDataVerifyMatchingBufferCount(ImDrawData* draw_data); - -//----------------------------------------------------------------------------- -// Helper: maintain/calculate moving average -//----------------------------------------------------------------------------- - -template<typename TYPE> -struct ImMovingAverage -{ - // Internal Fields - ImVector<TYPE> Samples; - TYPE Accum; - int Idx; - int FillAmount; - - // Functions - ImMovingAverage() { Accum = (TYPE)0; Idx = FillAmount = 0; } - void Init(int count) { Samples.resize(count); memset(Samples.Data, 0, (size_t)Samples.Size * sizeof(TYPE)); Accum = (TYPE)0; Idx = FillAmount = 0; } - void AddSample(TYPE v) { Accum += v - Samples[Idx]; Samples[Idx] = v; if (++Idx == Samples.Size) Idx = 0; if (FillAmount < Samples.Size) FillAmount++; } - TYPE GetAverage() const { return Accum / (TYPE)FillAmount; } - int GetSampleCount() const { return Samples.Size; } - bool IsFull() const { return FillAmount == Samples.Size; } -}; - -//----------------------------------------------------------------------------- -// Helper: Simple/dumb CSV parser -//----------------------------------------------------------------------------- - -struct ImGuiCsvParser -{ - // Public fields - int Columns = 0; // Number of columns in CSV file. - int Rows = 0; // Number of rows in CSV file. - - // Internal fields - char* _Data = NULL; // CSV file data. - ImVector<char*> _Index; // CSV table: _Index[row * _Columns + col]. - - // Functions - ImGuiCsvParser(int columns = -1) { Columns = columns; } - ~ImGuiCsvParser() { Clear(); } - bool Load(const char* file_name); // Open and parse a CSV file. - void Clear(); // Free allocated buffers. - const char* GetCell(int row, int col) { IM_ASSERT(0 <= row && row < Rows && 0 <= col && col < Columns); return _Index[row * Columns + col]; } -}; - -//----------------------------------------------------------------------------- -// Misc Dear ImGui extensions -//----------------------------------------------------------------------------- - -#if IMGUI_VERSION_NUM < 18924 -struct ImGuiTabBar; -struct ImGuiTabItem; -#endif - -namespace ImGui -{ - -IMGUI_API void ItemErrorFrame(ImU32 col); - -#if IMGUI_VERSION_NUM < 18927 -ImGuiID TableGetInstanceID(ImGuiTable* table, int instance_no = 0); -#endif - -// Str support for InputText() -IMGUI_API bool InputText(const char* label, Str* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); -IMGUI_API bool InputTextWithHint(const char* label, const char* hint, Str* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); -IMGUI_API bool InputTextMultiline(const char* label, Str* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); - -// Splitter -IMGUI_API bool Splitter(const char* id, float* value_1, float* value_2, int axis, int anchor = 0, float min_size_0 = -1.0f, float min_size_1 = -1.0f); - -// Misc -IMGUI_API ImFont* FindFontByPrefix(const char* name); - -// Legacy version support -#if IMGUI_VERSION_NUM < 18924 -IMGUI_API const char* TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); -#endif - -} diff --git a/vendor/zgui/libs/imgui_test_engine/thirdparty/README.md b/vendor/zgui/libs/imgui_test_engine/thirdparty/README.md deleted file mode 100644 index b9faa39..0000000 --- a/vendor/zgui/libs/imgui_test_engine/thirdparty/README.md +++ /dev/null @@ -1,7 +0,0 @@ -## Third party libraries used by Test Engine - -Always used: -- `Str/Str.h` simple string type, used by `imgui_test_engine` (Public Domain) - -Used if `IMGUI_TEST_ENGINE_ENABLE_CAPTURE` is defined to 1 (default: 1) -- `stb/imstb_image_write.h` image writer, used by `imgui_capture_tool` (MIT Licence OR Public Domain) diff --git a/vendor/zgui/libs/imgui_test_engine/thirdparty/Str/README.md b/vendor/zgui/libs/imgui_test_engine/thirdparty/Str/README.md deleted file mode 100644 index 3573ea9..0000000 --- a/vendor/zgui/libs/imgui_test_engine/thirdparty/Str/README.md +++ /dev/null @@ -1,71 +0,0 @@ -``` -Str -Simple C++ string type with an optional local buffer, by Omar Cornut -https://github.com/ocornut/str - -LICENSE -This software is in the public domain. Where that dedication is not -recognized, you are granted a perpetual, irrevocable license to copy, -distribute, and modify this file as you see fit. - -USAGE -Include Str.h in whatever places need to refer to it. -In ONE .cpp file, write '#define STR_IMPLEMENTATION' before the #include. -This expands out the actual implementation into that C/C++ file. - -NOTES -- This isn't a fully featured string class. -- It is a simple, bearable replacement to std::string that isn't heap abusive nor bloated (can actually be debugged by humans!). -- String are mutable. We don't maintain size so length() is not-constant time. -- Maximum string size currently limited to 2 MB (we allocate 21 bits to hold capacity). -- Local buffer size is currently limited to 1023 bytes (we allocate 10 bits to hold local buffer size). -- We could easily raise those limits if we are ok to increase the structure overhead in 32-bits mode. -- In "non-owned" mode for literals/reference we don't do any tracking/counting of references. -- Overhead is 8-bytes in 32-bits, 16-bytes in 64-bits (12 + alignment). -- I'm using this code but it hasn't been tested thoroughly. - -The idea is that you can provide an arbitrary sized local buffer if you expect string to fit -most of the time, and then you avoid using costly heap. - -No local buffer, always use heap, sizeof()==8~16 (depends if your pointers are 32-bits or 64-bits) - - Str s = "hey"; // use heap - -With a local buffer of 16 bytes, sizeof() == 8~16 + 16 bytes. - - Str16 s = "filename.h"; // copy into local buffer - Str16 s = "long_filename_not_very_long_but_longer_than_expected.h"; // use heap - -With a local buffer of 256 bytes, sizeof() == 8~16 + 256 bytes. - - Str256 s = "long_filename_not_very_long_but_longer_than_expected.h"; // copy into local buffer - -Common sizes are defined at the bottom of Str.h, you may define your own. - -Functions: - - Str256 s; - s.set("hello sailor"); // set (copy) - s.setf("%s/%s.tmp", folder, filename); // set (w/format) - s.append("hello"); // append. cost a length() calculation! - s.appendf("hello %d", 42); // append (w/format). cost a length() calculation! - s.set_ref("Hey!"); // set (literal/reference, just copy pointer, no tracking) - -Constructor helper for format string: add a trailing 'f' to the type. Underlying type is the same. - - Str256f filename("%s/%s.tmp", folder, filename); // construct (w/format) - fopen(Str256f("%s/%s.tmp, folder, filename).c_str(), "rb"); // construct (w/format), use as function param, destruct - -Constructor helper for reference/literal: - - StrRef ref("literal"); // copy pointer, no allocation, no string copy - StrRef ref2(GetDebugName()); // copy pointer. no tracking of anything whatsoever, know what you are doing! - -All StrXXX types derives from Str and instance hold the local buffer capacity. -So you can pass e.g. Str256* to a function taking base type Str* and it will be functional! - - void MyFunc(Str& s) { s = "Hello"; } // will use local buffer if available in Str instance - -(Using a template e.g. Str<N> we could remove the LocalBufSize storage but it would make passing typed Str<> to functions tricky. - Instead we don't use template so you can pass them around as the base type Str*. Also, templates are ugly.) -``` diff --git a/vendor/zgui/libs/imgui_test_engine/thirdparty/Str/Str.h b/vendor/zgui/libs/imgui_test_engine/thirdparty/Str/Str.h deleted file mode 100644 index ff197d3..0000000 --- a/vendor/zgui/libs/imgui_test_engine/thirdparty/Str/Str.h +++ /dev/null @@ -1,658 +0,0 @@ -// Str v0.32 -// Simple C++ string type with an optional local buffer, by Omar Cornut -// https://github.com/ocornut/str - -// LICENSE -// This software is in the public domain. Where that dedication is not -// recognized, you are granted a perpetual, irrevocable license to copy, -// distribute, and modify this file as you see fit. - -// USAGE -// Include this file in whatever places need to refer to it. -// In ONE .cpp file, write '#define STR_IMPLEMENTATION' before the #include of this file. -// This expands out the actual implementation into that C/C++ file. - - -/* -- This isn't a fully featured string class. -- It is a simple, bearable replacement to std::string that isn't heap abusive nor bloated (can actually be debugged by humans). -- String are mutable. We don't maintain size so length() is not-constant time. -- Maximum string size currently limited to 2 MB (we allocate 21 bits to hold capacity). -- Local buffer size is currently limited to 1023 bytes (we allocate 10 bits to hold local buffer size). -- In "non-owned" mode for literals/reference we don't do any tracking/counting of references. -- Overhead is 8-bytes in 32-bits, 16-bytes in 64-bits (12 + alignment). -- This code hasn't been tested very much. it is probably incomplete or broken. Made it for my own use. - -The idea is that you can provide an arbitrary sized local buffer if you expect string to fit -most of the time, and then you avoid using costly heap. - -No local buffer, always use heap, sizeof()==8~16 (depends if your pointers are 32-bits or 64-bits) - - Str s = "hey"; - -With a local buffer of 16 bytes, sizeof() == 8~16 + 16 bytes. - - Str16 s = "filename.h"; // copy into local buffer - Str16 s = "long_filename_not_very_long_but_longer_than_expected.h"; // use heap - -With a local buffer of 256 bytes, sizeof() == 8~16 + 256 bytes. - - Str256 s = "long_filename_not_very_long_but_longer_than_expected.h"; // copy into local buffer - -Common sizes are defined at the bottom of Str.h, you may define your own. - -Functions: - - Str256 s; - s.set("hello sailor"); // set (copy) - s.setf("%s/%s.tmp", folder, filename); // set (w/format) - s.append("hello"); // append. cost a length() calculation! - s.appendf("hello %d", 42); // append (w/format). cost a length() calculation! - s.set_ref("Hey!"); // set (literal/reference, just copy pointer, no tracking) - -Constructor helper for format string: add a trailing 'f' to the type. Underlying type is the same. - - Str256f filename("%s/%s.tmp", folder, filename); // construct (w/format) - fopen(Str256f("%s/%s.tmp, folder, filename).c_str(), "rb"); // construct (w/format), use as function param, destruct - -Constructor helper for reference/literal: - - StrRef ref("literal"); // copy pointer, no allocation, no string copy - StrRef ref2(GetDebugName()); // copy pointer. no tracking of anything whatsoever, know what you are doing! - -All StrXXX types derives from Str and instance hold the local buffer capacity. So you can pass e.g. Str256* to a function taking base type Str* and it will be functional. - - void MyFunc(Str& s) { s = "Hello"; } // will use local buffer if available in Str instance - -(Using a template e.g. Str<N> we could remove the LocalBufSize storage but it would make passing typed Str<> to functions tricky. - Instead we don't use template so you can pass them around as the base type Str*. Also, templates are ugly.) -*/ - -/* - CHANGELOG - 0.32 - added owned() accessor. - 0.31 - fixed various warnings. - 0.30 - turned into a single header file, removed Str.cpp. - 0.29 - fixed bug when calling reserve on non-owned strings (ie. when using StrRef or set_ref), and fixed <string> include. - 0.28 - breaking change: replaced Str32 by Str30 to avoid collision with Str32 from MacTypes.h . - 0.27 - added STR_API and basic .natvis file. - 0.26 - fixed set(cont char* src, const char* src_end) writing null terminator to the wrong position. - 0.25 - allow set(const char* NULL) or operator= NULL to clear the string. note that set() from range or other types are not allowed. - 0.24 - allow set_ref(const char* NULL) to clear the string. include fixes for linux. - 0.23 - added append(char). added append_from(int idx, XXX) functions. fixed some compilers warnings. - 0.22 - documentation improvements, comments. fixes for some compilers. - 0.21 - added StrXXXf() constructor to construct directly from a format string. -*/ - -/* -TODO -- Since we lose 4-bytes of padding on 64-bits architecture, perhaps just spread the header to 8-bytes and lift size limits? -- More functions/helpers. -*/ - -#ifndef STR_INCLUDED -#define STR_INCLUDED - -//------------------------------------------------------------------------- -// CONFIGURATION -//------------------------------------------------------------------------- - -#ifndef STR_MEMALLOC -#define STR_MEMALLOC malloc -#include <stdlib.h> -#endif -#ifndef STR_MEMFREE -#define STR_MEMFREE free -#include <stdlib.h> -#endif -#ifndef STR_ASSERT -#define STR_ASSERT assert -#include <assert.h> -#endif -#ifndef STR_API -#define STR_API -#endif -#include <stdarg.h> // for va_list -#include <string.h> // for strlen, strcmp, memcpy, etc. - -// Configuration: #define STR_SUPPORT_STD_STRING 0 to disable setters variants using const std::string& (on by default) -#ifndef STR_SUPPORT_STD_STRING -#define STR_SUPPORT_STD_STRING 1 -#endif - -// Configuration: #define STR_DEFINE_STR32 1 to keep defining Str32/Str32f, but be warned: on macOS/iOS, MacTypes.h also defines a type named Str32. -#ifndef STR_DEFINE_STR32 -#define STR_DEFINE_STR32 0 -#endif - -#if STR_SUPPORT_STD_STRING -#include <string> -#endif - -//------------------------------------------------------------------------- -// HEADERS -//------------------------------------------------------------------------- - -// This is the base class that you can pass around -// Footprint is 8-bytes (32-bits arch) or 16-bytes (64-bits arch) -class STR_API Str -{ - char* Data; // Point to LocalBuf() or heap allocated - int Capacity : 21; // Max 2 MB - int LocalBufSize : 10; // Max 1023 bytes - unsigned int Owned : 1; // Set when we have ownership of the pointed data (most common, unless using set_ref() method or StrRef constructor) - -public: - inline char* c_str() { return Data; } - inline const char* c_str() const { return Data; } - inline bool empty() const { return Data[0] == 0; } - inline int length() const { return (int)strlen(Data); } // by design, allow user to write into the buffer at any time - inline int capacity() const { return Capacity; } - inline bool owned() const { return Owned ? true : false; } - - inline void set_ref(const char* src); - int setf(const char* fmt, ...); - int setfv(const char* fmt, va_list args); - int setf_nogrow(const char* fmt, ...); - int setfv_nogrow(const char* fmt, va_list args); - int append(char c); - int append(const char* s, const char* s_end = NULL); - int appendf(const char* fmt, ...); - int appendfv(const char* fmt, va_list args); - int append_from(int idx, char c); - int append_from(int idx, const char* s, const char* s_end = NULL); // If you know the string length or want to append from a certain point - int appendf_from(int idx, const char* fmt, ...); - int appendfv_from(int idx, const char* fmt, va_list args); - - void clear(); - void reserve(int cap); - void reserve_discard(int cap); - void shrink_to_fit(); - - inline char& operator[](size_t i) { return Data[i]; } - inline char operator[](size_t i) const { return Data[i]; } - //explicit operator const char*() const{ return Data; } - - inline Str(); - inline Str(const char* rhs); - inline void set(const char* src); - inline void set(const char* src, const char* src_end); - inline Str& operator=(const char* rhs) { set(rhs); return *this; } - inline bool operator==(const char* rhs) const { return strcmp(c_str(), rhs) == 0; } - - inline Str(const Str& rhs); - inline void set(const Str& src); - inline Str& operator=(const Str& rhs) { set(rhs); return *this; } - inline bool operator==(const Str& rhs) const { return strcmp(c_str(), rhs.c_str()) == 0; } - -#if STR_SUPPORT_STD_STRING - inline Str(const std::string& rhs); - inline void set(const std::string& src); - inline Str& operator=(const std::string& rhs) { set(rhs); return *this; } - inline bool operator==(const std::string& rhs)const { return strcmp(c_str(), rhs.c_str()) == 0; } -#endif - - // Destructor for all variants - inline ~Str() - { - if (Owned && !is_using_local_buf()) - STR_MEMFREE(Data); - } - - static char* EmptyBuffer; - -protected: - inline char* local_buf() { return (char*)this + sizeof(Str); } - inline const char* local_buf() const { return (char*)this + sizeof(Str); } - inline bool is_using_local_buf() const { return Data == local_buf() && LocalBufSize != 0; } - - // Constructor for StrXXX variants with local buffer - Str(unsigned short local_buf_size) - { - STR_ASSERT(local_buf_size < 1024); - Data = local_buf(); - Data[0] = '\0'; - Capacity = local_buf_size; - LocalBufSize = local_buf_size; - Owned = 1; - } -}; - -void Str::set(const char* src) -{ - // We allow set(NULL) or via = operator to clear the string. - if (src == NULL) - { - clear(); - return; - } - int buf_len = (int)strlen(src)+1; - if (Capacity < buf_len) - reserve_discard(buf_len); - memcpy(Data, src, (size_t)buf_len); - Owned = 1; -} - -void Str::set(const char* src, const char* src_end) -{ - STR_ASSERT(src != NULL && src_end >= src); - int buf_len = (int)(src_end-src)+1; - if ((int)Capacity < buf_len) - reserve_discard(buf_len); - memcpy(Data, src, (size_t)(buf_len - 1)); - Data[buf_len-1] = 0; - Owned = 1; -} - -void Str::set(const Str& src) -{ - int buf_len = (int)strlen(src.c_str())+1; - if ((int)Capacity < buf_len) - reserve_discard(buf_len); - memcpy(Data, src.c_str(), (size_t)buf_len); - Owned = 1; -} - -#if STR_SUPPORT_STD_STRING -void Str::set(const std::string& src) -{ - int buf_len = (int)src.length()+1; - if ((int)Capacity < buf_len) - reserve_discard(buf_len); - memcpy(Data, src.c_str(), (size_t)buf_len); - Owned = 1; -} -#endif - -inline void Str::set_ref(const char* src) -{ - if (Owned && !is_using_local_buf()) - STR_MEMFREE(Data); - Data = src ? (char*)src : EmptyBuffer; - Capacity = 0; - Owned = 0; -} - -Str::Str() -{ - Data = EmptyBuffer; // Shared READ-ONLY initial buffer for 0 capacity - Capacity = 0; - LocalBufSize = 0; - Owned = 0; -} - -Str::Str(const Str& rhs) : Str() -{ - set(rhs); -} - -Str::Str(const char* rhs) : Str() -{ - set(rhs); -} - -#if STR_SUPPORT_STD_STRING -Str::Str(const std::string& rhs) : Str() -{ - set(rhs); -} -#endif - -// Literal/reference string -class StrRef : public Str -{ -public: - StrRef(const char* s) : Str() { set_ref(s); } -}; - -// Types embedding a local buffer -// NB: we need to override the constructor and = operator for both Str& and TYPENAME (without the later compiler will call a default copy operator) -#if STR_SUPPORT_STD_STRING - -#define STR_DEFINETYPE(TYPENAME, LOCALBUFSIZE) \ -class TYPENAME : public Str \ -{ \ - char local_buf[LOCALBUFSIZE]; \ -public: \ - TYPENAME() : Str(LOCALBUFSIZE) {} \ - TYPENAME(const Str& rhs) : Str(LOCALBUFSIZE) { set(rhs); } \ - TYPENAME(const char* rhs) : Str(LOCALBUFSIZE) { set(rhs); } \ - TYPENAME(const TYPENAME& rhs) : Str(LOCALBUFSIZE) { set(rhs); } \ - TYPENAME(const std::string& rhs) : Str(LOCALBUFSIZE) { set(rhs); } \ - TYPENAME& operator=(const char* rhs) { set(rhs); return *this; } \ - TYPENAME& operator=(const Str& rhs) { set(rhs); return *this; } \ - TYPENAME& operator=(const TYPENAME& rhs) { set(rhs); return *this; } \ - TYPENAME& operator=(const std::string& rhs) { set(rhs); return *this; } \ -}; - -#else - -#define STR_DEFINETYPE(TYPENAME, LOCALBUFSIZE) \ -class TYPENAME : public Str \ -{ \ - char local_buf[LOCALBUFSIZE]; \ -public: \ - TYPENAME() : Str(LOCALBUFSIZE) {} \ - TYPENAME(const Str& rhs) : Str(LOCALBUFSIZE) { set(rhs); } \ - TYPENAME(const char* rhs) : Str(LOCALBUFSIZE) { set(rhs); } \ - TYPENAME(const TYPENAME& rhs) : Str(LOCALBUFSIZE) { set(rhs); } \ - TYPENAME& operator=(const char* rhs) { set(rhs); return *this; } \ - TYPENAME& operator=(const Str& rhs) { set(rhs); return *this; } \ - TYPENAME& operator=(const TYPENAME& rhs) { set(rhs); return *this; } \ -}; - -#endif - -// Disable PVS-Studio warning V730: Not all members of a class are initialized inside the constructor (local_buf is not initialized and that is fine) -// -V:STR_DEFINETYPE:730 - -// Helper to define StrXXXf constructors -#define STR_DEFINETYPE_F(TYPENAME, TYPENAME_F) \ -class TYPENAME_F : public TYPENAME \ -{ \ -public: \ - TYPENAME_F(const char* fmt, ...) : TYPENAME() { va_list args; va_start(args, fmt); setfv(fmt, args); va_end(args); } \ -}; - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-private-field" // warning : private field 'local_buf' is not used -#endif - -// Declaring types for common sizes here -STR_DEFINETYPE(Str16, 16) -STR_DEFINETYPE(Str30, 30) -STR_DEFINETYPE(Str64, 64) -STR_DEFINETYPE(Str128, 128) -STR_DEFINETYPE(Str256, 256) -STR_DEFINETYPE(Str512, 512) - -// Declaring helper constructors to pass in format strings in one statement -STR_DEFINETYPE_F(Str16, Str16f) -STR_DEFINETYPE_F(Str30, Str30f) -STR_DEFINETYPE_F(Str64, Str64f) -STR_DEFINETYPE_F(Str128, Str128f) -STR_DEFINETYPE_F(Str256, Str256f) -STR_DEFINETYPE_F(Str512, Str512f) - -#if STR_DEFINE_STR32 -STR_DEFINETYPE(Str32, 32) -STR_DEFINETYPE_F(Str32, Str32f) -#endif - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -#endif // #ifndef STR_INCLUDED - -//------------------------------------------------------------------------- -// IMPLEMENTATION -//------------------------------------------------------------------------- - -#ifdef STR_IMPLEMENTATION - -#include <stdio.h> // for vsnprintf - -// On some platform vsnprintf() takes va_list by reference and modifies it. -// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it. -#ifndef va_copy -#define va_copy(dest, src) (dest = src) -#endif - -// Static empty buffer we can point to for empty strings -// Pointing to a literal increases the like-hood of getting a crash if someone attempts to write in the empty string buffer. -char* Str::EmptyBuffer = (char*)"\0NULL"; - -// Clear -void Str::clear() -{ - if (Owned && !is_using_local_buf()) - STR_MEMFREE(Data); - if (LocalBufSize) - { - Data = local_buf(); - Data[0] = '\0'; - Capacity = LocalBufSize; - Owned = 1; - } - else - { - Data = EmptyBuffer; - Capacity = 0; - Owned = 0; - } -} - -// Reserve memory, preserving the current of the buffer -void Str::reserve(int new_capacity) -{ - if (new_capacity <= Capacity) - return; - - char* new_data; - if (new_capacity < LocalBufSize) - { - // Disowned -> LocalBuf - new_data = local_buf(); - new_capacity = LocalBufSize; - } - else - { - // Disowned or LocalBuf -> Heap - new_data = (char*)STR_MEMALLOC((size_t)new_capacity * sizeof(char)); - } - - // string in Data might be longer than new_capacity if it wasn't owned, don't copy too much -#ifdef _MSC_VER - strncpy_s(new_data, (size_t)new_capacity, Data, (size_t)new_capacity - 1); -#else - strncpy(new_data, Data, (size_t)new_capacity - 1); -#endif - new_data[new_capacity - 1] = 0; - - if (Owned && !is_using_local_buf()) - STR_MEMFREE(Data); - - Data = new_data; - Capacity = new_capacity; - Owned = 1; -} - -// Reserve memory, discarding the current of the buffer (if we expect to be fully rewritten) -void Str::reserve_discard(int new_capacity) -{ - if (new_capacity <= Capacity) - return; - - if (Owned && !is_using_local_buf()) - STR_MEMFREE(Data); - - if (new_capacity < LocalBufSize) - { - // Disowned -> LocalBuf - Data = local_buf(); - Capacity = LocalBufSize; - } - else - { - // Disowned or LocalBuf -> Heap - Data = (char*)STR_MEMALLOC((size_t)new_capacity * sizeof(char)); - Capacity = new_capacity; - } - Owned = 1; -} - -void Str::shrink_to_fit() -{ - if (!Owned || is_using_local_buf()) - return; - int new_capacity = length() + 1; - if (Capacity <= new_capacity) - return; - - char* new_data = (char*)STR_MEMALLOC((size_t)new_capacity * sizeof(char)); - memcpy(new_data, Data, (size_t)new_capacity); - STR_MEMFREE(Data); - Data = new_data; - Capacity = new_capacity; -} - -// FIXME: merge setfv() and appendfv()? -int Str::setfv(const char* fmt, va_list args) -{ - // Needed for portability on platforms where va_list are passed by reference and modified by functions - va_list args2; - va_copy(args2, args); - - // MSVC returns -1 on overflow when writing, which forces us to do two passes - // FIXME-OPT: Find a way around that. -#ifdef _MSC_VER - int len = vsnprintf(NULL, 0, fmt, args); - STR_ASSERT(len >= 0); - - if (Capacity < len + 1) - reserve_discard(len + 1); - len = vsnprintf(Data, len + 1, fmt, args2); -#else - // First try - int len = vsnprintf(Owned ? Data : NULL, Owned ? (size_t)Capacity : 0, fmt, args); - STR_ASSERT(len >= 0); - - if (Capacity < len + 1) - { - reserve_discard(len + 1); - len = vsnprintf(Data, (size_t)len + 1, fmt, args2); - } -#endif - - STR_ASSERT(Owned); - return len; -} - -int Str::setf(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - int len = setfv(fmt, args); - va_end(args); - return len; -} - -int Str::setfv_nogrow(const char* fmt, va_list args) -{ - STR_ASSERT(Owned); - - if (Capacity == 0) - return 0; - - int w = vsnprintf(Data, (size_t)Capacity, fmt, args); - Data[Capacity - 1] = 0; - Owned = 1; - return (w == -1) ? Capacity - 1 : w; -} - -int Str::setf_nogrow(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - int len = setfv_nogrow(fmt, args); - va_end(args); - return len; -} - -int Str::append_from(int idx, char c) -{ - int add_len = 1; - if (Capacity < idx + add_len + 1) - reserve(idx + add_len + 1); - Data[idx] = c; - Data[idx + add_len] = 0; - STR_ASSERT(Owned); - return add_len; -} - -int Str::append_from(int idx, const char* s, const char* s_end) -{ - if (!s_end) - s_end = s + strlen(s); - int add_len = (int)(s_end - s); - if (Capacity < idx + add_len + 1) - reserve(idx + add_len + 1); - memcpy(Data + idx, (const void*)s, (size_t)add_len); - Data[idx + add_len] = 0; // Our source data isn't necessarily zero-terminated - STR_ASSERT(Owned); - return add_len; -} - -// FIXME: merge setfv() and appendfv()? -int Str::appendfv_from(int idx, const char* fmt, va_list args) -{ - // Needed for portability on platforms where va_list are passed by reference and modified by functions - va_list args2; - va_copy(args2, args); - - // MSVC returns -1 on overflow when writing, which forces us to do two passes - // FIXME-OPT: Find a way around that. -#ifdef _MSC_VER - int add_len = vsnprintf(NULL, 0, fmt, args); - STR_ASSERT(add_len >= 0); - - if (Capacity < idx + add_len + 1) - reserve(idx + add_len + 1); - add_len = vsnprintf(Data + idx, add_len + 1, fmt, args2); -#else - // First try - int add_len = vsnprintf(Owned ? Data + idx : NULL, Owned ? (size_t)(Capacity - idx) : 0, fmt, args); - STR_ASSERT(add_len >= 0); - - if (Capacity < idx + add_len + 1) - { - reserve(idx + add_len + 1); - add_len = vsnprintf(Data + idx, (size_t)add_len + 1, fmt, args2); - } -#endif - - STR_ASSERT(Owned); - return add_len; -} - -int Str::appendf_from(int idx, const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - int len = appendfv_from(idx, fmt, args); - va_end(args); - return len; -} - -int Str::append(char c) -{ - int cur_len = length(); - return append_from(cur_len, c); -} - -int Str::append(const char* s, const char* s_end) -{ - int cur_len = length(); - return append_from(cur_len, s, s_end); -} - -int Str::appendfv(const char* fmt, va_list args) -{ - int cur_len = length(); - return appendfv_from(cur_len, fmt, args); -} - -int Str::appendf(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - int len = appendfv(fmt, args); - va_end(args); - return len; -} - -#endif // #define STR_IMPLEMENTATION - -//------------------------------------------------------------------------- diff --git a/vendor/zgui/libs/imgui_test_engine/thirdparty/stb/imstb_image_write.h b/vendor/zgui/libs/imgui_test_engine/thirdparty/stb/imstb_image_write.h deleted file mode 100644 index 082eb69..0000000 --- a/vendor/zgui/libs/imgui_test_engine/thirdparty/stb/imstb_image_write.h +++ /dev/null @@ -1,1617 +0,0 @@ -/* stb_image_write - v1.13 - public domain - http://nothings.org/stb/stb_image_write.h - writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 - no warranty implied; use at your own risk - - Before #including, - - #define STB_IMAGE_WRITE_IMPLEMENTATION - - in the file that you want to have the implementation. - - Will probably not work correctly with strict-aliasing optimizations. - -ABOUT: - - This header file is a library for writing images to C stdio or a callback. - - The PNG output is not optimal; it is 20-50% larger than the file - written by a decent optimizing implementation; though providing a custom - zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. - This library is designed for source code compactness and simplicity, - not optimal image file size or run-time performance. - -BUILDING: - - You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. - You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace - malloc,realloc,free. - You can #define STBIW_MEMMOVE() to replace memmove() - You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function - for PNG compression (instead of the builtin one), it must have the following signature: - unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); - The returned data will be freed with STBIW_FREE() (free() by default), - so it must be heap allocated with STBIW_MALLOC() (malloc() by default), - -UNICODE: - - If compiling for Windows and you wish to use Unicode filenames, compile - with - #define STBIW_WINDOWS_UTF8 - and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert - Windows wchar_t filenames to utf8. - -USAGE: - - There are five functions, one for each image file format: - - int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); - int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); - int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); - int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); - int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); - - void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically - - There are also five equivalent functions that use an arbitrary write function. You are - expected to open/close your file-equivalent before and after calling these: - - int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); - int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); - int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); - int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); - int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); - - where the callback is: - void stbi_write_func(void *context, void *data, int size); - - You can configure it with these global variables: - int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE - int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression - int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode - - - You can define STBI_WRITE_NO_STDIO to disable the file variant of these - functions, so the library will not use stdio.h at all. However, this will - also disable HDR writing, because it requires stdio for formatted output. - - Each function returns 0 on failure and non-0 on success. - - The functions create an image file defined by the parameters. The image - is a rectangle of pixels stored from left-to-right, top-to-bottom. - Each pixel contains 'comp' channels of data stored interleaved with 8-bits - per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is - monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. - The *data pointer points to the first byte of the top-left-most pixel. - For PNG, "stride_in_bytes" is the distance in bytes from the first byte of - a row of pixels to the first byte of the next row of pixels. - - PNG creates output files with the same number of components as the input. - The BMP format expands Y to RGB in the file format and does not - output alpha. - - PNG supports writing rectangles of data even when the bytes storing rows of - data are not consecutive in memory (e.g. sub-rectangles of a larger image), - by supplying the stride between the beginning of adjacent rows. The other - formats do not. (Thus you cannot write a native-format BMP through the BMP - writer, both because it is in BGR order and because it may have padding - at the end of the line.) - - PNG allows you to set the deflate compression level by setting the global - variable 'stbi_write_png_compression_level' (it defaults to 8). - - HDR expects linear float data. Since the format is always 32-bit rgb(e) - data, alpha (if provided) is discarded, and for monochrome data it is - replicated across all three channels. - - TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed - data, set the global variable 'stbi_write_tga_with_rle' to 0. - - JPEG does ignore alpha channels in input data; quality is between 1 and 100. - Higher quality looks better but results in a bigger image. - JPEG baseline (no JPEG progressive). - -CREDITS: - - - Sean Barrett - PNG/BMP/TGA - Baldur Karlsson - HDR - Jean-Sebastien Guay - TGA monochrome - Tim Kelsey - misc enhancements - Alan Hickman - TGA RLE - Emmanuel Julien - initial file IO callback implementation - Jon Olick - original jo_jpeg.cpp code - Daniel Gibson - integrate JPEG, allow external zlib - Aarni Koskela - allow choosing PNG filter - - bugfixes: - github:Chribba - Guillaume Chereau - github:jry2 - github:romigrou - Sergio Gonzalez - Jonas Karlsson - Filip Wasil - Thatcher Ulrich - github:poppolopoppo - Patrick Boettcher - github:xeekworx - Cap Petschulat - Simon Rodriguez - Ivan Tikhonov - github:ignotion - Adam Schackart - -LICENSE - - See end of file for license information. - -*/ - -#ifndef INCLUDE_STB_IMAGE_WRITE_H -#define INCLUDE_STB_IMAGE_WRITE_H - -#include <stdlib.h> - -// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' -#ifndef STBIWDEF -#ifdef STB_IMAGE_WRITE_STATIC -#define STBIWDEF static -#else -#ifdef __cplusplus -#define STBIWDEF extern "C" -#else -#define STBIWDEF extern -#endif -#endif -#endif - -#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations -extern int stbi_write_tga_with_rle; -extern int stbi_write_png_compression_level; -extern int stbi_write_force_png_filter; -#endif - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); -STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); -STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); - -#ifdef STBI_WINDOWS_UTF8 -STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); -#endif -#endif - -typedef void stbi_write_func(void *context, void *data, int size); - -STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); -STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); -STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); - -STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); - -#endif//INCLUDE_STB_IMAGE_WRITE_H - -#ifdef STB_IMAGE_WRITE_IMPLEMENTATION - -#ifdef _WIN32 - #ifndef _CRT_SECURE_NO_WARNINGS - #define _CRT_SECURE_NO_WARNINGS - #endif - #ifndef _CRT_NONSTDC_NO_DEPRECATE - #define _CRT_NONSTDC_NO_DEPRECATE - #endif -#endif - -#ifndef STBI_WRITE_NO_STDIO -#include <stdio.h> -#endif // STBI_WRITE_NO_STDIO - -#include <stdarg.h> -#include <stdlib.h> -#include <string.h> -#include <math.h> - -#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) -// ok -#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) -// ok -#else -#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." -#endif - -#ifndef STBIW_MALLOC -#define STBIW_MALLOC(sz) malloc(sz) -#define STBIW_REALLOC(p,newsz) realloc(p,newsz) -#define STBIW_FREE(p) free(p) -#endif - -#ifndef STBIW_REALLOC_SIZED -#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) -#endif - - -#ifndef STBIW_MEMMOVE -#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) -#endif - - -#ifndef STBIW_ASSERT -#include <assert.h> -#define STBIW_ASSERT(x) assert(x) -#endif - -#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) - -#ifdef STB_IMAGE_WRITE_STATIC -static int stbi__flip_vertically_on_write=0; -static int stbi_write_png_compression_level = 8; -static int stbi_write_tga_with_rle = 1; -static int stbi_write_force_png_filter = -1; -#else -int stbi_write_png_compression_level = 8; -int stbi__flip_vertically_on_write=0; -int stbi_write_tga_with_rle = 1; -int stbi_write_force_png_filter = -1; -#endif - -STBIWDEF void stbi_flip_vertically_on_write(int flag) -{ - stbi__flip_vertically_on_write = flag; -} - -typedef struct -{ - stbi_write_func *func; - void *context; -} stbi__write_context; - -// initialize a callback-based context -static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) -{ - s->func = c; - s->context = context; -} - -#ifndef STBI_WRITE_NO_STDIO - -static void stbi__stdio_write(void *context, void *data, int size) -{ - fwrite(data,1,size,(FILE*) context); -} - -#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) -#ifdef __cplusplus -#define STBIW_EXTERN extern "C" -#else -#define STBIW_EXTERN extern -#endif -STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); -STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); - -STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) -{ - return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); -} -#endif - -static FILE *stbiw__fopen(char const *filename, char const *mode) -{ - FILE *f; -#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) - wchar_t wMode[64]; - wchar_t wFilename[1024]; - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) - return 0; - - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) - return 0; - -#if _MSC_VER >= 1400 - if (0 != _wfopen_s(&f, wFilename, wMode)) - f = 0; -#else - f = _wfopen(wFilename, wMode); -#endif - -#elif defined(_MSC_VER) && _MSC_VER >= 1400 - if (0 != fopen_s(&f, filename, mode)) - f=0; -#else - f = fopen(filename, mode); -#endif - return f; -} - -static int stbi__start_write_file(stbi__write_context *s, const char *filename) -{ - FILE *f = stbiw__fopen(filename, "wb"); - stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); - return f != NULL; -} - -static void stbi__end_write_file(stbi__write_context *s) -{ - fclose((FILE *)s->context); -} - -#endif // !STBI_WRITE_NO_STDIO - -typedef unsigned int stbiw_uint32; -typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; - -static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) -{ - while (*fmt) { - switch (*fmt++) { - case ' ': break; - case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); - s->func(s->context,&x,1); - break; } - case '2': { int x = va_arg(v,int); - unsigned char b[2]; - b[0] = STBIW_UCHAR(x); - b[1] = STBIW_UCHAR(x>>8); - s->func(s->context,b,2); - break; } - case '4': { stbiw_uint32 x = va_arg(v,int); - unsigned char b[4]; - b[0]=STBIW_UCHAR(x); - b[1]=STBIW_UCHAR(x>>8); - b[2]=STBIW_UCHAR(x>>16); - b[3]=STBIW_UCHAR(x>>24); - s->func(s->context,b,4); - break; } - default: - STBIW_ASSERT(0); - return; - } - } -} - -static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) -{ - va_list v; - va_start(v, fmt); - stbiw__writefv(s, fmt, v); - va_end(v); -} - -static void stbiw__putc(stbi__write_context *s, unsigned char c) -{ - s->func(s->context, &c, 1); -} - -static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) -{ - unsigned char arr[3]; - arr[0] = a; arr[1] = b; arr[2] = c; - s->func(s->context, arr, 3); -} - -static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) -{ - unsigned char bg[3] = { 255, 0, 255}, px[3]; - int k; - - if (write_alpha < 0) - s->func(s->context, &d[comp - 1], 1); - - switch (comp) { - case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case - case 1: - if (expand_mono) - stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp - else - s->func(s->context, d, 1); // monochrome TGA - break; - case 4: - if (!write_alpha) { - // composite against pink background - for (k = 0; k < 3; ++k) - px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; - stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); - break; - } - /* FALLTHROUGH */ - case 3: - stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); - break; - } - if (write_alpha > 0) - s->func(s->context, &d[comp - 1], 1); -} - -static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) -{ - stbiw_uint32 zero = 0; - int i,j, j_end; - - if (y <= 0) - return; - - if (stbi__flip_vertically_on_write) - vdir *= -1; - - if (vdir < 0) { - j_end = -1; j = y-1; - } else { - j_end = y; j = 0; - } - - for (; j != j_end; j += vdir) { - for (i=0; i < x; ++i) { - unsigned char *d = (unsigned char *) data + (j*x+i)*comp; - stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); - } - s->func(s->context, &zero, scanline_pad); - } -} - -static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) -{ - if (y < 0 || x < 0) { - return 0; - } else { - va_list v; - va_start(v, fmt); - stbiw__writefv(s, fmt, v); - va_end(v); - stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); - return 1; - } -} - -static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) -{ - int pad = (-x*3) & 3; - return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, - "11 4 22 4" "4 44 22 444444", - 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header - 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header -} - -STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) -{ - stbi__write_context s; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_bmp_core(&s, x, y, comp, data); -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) -{ - stbi__write_context s; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_bmp_core(&s, x, y, comp, data); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif //!STBI_WRITE_NO_STDIO - -static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) -{ - int has_alpha = (comp == 2 || comp == 4); - int colorbytes = has_alpha ? comp-1 : comp; - int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 - - if (y < 0 || x < 0) - return 0; - - if (!stbi_write_tga_with_rle) { - return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, - "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); - } else { - int i,j,k; - int jend, jdir; - - stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); - - if (stbi__flip_vertically_on_write) { - j = 0; - jend = y; - jdir = 1; - } else { - j = y-1; - jend = -1; - jdir = -1; - } - for (; j != jend; j += jdir) { - unsigned char *row = (unsigned char *) data + j * x * comp; - int len; - - for (i = 0; i < x; i += len) { - unsigned char *begin = row + i * comp; - int diff = 1; - len = 1; - - if (i < x - 1) { - ++len; - diff = memcmp(begin, row + (i + 1) * comp, comp); - if (diff) { - const unsigned char *prev = begin; - for (k = i + 2; k < x && len < 128; ++k) { - if (memcmp(prev, row + k * comp, comp)) { - prev += comp; - ++len; - } else { - --len; - break; - } - } - } else { - for (k = i + 2; k < x && len < 128; ++k) { - if (!memcmp(begin, row + k * comp, comp)) { - ++len; - } else { - break; - } - } - } - } - - if (diff) { - unsigned char header = STBIW_UCHAR(len - 1); - s->func(s->context, &header, 1); - for (k = 0; k < len; ++k) { - stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); - } - } else { - unsigned char header = STBIW_UCHAR(len - 129); - s->func(s->context, &header, 1); - stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); - } - } - } - } - return 1; -} - -STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) -{ - stbi__write_context s; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_tga_core(&s, x, y, comp, (void *) data); -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) -{ - stbi__write_context s; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif - -// ************************************************************************************************* -// Radiance RGBE HDR writer -// by Baldur Karlsson - -#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) - -static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) -{ - int exponent; - float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); - - if (maxcomp < 1e-32f) { - rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; - } else { - float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; - - rgbe[0] = (unsigned char)(linear[0] * normalize); - rgbe[1] = (unsigned char)(linear[1] * normalize); - rgbe[2] = (unsigned char)(linear[2] * normalize); - rgbe[3] = (unsigned char)(exponent + 128); - } -} - -static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) -{ - unsigned char lengthbyte = STBIW_UCHAR(length+128); - STBIW_ASSERT(length+128 <= 255); - s->func(s->context, &lengthbyte, 1); - s->func(s->context, &databyte, 1); -} - -static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) -{ - unsigned char lengthbyte = STBIW_UCHAR(length); - STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code - s->func(s->context, &lengthbyte, 1); - s->func(s->context, data, length); -} - -static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) -{ - unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; - unsigned char rgbe[4]; - float linear[3]; - int x; - - scanlineheader[2] = (width&0xff00)>>8; - scanlineheader[3] = (width&0x00ff); - - /* skip RLE for images too small or large */ - if (width < 8 || width >= 32768) { - for (x=0; x < width; x++) { - switch (ncomp) { - case 4: /* fallthrough */ - case 3: linear[2] = scanline[x*ncomp + 2]; - linear[1] = scanline[x*ncomp + 1]; - linear[0] = scanline[x*ncomp + 0]; - break; - default: - linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; - break; - } - stbiw__linear_to_rgbe(rgbe, linear); - s->func(s->context, rgbe, 4); - } - } else { - int c,r; - /* encode into scratch buffer */ - for (x=0; x < width; x++) { - switch(ncomp) { - case 4: /* fallthrough */ - case 3: linear[2] = scanline[x*ncomp + 2]; - linear[1] = scanline[x*ncomp + 1]; - linear[0] = scanline[x*ncomp + 0]; - break; - default: - linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; - break; - } - stbiw__linear_to_rgbe(rgbe, linear); - scratch[x + width*0] = rgbe[0]; - scratch[x + width*1] = rgbe[1]; - scratch[x + width*2] = rgbe[2]; - scratch[x + width*3] = rgbe[3]; - } - - s->func(s->context, scanlineheader, 4); - - /* RLE each component separately */ - for (c=0; c < 4; c++) { - unsigned char *comp = &scratch[width*c]; - - x = 0; - while (x < width) { - // find first run - r = x; - while (r+2 < width) { - if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) - break; - ++r; - } - if (r+2 >= width) - r = width; - // dump up to first run - while (x < r) { - int len = r-x; - if (len > 128) len = 128; - stbiw__write_dump_data(s, len, &comp[x]); - x += len; - } - // if there's a run, output it - if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd - // find next byte after run - while (r < width && comp[r] == comp[x]) - ++r; - // output run up to r - while (x < r) { - int len = r-x; - if (len > 127) len = 127; - stbiw__write_run_data(s, len, comp[x]); - x += len; - } - } - } - } - } -} - -static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) -{ - if (y <= 0 || x <= 0 || data == NULL) - return 0; - else { - // Each component is stored separately. Allocate scratch space for full output scanline. - unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); - int i, len; - char buffer[128]; - char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; - s->func(s->context, header, sizeof(header)-1); - -#ifdef __STDC_WANT_SECURE_LIB__ - len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); -#else - len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); -#endif - s->func(s->context, buffer, len); - - for(i=0; i < y; i++) - stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); - STBIW_FREE(scratch); - return 1; - } -} - -STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) -{ - stbi__write_context s; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_hdr_core(&s, x, y, comp, (float *) data); -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) -{ - stbi__write_context s; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif // STBI_WRITE_NO_STDIO - - -////////////////////////////////////////////////////////////////////////////// -// -// PNG writer -// - -#ifndef STBIW_ZLIB_COMPRESS -// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() -#define stbiw__sbraw(a) ((int *) (a) - 2) -#define stbiw__sbm(a) stbiw__sbraw(a)[0] -#define stbiw__sbn(a) stbiw__sbraw(a)[1] - -#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) -#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) -#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) - -#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) -#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) -#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) - -static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) -{ - int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; - void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); - STBIW_ASSERT(p); - if (p) { - if (!*arr) ((int *) p)[1] = 0; - *arr = (void *) ((int *) p + 2); - stbiw__sbm(*arr) = m; - } - return *arr; -} - -static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) -{ - while (*bitcount >= 8) { - stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); - *bitbuffer >>= 8; - *bitcount -= 8; - } - return data; -} - -static int stbiw__zlib_bitrev(int code, int codebits) -{ - int res=0; - while (codebits--) { - res = (res << 1) | (code & 1); - code >>= 1; - } - return res; -} - -static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) -{ - int i; - for (i=0; i < limit && i < 258; ++i) - if (a[i] != b[i]) break; - return i; -} - -static unsigned int stbiw__zhash(unsigned char *data) -{ - stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); - hash ^= hash << 3; - hash += hash >> 5; - hash ^= hash << 4; - hash += hash >> 17; - hash ^= hash << 25; - hash += hash >> 6; - return hash; -} - -#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) -#define stbiw__zlib_add(code,codebits) \ - (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) -#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) -// default huffman tables -#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) -#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) -#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) -#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) -#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) -#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) - -#define stbiw__ZHASH 16384 - -#endif // STBIW_ZLIB_COMPRESS - -STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) -{ -#ifdef STBIW_ZLIB_COMPRESS - // user provided a zlib compress implementation, use that - return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); -#else // use builtin - static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; - static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; - static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; - static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; - unsigned int bitbuf=0; - int i,j, bitcount=0; - unsigned char *out = NULL; - unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(char**)); - if (hash_table == NULL) - return NULL; - if (quality < 5) quality = 5; - - stbiw__sbpush(out, 0x78); // DEFLATE 32K window - stbiw__sbpush(out, 0x5e); // FLEVEL = 1 - stbiw__zlib_add(1,1); // BFINAL = 1 - stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman - - for (i=0; i < stbiw__ZHASH; ++i) - hash_table[i] = NULL; - - i=0; - while (i < data_len-3) { - // hash next 3 bytes of data to be compressed - int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; - unsigned char *bestloc = 0; - unsigned char **hlist = hash_table[h]; - int n = stbiw__sbcount(hlist); - for (j=0; j < n; ++j) { - if (hlist[j]-data > i-32768) { // if entry lies within window - int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); - if (d >= best) { best=d; bestloc=hlist[j]; } - } - } - // when hash table entry is too long, delete half the entries - if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { - STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); - stbiw__sbn(hash_table[h]) = quality; - } - stbiw__sbpush(hash_table[h],data+i); - - if (bestloc) { - // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal - h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); - hlist = hash_table[h]; - n = stbiw__sbcount(hlist); - for (j=0; j < n; ++j) { - if (hlist[j]-data > i-32767) { - int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); - if (e > best) { // if next match is better, bail on current match - bestloc = NULL; - break; - } - } - } - } - - if (bestloc) { - int d = (int) (data+i - bestloc); // distance back - STBIW_ASSERT(d <= 32767 && best <= 258); - for (j=0; best > lengthc[j+1]-1; ++j); - stbiw__zlib_huff(j+257); - if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); - for (j=0; d > distc[j+1]-1; ++j); - stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); - if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); - i += best; - } else { - stbiw__zlib_huffb(data[i]); - ++i; - } - } - // write out final bytes - for (;i < data_len; ++i) - stbiw__zlib_huffb(data[i]); - stbiw__zlib_huff(256); // end of block - // pad with 0 bits to byte boundary - while (bitcount) - stbiw__zlib_add(0,1); - - for (i=0; i < stbiw__ZHASH; ++i) - (void) stbiw__sbfree(hash_table[i]); - STBIW_FREE(hash_table); - - { - // compute adler32 on input - unsigned int s1=1, s2=0; - int blocklen = (int) (data_len % 5552); - j=0; - while (j < data_len) { - for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } - s1 %= 65521; s2 %= 65521; - j += blocklen; - blocklen = 5552; - } - stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); - stbiw__sbpush(out, STBIW_UCHAR(s2)); - stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); - stbiw__sbpush(out, STBIW_UCHAR(s1)); - } - *out_len = stbiw__sbn(out); - // make returned pointer freeable - STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); - return (unsigned char *) stbiw__sbraw(out); -#endif // STBIW_ZLIB_COMPRESS -} - -static unsigned int stbiw__crc32(unsigned char *buffer, int len) -{ -#ifdef STBIW_CRC32 - return STBIW_CRC32(buffer, len); -#else - static unsigned int crc_table[256] = - { - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, - 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, - 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, - 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, - 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, - 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, - 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, - 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, - 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, - 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, - 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, - 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, - 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, - 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, - 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, - 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, - 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, - 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, - 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, - 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, - 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, - 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, - 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, - 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, - 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, - 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, - 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, - 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, - 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, - 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, - 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, - 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D - }; - - unsigned int crc = ~0u; - int i; - for (i=0; i < len; ++i) - crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; - return ~crc; -#endif -} - -#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) -#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); -#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) - -static void stbiw__wpcrc(unsigned char **data, int len) -{ - unsigned int crc = stbiw__crc32(*data - len - 4, len+4); - stbiw__wp32(*data, crc); -} - -static unsigned char stbiw__paeth(int a, int b, int c) -{ - int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); - if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); - if (pb <= pc) return STBIW_UCHAR(b); - return STBIW_UCHAR(c); -} - -// @OPTIMIZE: provide an option that always forces left-predict or paeth predict -static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) -{ - static int mapping[] = { 0,1,2,3,4 }; - static int firstmap[] = { 0,1,0,5,6 }; - int *mymap = (y != 0) ? mapping : firstmap; - int i; - int type = mymap[filter_type]; - unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); - int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; - - if (type==0) { - memcpy(line_buffer, z, width*n); - return; - } - - // first loop isn't optimized since it's just one pixel - for (i = 0; i < n; ++i) { - switch (type) { - case 1: line_buffer[i] = z[i]; break; - case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; - case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; - case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; - case 5: line_buffer[i] = z[i]; break; - case 6: line_buffer[i] = z[i]; break; - } - } - switch (type) { - case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; - case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; - case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; - case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; - case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; - case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; - } -} - -STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) -{ - int force_filter = stbi_write_force_png_filter; - int ctype[5] = { -1, 0, 4, 2, 6 }; - unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; - unsigned char *out,*o, *filt, *zlib; - signed char *line_buffer; - int j,zlen; - - if (stride_bytes == 0) - stride_bytes = x * n; - - if (force_filter >= 5) { - force_filter = -1; - } - - filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; - line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } - for (j=0; j < y; ++j) { - int filter_type; - if (force_filter > -1) { - filter_type = force_filter; - stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); - } else { // Estimate the best filter by running through all of them: - int best_filter = 0, best_filter_val = 0x7fffffff, est, i; - for (filter_type = 0; filter_type < 5; filter_type++) { - stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); - - // Estimate the entropy of the line using this filter; the less, the better. - est = 0; - for (i = 0; i < x*n; ++i) { - est += abs((signed char) line_buffer[i]); - } - if (est < best_filter_val) { - best_filter_val = est; - best_filter = filter_type; - } - } - if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it - stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); - filter_type = best_filter; - } - } - // when we get here, filter_type contains the filter type, and line_buffer contains the data - filt[j*(x*n+1)] = (unsigned char) filter_type; - STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); - } - STBIW_FREE(line_buffer); - zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); - STBIW_FREE(filt); - if (!zlib) return 0; - - // each tag requires 12 bytes of overhead - out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); - if (!out) return 0; - *out_len = 8 + 12+13 + 12+zlen + 12; - - o=out; - STBIW_MEMMOVE(o,sig,8); o+= 8; - stbiw__wp32(o, 13); // header length - stbiw__wptag(o, "IHDR"); - stbiw__wp32(o, x); - stbiw__wp32(o, y); - *o++ = 8; - *o++ = STBIW_UCHAR(ctype[n]); - *o++ = 0; - *o++ = 0; - *o++ = 0; - stbiw__wpcrc(&o,13); - - stbiw__wp32(o, zlen); - stbiw__wptag(o, "IDAT"); - STBIW_MEMMOVE(o, zlib, zlen); - o += zlen; - STBIW_FREE(zlib); - stbiw__wpcrc(&o, zlen); - - stbiw__wp32(o,0); - stbiw__wptag(o, "IEND"); - stbiw__wpcrc(&o,0); - - STBIW_ASSERT(o == out + *out_len); - - return out; -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) -{ - FILE *f; - int len; - unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); - if (png == NULL) return 0; - - f = stbiw__fopen(filename, "wb"); - if (!f) { STBIW_FREE(png); return 0; } - fwrite(png, 1, len, f); - fclose(f); - STBIW_FREE(png); - return 1; -} -#endif - -STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) -{ - int len; - unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); - if (png == NULL) return 0; - func(context, png, len); - STBIW_FREE(png); - return 1; -} - - -/* *************************************************************************** - * - * JPEG writer - * - * This is based on Jon Olick's jo_jpeg.cpp: - * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html - */ - -static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, - 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; - -static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { - int bitBuf = *bitBufP, bitCnt = *bitCntP; - bitCnt += bs[1]; - bitBuf |= bs[0] << (24 - bitCnt); - while(bitCnt >= 8) { - unsigned char c = (bitBuf >> 16) & 255; - stbiw__putc(s, c); - if(c == 255) { - stbiw__putc(s, 0); - } - bitBuf <<= 8; - bitCnt -= 8; - } - *bitBufP = bitBuf; - *bitCntP = bitCnt; -} - -static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { - float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; - float z1, z2, z3, z4, z5, z11, z13; - - float tmp0 = d0 + d7; - float tmp7 = d0 - d7; - float tmp1 = d1 + d6; - float tmp6 = d1 - d6; - float tmp2 = d2 + d5; - float tmp5 = d2 - d5; - float tmp3 = d3 + d4; - float tmp4 = d3 - d4; - - // Even part - float tmp10 = tmp0 + tmp3; // phase 2 - float tmp13 = tmp0 - tmp3; - float tmp11 = tmp1 + tmp2; - float tmp12 = tmp1 - tmp2; - - d0 = tmp10 + tmp11; // phase 3 - d4 = tmp10 - tmp11; - - z1 = (tmp12 + tmp13) * 0.707106781f; // c4 - d2 = tmp13 + z1; // phase 5 - d6 = tmp13 - z1; - - // Odd part - tmp10 = tmp4 + tmp5; // phase 2 - tmp11 = tmp5 + tmp6; - tmp12 = tmp6 + tmp7; - - // The rotator is modified from fig 4-8 to avoid extra negations. - z5 = (tmp10 - tmp12) * 0.382683433f; // c6 - z2 = tmp10 * 0.541196100f + z5; // c2-c6 - z4 = tmp12 * 1.306562965f + z5; // c2+c6 - z3 = tmp11 * 0.707106781f; // c4 - - z11 = tmp7 + z3; // phase 5 - z13 = tmp7 - z3; - - *d5p = z13 + z2; // phase 6 - *d3p = z13 - z2; - *d1p = z11 + z4; - *d7p = z11 - z4; - - *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; -} - -static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { - int tmp1 = val < 0 ? -val : val; - val = val < 0 ? val-1 : val; - bits[1] = 1; - while(tmp1 >>= 1) { - ++bits[1]; - } - bits[0] = val & ((1<<bits[1])-1); -} - -static int stbiw__jpg_processDU(stbi__write_context *s, int *bitBuf, int *bitCnt, float *CDU, float *fdtbl, int DC, const unsigned short HTDC[256][2], const unsigned short HTAC[256][2]) { - const unsigned short EOB[2] = { HTAC[0x00][0], HTAC[0x00][1] }; - const unsigned short M16zeroes[2] = { HTAC[0xF0][0], HTAC[0xF0][1] }; - int dataOff, i, diff, end0pos; - int DU[64]; - - // DCT rows - for(dataOff=0; dataOff<64; dataOff+=8) { - stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff+1], &CDU[dataOff+2], &CDU[dataOff+3], &CDU[dataOff+4], &CDU[dataOff+5], &CDU[dataOff+6], &CDU[dataOff+7]); - } - // DCT columns - for(dataOff=0; dataOff<8; ++dataOff) { - stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff+8], &CDU[dataOff+16], &CDU[dataOff+24], &CDU[dataOff+32], &CDU[dataOff+40], &CDU[dataOff+48], &CDU[dataOff+56]); - } - // Quantize/descale/zigzag the coefficients - for(i=0; i<64; ++i) { - float v = CDU[i]*fdtbl[i]; - // DU[stbiw__jpg_ZigZag[i]] = (int)(v < 0 ? ceilf(v - 0.5f) : floorf(v + 0.5f)); - // ceilf() and floorf() are C99, not C89, but I /think/ they're not needed here anyway? - DU[stbiw__jpg_ZigZag[i]] = (int)(v < 0 ? v - 0.5f : v + 0.5f); - } - - // Encode DC - diff = DU[0] - DC; - if (diff == 0) { - stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTDC[0]); - } else { - unsigned short bits[2]; - stbiw__jpg_calcBits(diff, bits); - stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTDC[bits[1]]); - stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); - } - // Encode ACs - end0pos = 63; - for(; (end0pos>0)&&(DU[end0pos]==0); --end0pos) { - } - // end0pos = first element in reverse order !=0 - if(end0pos == 0) { - stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); - return DU[0]; - } - for(i = 1; i <= end0pos; ++i) { - int startpos = i; - int nrzeroes; - unsigned short bits[2]; - for (; DU[i]==0 && i<=end0pos; ++i) { - } - nrzeroes = i-startpos; - if ( nrzeroes >= 16 ) { - int lng = nrzeroes>>4; - int nrmarker; - for (nrmarker=1; nrmarker <= lng; ++nrmarker) - stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); - nrzeroes &= 15; - } - stbiw__jpg_calcBits(DU[i], bits); - stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); - stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); - } - if(end0pos != 63) { - stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); - } - return DU[0]; -} - -static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { - // Constants that don't pollute global namespace - static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; - static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; - static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; - static const unsigned char std_ac_luminance_values[] = { - 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, - 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, - 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, - 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, - 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, - 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, - 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa - }; - static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; - static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; - static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; - static const unsigned char std_ac_chrominance_values[] = { - 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, - 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, - 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, - 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, - 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, - 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, - 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa - }; - // Huffman tables - static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; - static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; - static const unsigned short YAC_HT[256][2] = { - {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, - {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} - }; - static const unsigned short UVAC_HT[256][2] = { - {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, - {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} - }; - static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, - 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; - static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, - 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; - static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, - 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; - - int row, col, i, k; - float fdtbl_Y[64], fdtbl_UV[64]; - unsigned char YTable[64], UVTable[64]; - - if(!data || !width || !height || comp > 4 || comp < 1) { - return 0; - } - - quality = quality ? quality : 90; - quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; - quality = quality < 50 ? 5000 / quality : 200 - quality * 2; - - for(i = 0; i < 64; ++i) { - int uvti, yti = (YQT[i]*quality+50)/100; - YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); - uvti = (UVQT[i]*quality+50)/100; - UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); - } - - for(row = 0, k = 0; row < 8; ++row) { - for(col = 0; col < 8; ++col, ++k) { - fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); - fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); - } - } - - // Write Headers - { - static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; - static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; - const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), - 3,1,0x11,0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; - s->func(s->context, (void*)head0, sizeof(head0)); - s->func(s->context, (void*)YTable, sizeof(YTable)); - stbiw__putc(s, 1); - s->func(s->context, UVTable, sizeof(UVTable)); - s->func(s->context, (void*)head1, sizeof(head1)); - s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); - s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); - stbiw__putc(s, 0x10); // HTYACinfo - s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); - s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); - stbiw__putc(s, 1); // HTUDCinfo - s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); - s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); - stbiw__putc(s, 0x11); // HTUACinfo - s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); - s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); - s->func(s->context, (void*)head2, sizeof(head2)); - } - - // Encode 8x8 macroblocks - { - static const unsigned short fillBits[] = {0x7F, 7}; - const unsigned char *imageData = (const unsigned char *)data; - int DCY=0, DCU=0, DCV=0; - int bitBuf=0, bitCnt=0; - // comp == 2 is grey+alpha (alpha is ignored) - int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; - int x, y, pos; - for(y = 0; y < height; y += 8) { - for(x = 0; x < width; x += 8) { - float YDU[64], UDU[64], VDU[64]; - for(row = y, pos = 0; row < y+8; ++row) { - // row >= height => use last input row - int clamped_row = (row < height) ? row : height - 1; - int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; - for(col = x; col < x+8; ++col, ++pos) { - float r, g, b; - // if col >= width => use pixel from last input column - int p = base_p + ((col < width) ? col : (width-1))*comp; - - r = imageData[p+0]; - g = imageData[p+ofsG]; - b = imageData[p+ofsB]; - YDU[pos]=+0.29900f*r+0.58700f*g+0.11400f*b-128; - UDU[pos]=-0.16874f*r-0.33126f*g+0.50000f*b; - VDU[pos]=+0.50000f*r-0.41869f*g-0.08131f*b; - } - } - - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); - DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); - } - } - - // Do the bit alignment of the EOI marker - stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); - } - - // EOI - stbiw__putc(s, 0xFF); - stbiw__putc(s, 0xD9); - - return 1; -} - -STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) -{ - stbi__write_context s; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); -} - - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) -{ - stbi__write_context s; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif - -#endif // STB_IMAGE_WRITE_IMPLEMENTATION - -/* Revision history - 1.10 (2019-02-07) - support utf8 filenames in Windows; fix warnings and platform ifdefs - 1.09 (2018-02-11) - fix typo in zlib quality API, improve STB_I_W_STATIC in C++ - 1.08 (2018-01-29) - add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter - 1.07 (2017-07-24) - doc fix - 1.06 (2017-07-23) - writing JPEG (using Jon Olick's code) - 1.05 ??? - 1.04 (2017-03-03) - monochrome BMP expansion - 1.03 ??? - 1.02 (2016-04-02) - avoid allocating large structures on the stack - 1.01 (2016-01-16) - STBIW_REALLOC_SIZED: support allocators with no realloc support - avoid race-condition in crc initialization - minor compile issues - 1.00 (2015-09-14) - installable file IO function - 0.99 (2015-09-13) - warning fixes; TGA rle support - 0.98 (2015-04-08) - added STBIW_MALLOC, STBIW_ASSERT etc - 0.97 (2015-01-18) - fixed HDR asserts, rewrote HDR rle logic - 0.96 (2015-01-17) - add HDR output - fix monochrome BMP - 0.95 (2014-08-17) - add monochrome TGA output - 0.94 (2014-05-31) - rename private functions to avoid conflicts with stb_image.h - 0.93 (2014-05-27) - warning fixes - 0.92 (2010-08-01) - casts to unsigned char to fix warnings - 0.91 (2010-07-17) - first public release - 0.90 first internal release -*/ - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ |
