2024-07-04 00:55:17 +00:00
// 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
//-------------------------------------------------------------------------
2024-09-08 23:52:23 +00:00
ImGuiTestRefDesc : : ImGuiTestRefDesc ( const ImGuiTestRef & ref )
2024-07-04 00:55:17 +00:00
{
2024-09-08 23:52:23 +00:00
if ( ref . Path & & ref . ID ! = 0 )
ImFormatString ( Buf , IM_ARRAYSIZE ( Buf ) , " '%s' (id 0x%08X) " , ref . Path , ref . ID ) ;
else if ( ref . Path )
ImFormatString ( Buf , IM_ARRAYSIZE ( Buf ) , " '%s' " , ref . Path ) ;
2024-07-04 00:55:17 +00:00
else
2024-09-08 23:52:23 +00:00
ImFormatString ( Buf , IM_ARRAYSIZE ( Buf ) , " 0x%08X " , ref . ID ) ;
}
ImGuiTestRefDesc : : ImGuiTestRefDesc ( const ImGuiTestRef & ref , const ImGuiTestItemInfo & item )
{
if ( ref . Path & & item . ID ! = 0 )
ImFormatString ( Buf , IM_ARRAYSIZE ( Buf ) , " '%s' (id 0x%08X) " , ref . Path , item . ID ) ;
else if ( ref . Path )
ImFormatString ( Buf , IM_ARRAYSIZE ( Buf ) , " '%s' " , ref . Path ) ;
else
ImFormatString ( Buf , IM_ARRAYSIZE ( Buf ) , " 0x%08X (label \" %s \" ) " , ref . ID , item . DebugLabel ) ;
2024-07-04 00:55:17 +00:00
}
//-------------------------------------------------------------------------
// [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 - - ;
}
}
// 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
2024-09-08 23:52:23 +00:00
LogDebug ( " (returning to parent test) " ) ;
2024-07-04 00:55:17 +00:00
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 ;
if ( file ! = NULL )
2024-09-08 23:52:23 +00:00
{
file = ImPathFindFilename ( file ) ;
2024-07-04 00:55:17 +00:00
LogError ( " SuspendTestFunc() at %s:%d " , file , line ) ;
2024-09-08 23:52:23 +00:00
}
2024-07-04 00:55:17 +00:00
else
2024-09-08 23:52:23 +00:00
{
2024-07-04 00:55:17 +00:00
LogError ( " SuspendTestFunc() " ) ;
2024-09-08 23:52:23 +00:00
}
2024-07-04 00:55:17 +00:00
// 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 ;
2024-09-08 23:52:23 +00:00
LogDebug ( " SleepNoSkip %f seconds (in %f increments) " , time , framestep_in_second ) ;
2024-07-04 00:55:17 +00:00
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 ;
}
}
2024-09-08 23:52:23 +00:00
// Shortcut for when we have a window pointer, avoid mistakes with slashes in child names.
2024-07-04 00:55:17 +00:00
void ImGuiTestContext : : SetRef ( ImGuiWindow * window )
{
IMGUI_TEST_CONTEXT_REGISTER_DEPTH ( this ) ;
IM_CHECK_SILENT ( window ! = NULL ) ;
2024-09-08 23:52:23 +00:00
LogDebug ( " SetRef '%s' 0x%08X " , window - > Name , window - > ID ) ;
2024-07-04 00:55:17 +00:00
// 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 ) ;
}
2024-09-08 23:52:23 +00:00
// It is exceptionally OK to call SetRef() in GuiFunc, as a facility to call seeded ctx->GetId() in GuiFunc.
2024-07-04 00:55:17 +00:00
// 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 )
2024-09-08 23:52:23 +00:00
LogDebug ( " SetRef '%s' 0x%08X " , ref . Path ? ref . Path : " NULL " , ref . ID ) ;
2024-07-04 00:55:17 +00:00
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 ;
2024-09-08 23:52:23 +00:00
// Early out
if ( ref . IsEmpty ( ) )
return ;
2024-07-04 00:55:17 +00:00
// Try to infer window
2024-09-08 23:52:23 +00:00
// (This is in order to set viewport, uncollapse window, and store its base id for leading "/" operator)
// (0) Windows is fully specified in path?
2024-07-04 00:55:17 +00:00
ImGuiWindow * window = GetWindowByRef ( " " ) ;
2024-09-08 23:52:23 +00:00
// (1) Try first element of ref path, it is most likely a window name and item lookup won't be necessary.
2024-07-04 00:55:17 +00:00
if ( window = = NULL & & ref . Path ! = NULL )
{
2024-09-08 23:52:23 +00:00
// "Window/SomeItem" -> search for "Window"
2024-07-04 00:55:17 +00:00
const char * name_begin = ref . Path ;
2024-09-08 23:52:23 +00:00
while ( * name_begin = = ' / ' ) // Skip leading slashes
name_begin + + ;
2024-07-04 00:55:17 +00:00
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 )
2024-09-08 23:52:23 +00:00
{
ImGuiTestItemInfo item_info = ItemInfo ( RefID , ImGuiTestOpFlags_NoError ) ;
if ( item_info . ID ! = 0 )
window = item_info . Window ;
}
2024-07-04 00:55:17 +00:00
2024-09-08 23:52:23 +00:00
// Set viewport and base ID for single "/" operator.
2024-07-04 00:55:17 +00:00
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 ;
}
ImGuiWindow * ImGuiTestContext : : GetWindowByRef ( ImGuiTestRef ref )
{
2024-09-08 23:52:23 +00:00
ImGuiID window_id = GetID ( ref ) ;
2024-07-04 00:55:17 +00:00
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 ;
}
2024-09-08 23:52:23 +00:00
# if IMGUI_TEST_ENGINE_ENABLE_CAPTURE
2024-07-04 00:55:17 +00:00
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 ) ;
}
2024-09-08 23:52:23 +00:00
# endif
2024-07-04 00:55:17 +00:00
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 ) ;
2024-09-08 23:52:23 +00:00
LogWarning ( " Skipped screenshot capture: disabled by IMGUI_TEST_ENGINE_ENABLE_CAPTURE=0. " ) ;
2024-07-04 00:55:17 +00:00
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 ) ;
2024-09-08 23:52:23 +00:00
LogWarning ( " Skipped recording GIF: disabled by IMGUI_TEST_ENGINE_ENABLE_CAPTURE=0. " ) ;
2024-07-04 00:55:17 +00:00
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 ;
2024-09-08 23:52:23 +00:00
# if IMGUI_TEST_ENGINE_ENABLE_CAPTURE
2024-07-04 00:55:17 +00:00
// 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 ) ;
}
2024-09-08 23:52:23 +00:00
# endif
2024-07-04 00:55:17 +00:00
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 )
{
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo base_item = ItemInfo ( task - > InPrefixId , ImGuiTestOpFlags_NoError ) ;
ImGuiWindow * window = ( base_item . ID ! = 0 ) ? base_item . Window : GetWindowByRef ( task - > InPrefixId ) ;
2024-07-04 00:55:17 +00:00
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 ;
}
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
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo ImGuiTestContext : : ItemInfo ( ImGuiTestRef ref , ImGuiTestOpFlags flags )
2024-07-04 00:55:17 +00:00
{
if ( IsError ( ) )
return ItemInfoNull ( ) ;
2024-09-08 23:52:23 +00:00
const ImGuiTestOpFlags SUPPORTED_FLAGS = ImGuiTestOpFlags_NoError ;
IM_ASSERT ( ( flags & ~ SUPPORTED_FLAGS ) = = 0 ) ;
2024-07-04 00:55:17 +00:00
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 )
2024-09-08 23:52:23 +00:00
return * item ;
2024-07-04 00:55:17 +00:00
ImGuiTestEngine_Yield ( Engine ) ;
retries + + ;
}
ItemInfoErrorLog ( this , ref , full_id , flags ) ;
return ItemInfoNull ( ) ;
}
// Supported values for ImGuiTestOpFlags:
// - ImGuiTestOpFlags_NoError
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo ImGuiTestContext : : ItemInfoOpenFullPath ( ImGuiTestRef ref , ImGuiTestOpFlags flags )
2024-07-04 00:55:17 +00:00
{
// First query
bool can_open_full_path = ( ref . Path ! = NULL ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo item = ItemInfo ( ref , ( can_open_full_path ? ImGuiTestOpFlags_NoError : ImGuiTestOpFlags_None ) | ( flags & ImGuiTestOpFlags_NoError ) ) ;
if ( item . ID ! = 0 )
2024-07-04 00:55:17 +00:00
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 ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo parent_item = ItemInfo ( parent_id . c_str ( ) , ImGuiTestOpFlags_NoError ) ;
if ( parent_item . ID ! = 0 )
2024-07-04 00:55:17 +00:00
{
# ifdef IMGUI_HAS_DOCK
2024-09-08 23:52:23 +00:00
ImGuiWindow * parent_window = parent_item . Window ;
2024-07-04 00:55:17 +00:00
# endif
2024-09-08 23:52:23 +00:00
if ( ( parent_item . StatusFlags & ImGuiItemStatusFlags_Openable ) ! = 0 & & ( parent_item . StatusFlags & ImGuiItemStatusFlags_Opened ) = = 0 )
2024-07-04 00:55:17 +00:00
{
// Open intermediary item
2024-09-08 23:52:23 +00:00
if ( ( parent_item . InFlags & ImGuiItemFlags_Disabled ) = = 0 ) // FIXME: Report disabled state in log?
2024-07-04 00:55:17 +00:00
{
2024-09-08 23:52:23 +00:00
ItemAction ( ImGuiTestAction_Open , parent_item . ID , ImGuiTestOpFlags_NoAutoOpenFullPath ) ;
2024-07-04 00:55:17 +00:00
opened_parents + + ;
}
}
# ifdef IMGUI_HAS_DOCK
2024-09-08 23:52:23 +00:00
else if ( parent_window - > ID = = parent_item . ID & & parent_window - > DockIsActive & & parent_window - > DockTabIsVisible = = false )
2024-07-04 00:55:17 +00:00
{
// Make tab visible
2024-09-08 23:52:23 +00:00
ItemClick ( parent_item . ID ) ;
2024-07-04 00:55:17 +00:00
opened_parents + + ;
}
# endif
}
}
if ( opened_parents > 0 )
item = ItemInfo ( ref , ( flags & ImGuiTestOpFlags_NoError ) ) ;
2024-09-08 23:52:23 +00:00
if ( item . ID = = 0 )
2024-07-04 00:55:17 +00:00
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);'
2024-09-08 23:52:23 +00:00
// Supported values for ImGuiTestOpFlags:
// - ImGuiTestOpFlags_NoError
2024-07-04 00:55:17 +00:00
// Todos:
// - FIXME: Missing support for wildcards.
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo ImGuiTestContext : : WindowInfo ( ImGuiTestRef ref , ImGuiTestOpFlags flags )
2024-07-04 00:55:17 +00:00
{
if ( IsError ( ) )
return ItemInfoNull ( ) ;
IMGUI_TEST_CONTEXT_REGISTER_DEPTH ( this ) ;
// Query by ID (not very useful but supported)
if ( ref . ID ! = 0 )
{
2024-09-08 23:52:23 +00:00
LogDebug ( " WindowInfo: by id: 0x%08X " , ref . ID ) ;
2024-07-04 00:55:17 +00:00
IM_ASSERT ( ref . Path = = NULL ) ;
ImGuiWindow * window = GetWindowByRef ( ref ) ;
if ( window = = NULL )
{
2024-09-08 23:52:23 +00:00
if ( ( flags & ImGuiTestOpFlags_NoError ) = = 0 )
LogError ( " WindowInfo: cannot find window by ID! " ) ; // FIXME: What if we want to query a not-yet-existing window by ID?
2024-07-04 00:55:17 +00:00
return ItemInfoNull ( ) ;
}
2024-09-08 23:52:23 +00:00
return ItemInfo ( window - > ID , flags & ImGuiTestOpFlags_NoError ) ;
2024-07-04 00:55:17 +00:00
}
// 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 )
{
2024-09-08 23:52:23 +00:00
if ( ( flags & ImGuiTestOpFlags_NoError ) = = 0 )
LogError ( " WindowInfo: element \" %s \" doesn't seem to exist. " , part_name . c_str ( ) ) ;
2024-07-04 00:55:17 +00:00
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 )
{
2024-09-08 23:52:23 +00:00
if ( ( flags & ImGuiTestOpFlags_NoError ) = = 0 )
LogError ( " WindowInfo: element doesn't seem to exist or isn't a window. " ) ;
2024-07-04 00:55:17 +00:00
return ItemInfoNull ( ) ;
}
2024-09-08 23:52:23 +00:00
return ItemInfo ( window - > ID , flags & ImGuiTestOpFlags_NoError ) ;
2024-07-04 00:55:17 +00:00
}
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
2024-09-08 23:52:23 +00:00
static bool ScrollToWithScrollbar ( ImGuiTestContext * ctx , ImGuiWindow * window , ImGuiAxis axis , float scroll_target )
{
ImGuiContext & g = * ctx - > UiContext ;
ctx - > Yield ( ) ;
ctx - > WindowFocus ( window - > ID ) ;
if ( window - > ScrollbarSizes [ axis ^ 1 ] < = 0.0f ) // Verify if still exists after yield
return false ;
const ImRect scrollbar_rect = ImGui : : GetWindowScrollbarRect ( window , axis ) ;
const float scrollbar_size_v = scrollbar_rect . Max [ axis ] - scrollbar_rect . Min [ axis ] ;
const float window_resize_grip_size = ImTrunc ( ImMax ( g . FontSize * 1.35f , window - > WindowRounding + 1.0f + g . FontSize * 0.2f ) ) ;
// In case of a very small window, directly use SetScrollX/Y function to prevent resizing it
// FIXME-TESTS: GetWindowScrollbarMousePositionForScroll() doesn't return the exact value when scrollbar grip is too small
if ( scrollbar_size_v < window_resize_grip_size )
return false ;
ctx - > MouseSetViewport ( window ) ;
const float scroll_src = window - > Scroll [ axis ] ;
ImVec2 scrollbar_src_pos = GetWindowScrollbarMousePositionForScroll ( window , axis , scroll_src ) ;
scrollbar_src_pos [ axis ] = ImMin ( scrollbar_src_pos [ axis ] , scrollbar_rect . Min [ axis ] + scrollbar_size_v - window_resize_grip_size ) ;
ctx - > MouseMoveToPos ( scrollbar_src_pos ) ;
ctx - > MouseDown ( 0 ) ;
ctx - > SleepStandard ( ) ;
ImVec2 scrollbar_dst_pos = GetWindowScrollbarMousePositionForScroll ( window , axis , scroll_target ) ;
ctx - > MouseMoveToPos ( scrollbar_dst_pos ) ;
ctx - > MouseUp ( 0 ) ;
ctx - > SleepStandard ( ) ;
// Verify that things worked
const float scroll_result = window - > Scroll [ axis ] ;
if ( ImFabs ( scroll_result - scroll_target ) < 1.0f )
return true ;
// FIXME-TESTS: Investigate
ctx - > LogWarning ( " Failed to set Scroll%c. Requested %.2f, got %.2f. " , ' X ' + axis , scroll_target , scroll_result ) ;
return true ;
}
2024-07-04 00:55:17 +00:00
// 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
2024-09-08 23:52:23 +00:00
const ImGuiTestItemInfo scrollbar_item = ItemInfo ( ImGui : : GetWindowScrollbarID ( window , axis ) , ImGuiTestOpFlags_NoError ) ;
if ( scrollbar_item . ID ! = 0 & & /*EngineIO->ConfigRunSpeed != ImGuiTestRunSpeed_Fast && */ ! ( flags & ImGuiTestOpFlags_NoFocusWindow ) )
2024-07-04 00:55:17 +00:00
{
2024-09-08 23:52:23 +00:00
if ( ScrollToWithScrollbar ( this , window , axis , scroll_target_clamp ) )
2024-07-04 00:55:17 +00:00
{
// 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 ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo item = ItemInfo ( ref ) ;
2024-07-04 00:55:17 +00:00
ImGuiTestRefDesc desc ( ref , item ) ;
LogDebug ( " ScrollToItem %c %s " , ' X ' + axis , desc . c_str ( ) ) ;
2024-09-08 23:52:23 +00:00
if ( item . ID = = 0 )
2024-07-04 00:55:17 +00:00
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 )
2024-09-08 23:52:23 +00:00
if ( ImGuiTabBar * tab_bar = g . TabBars . GetByKey ( item . ParentID ) )
2024-07-04 00:55:17 +00:00
if ( tab_bar - > Flags & ImGuiTabBarFlags_FittingPolicyScroll )
{
2024-09-08 23:52:23 +00:00
ScrollToTabItem ( tab_bar , item . ID ) ;
2024-07-04 00:55:17 +00:00
return ;
}
2024-09-08 23:52:23 +00:00
ImGuiWindow * window = item . Window ;
float item_curr = ImFloor ( item . RectFull . GetCenter ( ) [ axis ] ) ;
2024-07-04 00:55:17 +00:00
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 ;
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo item = ItemInfo ( ref ) ;
2024-07-04 00:55:17 +00:00
ImGuiTestRefDesc desc ( ref , item ) ;
LogDebug ( " NavMove to %s " , desc . c_str ( ) ) ;
2024-09-08 23:52:23 +00:00
if ( item . ID = = 0 )
2024-07-04 00:55:17 +00:00
return ;
if ( EngineIO - > ConfigRunSpeed = = ImGuiTestRunSpeed_Cinematic )
SleepStandard ( ) ;
// Focus window before scrolling/moving so things are nicely visible
2024-09-08 23:52:23 +00:00
WindowFocus ( item . Window - > ID ) ;
2024-07-04 00:55:17 +00:00
// 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 ) ;
2024-09-08 23:52:23 +00:00
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 ) ;
2024-07-04 00:55:17 +00:00
g . NavDisableHighlight = false ;
g . NavDisableMouseHover = g . NavMousePosDirty = true ;
2024-09-08 23:52:23 +00:00
ImGui : : ScrollToBringRectIntoView ( item . Window , item . RectFull ) ;
2024-07-04 00:55:17 +00:00
while ( g . NavMoveSubmitted )
Yield ( ) ;
Yield ( ) ;
if ( ! Abort )
2024-09-08 23:52:23 +00:00
if ( g . NavId ! = item . ID )
2024-07-04 00:55:17 +00:00
IM_ERRORF_NOHDR ( " Unable to set NavId to %s " , desc . c_str ( ) ) ;
}
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
2024-09-08 23:52:23 +00:00
static ImVec2 GetMouseAimingPos ( const ImGuiTestItemInfo & item , ImGuiTestOpFlags flags )
2024-07-04 00:55:17 +00:00
{
2024-09-08 23:52:23 +00:00
ImRect r = item . RectClipped ;
2024-07-04 00:55:17 +00:00
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 ;
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo item ;
2024-07-04 00:55:17 +00:00
if ( flags & ImGuiTestOpFlags_NoAutoOpenFullPath )
item = ItemInfo ( ref ) ;
else
item = ItemInfoOpenFullPath ( ref ) ;
ImGuiTestRefDesc desc ( ref , item ) ;
LogDebug ( " MouseMove to %s " , desc . c_str ( ) ) ;
2024-09-08 23:52:23 +00:00
if ( item . ID = = 0 )
2024-07-04 00:55:17 +00:00
return ;
2024-09-08 23:52:23 +00:00
ImGuiWindow * window = item . Window ;
if ( ! window - > WasActive )
2024-07-04 00:55:17 +00:00
{
2024-09-08 23:52:23 +00:00
LogError ( " Window '%s' is not active! " , window - > Name ) ;
2024-07-04 00:55:17 +00:00
return ;
}
// 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
{
ImRect window_r = window - > InnerClipRect ;
window_r . Expand ( ImVec2 ( - g . WindowsHoverPadding . x , - g . WindowsHoverPadding . y ) ) ;
ImRect item_r_clipped ;
2024-09-08 23:52:23 +00:00
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 ) ;
2024-07-04 00:55:17 +00:00
// 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.
2024-09-08 23:52:23 +00:00
float visibility_ratio_x = ( item_r_clipped . GetWidth ( ) + 1.0f ) / ( item . RectFull . GetWidth ( ) + 1.0f ) ;
float visibility_ratio_y = ( item_r_clipped . GetHeight ( ) + 1.0f ) / ( item . RectFull . GetHeight ( ) + 1.0f ) ;
if ( item . NavLayer = = ImGuiNavLayer_Main )
{
if ( visibility_ratio_x < 0.70f )
ScrollToItem ( ref , ImGuiAxis_X , ImGuiTestOpFlags_NoFocusWindow ) ;
if ( visibility_ratio_y < 0.90f )
ScrollToItem ( ref , ImGuiAxis_Y , ImGuiTestOpFlags_NoFocusWindow ) ;
}
// FIXME: Scroll parent window
2024-07-04 00:55:17 +00:00
}
2024-09-08 23:52:23 +00:00
// Menu layer is not scrollable: attempt to resize window.
if ( item . NavLayer = = ImGuiNavLayer_Menu )
2024-07-04 00:55:17 +00:00
{
2024-09-08 23:52:23 +00:00
// 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 )
2024-07-04 00:55:17 +00:00
{
2024-09-08 23:52:23 +00:00
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 )
2024-07-04 00:55:17 +00:00
{
2024-09-08 23:52:23 +00:00
LogDebug ( " Will attempt to resize window to make item in menu layer visible. " ) ;
WindowResize ( window - > ID , window - > Size + ImVec2 ( extra_width_desired , 0.0f ) ) ;
2024-07-04 00:55:17 +00:00
}
}
}
2024-09-08 23:52:23 +00:00
// Update item
item = ItemInfo ( item . ID ) ;
2024-07-04 00:55:17 +00:00
// FIXME-TESTS-NOT_SAME_AS_END_USER
2024-09-08 23:52:23 +00:00
ImVec2 pos = item . RectFull . GetCenter ( ) ;
if ( WindowTeleportToMakePosVisible ( window - > ID , pos ) )
item = ItemInfo ( item . ID ) ;
2024-07-04 00:55:17 +00:00
2024-09-08 23:52:23 +00:00
// Keep a copy of item info
const ImGuiTestItemInfo item_initial_state = item ;
2024-07-04 00:55:17 +00:00
// Target point
pos = GetMouseAimingPos ( item , flags ) ;
// Focus window
2024-09-08 23:52:23 +00:00
if ( ! ( flags & ImGuiTestOpFlags_NoFocusWindow ) & & item . Window ! = NULL )
2024-07-04 00:55:17 +00:00
{
// 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.
2024-09-08 23:52:23 +00:00
bool is_covered = FindHoveredWindowAtPos ( pos ) ! = item . Window ;
# if IMGUI_VERSION_NUM >= 18944
bool is_inhibited = ImGui : : IsWindowContentHoverable ( item . Window ) = = false ;
# else
bool is_inhibited = false ;
# endif
2024-07-04 00:55:17 +00:00
// 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 )
2024-09-08 23:52:23 +00:00
WindowBringToFront ( item . Window - > ID ) ;
2024-07-04 00:55:17 +00:00
}
// Another is window active test (in the case focus change has a side effect but also as we have yield an extra frame)
2024-09-08 23:52:23 +00:00
if ( ! item . Window - > WasActive )
2024-07-04 00:55:17 +00:00
{
2024-09-08 23:52:23 +00:00
LogError ( " Window '%s' is not active (after aiming) " , item . Window - > Name ) ;
2024-07-04 00:55:17 +00:00
return ;
}
2024-09-08 23:52:23 +00:00
MouseSetViewport ( item . Window ) ;
2024-07-04 00:55:17 +00:00
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
2024-09-08 23:52:23 +00:00
bool is_covered = FindHoveredWindowAtPos ( pos ) ! = item . Window ;
# if IMGUI_VERSION_NUM >= 18944
bool is_inhibited = ImGui : : IsWindowContentHoverable ( item . Window ) = = false ;
# else
bool is_inhibited = false ;
# endif
2024-07-04 00:55:17 +00:00
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 ;
2024-09-08 23:52:23 +00:00
is_hovered_item = ( hovered_id = = item . ID ) ;
2024-07-04 00:55:17 +00:00
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 )
2024-09-08 23:52:23 +00:00
if ( hovered_window - > ID = = item . ID & & hovered_window = = item . Window )
2024-07-04 00:55:17 +00:00
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 ) ;
return ;
}
}
2024-09-08 23:52:23 +00:00
// Update item
item = ItemInfo ( item . ID ) ;
// Log message
2024-07-04 00:55:17 +00:00
ImVec2 pos_old = item_initial_state . RectFull . Min ;
2024-09-08 23:52:23 +00:00
ImVec2 pos_new = item . RectFull . Min ;
2024-07-04 00:55:17 +00:00
ImVec2 size_old = item_initial_state . RectFull . GetSize ( ) ;
2024-09-08 23:52:23 +00:00
ImVec2 size_new = item . RectFull . GetSize ( ) ;
2024-07-04 00:55:17 +00:00
Str256f error_message (
" Unable to Hover %s: \n "
2024-09-08 23:52:23 +00:00
" - Expected item 0x%08X in window '%s', targeted position: (%.1f,%.1f)' \n "
" - Hovered id was 0x%08X in '%s'. \n "
" - Before mouse move: Item Pos (%6.1f,%6.1f) Size (%6.1f,%6.1f) \n "
" - After mouse move: Item Pos (%6.1f,%6.1f) Size (%6.1f,%6.1f) " ,
2024-07-04 00:55:17 +00:00
desc . c_str ( ) ,
2024-09-08 23:52:23 +00:00
item . ID , item . Window ? item . Window - > Name : " <NULL> " , pos . x , pos . y ,
2024-07-04 00:55:17 +00:00
hovered_id , g . HoveredWindow ? g . HoveredWindow - > Name : " " ,
2024-09-08 23:52:23 +00:00
pos_old . x , pos_old . y , size_old . x , size_old . y ,
pos_new . x , pos_new . y , size_new . x , size_new . y ) ;
2024-07-04 00:55:17 +00:00
IM_ERRORF_NOHDR ( " %s " , error_message . c_str ( ) ) ;
}
}
}
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 ) ;
2024-09-08 23:52:23 +00:00
LogDebug ( " WindowTeleportToMakePosVisible '%s' delta (%.1f,%.1f) " , window - > Name , delta . x , delta . y ) ;
2024-07-04 00:55:17 +00:00
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.
2024-09-08 23:52:23 +00:00
// Supported values for ImGuiTestOpFlags:
// - ImGuiTestOpFlags_NoYield
void ImGuiTestContext : : MouseTeleportToPos ( ImVec2 target , ImGuiTestOpFlags flags )
2024-07-04 00:55:17 +00:00
{
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 ;
2024-09-08 23:52:23 +00:00
if ( ( flags & ImGuiTestOpFlags_NoYield ) = = 0 )
{
ImGuiTestEngine_Yield ( Engine ) ;
ImGuiTestEngine_Yield ( Engine ) ;
}
2024-07-04 00:55:17 +00:00
}
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 ) ;
}
ImGuiWindow * ImGuiTestContext : : FindHoveredWindowAtPos ( const ImVec2 & pos )
{
2024-09-08 23:52:23 +00:00
# if IMGUI_VERSION_NUM >= 19062
ImGuiWindow * hovered_window = NULL ;
ImGui : : FindHoveredWindowEx ( pos , true , & hovered_window , NULL ) ;
return hovered_window ;
# else
// Modeled on FindHoveredWindow() in imgui.cpp.
// Ideally that core function would be refactored to avoid this copy.
// - Need to take account of MovingWindow specificities and early out.
// - Need to be able to skip viewport compare.
// So for now we use a custom function.
2024-07-04 00:55:17 +00:00
ImGuiContext & g = * UiContext ;
2024-09-08 23:52:23 +00:00
ImVec2 padding_regular = g . Style . TouchExtraPadding ;
ImVec2 padding_for_resize = g . IO . ConfigWindowsResizeFromEdges ? g . WindowsHoverPadding : padding_regular ;
2024-07-04 00:55:17 +00:00
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)
2024-09-08 23:52:23 +00:00
ImVec2 hit_padding = ( window - > Flags & ( ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize ) ) ? padding_regular : padding_for_resize ;
ImRect r = window - > OuterRectClipped ;
r . Expand ( hit_padding ) ;
if ( ! r . Contains ( pos ) )
2024-07-04 00:55:17 +00:00
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 ;
2024-09-08 23:52:23 +00:00
# endif
2024-07-04 00:55:17 +00:00
}
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
{
2024-09-08 23:52:23 +00:00
# if IMGUI_VERSION_NUM >= 19071
const float h = window - > TitleBarHeight ;
# else
2024-07-04 00:55:17 +00:00
const float h = window - > TitleBarHeight ( ) ;
2024-09-08 23:52:23 +00:00
# endif
2024-07-04 00:55:17 +00:00
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 ) ;
2024-09-08 23:52:23 +00:00
# if IMGUI_VERSION_NUM < 19063
2024-07-04 00:55:17 +00:00
KeyPress ( ImGuiKey_A | ImGuiMod_Shortcut ) ;
2024-09-08 23:52:23 +00:00
# else
KeyPress ( ImGuiKey_A | ImGuiMod_Ctrl ) ;
# endif
2024-07-04 00:55:17 +00:00
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 ) ;
2024-09-08 23:52:23 +00:00
# if IMGUI_VERSION_NUM < 19063
2024-07-04 00:55:17 +00:00
KeyPress ( ImGuiKey_A | ImGuiMod_Shortcut ) ;
2024-09-08 23:52:23 +00:00
# else
KeyPress ( ImGuiKey_A | ImGuiMod_Ctrl ) ;
# endif
2024-07-04 00:55:17 +00:00
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
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo parent_item = ItemInfo ( parent , ImGuiTestOpFlags_NoError ) ;
2024-07-04 00:55:17 +00:00
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
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo item ;
2024-07-04 00:55:17 +00:00
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) " ) ;
2024-09-08 23:52:23 +00:00
if ( item . ID = = 0 )
2024-07-04 00:55:17 +00:00
{
if ( flags & ImGuiTestOpFlags_NoError )
LogDebug ( " Action skipped: Item doesn't exist + used ImGuiTestOpFlags_NoError. " ) ;
return ;
}
// Automatically uncollapse by default
2024-09-08 23:52:23 +00:00
if ( item . Window & & ! ( OpFlags & ImGuiTestOpFlags_NoAutoUncollapse ) )
WindowCollapse ( item . Window - > ID , false ) ;
2024-07-04 00:55:17 +00:00
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
2024-09-08 23:52:23 +00:00
if ( ( item . StatusFlags & ImGuiItemStatusFlags_Opened ) = = 0 )
2024-07-04 00:55:17 +00:00
{
MouseMove ( ref , flags ) ;
// Some item may open just by hovering, give them that chance
2024-09-08 23:52:23 +00:00
item = ItemInfo ( item . ID ) ;
if ( ( item . StatusFlags & ImGuiItemStatusFlags_Opened ) = = 0 )
2024-07-04 00:55:17 +00:00
{
MouseClick ( 0 ) ;
2024-09-08 23:52:23 +00:00
item = ItemInfo ( item . ID ) ;
if ( ( item . StatusFlags & ImGuiItemStatusFlags_Opened ) = = 0 )
2024-07-04 00:55:17 +00:00
{
MouseDoubleClick ( 0 ) ; // Attempt a double-click // FIXME-TESTS: let's not start doing those fuzzy things..
2024-09-08 23:52:23 +00:00
item = ItemInfo ( item . ID ) ;
if ( ( item . StatusFlags & ImGuiItemStatusFlags_Opened ) = = 0 )
IM_ERRORF_NOHDR ( " Unable to Open item: '%s' in '%s' " , desc . c_str ( ) , item . Window ? item . Window - > Name : " N/A " ) ;
2024-07-04 00:55:17 +00:00
}
}
//Yield();
}
}
else if ( action = = ImGuiTestAction_Close )
{
IM_ASSERT ( action_arg = = NULL ) ; // Unused
2024-09-08 23:52:23 +00:00
if ( ( item . StatusFlags & ImGuiItemStatusFlags_Opened ) ! = 0 )
2024-07-04 00:55:17 +00:00
{
ItemClick ( ref , 0 , flags ) ;
2024-09-08 23:52:23 +00:00
item = ItemInfo ( item . ID ) ;
if ( ( item . StatusFlags & ImGuiItemStatusFlags_Opened ) ! = 0 )
2024-07-04 00:55:17 +00:00
{
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?
2024-09-08 23:52:23 +00:00
item = ItemInfo ( item . ID ) ;
if ( ( item . StatusFlags & ImGuiItemStatusFlags_Opened ) ! = 0 )
2024-07-04 00:55:17 +00:00
IM_ERRORF_NOHDR ( " Unable to Close item: %s " , ImGuiTestRefDesc ( ref , item ) . c_str ( ) ) ;
}
Yield ( ) ;
}
}
else if ( action = = ImGuiTestAction_Check )
{
IM_ASSERT ( action_arg = = NULL ) ; // Unused
2024-09-08 23:52:23 +00:00
if ( ( item . StatusFlags & ImGuiItemStatusFlags_Checkable ) & & ! ( item . StatusFlags & ImGuiItemStatusFlags_Checked ) )
2024-07-04 00:55:17 +00:00
{
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
2024-09-08 23:52:23 +00:00
if ( ( item . StatusFlags & ImGuiItemStatusFlags_Checkable ) & & ( item . StatusFlags & ImGuiItemStatusFlags_Checked ) )
2024-07-04 00:55:17 +00:00
{
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
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo parent_info = ItemInfoOpenFullPath ( ref_parent ) ;
if ( parent_info . ID ! = 0 )
2024-07-04 00:55:17 +00:00
{
// Open parent
if ( action = = ImGuiTestAction_Open )
2024-09-08 23:52:23 +00:00
if ( ( parent_info . StatusFlags & ImGuiItemStatusFlags_Openable ) & & ( parent_info . InFlags & ImGuiItemFlags_Disabled ) = = 0 )
2024-07-04 00:55:17 +00:00
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 ) ;
}
2024-09-08 23:52:23 +00:00
2024-07-04 00:55:17 +00:00
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 ) ;
}
2024-09-08 23:52:23 +00:00
// Supported values for ImGuiTestOpFlags:
// - ImGuiTestOpFlags_NoError
bool ImGuiTestContext : : ItemSelectAndReadValue ( ImGuiTestRef ref , ImGuiDataType data_type , void * out_data , ImGuiTestOpFlags flags )
{
if ( IsError ( ) )
return false ;
const ImGuiDataTypeInfo * data_type_info = ImGui : : DataTypeGetInfo ( data_type ) ;
const ImGuiTestOpFlags SUPPORTED_FLAGS = ImGuiTestOpFlags_NoError ;
IM_ASSERT ( ( flags & ~ SUPPORTED_FLAGS ) = = 0 ) ;
IMGUI_TEST_CONTEXT_REGISTER_DEPTH ( this ) ;
LogDebug ( " ItemSelectReadValue '%s' 0x%08X as %s " , ref . Path ? ref . Path : " NULL " , ref . ID , data_type_info - > Name ) ;
IM_CHECK_SILENT_RETV ( out_data ! = NULL , false ) ;
Str256 backup_clipboard = ImGui : : GetClipboardText ( ) ;
ItemInput ( ref , flags ) ;
# if IMGUI_VERSION_NUM < 19063
KeyPress ( ImGuiKey_A | ImGuiMod_Shortcut ) ;
KeyPress ( ImGuiKey_C | ImGuiMod_Shortcut ) ; // Copy to clipboard
# else
KeyPress ( ImGuiKey_A | ImGuiMod_Ctrl ) ;
KeyPress ( ImGuiKey_C | ImGuiMod_Ctrl ) ; // Copy to clipboard
# endif
KeyPress ( ImGuiKey_Enter ) ;
const char * clipboard = ImGui : : GetClipboardText ( ) ;
bool ret = ImGui : : DataTypeApplyFromText ( clipboard , data_type , out_data , data_type_info - > ScanFmt ) ;
if ( ret = = false )
{
if ( ( flags & ImGuiTestOpFlags_NoError ) = = 0 )
{
LogError ( " Unable to parse buffer '%s' as %s " , clipboard , data_type_info - > Name ) ;
IM_CHECK_RETV ( ret , false ) ;
}
}
ImGui : : SetClipboardText ( backup_clipboard . c_str ( ) ) ;
return ret ;
}
void ImGuiTestContext : : ItemSelectAndReadValue ( ImGuiTestRef ref , int * out_v )
{
ItemSelectAndReadValue ( ref , ImGuiDataType_S32 , ( void * ) out_v ) ;
}
void ImGuiTestContext : : ItemSelectAndReadValue ( ImGuiTestRef ref , float * out_v )
{
ItemSelectAndReadValue ( ref , ImGuiDataType_Float , ( void * ) out_v ) ;
}
2024-07-04 00:55:17 +00:00
void ImGuiTestContext : : ItemHold ( ImGuiTestRef ref , float time )
{
if ( IsError ( ) )
return ;
IMGUI_TEST_CONTEXT_REGISTER_DEPTH ( this ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestRefDesc desc ( ref ) ;
LogDebug ( " ItemHold %s " , desc . c_str ( ) ) ;
2024-07-04 00:55:17 +00:00
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 ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestRefDesc desc ( ref ) ;
LogDebug ( " ItemHoldForFrames %s " , desc . c_str ( ) ) ;
2024-07-04 00:55:17 +00:00
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 ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo item_src = ItemInfo ( ref_src ) ;
ImGuiTestItemInfo item_dst = ItemInfo ( ref_dst ) ;
2024-07-04 00:55:17 +00:00
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.
2024-09-08 23:52:23 +00:00
// Don't lift the threshold in the same frame as calling MouseDown() as it can trigger two actions.
Yield ( ) ;
2024-07-04 00:55:17 +00:00
MouseLiftDragThreshold ( ) ;
MouseMove ( ref_dst , ImGuiTestOpFlags_NoCheckHoveredId ) ;
2024-09-08 23:52:23 +00:00
2024-07-04 00:55:17 +00:00
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 ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo item_src = ItemInfo ( ref_src ) ;
ImGuiTestItemInfo item_dst = ItemInfo ( ref_dst ) ;
2024-07-04 00:55:17 +00:00
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.
2024-09-08 23:52:23 +00:00
if ( item_dst . Window ! = NULL )
WindowBringToFront ( item_dst . Window - > ID ) ;
2024-07-04 00:55:17 +00:00
// Use item_src/item_dst instead of ref_src/ref_dst so references with e.g. //$FOCUSED are latched once in the ItemInfo() call.
2024-09-08 23:52:23 +00:00
MouseMove ( item_src . ID , ImGuiTestOpFlags_NoCheckHoveredId ) ;
2024-07-04 00:55:17 +00:00
SleepStandard ( ) ;
MouseDown ( button ) ;
// Enforce lifting drag threshold even if both item are exactly at the same location.
2024-09-08 23:52:23 +00:00
// Don't lift the threshold in the same frame as calling MouseDown() as it can trigger two actions.
Yield ( ) ;
2024-07-04 00:55:17 +00:00
MouseLiftDragThreshold ( ) ;
2024-09-08 23:52:23 +00:00
MouseMove ( item_dst . ID , ImGuiTestOpFlags_NoCheckHoveredId | ImGuiTestOpFlags_NoFocusWindow ) ;
2024-07-04 00:55:17 +00:00
SleepStandard ( ) ;
MouseUp ( button ) ;
}
void ImGuiTestContext : : ItemDragWithDelta ( ImGuiTestRef ref_src , ImVec2 pos_delta )
{
if ( IsError ( ) )
return ;
IMGUI_TEST_CONTEXT_REGISTER_DEPTH ( this ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo item_src = ItemInfo ( ref_src ) ;
2024-07-04 00:55:17 +00:00
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 )
{
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo item = ItemInfo ( ref , ImGuiTestOpFlags_NoError ) ;
return item . ID ! = 0 ;
2024-07-04 00:55:17 +00:00
}
// May want to add support for ImGuiTestOpFlags_NoError if item does not exist?
bool ImGuiTestContext : : ItemIsChecked ( ImGuiTestRef ref )
{
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo item = ItemInfo ( ref ) ;
return ( item . StatusFlags & ImGuiItemStatusFlags_Checked ) ! = 0 ;
2024-07-04 00:55:17 +00:00
}
// May want to add support for ImGuiTestOpFlags_NoError if item does not exist?
bool ImGuiTestContext : : ItemIsOpened ( ImGuiTestRef ref )
{
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo item = ItemInfo ( ref ) ;
return ( item . StatusFlags & ImGuiItemStatusFlags_Opened ) ! = 0 ;
2024-07-04 00:55:17 +00:00
}
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 ( ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo item = ItemInfo ( ref , ImGuiTestOpFlags_NoError ) ;
if ( item . ID = = 0 )
2024-07-04 00:55:17 +00:00
return ;
2024-09-08 23:52:23 +00:00
if ( item . TimestampMain + 1 > = ImGuiTestEngine_GetFrameCount ( Engine ) & & item . TimestampStatus = = item . TimestampMain )
IM_CHECK_SILENT ( ( ( item . StatusFlags & ImGuiItemStatusFlags_Checked ) ! = 0 ) = = checked ) ;
2024-07-04 00:55:17 +00:00
}
// FIXME-TESTS: Could this be handled by ItemClose()?
void ImGuiTestContext : : TabClose ( ImGuiTestRef ref )
{
if ( IsError ( ) )
return ;
IMGUI_TEST_CONTEXT_REGISTER_DEPTH ( this ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestRefDesc desc ( ref ) ;
LogDebug ( " TabClose %s " , desc . c_str ( ) ) ;
2024-07-04 00:55:17 +00:00
// 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 ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestRefDesc desc ( ref ) ;
LogDebug ( " MenuAction %s " , desc . c_str ( ) ) ;
2024-07-04 00:55:17 +00:00
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
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo item = ItemInfo ( buf . c_str ( ) ) ;
IM_CHECK_SILENT ( item . ID ! = 0 ) ;
bool has_latest_status = ( item . TimestampStatus = = UiContext - > FrameCount ) ;
if ( ( item . StatusFlags & ImGuiItemStatusFlags_Opened ) = = 0 | | ! has_latest_status ) // Open menus can be ignored completely.
2024-07-04 00:55:17 +00:00
{
// 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 )
{
2024-09-08 23:52:23 +00:00
MouseSetViewport ( item . Window ) ;
if ( Inputs - > MousePosValue . x < = item . RectFull . Min . x | | Inputs - > MousePosValue . x > = item . RectFull . Max . x )
MouseMoveToPos ( ImVec2 ( item . RectFull . GetCenter ( ) . x , Inputs - > MousePosValue . y ) ) ;
if ( Inputs - > MousePosValue . y < = item . RectFull . Min . y | | Inputs - > MousePosValue . y > = item . RectFull . Max . y )
MouseMoveToPos ( ImVec2 ( Inputs - > MousePosValue . x , item . RectFull . GetCenter ( ) . y ) ) ;
2024-07-04 00:55:17 +00:00
}
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 ( ) ) ;
}
}
2024-09-08 23:52:23 +00:00
current_window = GetWindowByRef ( Str16f ( " //##Menu_%02d " , depth ) . c_str ( ) ) ;
2024-07-04 00:55:17 +00:00
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 )
{
2024-09-08 23:52:23 +00:00
ImGuiTestItemInfo info2 = ItemInfo ( item . ID ) ; // refresh info
if ( ( info2 . InFlags & ImGuiItemFlags_Disabled ) ! = 0 ) // FIXME: Report disabled state in log? Make that optional?
2024-07-04 00:55:17 +00:00
continue ;
2024-09-08 23:52:23 +00:00
if ( ( info2 . StatusFlags & ImGuiItemStatusFlags_Checkable ) = = 0 )
2024-07-04 00:55:17 +00:00
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 ;
}
2024-09-08 23:52:23 +00:00
// Usage: ComboClick("ComboName/ItemName");
2024-07-04 00:55:17 +00:00
void ImGuiTestContext : : ComboClick ( ImGuiTestRef ref )
{
if ( IsError ( ) )
return ;
IMGUI_TEST_CONTEXT_REGISTER_DEPTH ( this ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestRefDesc desc ( ref ) ;
LogDebug ( " ComboClick %s " , desc . c_str ( ) ) ;
2024-07-04 00:55:17 +00:00
2024-09-08 23:52:23 +00:00
IM_ASSERT ( ref . Path ! = NULL ) ; // Should always pass an actual path, not an ID.
2024-07-04 00:55:17 +00:00
const char * path = ref . Path ;
const char * path_end = path + strlen ( path ) ;
const char * p = ImStrchrRangeWithEscaping ( path , path_end , ' / ' ) ;
2024-09-08 23:52:23 +00:00
if ( p = = NULL )
{
LogError ( " Error: path should contains a / separator, e.g. ComboClick( \" mycombo/myitem \" ) " ) ;
IM_CHECK ( p ! = NULL ) ;
}
2024-07-04 00:55:17 +00:00
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 ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestRefDesc desc ( ref ) ;
LogDebug ( " TableOpenContextMenu %s " , desc . c_str ( ) ) ;
2024-07-04 00:55:17 +00:00
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 ) ;
2024-09-08 23:52:23 +00:00
return ( ImGuiSortDirection ) column - > SortDirection ;
2024-07-04 00:55:17 +00:00
}
void ImGuiTestContext : : TableSetColumnEnabled ( ImGuiTestRef ref , const char * label , bool enabled )
{
if ( IsError ( ) )
return ;
IMGUI_TEST_CONTEXT_REGISTER_DEPTH ( this ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestRefDesc desc ( ref ) ;
LogDebug ( " TableSetColumnEnabled %s label '%s' enabled = %d " , desc . c_str ( ) , label , enabled ) ;
2024-07-04 00:55:17 +00:00
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 ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestRefDesc desc ( ref ) ;
LogDebug ( " TableResizeColumn %s column %d width %.2f " , desc . c_str ( ) , column_n , width ) ;
2024-07-04 00:55:17 +00:00
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 ) ;
2024-09-08 23:52:23 +00:00
ImGuiTestRefDesc desc ( ref ) ;
2024-07-04 00:55:17 +00:00
LogDebug ( " WindowFocus('%s') " , desc . c_str ( ) ) ;
ImGuiWindow * window = GetWindowByRef ( ref ) ;
IM_CHECK_SILENT ( window ! = NULL ) ;
if ( window )
{
ImGui : : FocusWindow ( window ) ; // FIXME-TESTS-NOT_SAME_AS_END_USER: In theory should be replaced by click on title-bar or tab?
Yield ( ) ;
}
// We cannot guarantee this will work 100%
// - Some modal inhibition may kick-in.
// - Because merely hovering an item may e.g. open a window or change focus.
// In particular this can be the case with MenuItem. So trying to Open a MenuItem may lead to its child opening while hovering,
// causing this function to seemingly fail (even if the end goal was reached).
ImGuiContext & g = * UiContext ;
if ( ( window ! = g . NavWindow ) & & ! ( flags & ImGuiTestOpFlags_NoError ) )
LogDebug ( " -- Expected focused window '%s', but '%s' got focus back. " , window - > Name , g . NavWindow ? g . NavWindow - > Name : " <NULL> " ) ;
}
// Supported values for ImGuiTestOpFlags:
// - ImGuiTestOpFlags_NoError
// - ImGuiTestOpFlags_NoFocusWindow
// FIXME: In principle most calls to this could be replaced by WindowFocus()?
void ImGuiTestContext : : WindowBringToFront ( ImGuiTestRef ref , ImGuiTestOpFlags flags )
{
ImGuiContext & g = * UiContext ;
if ( IsError ( ) )
return ;
ImGuiWindow * window = GetWindowByRef ( ref ) ;
IM_CHECK_SILENT ( window ! = NULL ) ;
IMGUI_TEST_CONTEXT_REGISTER_DEPTH ( this ) ;
if ( window ! = g . NavWindow & & ! ( flags & ImGuiTestOpFlags_NoFocusWindow ) )
{
LogDebug ( " WindowBringToFront()->FocusWindow('%s') " , window - > Name ) ;
ImGui : : FocusWindow ( window ) ; // FIXME-TESTS-NOT_SAME_AS_END_USER: In theory should be replaced by click on title-bar or tab?
Yield ( 2 ) ;
}
else if ( window - > RootWindow ! = g . Windows . back ( ) - > RootWindow )
{
LogDebug ( " BringWindowToDisplayFront('%s') (window.back=%s) " , window - > Name , g . Windows . back ( ) - > Name ) ;
ImGui : : BringWindowToDisplayFront ( window ) ; // FIXME-TESTS-NOT_SAME_AS_END_USER: This is not an actually possible action for end-user.
Yield ( 2 ) ;
}
// Same as WindowFocus()
if ( ( window ! = g . NavWindow ) & & ! ( flags & ImGuiTestOpFlags_NoError ) )
LogDebug ( " -- Expected focused window '%s', but '%s' got focus back. " , window - > Name , g . NavWindow ? g . NavWindow - > Name : " <NULL> " ) ;
}
// Supported values for ImGuiTestOpFlags:
// - ImGuiTestOpFlags_NoFocusWindow
void ImGuiTestContext : : WindowMove ( ImGuiTestRef ref , ImVec2 input_pos , ImVec2 pivot , ImGuiTestOpFlags flags )
{
if ( IsError ( ) )
return ;
ImGuiWindow * window = GetWindowByRef ( ref ) ;
IM_CHECK_SILENT ( window ! = NULL ) ;
IMGUI_TEST_CONTEXT_REGISTER_DEPTH ( this ) ;
2024-09-08 23:52:23 +00:00
LogDebug ( " WindowMove '%s' (%.1f,%.1f) " , window - > Name , input_pos . x , input_pos . y ) ;
2024-07-04 00:55:17 +00:00
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 ) ;
2024-09-08 23:52:23 +00:00
LogDebug ( " WindowResize '%s' (%.1f,%.1f) " , window - > Name , size . x , size . y ) ;
2024-07-04 00:55:17 +00:00
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 ( ) ;
2024-09-08 23:52:23 +00:00
// Aim at resize border or resize corner
ImGuiID border_x2 = ImGui : : GetWindowResizeBorderID ( window , ImGuiDir_Right ) ;
ImGuiID border_y2 = ImGui : : GetWindowResizeBorderID ( window , ImGuiDir_Down ) ;
ImGuiID resize_br = ImGui : : GetWindowResizeCornerID ( window , 0 ) ;
ImGuiID id ;
if ( ImAbs ( size . x - window - > Size . x ) < 0.0001f & & ItemExists ( border_y2 ) )
id = border_y2 ;
else if ( ImAbs ( size . y - window - > Size . y ) < 0.0001f & & ItemExists ( border_x2 ) )
id = border_x2 ;
else
id = resize_br ;
2024-07-04 00:55:17 +00:00
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 ) ;
2024-09-08 23:52:23 +00:00
Yield ( ) ;
2024-07-04 00:55:17 +00:00
MouseLiftDragThreshold ( ) ;
if ( window_src - > DockIsActive )
MouseMoveToPos ( g . IO . MousePos + ImVec2 ( 0 , ImGui : : GetFrameHeight ( ) * 2.0f ) ) ;
2024-09-08 23:52:23 +00:00
else
Yield ( ) ; // A yield is necessary to start the moving, otherwise if by the time we call MouseSetViewport() no frame has elapsed and the viewports differs, dragging will fail.
2024-07-04 00:55:17 +00:00
// (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 ;
2024-09-08 23:52:23 +00:00
# if IMGUI_VERSION_NUM >= 19071
const float h = node - > Windows [ 0 ] - > TitleBarHeight ;
# else
2024-07-04 00:55:17 +00:00
const float h = node - > Windows [ 0 ] - > TitleBarHeight ( ) ;
2024-09-08 23:52:23 +00:00
# endif
2024-07-04 00:55:17 +00:00
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 ;
2024-09-08 23:52:23 +00:00
# if IMGUI_VERSION_NUM >= 19071
const float h = window - > TitleBarHeight ;
# else
2024-07-04 00:55:17 +00:00
const float h = window - > TitleBarHeight ( ) ;
2024-09-08 23:52:23 +00:00
# endif
2024-07-04 00:55:17 +00:00
if ( ! UiContext - > IO . ConfigDockingWithShift )
KeyDown ( ImGuiMod_Shift ) ;
ItemDragWithDelta ( window - > TabId , ImVec2 ( h , h ) * - 2 ) ;
if ( ! UiContext - > IO . ConfigDockingWithShift )
KeyUp ( ImGuiMod_Shift ) ;
Yield ( ) ;
}
# endif // #ifdef IMGUI_HAS_DOCK
//-------------------------------------------------------------------------
// ImGuiTestContext - Performance Tools
//-------------------------------------------------------------------------
// Calculate the reference DeltaTime, averaged over PerfIterations/500 frames, with GuiFunc disabled.
void ImGuiTestContext : : PerfCalcRef ( )
{
LogDebug ( " Measuring ref dt... " ) ;
RunFlags | = ImGuiTestRunFlags_GuiFuncDisable ;
ImMovingAverage < double > delta_times ;
delta_times . Init ( PerfIterations ) ;
for ( int n = 0 ; n < PerfIterations & & ! Abort ; n + + )
{
Yield ( ) ;
delta_times . AddSample ( UiContext - > IO . DeltaTime ) ;
}
PerfRefDt = delta_times . GetAverage ( ) ;
RunFlags & = ~ ImGuiTestRunFlags_GuiFuncDisable ;
}
void ImGuiTestContext : : PerfCapture ( const char * category , const char * test_name , const char * csv_file )
{
if ( IsError ( ) )
return ;
// Calculate reference average DeltaTime if it wasn't explicitly called by TestFunc
if ( PerfRefDt < 0.0 )
PerfCalcRef ( ) ;
IM_ASSERT ( PerfRefDt > = 0.0 ) ;
// Yield for the average to stabilize
LogDebug ( " Measuring GUI dt... " ) ;
ImMovingAverage < double > delta_times ;
delta_times . Init ( PerfIterations ) ;
for ( int n = 0 ; n < PerfIterations & & ! Abort ; n + + )
{
Yield ( ) ;
delta_times . AddSample ( UiContext - > IO . DeltaTime ) ;
}
if ( Abort )
return ;
double dt_curr = delta_times . GetAverage ( ) ;
double dt_ref_ms = PerfRefDt * 1000 ;
double dt_delta_ms = ( dt_curr - PerfRefDt ) * 1000 ;
const ImBuildInfo * build_info = ImBuildGetCompilationInfo ( ) ;
// Display results
// FIXME-TESTS: Would be nice if we could submit a custom marker (e.g. branch/feature name)
LogInfo ( " [PERF] Conditions: Stress x%d, %s, %s, %s, %s, %s " ,
PerfStressAmount , build_info - > Type , build_info - > Cpu , build_info - > OS , build_info - > Compiler , build_info - > Date ) ;
LogInfo ( " [PERF] Result: %+6.3f ms (from ref %+6.3f) " , dt_delta_ms , dt_ref_ms ) ;
ImGuiPerfToolEntry entry ;
entry . Timestamp = Engine - > BatchStartTime ;
entry . Category = category ? category : Test - > Category ;
entry . TestName = test_name ? test_name : Test - > Name ;
entry . DtDeltaMs = dt_delta_ms ;
entry . PerfStressAmount = PerfStressAmount ;
entry . GitBranchName = EngineIO - > GitBranchName ;
entry . BuildType = build_info - > Type ;
entry . Cpu = build_info - > Cpu ;
entry . OS = build_info - > OS ;
entry . Compiler = build_info - > Compiler ;
entry . Date = build_info - > Date ;
ImGuiTestEngine_PerfToolAppendToCSV ( Engine - > PerfTool , & entry , csv_file ) ;
// Disable the "Success" message
RunFlags | = ImGuiTestRunFlags_NoSuccessMsg ;
}
//-------------------------------------------------------------------------