From 1f9b827badb2de4c4eaae11c0d02242ec90af7f6 Mon Sep 17 00:00:00 2001 From: Nic Gaffney Date: Mon, 13 Oct 2025 01:19:27 -0500 Subject: Updating to zig 0.15.1 --- .../libs/imgui_test_engine/imgui_te_context.cpp | 3960 -------------------- 1 file changed, 3960 deletions(-) delete mode 100644 vendor/zgui/libs/imgui_test_engine/imgui_te_context.cpp (limited to 'vendor/zgui/libs/imgui_test_engine/imgui_te_context.cpp') 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 : "", 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 : ""); -} - -// 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 : ""); -} - -// 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 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 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; -} - -//------------------------------------------------------------------------- -- cgit v1.2.3