diff --git a/.gitignore b/.gitignore index 606ebe3..1121e44 100644 --- a/.gitignore +++ b/.gitignore @@ -80,4 +80,5 @@ Plugins/*/Intermediate/* DerivedDataCache/* .vsconfig enc_temp_folder/* +*.user Release diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini index 68ba984..5a070a6 100644 --- a/Config/DefaultEngine.ini +++ b/Config/DefaultEngine.ini @@ -2,6 +2,7 @@ GameDefaultMap=/Game/Maps/L_Level1.L_Level1 EditorStartupMap=/Game/Maps/L_Level1.L_Level1 GlobalDefaultGameMode="/Script/CogSample.CogSampleGameMode" +GameInstanceClass=/Script/Engine.GameInstance [/Script/Engine.RendererSettings] r.ReflectionMethod=1 @@ -110,10 +111,11 @@ ManualIPAddress= +DefaultChannelResponses=(Channel=ECC_GameTraceChannel1,DefaultResponse=ECR_Ignore,bTraceType=False,bStaticObject=False,Name="CharacterMesh") +DefaultChannelResponses=(Channel=ECC_GameTraceChannel2,DefaultResponse=ECR_Ignore,bTraceType=False,bStaticObject=False,Name="Projectile") +DefaultChannelResponses=(Channel=ECC_GameTraceChannel3,DefaultResponse=ECR_Ignore,bTraceType=True,bStaticObject=False,Name="TraceCustom") -+EditProfiles=(Name="Pawn",CustomResponses=((Channel="Camera",Response=ECR_Ignore),(Channel="Projectile",Response=ECR_Ignore),(Channel="TraceCustom"))) -+EditProfiles=(Name="CharacterMesh",CustomResponses=((Channel="Camera",Response=ECR_Ignore),(Channel="Projectile",Response=ECR_Overlap),(Channel="TraceCustom",Response=ECR_Ignore),(Channel="CharacterMesh",Response=ECR_Overlap))) -+EditProfiles=(Name="BlockAll",CustomResponses=((Channel="Projectile"))) -+EditProfiles=(Name="OverlapAll",CustomResponses=((Channel="Projectile",Response=ECR_Overlap))) ++DefaultChannelResponses=(Channel=ECC_GameTraceChannel4,DefaultResponse=ECR_Ignore,bTraceType=True,bStaticObject=False,Name="TraceCogSelection") ++EditProfiles=(Name="Pawn",CustomResponses=((Channel="Camera",Response=ECR_Ignore),(Channel="Projectile",Response=ECR_Ignore),(Channel="TraceCustom"),(Channel="TraceCogSelection"))) ++EditProfiles=(Name="CharacterMesh",CustomResponses=((Channel="Camera",Response=ECR_Ignore),(Channel="Projectile",Response=ECR_Overlap),(Channel="TraceCustom",Response=ECR_Ignore),(Channel="CharacterMesh",Response=ECR_Overlap),(Channel="TraceCogSelection"))) ++EditProfiles=(Name="BlockAll",CustomResponses=((Channel="Projectile"),(Channel="TraceCogSelection"))) ++EditProfiles=(Name="OverlapAll",CustomResponses=((Channel="Projectile",Response=ECR_Overlap),(Channel="TraceCogSelection"))) +EditProfiles=(Name="BlockAllDynamic",CustomResponses=((Channel="Projectile"))) +EditProfiles=(Name="OverlapAllDynamic",CustomResponses=((Channel="Projectile",Response=ECR_Overlap))) +EditProfiles=(Name="IgnoreOnlyPawn",CustomResponses=((Channel="Projectile"))) diff --git a/Config/DefaultInput.ini b/Config/DefaultInput.ini index 5387297..65517ec 100644 --- a/Config/DefaultInput.ini +++ b/Config/DefaultInput.ini @@ -69,6 +69,7 @@ bCaptureMouseOnLaunch=True bEnableLegacyInputScales=True bEnableMotionControls=True bFilterInputByPlatformUser=False +bEnableInputDeviceSubsystem=True bShouldFlushPressedKeysOnViewportFocusLost=True bEnableDynamicComponentInputBinding=True bAlwaysShowTouchInterface=False diff --git a/Content/Core/Debug/Cheats/BP_Cheat_TeleportToAim.uasset b/Content/Core/Debug/Cheats/BP_Cheat_TeleportToAim.uasset new file mode 100644 index 0000000..0dd551d Binary files /dev/null and b/Content/Core/Debug/Cheats/BP_Cheat_TeleportToAim.uasset differ diff --git a/Content/Core/Debug/Cheats/BP_Cheat_TeleportToPlayer.uasset b/Content/Core/Debug/Cheats/BP_Cheat_TeleportToPlayer.uasset new file mode 100644 index 0000000..0b31f73 Binary files /dev/null and b/Content/Core/Debug/Cheats/BP_Cheat_TeleportToPlayer.uasset differ diff --git a/Content/Core/Debug/Cheats/GA_Cheat_Teleport.uasset b/Content/Core/Debug/Cheats/GA_Cheat_Teleport.uasset new file mode 100644 index 0000000..d0ba82a Binary files /dev/null and b/Content/Core/Debug/Cheats/GA_Cheat_Teleport.uasset differ diff --git a/Content/Core/Debug/Cheats/GE_Cheat_Invisible.uasset b/Content/Core/Debug/Cheats/GE_Cheat_Invisible.uasset new file mode 100644 index 0000000..ea3eb68 Binary files /dev/null and b/Content/Core/Debug/Cheats/GE_Cheat_Invisible.uasset differ diff --git a/Content/Core/Debug/DA_Debug_Engine.uasset b/Content/Core/Debug/DA_Debug_Engine.uasset index cc7952c..83aee1b 100644 Binary files a/Content/Core/Debug/DA_Debug_Engine.uasset and b/Content/Core/Debug/DA_Debug_Engine.uasset differ diff --git a/Content/Core/Mannequins/Animations/ABP_Manny.uasset b/Content/Core/Mannequins/Animations/ABP_Manny.uasset index ac777f9..518cd5e 100644 Binary files a/Content/Core/Mannequins/Animations/ABP_Manny.uasset and b/Content/Core/Mannequins/Animations/ABP_Manny.uasset differ diff --git a/Content/Core/Mannequins/Meshes/SKM_Manny.uasset b/Content/Core/Mannequins/Meshes/SKM_Manny.uasset index 3bc3208..238f818 100644 Binary files a/Content/Core/Mannequins/Meshes/SKM_Manny.uasset and b/Content/Core/Mannequins/Meshes/SKM_Manny.uasset differ diff --git a/Content/Core/Mannequins/Meshes/SKM_Manny_Simple.uasset b/Content/Core/Mannequins/Meshes/SKM_Manny_Simple.uasset index b03545e..c74a4a6 100644 Binary files a/Content/Core/Mannequins/Meshes/SKM_Manny_Simple.uasset and b/Content/Core/Mannequins/Meshes/SKM_Manny_Simple.uasset differ diff --git a/Content/Core/Mannequins/Meshes/SKM_Quinn.uasset b/Content/Core/Mannequins/Meshes/SKM_Quinn.uasset index e799d90..7e0b39d 100644 Binary files a/Content/Core/Mannequins/Meshes/SKM_Quinn.uasset and b/Content/Core/Mannequins/Meshes/SKM_Quinn.uasset differ diff --git a/Content/Core/Mannequins/Meshes/SKM_Quinn_Simple.uasset b/Content/Core/Mannequins/Meshes/SKM_Quinn_Simple.uasset index 4b48dac..a8dce2c 100644 Binary files a/Content/Core/Mannequins/Meshes/SKM_Quinn_Simple.uasset and b/Content/Core/Mannequins/Meshes/SKM_Quinn_Simple.uasset differ diff --git a/Content/Core/Mannequins/Meshes/SK_Mannequin.uasset b/Content/Core/Mannequins/Meshes/SK_Mannequin.uasset index 029d67f..9dc37b4 100644 Binary files a/Content/Core/Mannequins/Meshes/SK_Mannequin.uasset and b/Content/Core/Mannequins/Meshes/SK_Mannequin.uasset differ diff --git a/Content/Core/Mannequins/Meshes/SK_Mannequin_AnimBlueprint.uasset b/Content/Core/Mannequins/Meshes/SK_Mannequin_AnimBlueprint.uasset new file mode 100644 index 0000000..961cd02 Binary files /dev/null and b/Content/Core/Mannequins/Meshes/SK_Mannequin_AnimBlueprint.uasset differ diff --git a/Content/Core/Mannequins/Rigs/CR_Mannequin_Body.uasset b/Content/Core/Mannequins/Rigs/CR_Mannequin_Body.uasset deleted file mode 100644 index b140ed3..0000000 Binary files a/Content/Core/Mannequins/Rigs/CR_Mannequin_Body.uasset and /dev/null differ diff --git a/Content/Maps/L_Empty.umap b/Content/Maps/L_Empty.umap new file mode 100644 index 0000000..282fbed Binary files /dev/null and b/Content/Maps/L_Empty.umap differ diff --git a/Content/__ExternalActors__/Maps/L_Empty/0/75/0OZ36QJ901HLSUZ9FVR2ZY.uasset b/Content/__ExternalActors__/Maps/L_Empty/0/75/0OZ36QJ901HLSUZ9FVR2ZY.uasset new file mode 100644 index 0000000..6d04798 Binary files /dev/null and b/Content/__ExternalActors__/Maps/L_Empty/0/75/0OZ36QJ901HLSUZ9FVR2ZY.uasset differ diff --git a/Content/__ExternalActors__/Maps/L_Empty/0/CP/3G15ZK1UHC1MI8DUUZOL8F.uasset b/Content/__ExternalActors__/Maps/L_Empty/0/CP/3G15ZK1UHC1MI8DUUZOL8F.uasset new file mode 100644 index 0000000..efa02d4 Binary files /dev/null and b/Content/__ExternalActors__/Maps/L_Empty/0/CP/3G15ZK1UHC1MI8DUUZOL8F.uasset differ diff --git a/Content/__ExternalActors__/Maps/L_Empty/0/UR/3Z51RKMVRXLMJJ3SZHFT4H.uasset b/Content/__ExternalActors__/Maps/L_Empty/0/UR/3Z51RKMVRXLMJJ3SZHFT4H.uasset new file mode 100644 index 0000000..8922dd1 Binary files /dev/null and b/Content/__ExternalActors__/Maps/L_Empty/0/UR/3Z51RKMVRXLMJJ3SZHFT4H.uasset differ diff --git a/Content/__ExternalActors__/Maps/L_Empty/2/LY/9KDMIUCKSOXQ6US2KZI74O.uasset b/Content/__ExternalActors__/Maps/L_Empty/2/LY/9KDMIUCKSOXQ6US2KZI74O.uasset new file mode 100644 index 0000000..a7d0286 Binary files /dev/null and b/Content/__ExternalActors__/Maps/L_Empty/2/LY/9KDMIUCKSOXQ6US2KZI74O.uasset differ diff --git a/Content/__ExternalActors__/Maps/L_Empty/4/CE/N7FSU2VEKU4PQHMH2P7PSK.uasset b/Content/__ExternalActors__/Maps/L_Empty/4/CE/N7FSU2VEKU4PQHMH2P7PSK.uasset new file mode 100644 index 0000000..b7b3c29 Binary files /dev/null and b/Content/__ExternalActors__/Maps/L_Empty/4/CE/N7FSU2VEKU4PQHMH2P7PSK.uasset differ diff --git a/Content/__ExternalActors__/Maps/L_Empty/4/H1/8PZ4HE76HLXQ8IQ4840PL7.uasset b/Content/__ExternalActors__/Maps/L_Empty/4/H1/8PZ4HE76HLXQ8IQ4840PL7.uasset new file mode 100644 index 0000000..d6f18b0 Binary files /dev/null and b/Content/__ExternalActors__/Maps/L_Empty/4/H1/8PZ4HE76HLXQ8IQ4840PL7.uasset differ diff --git a/Content/__ExternalActors__/Maps/L_Empty/6/MT/CI4JQPHLYOVIX5ICAF4KR7.uasset b/Content/__ExternalActors__/Maps/L_Empty/6/MT/CI4JQPHLYOVIX5ICAF4KR7.uasset new file mode 100644 index 0000000..62bb1cc Binary files /dev/null and b/Content/__ExternalActors__/Maps/L_Empty/6/MT/CI4JQPHLYOVIX5ICAF4KR7.uasset differ diff --git a/Content/__ExternalActors__/Maps/L_Empty/7/07/RYEX70GFKJU4VMSO6O0W5P.uasset b/Content/__ExternalActors__/Maps/L_Empty/7/07/RYEX70GFKJU4VMSO6O0W5P.uasset new file mode 100644 index 0000000..8dd2c9b Binary files /dev/null and b/Content/__ExternalActors__/Maps/L_Empty/7/07/RYEX70GFKJU4VMSO6O0W5P.uasset differ diff --git a/Content/__ExternalActors__/Maps/L_Empty/8/SW/O5B9E8OOTV031W3DFKREOR.uasset b/Content/__ExternalActors__/Maps/L_Empty/8/SW/O5B9E8OOTV031W3DFKREOR.uasset new file mode 100644 index 0000000..c9fb72f Binary files /dev/null and b/Content/__ExternalActors__/Maps/L_Empty/8/SW/O5B9E8OOTV031W3DFKREOR.uasset differ diff --git a/Content/__ExternalActors__/Maps/L_Empty/B/PX/5FGIM0FAYC573TR4P8GJIS.uasset b/Content/__ExternalActors__/Maps/L_Empty/B/PX/5FGIM0FAYC573TR4P8GJIS.uasset new file mode 100644 index 0000000..9d5a88b Binary files /dev/null and b/Content/__ExternalActors__/Maps/L_Empty/B/PX/5FGIM0FAYC573TR4P8GJIS.uasset differ diff --git a/Content/__ExternalActors__/Maps/L_Empty/E/KO/KKQVLO3QD0PB479NDCS9WV.uasset b/Content/__ExternalActors__/Maps/L_Empty/E/KO/KKQVLO3QD0PB479NDCS9WV.uasset new file mode 100644 index 0000000..ceb11a0 Binary files /dev/null and b/Content/__ExternalActors__/Maps/L_Empty/E/KO/KKQVLO3QD0PB479NDCS9WV.uasset differ diff --git a/Content/__ExternalActors__/Maps/L_Level2/A/8F/FGVI5ODN3IRJPMIZNYLQLC.uasset b/Content/__ExternalActors__/Maps/L_Level2/A/8F/FGVI5ODN3IRJPMIZNYLQLC.uasset new file mode 100644 index 0000000..982b705 Binary files /dev/null and b/Content/__ExternalActors__/Maps/L_Level2/A/8F/FGVI5ODN3IRJPMIZNYLQLC.uasset differ diff --git a/Plugins/Cog/Cog.uplugin b/Plugins/Cog/Cog.uplugin index cd70be4..882c49d 100644 --- a/Plugins/Cog/Cog.uplugin +++ b/Plugins/Cog/Cog.uplugin @@ -22,7 +22,7 @@ "LoadingPhase": "Default" }, { - "Name": "CogWindow", + "Name": "Cog", "Type": "Runtime", "LoadingPhase": "Default" }, diff --git a/Plugins/Cog/Config/DefaultCog.ini b/Plugins/Cog/Config/DefaultCog.ini index 4c29e40..6030448 100644 --- a/Plugins/Cog/Config/DefaultCog.ini +++ b/Plugins/Cog/Config/DefaultCog.ini @@ -1,2 +1,4 @@ [CoreRedirects] +StructRedirects=(OldName="/Script/CogDebug.CogLogCategory",NewName="/Script/CogCommon.CogLogCategory") ++PropertyRedirects=(OldName="/Script/CogEngine.CogEngineCheatCategory.PersistentEffects",NewName="/Script/CogEngine.CogEngineCheatCategory.PersistentCheats") ++PropertyRedirects=(OldName="/Script/CogEngine.CogEngineCheatCategory.InstantEffects",NewName="/Script/CogEngine.CogEngineCheatCategory.InstantCheats") diff --git a/Plugins/Cog/Source/CogWindow/CogWindow.Build.cs b/Plugins/Cog/Source/Cog/Cog.Build.cs similarity index 89% rename from Plugins/Cog/Source/CogWindow/CogWindow.Build.cs rename to Plugins/Cog/Source/Cog/Cog.Build.cs index 6f990d5..128f9ab 100644 --- a/Plugins/Cog/Source/CogWindow/CogWindow.Build.cs +++ b/Plugins/Cog/Source/Cog/Cog.Build.cs @@ -1,8 +1,8 @@ using UnrealBuildTool; -public class CogWindow : ModuleRules +public class Cog : ModuleRules { - public CogWindow(ReadOnlyTargetRules Target) : base(Target) + public Cog(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; diff --git a/Plugins/Cog/Source/CogWindow/Private/CogWindowConsoleCommandManager.cpp b/Plugins/Cog/Source/Cog/Private/CogConsoleCommandManager.cpp similarity index 59% rename from Plugins/Cog/Source/CogWindow/Private/CogWindowConsoleCommandManager.cpp rename to Plugins/Cog/Source/Cog/Private/CogConsoleCommandManager.cpp index 0db517a..99afdd3 100644 --- a/Plugins/Cog/Source/CogWindow/Private/CogWindowConsoleCommandManager.cpp +++ b/Plugins/Cog/Source/Cog/Private/CogConsoleCommandManager.cpp @@ -1,11 +1,11 @@ -#include "CogWindowConsoleCommandManager.h" +#include "CogConsoleCommandManager.h" #include "Engine/World.h" -TMap FCogWindowConsoleCommandManager::CommandMap; +TMap FCogConsoleCommandManager::CommandMap; //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand(const TCHAR* InName, const TCHAR* InHelp, UWorld* InWorld, const FCogWindowConsoleCommandDelegate& InDelegate) +void FCogConsoleCommandManager::RegisterWorldConsoleCommand(const TCHAR* InName, const TCHAR* InHelp, UWorld* InWorld, const FCogWindowConsoleCommandDelegate& InDelegate) { FCogCommandInfo& commandInfo = CommandMap.FindOrAdd(InName); @@ -20,13 +20,15 @@ void FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand(const TCHAR* I { FCogCommandInfo* commandInfo = CommandMap.Find(InName); if (commandInfo == nullptr) - { - return; - } + { return; } + FWorldContext* WorldContext = GEngine->GetWorldContextFromWorld(InCommandWorld); + if (WorldContext == nullptr) + { return; } + for (auto& receiver : commandInfo->Receivers) { - if (receiver.World == InCommandWorld) + if (receiver.PIEInstance == WorldContext->PIEInstance) { receiver.Delegate.ExecuteIfBound(Args, InCommandWorld); break; @@ -37,21 +39,28 @@ void FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand(const TCHAR* I ); } - FCogCommandReceiver& receiver = commandInfo.Receivers.AddDefaulted_GetRef(); - receiver.World = InWorld; - receiver.Delegate = InDelegate; + if (const FWorldContext* WorldContext = GEngine->GetWorldContextFromWorld(InWorld)) + { + FCogCommandReceiver& receiver = commandInfo.Receivers.AddDefaulted_GetRef(); + receiver.PIEInstance = WorldContext->PIEInstance; + receiver.Delegate = InDelegate; + } } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowConsoleCommandManager::UnregisterAllWorldConsoleCommands(const UWorld* InWorld) +void FCogConsoleCommandManager::UnregisterAllWorldConsoleCommands(const UWorld* InWorld) { + FWorldContext* WorldContext = GEngine->GetWorldContextFromWorld(InWorld); + if (WorldContext == nullptr) + { return; } + for (auto& kv : CommandMap) { FCogCommandInfo& commandInfo = kv.Value; for (int32 i = commandInfo.Receivers.Num() - 1; i >= 0; --i) { - if (commandInfo.Receivers[i].World == InWorld) + if (commandInfo.Receivers[i].PIEInstance == WorldContext->PIEInstance) { commandInfo.Receivers.RemoveAt(i); } @@ -63,4 +72,4 @@ void FCogWindowConsoleCommandManager::UnregisterAllWorldConsoleCommands(const UW commandInfo.ConsoleObject = nullptr; } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Plugins/Cog/Source/CogWindow/Private/CogWindowHelper.cpp b/Plugins/Cog/Source/Cog/Private/CogHelper.cpp similarity index 80% rename from Plugins/Cog/Source/CogWindow/Private/CogWindowHelper.cpp rename to Plugins/Cog/Source/Cog/Private/CogHelper.cpp index b711717..0c1e722 100644 --- a/Plugins/Cog/Source/CogWindow/Private/CogWindowHelper.cpp +++ b/Plugins/Cog/Source/Cog/Private/CogHelper.cpp @@ -1,4 +1,4 @@ -#include "CogWindowHelper.h" +#include "CogHelper.h" #include "AssetRegistry/AssetRegistryModule.h" #include "AssetRegistry/IAssetRegistry.h" @@ -7,7 +7,7 @@ #include "imgui.h" //---------------------------------------------------------------------------------------------------------------------- -const UObject* FCogWindowHelper::GetFirstAssetByClass(const TSubclassOf AssetClass) +const UObject* FCogHelper::GetFirstAssetByClass(const TSubclassOf& AssetClass) { const IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")).Get(); @@ -23,7 +23,7 @@ const UObject* FCogWindowHelper::GetFirstAssetByClass(const TSubclassOf } //-------------------------------------------------------------------------------------------------------------------------- -FString FCogWindowHelper::GetActorName(const AActor* Actor) +FString FCogHelper::GetActorName(const AActor* Actor) { if (Actor == nullptr) { @@ -34,7 +34,7 @@ FString FCogWindowHelper::GetActorName(const AActor* Actor) } //-------------------------------------------------------------------------------------------------------------------------- -FString FCogWindowHelper::GetActorName(const AActor& Actor) +FString FCogHelper::GetActorName(const AActor& Actor) { #if WITH_EDITOR @@ -50,7 +50,7 @@ FString FCogWindowHelper::GetActorName(const AActor& Actor) //----------------------------------------------------------------------------------------- -bool FCogWindowHelper::ComputeBoundingBoxScreenPosition(const APlayerController* PlayerController, const FVector& Origin, const FVector& Extent, FVector2D& Min, FVector2D& Max) +bool FCogHelper::ComputeBoundingBoxScreenPosition(const APlayerController* PlayerController, const FVector& Origin, const FVector& Extent, FVector2D& Min, FVector2D& Max) { FVector Corners[8]; Corners[0].Set(-Extent.X, -Extent.Y, -Extent.Z); // - - - @@ -89,4 +89,12 @@ bool FCogWindowHelper::ComputeBoundingBoxScreenPosition(const APlayerController* Max.Y = FMath::Min(DisplaySize.y * 2, Max.Y); return true; -} \ No newline at end of file +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogHelper::IsTraceChannelHidden(const UCollisionProfile& InCollisionProfile, const ECollisionChannel InCollisionChannel) +{ + + + return false; +} diff --git a/Plugins/Cog/Source/CogWindow/Private/CogWindowModule.cpp b/Plugins/Cog/Source/Cog/Private/CogModule.cpp similarity index 59% rename from Plugins/Cog/Source/CogWindow/Private/CogWindowModule.cpp rename to Plugins/Cog/Source/Cog/Private/CogModule.cpp index 99673c8..fbc5df2 100644 --- a/Plugins/Cog/Source/CogWindow/Private/CogWindowModule.cpp +++ b/Plugins/Cog/Source/Cog/Private/CogModule.cpp @@ -1,17 +1,17 @@ -#include "CogWindowModule.h" +#include "CogModule.h" -#define LOCTEXT_NAMESPACE "FCogWindowModule" +#define LOCTEXT_NAMESPACE "FCogModule" //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowModule::StartupModule() +void FCogModule::StartupModule() { } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowModule::ShutdownModule() +void FCogModule::ShutdownModule() { } #undef LOCTEXT_NAMESPACE -IMPLEMENT_MODULE(FCogWindowModule, CogWindow) \ No newline at end of file +IMPLEMENT_MODULE(FCogModule, Cog) \ No newline at end of file diff --git a/Plugins/Cog/Source/Cog/Private/CogSubsystem.cpp b/Plugins/Cog/Source/Cog/Private/CogSubsystem.cpp new file mode 100644 index 0000000..d38a487 --- /dev/null +++ b/Plugins/Cog/Source/Cog/Private/CogSubsystem.cpp @@ -0,0 +1,1127 @@ +#include "CogSubsystem.h" + +#include "CogCommon.h" +#include "CogDebugDrawImGui.h" +#include "CogImguiHelper.h" +#include "CogImguiInputHelper.h" +#include "CogWindow_Layouts.h" +#include "CogWindow_Settings.h" +#include "CogWindow_Spacing.h" +#include "CogConsoleCommandManager.h" +#include "CogDebugPluginSubsystem.h" +#include "CogHelper.h" +#include "CogWidgets.h" +#include "Engine/Engine.h" +#include "GameFramework/PlayerInput.h" +#include "imgui_internal.h" +#include "Misc/CoreMisc.h" +#include "NetImgui_Api.h" + +FString UCogSubsystem::ToggleInputCommand = TEXT("Cog.ToggleInput"); +FString UCogSubsystem::DisableInputCommand = TEXT("Cog.DisableInput"); +FString UCogSubsystem::LoadLayoutCommand = TEXT("Cog.LoadLayout"); +FString UCogSubsystem::SaveLayoutCommand = TEXT("Cog.SaveLayout"); +FString UCogSubsystem::ResetLayoutCommand = TEXT("Cog.ResetLayout"); + +//-------------------------------------------------------------------------------------------------------------------------- +bool UCogSubsystem::ShouldCreateSubsystem(UObject* Outer) const +{ + if (Super::ShouldCreateSubsystem(Outer) == false) + { return false; } + +#if ENABLE_COG + return true; +#else + return false; +#endif +} + +//-------------------------------------------------------------------------------------------------------------------------- +TStatId UCogSubsystem::GetStatId() const +{ + RETURN_QUICK_DECLARE_CYCLE_STAT(UCogSubsystemBase, STATGROUP_Tickables); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::PostInitialize() +{ + Super::PostInitialize(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::Deinitialize() +{ + Shutdown(); + + Super::Deinitialize(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::TryInitialize(UWorld& World) +{ + if (bIsInitialized) + { return; } + + FWorldContext* WorldContext = GEngine->GetWorldContextFromWorld(&World); + if (WorldContext == nullptr) + { return; } + + if (WorldContext->GameViewport == nullptr && IsRunningDedicatedServer() == false) + { return; } + + UE_LOG(LogCogImGui, Verbose, TEXT("UCogSubsystem::TryInitialize | World:%s %p"), *World.GetName(), &World); + + Context.Initialize(WorldContext->GameViewport.Get()); + + FCogImGuiContextScope ImGuiContextScope(Context); + + ImGuiSettingsHandler IniHandler; + IniHandler.TypeName = "Cog"; + IniHandler.TypeHash = ImHashStr("Cog"); + IniHandler.ClearAllFn = SettingsHandler_ClearAll; + IniHandler.ReadOpenFn = SettingsHandler_ReadOpen; + IniHandler.ReadLineFn = SettingsHandler_ReadLine; + IniHandler.ApplyAllFn = SettingsHandler_ApplyAll; + IniHandler.WriteAllFn = SettingsHandler_WriteAll; + IniHandler.UserData = this; + ImGui::AddSettingsHandler(&IniHandler); + + SpaceWindows.Add(AddWindow("Spacing 1")); + SpaceWindows.Add(AddWindow("Spacing 2")); + SpaceWindows.Add(AddWindow("Spacing 3")); + SpaceWindows.Add(AddWindow("Spacing 4")); + + Settings = GetConfig(); + + UCogWindowConfig_Settings* SettingsPtr = Settings.Get(); + AddShortcut(SettingsPtr, &UCogWindowConfig_Settings::Shortcut_ToggleImguiInput).BindLambda([this] () { ToggleInputMode(); }); + AddShortcut(SettingsPtr, &UCogWindowConfig_Settings::Shortcut_LoadLayout1).BindLambda([this] (){ LoadLayout(1); }); + AddShortcut(SettingsPtr, &UCogWindowConfig_Settings::Shortcut_LoadLayout2).BindLambda([this] (){ LoadLayout(2); }); + AddShortcut(SettingsPtr, &UCogWindowConfig_Settings::Shortcut_LoadLayout3).BindLambda([this] (){ LoadLayout(3); }); + AddShortcut(SettingsPtr, &UCogWindowConfig_Settings::Shortcut_LoadLayout4).BindLambda([this] (){ LoadLayout(4); }); + AddShortcut(SettingsPtr, &UCogWindowConfig_Settings::Shortcut_SaveLayout1).BindLambda([this] (){ SaveLayout(1); }); + AddShortcut(SettingsPtr, &UCogWindowConfig_Settings::Shortcut_SaveLayout2).BindLambda([this] (){ SaveLayout(2); }); + AddShortcut(SettingsPtr, &UCogWindowConfig_Settings::Shortcut_SaveLayout3).BindLambda([this] (){ SaveLayout(3); }); + AddShortcut(SettingsPtr, &UCogWindowConfig_Settings::Shortcut_SaveLayout4).BindLambda([this] (){ SaveLayout(4); }); + AddShortcut(SettingsPtr, &UCogWindowConfig_Settings::Shortcut_ResetLayout).BindLambda([this] (){ ResetLayout(); }); + + LayoutsWindow = AddWindow("Window.Layouts"); + SettingsWindow = AddWindow("Window.Settings"); + + for (FCogWindow* Window : Windows) + { + InitializeWindow(Window); + } + + FCogConsoleCommandManager::RegisterWorldConsoleCommand( + *ToggleInputCommand, + TEXT("Toggle the input focus between the Game and ImGui"), + GetWorld(), + FCogWindowConsoleCommandDelegate::CreateLambda([this](const TArray& InArgs, UWorld* InWorld) + { + ToggleInputMode(); + })); + + FCogConsoleCommandManager::RegisterWorldConsoleCommand( + *DisableInputCommand, + TEXT("Disable ImGui input"), + GetWorld(), + FCogWindowConsoleCommandDelegate::CreateLambda([this](const TArray& InArgs, UWorld* InWorld) + { + DisableInputMode(); + })); + + FCogConsoleCommandManager::RegisterWorldConsoleCommand( + *ResetLayoutCommand, + TEXT("Reset the layout."), + GetWorld(), + FCogWindowConsoleCommandDelegate::CreateLambda([this](const TArray& InArgs, UWorld* InWorld) + { + if (InArgs.Num() > 0) + { + ResetLayout(); + } + })); + + FCogConsoleCommandManager::RegisterWorldConsoleCommand( + *LoadLayoutCommand, + TEXT("Load the layout. Cog.LoadLayout "), + GetWorld(), + FCogWindowConsoleCommandDelegate::CreateLambda([this](const TArray& InArgs, UWorld* InWorld) + { + if (InArgs.Num() > 0) + { + LoadLayout(FCString::Atoi(*InArgs[0])); + } + })); + + FCogConsoleCommandManager::RegisterWorldConsoleCommand( + *SaveLayoutCommand, + TEXT("Save the layout. Cog.SaveLayout "), + GetWorld(), + FCogWindowConsoleCommandDelegate::CreateLambda([this](const TArray& InArgs, UWorld* InWorld) + { + if (InArgs.Num() > 0) + { + SaveLayout(FCString::Atoi(*InArgs[0])); + } + })); + + + bIsInitialized = true; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::Shutdown() +{ + FCogImGuiContextScope ImGuiContextScope(Context); + + if (bIsInitialized) + { + Context.SaveSettings(); + } + + for (FCogWindow* Window : Windows) + { + Window->Shutdown(); + delete Window; + } + Windows.Empty(); + + if (bIsInitialized) + { + Context.Shutdown(); + } + + FCogConsoleCommandManager::UnregisterAllWorldConsoleCommands(GetWorld()); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::SaveAllSettings() +{ + //------------------------------------------------------------------ + // Call PreSaveConfig before destroying imgui context + // if PreSaveConfig needs to read ImGui IO for example + //------------------------------------------------------------------ + for (FCogWindow* Window : Windows) + { + Window->PreSaveConfig(); + } + + for (UCogCommonConfig* Config : Configs) + { + Config->SaveConfig(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::ReloadAllSettings() +{ + for (UCogCommonConfig* Config : Configs) + { + Config->ReloadConfig(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::Tick(float InDeltaTime) +{ + UWorld* World = GetWorld(); + + //---------------------------------------------------------------------------------------------- + // When changing world the DebugExecBindings can change. + //---------------------------------------------------------------------------------------------- + if (World != CurrentWorld.Get()) + { + RequestDisableCommandsConflictingWithShortcuts(); + } + CurrentWorld = World; + + if (World == nullptr) + { return; } + + if (bIsInitialized == false) + { + TryInitialize(*World); + return; + } + + FCogImGuiContextScope ImGuiContextScope(Context); + + UpdatePlayerControllers(*World); + + if (auto* LocalPlayerControllerPtr = LocalPlayerController.Get()) + { + if (UPlayerInput* PlayerInput = LocalPlayerControllerPtr->PlayerInput) + { + //------------------------------------------------------------------ + // We must wait for the player input to be valid to disable + // DebugExecBindings conflicting with our shortcuts. + //------------------------------------------------------------------ + TryDisableCommandsConflictingWithShortcuts(PlayerInput); + } + } + + if (LayoutToLoad != -1) + { + const FString Filename = FCogImguiHelper::GetIniFilePath(FString::Printf(TEXT("ImGui_Layout_%d"), LayoutToLoad)); + ImGui::LoadIniSettingsFromDisk(TCHAR_TO_ANSI(*Filename)); + LayoutToLoad = -1; + } + + for (FCogWindow* Window : Windows) + { + Window->GameTick(InDeltaTime); + } + + const bool ShouldSkipRendering = NetImgui::IsConnected() && bIsSelectionModeActive == false; + Context.SetSkipRendering(ShouldSkipRendering); + + if (Context.BeginFrame(InDeltaTime)) + { + Render(InDeltaTime); + Context.EndFrame(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::RequestDisableCommandsConflictingWithShortcuts() +{ + NumExecBindingsChecked = INDEX_NONE; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::SetLocalPlayerController(APlayerController& PlayerController) +{ + if (LocalPlayerController.Get() == &PlayerController) + { return; } + + LocalPlayerController = &PlayerController; + InputComponent.Reset(); + + if (UInputComponent* InputComponentPtr = NewObject(&PlayerController, TEXT("Cog_Input"))) + { + InputComponent = InputComponentPtr; + PlayerController.PushInputComponent(InputComponentPtr); + } + + for (FCogShortcut& Shortcut : Shortcuts) + { + BindShortcut(Shortcut); + } + + if (LocalPlayerController->PlayerInput != nullptr) + { + FCogImguiInputHelper::DisableCommandsConflictingWithShortcuts(*LocalPlayerController->PlayerInput); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::UpdatePlayerControllers(UWorld& World) +{ + TArray PluginSubsystems; + + ServerPlayerControllers.RemoveAll([] (TWeakObjectPtr PlayerController) + { + return PlayerController.IsValid() == false; + }); + + for (FConstPlayerControllerIterator It = World.GetPlayerControllerIterator(); It; ++It) + { + APlayerController* PlayerController = It->Get(); + if (IsValid(PlayerController) == false) + { continue; } + + if (PlayerController->IsLocalController()) + { + SetLocalPlayerController(*PlayerController); + } + + if (World.GetNetMode() != NM_Client) + { + if (ServerPlayerControllers.Contains(PlayerController)) + { continue; } + + ServerPlayerControllers.Add(PlayerController); + + if (PluginSubsystems.IsEmpty()) + { + PluginSubsystems = World.GetGameInstance()->GetSubsystemArrayCopy(); + } + + for (UCogDebugPluginSubsystem* PluginSubsystem : PluginSubsystems) + { + PluginSubsystem->OnPlayerControllerReady(PlayerController); + } + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::Render(float DeltaTime) +{ + FCogImGuiContextScope ImGuiContextScope(Context); + + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); + ImGui::DockSpaceOverViewport(0, nullptr, ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoDockingOverCentralNode | ImGuiDockNodeFlags_AutoHideTabBar); + ImGui::PopStyleColor(1); + + const bool bCompactSaved = Settings->bCompactMode; + if (bCompactSaved) + { + FCogWidgets::PushStyleCompact(); + } + + //---------------------------------------------------------------------- + // There is no need to have Imgui input enabled if the imgui rendering + // is only done on the NetImgui server. So we disable imgui input. + //---------------------------------------------------------------------- + if (Context.GetEnableInput() && NetImgui::IsConnected() && bIsSelectionModeActive == false) + { + Context.SetEnableInput(false); + } + + if ((Context.GetEnableInput() || NetImgui::IsConnected()) && bIsSelectionModeActive == false) + { + RenderMainMenu(); + } + + for (FCogWindow* Window : Windows) + { + Window->RenderTick(DeltaTime); + + if (Window->GetIsVisible() && bIsSelectionModeActive == false) + { + if (Settings->bTransparentMode) + { + ImGui::SetNextWindowBgAlpha(0.35f); + } + + Window->Render(DeltaTime); + } + } + + if (bCompactSaved) + { + FCogWidgets::PopStyleCompact(); + } + + FCogDebugDrawImGui::Draw(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::AddWindow(FCogWindow* Window, const FString& Name) +{ + if (Windows.ContainsByPredicate([&](const FCogWindow* w) { return w->GetName() == Name; })) + { + COG_LOG_FUNC(LogCogImGui, ELogVerbosity::Warning, TEXT("Trying to add a window, but one already exist with the same name: %s"), *Name); + return; + } + + Window->SetFullName(Name); + Window->SetOwner(this); + Windows.Add(Window); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::InitializeWindow(FCogWindow* Window) +{ + Window->Initialize(); + + if (Window->HasWidget()) + { + Widgets.Add(Window); + //Widgets.Sort() + } + + if (Window->bShowInMainMenu) + { + if (FMenu* Menu = AddMenu(Window->GetFullName())) + { + Menu->Window = Window; + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogWindow* UCogSubsystem::FindWindowByID(const ImGuiID ID) +{ + for (FCogWindow* Window : Windows) + { + if (Window->GetID() == ID) + { + return Window; + } + } + return nullptr; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::ResetLayout() +{ + FCogImGuiContextScope ImGuiContextScope(Context); + + for (const FCogWindow* Window : Windows) + { + ImGui::SetWindowPos(TCHAR_TO_ANSI(*Window->GetName()), ImVec2(10, 10), ImGuiCond_Always); + } + + ImGui::ClearIniSettings(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::CloseAllWindows() +{ + for (FCogWindow* Window : Windows) + { + Window->SetIsVisible(false); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::LoadLayout(const int32 LayoutIndex) +{ + for (FCogWindow* Window : Windows) + { + Window->SetIsVisible(false); + } + + LayoutToLoad = LayoutIndex; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::SaveLayout(const int32 LayoutIndex) +{ + FCogImGuiContextScope ImGuiContextScope(Context); + + const FString Filename = *FCogImguiHelper::GetIniFilePath(FString::Printf(TEXT("imgui_layout_%d"), LayoutIndex)); + ImGui::SaveIniSettingsToDisk(TCHAR_TO_ANSI(*Filename)); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::SortMainMenu() +{ + MainMenu.SubMenus.Empty(); + + TArray SortedWindows = Windows; + SortedWindows.Sort([](const FCogWindow& Lhs, const FCogWindow& Rhs) { return Lhs.GetFullName() < Rhs.GetFullName(); }); + + for (FCogWindow* Window : SortedWindows) + { + if (FMenu* Menu = AddMenu(Window->GetFullName())) + { + Menu->Window = Window; + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +UCogSubsystem::FMenu* UCogSubsystem::AddMenu(const FString& Name) +{ + TArray Path; + Name.ParseIntoArray(Path, TEXT(".")); + + FMenu* CurrentMenu = &MainMenu; + for (int i = 0; i < Path.Num(); ++i) + { + FString MenuName = Path[i]; + + int SubMenuIndex = CurrentMenu->SubMenus.IndexOfByPredicate([&](const FMenu& Menu) { return Menu.Name == MenuName; }); + if (SubMenuIndex != -1) + { + CurrentMenu = &CurrentMenu->SubMenus[SubMenuIndex]; + } + else + { + CurrentMenu = &CurrentMenu->SubMenus.AddDefaulted_GetRef(); + CurrentMenu->Name = MenuName; + } + } + + return CurrentMenu; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::RenderMainMenu() +{ + bIsRenderingInMainMenu = true; + + //----------------------------------------------------------------------------------------------- + // Prevent having a small gap on the right of the main menu, where some widgets are displayed + //----------------------------------------------------------------------------------------------- + ImGui::PushStyleVarX(ImGuiStyleVar_WindowPadding, 0.0f); + + const bool ShowMainMenu = ImGui::BeginMainMenuBar(); + + ImGui::PopStyleVar(); + + if (ShowMainMenu) + { + for (FMenu& Menu : MainMenu.SubMenus) + { + RenderOptionMenu(Menu); + } + + if (ImGui::BeginMenu("Window")) + { + if (ImGui::MenuItem("Close All Windows")) + { + CloseAllWindows(); + } + + ImGui::Separator(); + + RenderMenuItem(*LayoutsWindow, "Layouts"); + RenderMenuItem(*SettingsWindow, "Settings"); + + if (ImGui::BeginMenu("Spacing")) + { + for (FCogWindow* SpaceWindow : SpaceWindows) + { + bool bSpaceVisible = SpaceWindow->GetIsVisible(); + if (ImGui::MenuItem(TCHAR_TO_ANSI(*SpaceWindow->GetName()), nullptr, &bSpaceVisible)) + { + SpaceWindow->SetIsVisible(bSpaceVisible); + } + } + ImGui::EndMenu(); + } + + ImGui::EndMenu(); + } + + RenderWidgets(); + ImGui::EndMainMenuBar(); + } + + bIsRenderingInMainMenu = false; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::RenderWidgets() +{ + int32 NumVisibleWidgets = 0; + for (int i = 0; i < Widgets.Num(); ++i) + { + FCogWindow* Window = Widgets[i]; + if (Window->GetIsWidgetVisible()) + { + NumVisibleWidgets++; + } + } + + if (NumVisibleWidgets == 0) + { return; } + + const bool AddLeftColumn = Settings->WidgetAlignment == ECogWidgetAlignment::Right + || Settings->WidgetAlignment == ECogWidgetAlignment::Manual + || Settings->WidgetAlignment == ECogWidgetAlignment::Center; + + const bool AddRightColumn = Settings->WidgetAlignment == ECogWidgetAlignment::Center; + + int32 NumColumns = NumVisibleWidgets; + if (AddLeftColumn) + { + NumColumns++; + } + + if (AddRightColumn) + { + NumColumns ++; + } + + ImGuiTableFlags Flags = ImGuiTableFlags_None; + if (Settings->ShowWidgetBorders) + { + Flags |= ImGuiTableFlags_BordersInnerV; + if (Settings->WidgetAlignment == ECogWidgetAlignment::Left) + { + Flags |= ImGuiTableFlags_BordersOuterV; + } + } + + if (Settings->WidgetAlignment == ECogWidgetAlignment::Manual) + { + Flags |= ImGuiTableFlags_Resizable; + if (Settings->ShowWidgetBorders == false) + { + Flags |= ImGuiTableFlags_NoBordersInBodyUntilResize; + } + } + + //ImGui::PushStyleVarX(ImGuiStyleVar_CellPadding, 0.0f); + + if (ImGui::BeginTable("Widgets", NumColumns, Flags)) + { + if (AddLeftColumn) + { + ImGui::TableSetupColumn("Stretch", ImGuiTableColumnFlags_WidthStretch); + } + + for (int i = 0; i < NumVisibleWidgets; ++i) + { + ImGui::TableSetupColumn("Fixed", ImGuiTableColumnFlags_WidthFixed); + } + + if (AddRightColumn) + { + ImGui::TableSetupColumn("Stretch", ImGuiTableColumnFlags_WidthStretch); + } + + ImGui::TableNextRow(); + + if (AddLeftColumn) + { + ImGui::TableNextColumn(); + } + + //--------------------------------------------------------------------- + // Widgets + //--------------------------------------------------------------------- + for (int column = 0; column < Widgets.Num(); ++column) + { + ImGui::PushID(column); + + FCogWindow* Window = Widgets[column]; + if (Window->GetIsWidgetVisible()) + { + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 2); + Window->RenderMainMenuWidget(); + } + + ImGui::PopID(); + } + + if (AddRightColumn) + { + ImGui::TableNextColumn(); + } + + ImGui::EndTable(); + } + //ImGui::PopStyleVar(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::RenderOptionMenu(FMenu& Menu) +{ + if (Menu.Window != nullptr) + { + RenderMenuItem(*Menu.Window, TCHAR_TO_ANSI(*Menu.Name)); + } + else + { + if (ImGui::BeginMenu(TCHAR_TO_ANSI(*Menu.Name))) + { + for (FMenu& SubMenu : Menu.SubMenus) + { + RenderOptionMenu(SubMenu); + } + ImGui::EndMenu(); + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::RenderMenuItem(FCogWindow& Window, const char* MenuItemName) +{ + if (Settings->bShowWindowsInMainMenu) + { + ImGui::SetNextWindowSizeConstraints( + ImVec2(FCogWidgets::GetFontWidth() * 40, ImGui::GetTextLineHeightWithSpacing() * 5), + ImVec2(FCogWidgets::GetFontWidth() * 50, ImGui::GetTextLineHeightWithSpacing() * 60)); + + if (ImGui::BeginMenu(MenuItemName)) + { + Window.RenderContent(); + ImGui::EndMenu(); + } + + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) + { + Window.SetIsVisible(!Window.GetIsVisible()); + } + + RenderMenuItemHelp(Window); + } + else + { + bool bIsVisible = Window.GetIsVisible(); + if (ImGui::MenuItem(MenuItemName, nullptr, &bIsVisible)) + { + Window.SetIsVisible(bIsVisible); + } + + RenderMenuItemHelp(Window); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::RenderMenuItemHelp(FCogWindow& Window) +{ + if (Settings->bShowHelp) + { + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() - FCogWidgets::GetFontWidth() * 3.0f); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::PushStyleColor(ImGuiCol_PopupBg, IM_COL32(29, 42, 62, 240)); + const float HelpWidth = FCogWidgets::GetFontWidth() * 80; + ImGui::SetNextWindowSizeConstraints(ImVec2(HelpWidth / 2.0f, 0.0f), ImVec2(HelpWidth, FLT_MAX)); + if (ImGui::BeginTooltip()) + { + ImGui::PushTextWrapPos(HelpWidth - 1 * FCogWidgets::GetFontWidth()); + Window.RenderHelp(); + ImGui::Separator(); + ImGui::TextDisabled("Help can be hidden in Window/Settings."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::PopStyleColor(); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(FCogWidgets::GetFontWidth() * 1, 0)); + } +} + + + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::SettingsHandler_ClearAll(ImGuiContext* Context, ImGuiSettingsHandler* Handler) +{ +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::SettingsHandler_ApplyAll(ImGuiContext* Context, ImGuiSettingsHandler* Handler) +{ + UCogSubsystem* CogSubsystem = static_cast(Handler->UserData); + + CogSubsystem->Widgets.Sort([](const FCogWindow& Window1, const FCogWindow& Window2) + { + return Window1.GetWidgetOrderIndex() < Window2.GetWidgetOrderIndex(); + }); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void* UCogSubsystem::SettingsHandler_ReadOpen(ImGuiContext* Context, ImGuiSettingsHandler* Handler, const char* Name) +{ + if (strcmp(Name, "Windows") == 0) + { + return reinterpret_cast(1); + } + + if (strcmp(Name, "Widgets") == 0) + { + UCogSubsystem* CogSubsystem = static_cast(Handler->UserData); + CogSubsystem->WidgetsOrderIndex = 0; + + return reinterpret_cast(2); + } + + return nullptr; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::SettingsHandler_ReadLine(ImGuiContext* Context, ImGuiSettingsHandler* Handler, void* Entry, const char* Line) +{ + //----------------------------------------------------------------------------------- + // Load the visibility of windows. + //----------------------------------------------------------------------------------- + if (Entry == reinterpret_cast(1)) + { + ImGuiID Id; + int32 ShowMenu; +#if PLATFORM_WINDOWS || PLATFORM_MICROSOFT + if (sscanf_s(Line, "0x%08X %d", &Id, &ShowMenu) == 2) +#else + if (sscanf(Line, "0x%08X", &Id) == 1) +#endif + { + UCogSubsystem* CogSubsystem = static_cast(Handler->UserData); + if (FCogWindow* Window = CogSubsystem->FindWindowByID(Id)) + { + Window->SetIsVisible(true); + Window->bShowMenu = (ShowMenu > 0); + } + } + } + //----------------------------------------------------------------------------------- + // Load which widgets are present in the main menu bar and with what order. + //----------------------------------------------------------------------------------- + else if (Entry == reinterpret_cast(2)) + { + ImGuiID Id; + int32 Visible = false; +#if PLATFORM_WINDOWS || PLATFORM_MICROSOFT + if (sscanf_s(Line, "0x%08X %d", &Id, &Visible) == 2) +#else + if (sscanf(Line, "0x%08X %d", &Id, &Visible) == 2) +#endif + { + UCogSubsystem* CogSubsystem = static_cast(Handler->UserData); + if (FCogWindow* Window = CogSubsystem->FindWindowByID(Id)) + { + Window->SetWidgetOrderIndex(CogSubsystem->WidgetsOrderIndex); + Window->SetIsWidgetVisible(Visible > 0); + } + + CogSubsystem->WidgetsOrderIndex++; + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::SettingsHandler_WriteAll(ImGuiContext* Context, ImGuiSettingsHandler* Handler, ImGuiTextBuffer* Buffer) +{ + UCogSubsystem* CogSubsystem = static_cast(Handler->UserData); + + CogSubsystem->SaveAllSettings(); + + //----------------------------------------------------------------------------------- + // Save the visibility of windows. Example: + // [Cog][Windows] + // 0xB5D96693 + // 0xBF3390B5 + //----------------------------------------------------------------------------------- + Buffer->appendf("[%s][Windows]\n", Handler->TypeName); + for (const FCogWindow* Window : CogSubsystem->Windows) + { + if (Window->GetIsVisible()) + { + Buffer->appendf("0x%08X %d\n", Window->GetID(), static_cast(Window->bShowMenu)); + } + } + Buffer->append("\n"); + + //----------------------------------------------------------------------------------- + // Save which widgets are present in the main menu bar and with what order. Example: + // [Cog][Widgets] + // 0x639F1181 1 + // 0x52BDE3E0 1 + //----------------------------------------------------------------------------------- + Buffer->appendf("[%s][Widgets]\n", Handler->TypeName); + for (const FCogWindow* Window : CogSubsystem->Widgets) + { + Buffer->appendf("0x%08X %d\n", Window->GetID(), Window->GetIsWidgetVisible()); + } + Buffer->append("\n"); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::ResetAllWindowsConfig() +{ + for (FCogWindow* Window : Windows) + { + Window->ResetConfig(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::AddCommand(UPlayerInput* PlayerInput, const FString& Command, const FKey& Key) +{ + if (PlayerInput == nullptr) + { return; } + + //--------------------------------------------------- + // Reassign conflicting commands + //--------------------------------------------------- + for (FKeyBind& KeyBind : PlayerInput->DebugExecBindings) + { + if (KeyBind.Key == Key && KeyBind.Command != Command) + { + KeyBind.Control = true; + KeyBind.bIgnoreCtrl = false; + } + } + + //--------------------------------------------------- + // Find or add desired command + //--------------------------------------------------- + FKeyBind* ExistingKeyBind = PlayerInput->DebugExecBindings.FindByPredicate([Command](const FKeyBind& KeyBind) { return KeyBind.Command == Command; }); + if (ExistingKeyBind == nullptr) + { + ExistingKeyBind = &PlayerInput->DebugExecBindings.AddDefaulted_GetRef(); + } + + //--------------------------------------------------- + // Assign the key to the command + //--------------------------------------------------- + FKeyBind CogKeyBind; + CogKeyBind.Command = Command; + CogKeyBind.Control = false; + CogKeyBind.bIgnoreCtrl = true; + CogKeyBind.Key = Key; + + *ExistingKeyBind = CogKeyBind; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::SortCommands(UPlayerInput* PlayerInput) +{ + PlayerInput->DebugExecBindings.Sort([](const FKeyBind& Key1, const FKeyBind& Key2) + { + return Key1.Command.Compare(Key2.Command) < 0; + }); +} + +//-------------------------------------------------------------------------------------------------------------------------- +UCogCommonConfig* UCogSubsystem::GetConfig(const TSubclassOf& ConfigClass) +{ + const UClass* Class = ConfigClass.Get(); + + for (UCogCommonConfig* Config : Configs) + { + if (Config && Config->IsA(Class)) + { return Cast(Config); } + } + + UCogCommonConfig* Config = NewObject(this, Class); + Config->Reset(); + Config->ReloadConfig(); + + Configs.Add(Config); + return Config; +} + +//-------------------------------------------------------------------------------------------------------------------------- +const UObject* UCogSubsystem::GetAsset(const TSubclassOf& AssetClass) const +{ + const UClass* Class = AssetClass.Get(); + + for (const UObject* Asset : Assets) + { + if (Asset && Asset->IsA(Class)) + { return Asset; } + } + + const UObject* Asset = FCogHelper::GetFirstAssetByClass(AssetClass); + if (Asset == nullptr) + { return nullptr; } + + Assets.Add(Asset); + + return Asset; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::ToggleInputMode() +{ + UE_LOG(LogCogImGui, Verbose, TEXT("UCogSubsystem::ToggleInputMode")); + Context.SetEnableInput(!Context.GetEnableInput()); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::DisableInputMode() +{ + UE_LOG(LogCogImGui, Verbose, TEXT("UCogSubsystem::DisableInputMode")); + Context.SetEnableInput(false); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::SetActivateSelectionMode(const bool Value) +{ + SelectionModeActiveCounter = FMath::Max(SelectionModeActiveCounter + (Value ? 1 : -1), 0); + bIsSelectionModeActive = SelectionModeActiveCounter > 0; + + if (bIsSelectionModeActive) + { + bIsInputEnabledBeforeEnteringSelectionMode = GetContext().GetEnableInput(); + + Context.SetEnableInput(true); + } + else + { + GetContext().SetEnableInput(bIsInputEnabledBeforeEnteringSelectionMode); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool UCogSubsystem::GetActivateSelectionMode() const +{ + return SelectionModeActiveCounter > 0; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FInputActionHandlerSignature& UCogSubsystem::AddShortcut(const UObject& InInstance, const FProperty& InProperty) +{ + FCogShortcut& Shortcut = Shortcuts.AddDefaulted_GetRef(); + Shortcut.PropertyName = InProperty.GetFName(); + Shortcut.Config = &InInstance; + return Shortcut.Delegate; +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool UCogSubsystem::BindShortcut(FCogShortcut& InShortcut) const +{ + UObject* Config = const_cast(InShortcut.Config.Get()); + if (Config == nullptr) + { return false; } + + const FStructProperty* StructProperty = CastField(Config->GetClass()->FindPropertyByName(InShortcut.PropertyName)); + if (StructProperty == nullptr) + { return false; } + + const FInputChord* InputChord = static_cast(StructProperty->ContainerPtrToValuePtr(Config)); + if (InputChord == nullptr) + { return false; } + + FInputKeyBinding InputBinding(*InputChord, IE_Pressed); + InputBinding.KeyDelegate.GetDelegateForManualSet() = InShortcut.Delegate; + + if (UInputComponent* InputComponentPtr = InputComponent.Get()) + { + InputComponentPtr->KeyBindings.Add(InputBinding); + } + + InShortcut.InputChord = *InputChord; + + FCogImguiInputHelper::GetPrioritizedShortcuts().Add(*InputChord); + return true; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::RebindShortcut(const UCogCommonConfig& InConfig, const FProperty& InProperty) +{ + // Ideally would unbind and rebind only the provided shortcut, but we currently rebind all shortcuts. + + UInputComponent* InputComponentPtr = InputComponent.Get(); + if (InputComponentPtr == nullptr) + { return; } + + for (auto& KeyBinding : InputComponentPtr->KeyBindings) + { + KeyBinding.KeyDelegate.Unbind(); + } + InputComponentPtr->KeyBindings.Empty(); + + for (FCogShortcut& Shortcut : Shortcuts) + { + BindShortcut(Shortcut); + } + + RequestDisableCommandsConflictingWithShortcuts(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogSubsystem::TryDisableCommandsConflictingWithShortcuts(UPlayerInput* PlayerInput) +{ + const int32 NewNumExecBindings = PlayerInput->DebugExecBindings.Num(); + if (NumExecBindingsChecked == NewNumExecBindings) + { return; } + + NumExecBindingsChecked = NewNumExecBindings;; + + if (Settings->bDisableConflictingCommands == false) + { return; } + + TArray& PrioritizedShortcuts = FCogImguiInputHelper::GetPrioritizedShortcuts(); + for (const FCogShortcut& Shortcut : Shortcuts) + { + PrioritizedShortcuts.Add(Shortcut.InputChord); + } + + FCogImguiInputHelper::DisableCommandsConflictingWithShortcuts(*PlayerInput); +} \ No newline at end of file diff --git a/Plugins/Cog/Source/CogWindow/Private/CogWindowWidgets.cpp b/Plugins/Cog/Source/Cog/Private/CogWidgets.cpp similarity index 55% rename from Plugins/Cog/Source/CogWindow/Private/CogWindowWidgets.cpp rename to Plugins/Cog/Source/Cog/Private/CogWidgets.cpp index 991df58..71c5a4e 100644 --- a/Plugins/Cog/Source/CogWindow/Private/CogWindowWidgets.cpp +++ b/Plugins/Cog/Source/Cog/Private/CogWidgets.cpp @@ -1,12 +1,12 @@ -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "CogDebug.h" #include "CogImguiHelper.h" #include "CogImguiInputHelper.h" -#include "CogImguiKeyInfo.h" -#include "CogWindowHelper.h" +#include "CogHelper.h" #include "Components/PrimitiveComponent.h" #include "EngineUtils.h" +#include "imgui.h" #include "GameFramework/Pawn.h" #include "GameFramework/PlayerInput.h" #include "imgui.h" @@ -19,7 +19,7 @@ #endif //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::BeginTableTooltip() +bool FCogWidgets::BeginTableTooltip() { ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4, 4)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); @@ -33,7 +33,7 @@ bool FCogWindowWidgets::BeginTableTooltip() } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::EndTableTooltip() +void FCogWidgets::EndTableTooltip() { ImGui::EndTooltip(); ImGui::PopStyleColor(); @@ -41,7 +41,37 @@ void FCogWindowWidgets::EndTableTooltip() } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::BeginItemTableTooltip() +bool FCogWidgets::BeginItemTooltipWrappedText() +{ + const bool result = ImGui::BeginItemTooltip(); + if (result == false) + { return false; } + + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + return true; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWidgets::EndItemTooltipWrappedText() +{ + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogWidgets::ItemTooltipWrappedText(const char* InText) +{ + const bool result = BeginItemTooltipWrappedText(); + if (result) + { + ImGui::TextUnformatted(InText); + EndItemTooltipWrappedText(); + } + return result; +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogWidgets::BeginItemTableTooltip() { if (ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort) == false) { return false; } @@ -50,13 +80,13 @@ bool FCogWindowWidgets::BeginItemTableTooltip() } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::EndItemTableTooltip() +void FCogWidgets::EndItemTableTooltip() { return EndTableTooltip(); } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::ThinSeparatorText(const char* Label) +void FCogWidgets::ThinSeparatorText(const char* Label) { ImGui::PushStyleVar(ImGuiStyleVar_SeparatorTextBorderSize, 2); ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(100, 100, 100, 255)); @@ -68,7 +98,7 @@ void FCogWindowWidgets::ThinSeparatorText(const char* Label) } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::DarkCollapsingHeader(const char* InLabel, ImGuiTreeNodeFlags InFlags) +bool FCogWidgets::DarkCollapsingHeader(const char* InLabel, ImGuiTreeNodeFlags InFlags) { ImGui::PushStyleColor(ImGuiCol_Header, IM_COL32(66, 66, 66, 79)); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, IM_COL32(62, 62, 62, 204)); @@ -79,7 +109,7 @@ bool FCogWindowWidgets::DarkCollapsingHeader(const char* InLabel, ImGuiTreeNodeF } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::ProgressBarCentered(float Fraction, const ImVec2& Size, const char* Overlay) +void FCogWidgets::ProgressBarCentered(float Fraction, const ImVec2& Size, const char* Overlay) { ImGuiWindow* window = FCogImguiHelper::GetCurrentWindow(); if (window->SkipItems) @@ -99,7 +129,6 @@ void FCogWindowWidgets::ProgressBarCentered(float Fraction, const ImVec2& Size, Fraction = ImSaturate(Fraction); ImGui::RenderFrame(bb.Min, bb.Max, ImGui::GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); - const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, Fraction), bb.Max.y); ImGui::RenderRectFilledRangeH(window->DrawList, bb, ImGui::GetColorU32(ImGuiCol_PlotHistogram), 0.0f, Fraction, style.FrameRounding); // Default displaying the fraction as percentage string, but user can override it @@ -110,7 +139,7 @@ void FCogWindowWidgets::ProgressBarCentered(float Fraction, const ImVec2& Size, Overlay = overlay_buf; } - ImVec2 overlay_size = ImGui::CalcTextSize(Overlay, NULL); + ImVec2 overlay_size = ImGui::CalcTextSize(Overlay, nullptr); if (overlay_size.x > 0.0f) { @@ -118,17 +147,16 @@ void FCogWindowWidgets::ProgressBarCentered(float Fraction, const ImVec2& Size, ImVec2 pos2(pos1.x + 1, pos1.y + 1); ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::RenderTextClipped(pos2, bb.Max, Overlay, NULL, &overlay_size, ImVec2(0.0f, 0.5f), &bb); + ImGui::RenderTextClipped(pos2, bb.Max, Overlay, nullptr, &overlay_size, ImVec2(0.0f, 0.5f), &bb); ImGui::PopStyleColor(); - ImGui::RenderTextClipped(pos1, bb.Max, Overlay, NULL, &overlay_size, ImVec2(0.0f, 0.5f), &bb); + ImGui::RenderTextClipped(pos1, bb.Max, Overlay, nullptr, &overlay_size, ImVec2(0.0f, 0.5f), &bb); } } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::ToggleMenuButton(bool* Value, const char* Text, const ImVec4& TrueColor) +bool FCogWidgets::ToggleMenuButton(bool* Value, const char* Text, const ImVec4& TrueColor) { - bool IsPressed = false; bool IsTrue = *Value; if (IsTrue) { @@ -141,7 +169,7 @@ bool FCogWindowWidgets::ToggleMenuButton(bool* Value, const char* Text, const Im ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); } - IsPressed = ImGui::Button(Text); + bool IsPressed = ImGui::Button(Text); if (IsPressed) { *Value = !*Value; @@ -160,13 +188,13 @@ bool FCogWindowWidgets::ToggleMenuButton(bool* Value, const char* Text, const Im } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::ToggleButton(bool* Value, const char* Text, const ImVec4& TrueColor, const ImVec4& FalseColor, const ImVec2& Size) +bool FCogWidgets::ToggleButton(bool* Value, const char* Text, const ImVec4& TrueColor, const ImVec4& FalseColor, const ImVec2& Size) { return ToggleButton(Value, Text, Text, TrueColor, FalseColor, Size); } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::ToggleButton(bool* Value, const char* TextTrue, const char* TextFalse, const ImVec4& TrueColor, const ImVec4& FalseColor, const ImVec2& Size) +bool FCogWidgets::ToggleButton(bool* Value, const char* TextTrue, const char* TextFalse, const ImVec4& TrueColor, const ImVec4& FalseColor, const ImVec2& Size) { bool IsPressed = false; if (*Value) @@ -201,7 +229,7 @@ bool FCogWindowWidgets::ToggleButton(bool* Value, const char* TextTrue, const ch } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::SliderWithReset(const char* Name, float* Value, float Min, float Max, const float& ResetValue, const char* Format) +void FCogWidgets::SliderWithReset(const char* Name, float* Value, float Min, float Max, const float& ResetValue, const char* Format) { ImGui::SliderFloat(Name, Value, Min, Max, Format); @@ -223,7 +251,7 @@ void FCogWindowWidgets::SliderWithReset(const char* Name, float* Value, float Mi } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::HelpMarker(const char* Text) +void FCogWidgets::HelpMarker(const char* Text) { ImGui::TextDisabled("(?)"); if (ImGui::IsItemHovered()) @@ -237,22 +265,22 @@ void FCogWindowWidgets::HelpMarker(const char* Text) } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::PushStyleCompact() +void FCogWidgets::PushStyleCompact() { ImGuiStyle& style = ImGui::GetStyle(); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.WindowPadding.x * 0.60f, (float)(int)(style.WindowPadding.y * 0.60f))); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x * 0.60f, (float)(int)(style.FramePadding.y * 0.60f))); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 0.60f, (float)(int)(style.ItemSpacing.y * 0.60f))); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.WindowPadding.x * 0.60f, static_cast(style.WindowPadding.y * 0.60f))); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x * 0.60f, static_cast(style.FramePadding.y * 0.60f))); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x * 0.60f, static_cast(style.ItemSpacing.y * 0.60f))); } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::PopStyleCompact() +void FCogWidgets::PopStyleCompact() { ImGui::PopStyleVar(3); } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::AddTextWithShadow(ImDrawList* DrawList, const ImVec2& Position, ImU32 Color, const char* TextBegin, const char* TextEnd /*= NULL*/) +void FCogWidgets::AddTextWithShadow(ImDrawList* DrawList, const ImVec2& Position, ImU32 Color, const char* TextBegin, const char* TextEnd /*= NULL*/) { float Alpha = ImGui::ColorConvertU32ToFloat4(Color).w; DrawList->AddText(Position + ImVec2(1.0f, 1.0f), ImGui::ColorConvertFloat4ToU32(ImVec4(0, 0, 0, Alpha)), TextBegin, TextEnd); @@ -260,68 +288,100 @@ void FCogWindowWidgets::AddTextWithShadow(ImDrawList* DrawList, const ImVec2& Po } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::SearchBar(ImGuiTextFilter& Filter, float Width /*= -1*/) +bool FCogWidgets::SearchBar(const char* InLabel, ImGuiTextFilter& InFilter, float InWidth /*= -1*/) { - const ImGuiWindow* Window = FCogImguiHelper::GetCurrentWindow(); - const ImVec2 Pos1 = Window->DC.CursorPos; - Filter.Draw("##Filter", Width); - const ImVec2 Pos2 = Window->DC.CursorPosPrevLine; - - if (ImGui::IsItemActive() == false && Filter.Filters.empty()) + if (InWidth != 0.0f) { - static const char* Text = "Search"; - const float Height = ImGui::GetFrameHeight(); - const ImGuiContext& g = *ImGui::GetCurrentContext(); - const ImVec2 Min = Pos1 + ImVec2(g.Style.ItemSpacing.x, 0.0f); - const ImVec2 Max = Pos2 + ImVec2(-g.Style.ItemSpacing.x, Height); - const ImRect BB(Min, Max); - const ImVec2 TextSize = ImGui::CalcTextSize(Text, nullptr); - ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 128)); - ImGui::RenderTextClipped(Min, Max, Text, nullptr, &TextSize, ImVec2(0.0f, 0.5f), &BB); - ImGui::PopStyleColor(); + ImGui::SetNextItemWidth(InWidth); } + // + bool value_changed = ImGui::InputTextWithHint(InLabel, "Search", InFilter.InputBuf, IM_ARRAYSIZE(InFilter.InputBuf)); + if (value_changed) + { + InFilter.Build(); + } + return value_changed; } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::PushBackColor(const ImVec4& Color) +void FCogWidgets::PushButtonBackColor(const ImVec4& Color) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(Color.x, Color.y, Color.z, Color.w * 0.25f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(Color.x, Color.y, Color.z, Color.w * 0.3f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(Color.x, Color.y, Color.z, Color.w * 0.5f)); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWidgets::PopButtonBackColor() +{ + ImGui::PopStyleColor(3); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWidgets::PushFrameBackColor(const ImVec4& Color) +{ ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(Color.x, Color.y, Color.z, Color.w * 0.25f)); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(Color.x, Color.y, Color.z, Color.w * 0.3f)); ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(Color.x, Color.y, Color.z, Color.w * 0.5f)); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWidgets::PopFrameBackColor() +{ + ImGui::PopStyleColor(3); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWidgets::PushSliderBackColor(const ImVec4& Color) +{ ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(Color.x, Color.y, Color.z, Color.w * 0.8f)); ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, ImVec4(Color.x, Color.y, Color.z, Color.w * 1.0f)); ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(Color.x, Color.y, Color.z, Color.w * 0.8f)); } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::PopBackColor() +void FCogWidgets::PopSliderBackColor() { - ImGui::PopStyleColor(9); + ImGui::PopStyleColor(3); } //-------------------------------------------------------------------------------------------------------------------------- -float FCogWindowWidgets::GetShortWidth() +void FCogWidgets::PushBackColor(const ImVec4& Color) +{ + PushButtonBackColor(Color); + PushFrameBackColor(Color); + PushSliderBackColor(Color); +} + + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWidgets::PopBackColor() +{ + PopSliderBackColor(); + PopFrameBackColor(); + PopButtonBackColor(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +float FCogWidgets::GetShortWidth() { return ImGui::GetFontSize() * 10; } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::SetNextItemToShortWidth() +void FCogWidgets::SetNextItemToShortWidth() { ImGui::SetNextItemWidth(GetShortWidth()); } //-------------------------------------------------------------------------------------------------------------------------- -float FCogWindowWidgets::GetFontWidth() +float FCogWidgets::GetFontWidth() { return ImGui::GetFontSize() * 0.5f; } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::ComboboxEnum(const char* Label, UObject* Object, const char* FieldName, uint8* PointerToEnumValue) +bool FCogWidgets::ComboboxEnum(const char* Label, const UObject* Object, const char* FieldName, uint8* PointerToEnumValue) { const FEnumProperty* EnumProperty = CastField(Object->GetClass()->FindPropertyByName(FName(FieldName))); if (EnumProperty == nullptr) @@ -333,7 +393,7 @@ bool FCogWindowWidgets::ComboboxEnum(const char* Label, UObject* Object, const c } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::ComboboxEnum(const char* Label, const FEnumProperty* EnumProperty, uint8* PointerToValue) +bool FCogWidgets::ComboboxEnum(const char* Label, const FEnumProperty* EnumProperty, uint8* PointerToValue) { bool HasChanged = false; @@ -351,7 +411,7 @@ bool FCogWindowWidgets::ComboboxEnum(const char* Label, const FEnumProperty* Enu } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::ComboboxEnum(const char* Label, UEnum* Enum, int64 CurrentValue, int64& NewValue) +bool FCogWidgets::ComboboxEnum(const char* Label, const UEnum* Enum, int64 CurrentValue, int64& NewValue) { bool HasChanged = false; @@ -391,7 +451,7 @@ bool FCogWindowWidgets::ComboboxEnum(const char* Label, UEnum* Enum, int64 Curre } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::CheckBoxState(const char* Label, ECheckBoxState& State, bool ShowTooltip) +bool FCogWidgets::CheckBoxState(const char* Label, ECheckBoxState& State, bool ShowTooltip) { const char* TooltipText = "Invalid"; @@ -454,14 +514,18 @@ bool FCogWindowWidgets::CheckBoxState(const char* Label, ECheckBoxState& State, } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::InputKey(const char* Label, FCogImGuiKeyInfo& KeyInfo) +bool FCogWidgets::InputChord(const char* Label, FInputChord& InInputChord) { ImGui::PushID(Label); ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted(Label); + ImGui::BeginDisabled(); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); + ImGui::InputText("##Shortcut", const_cast(Label), IM_ARRAYSIZE(Label)); + ImGui::EndDisabled(); - const bool HasChanged = InputKey(KeyInfo); + ImGui::SameLine(); + const bool HasChanged = InputChord(InInputChord); ImGui::PopID(); @@ -469,19 +533,62 @@ bool FCogWindowWidgets::InputKey(const char* Label, FCogImGuiKeyInfo& KeyInfo) } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::InputKey(FCogImGuiKeyInfo& KeyInfo) +bool FCogWidgets::InputChord(FInputChord& InInputChord) { - static TArray AllKeys; - if (AllKeys.IsEmpty()) - { - EKeys::GetAllKeys(AllKeys); - } - bool HasKeyChanged = false; - ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6); - if (ImGui::BeginCombo("##Key", TCHAR_TO_ANSI(*KeyInfo.Key.ToString()), ImGuiComboFlags_HeightLarge)) + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); + HasKeyChanged |= Key(InInputChord.Key); + + bool Value = false; + + ImGui::SameLine(); + Value = static_cast(InInputChord.bCtrl); + HasKeyChanged |= ImGui::Selectable("Ctrl", &Value, ImGuiSelectableFlags_None, ImVec2(ImGui::GetFontSize() * 2, 0)); + InInputChord.bCtrl = Value; + + ImGui::SameLine(); + Value = static_cast(InInputChord.bShift); + HasKeyChanged |= ImGui::Selectable("Shift", &Value, ImGuiSelectableFlags_None, ImVec2(ImGui::GetFontSize() * 3, 0)); + InInputChord.bShift = Value; + + ImGui::SameLine(); + Value = static_cast(InInputChord.bAlt); + HasKeyChanged |= ImGui::Selectable("Alt", &Value, ImGuiSelectableFlags_None, ImVec2(ImGui::GetFontSize() * 2, 0)); + InInputChord.bAlt = Value; + + ImGui::SameLine(); + Value = static_cast(InInputChord.bCmd); + HasKeyChanged |= ImGui::Selectable("Cmd", &Value, ImGuiSelectableFlags_None, ImVec2(ImGui::GetFontSize() * 2, 0)); + InInputChord.bCmd = Value; + + return HasKeyChanged; +} + + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogWidgets::Key(FKey& InKey) +{ + bool HasKeyChanged = false; + if (ImGui::BeginCombo("##Key", TCHAR_TO_ANSI(*InKey.ToString()), ImGuiComboFlags_HeightLarge)) { + { + bool IsSelected = InKey == FKey(); + if (ImGui::Selectable("None", IsSelected)) + { + InKey = FKey(); + HasKeyChanged = true; + } + } + + ImGui::Separator(); + + static TArray AllKeys; + if (AllKeys.IsEmpty()) + { + EKeys::GetAllKeys(AllKeys); + } + for (int32 i = 0; i < AllKeys.Num(); ++i) { const FKey Key = AllKeys[i]; @@ -490,62 +597,98 @@ bool FCogWindowWidgets::InputKey(FCogImGuiKeyInfo& KeyInfo) continue; } - bool IsSelected = KeyInfo.Key == Key; + bool IsSelected = InKey == Key; if (ImGui::Selectable(TCHAR_TO_ANSI(*Key.ToString()), IsSelected)) { - KeyInfo.Key = Key; + InKey = Key; HasKeyChanged = true; } } ImGui::EndCombo(); } - ImGui::SameLine(); - HasKeyChanged |= CheckBoxState("Ctrl", KeyInfo.Ctrl); - - ImGui::SameLine(); - HasKeyChanged |= CheckBoxState("Shift", KeyInfo.Shift); - - ImGui::SameLine(); - HasKeyChanged |= CheckBoxState("Alt", KeyInfo.Alt); - - ImGui::SameLine(); - HasKeyChanged |= CheckBoxState("Cmd", KeyInfo.Cmd); - return HasKeyChanged; } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::KeyBind(FKeyBind& KeyBind) +bool FCogWidgets::KeyBind(FKeyBind& InKeyBind) { static char Buffer[256] = ""; - const auto Str = StringCast(*KeyBind.Command); + const auto Str = StringCast(*InKeyBind.Command); ImStrncpy(Buffer, Str.Get(), IM_ARRAYSIZE(Buffer)); + bool Disable = !InKeyBind.bDisabled; + if (ImGui::Checkbox("##Disable", &Disable)) + { + InKeyBind.bDisabled = !Disable; + } + if (InKeyBind.bDisabled) + { + ImGui::SetItemTooltip("Enable command"); + } + else + { + ImGui::SetItemTooltip("Disable command"); + } + + if (InKeyBind.bDisabled) + { + ImGui::BeginDisabled(); + } + + ImGui::SameLine(); + bool HasChanged = false; ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); if (ImGui::InputText("##Command", Buffer, IM_ARRAYSIZE(Buffer))) { - KeyBind.Command = FString(Buffer); + InKeyBind.Command = FString(Buffer); HasChanged = true; } - FCogImGuiKeyInfo KeyInfo; - FCogImguiInputHelper::KeyBindToKeyInfo(KeyBind, KeyInfo); - ImGui::SameLine(); - if (InputKey(KeyInfo)) + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6); + HasChanged |= Key(InKeyBind.Key); + { - HasChanged = true; - FCogImguiInputHelper::KeyInfoToKeyBind(KeyInfo, KeyBind); + ImGui::SameLine(); + ECheckBoxState State = FCogImguiInputHelper::MakeCheckBoxState(InKeyBind.Control, InKeyBind.bIgnoreCtrl); + HasChanged |= CheckBoxState("Ctrl", State); + BREAK_CHECKBOX_STATE(State, InKeyBind.Control, InKeyBind.bIgnoreCtrl); } + { + ImGui::SameLine(); + ECheckBoxState State = FCogImguiInputHelper::MakeCheckBoxState(InKeyBind.Shift, InKeyBind.bIgnoreShift); + HasChanged |= CheckBoxState("Shift", State); + BREAK_CHECKBOX_STATE(State, InKeyBind.Shift, InKeyBind.bIgnoreShift); + } + + { + ImGui::SameLine(); + ECheckBoxState State = FCogImguiInputHelper::MakeCheckBoxState(InKeyBind.Alt, InKeyBind.bIgnoreAlt); + HasChanged |= CheckBoxState("Alt", State); + BREAK_CHECKBOX_STATE(State, InKeyBind.Alt, InKeyBind.bIgnoreAlt); + } + + { + ImGui::SameLine(); + ECheckBoxState State = FCogImguiInputHelper::MakeCheckBoxState(InKeyBind.Cmd, InKeyBind.bIgnoreCmd); + HasChanged |= CheckBoxState("Cmd", State); + BREAK_CHECKBOX_STATE(State, InKeyBind.Cmd, InKeyBind.bIgnoreCmd); + } + + if (InKeyBind.bDisabled) + { + ImGui::EndDisabled(); + } + return HasChanged; } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::ButtonWithTooltip(const char* Text, const char* Tooltip) +bool FCogWidgets::ButtonWithTooltip(const char* Text, const char* Tooltip) { bool IsPressed = ImGui::Button(Text); @@ -558,7 +701,7 @@ bool FCogWindowWidgets::ButtonWithTooltip(const char* Text, const char* Tooltip) } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::DeleteArrayItemButton() +bool FCogWidgets::DeleteArrayItemButton() { bool IsPressed = ImGui::Button("x"); @@ -571,7 +714,7 @@ bool FCogWindowWidgets::DeleteArrayItemButton() } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::MultiChoiceButton(const char* Label, bool IsSelected, const ImVec2& Size) +bool FCogWidgets::MultiChoiceButton(const char* Label, bool IsSelected, const ImVec2& Size) { if (IsSelected) { @@ -599,27 +742,27 @@ bool FCogWindowWidgets::MultiChoiceButton(const char* Label, bool IsSelected, co } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::MultiChoiceButtonsInt(TArray& Values, int32& Value, const ImVec2& Size) +bool FCogWidgets::MultiChoiceButtonsInt(TArray& InValues, int32& InCurrentValue, const ImVec2& InSize, bool InInline) { ImGuiStyle& Style = ImGui::GetStyle(); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(Style.WindowPadding.x * 0.40f, (float)(int)(Style.WindowPadding.y * 0.60f))); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(Style.FramePadding.x * 0.40f, (float)(int)(Style.FramePadding.y * 0.60f))); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(Style.ItemSpacing.x * 0.30f, (float)(int)(Style.ItemSpacing.y * 0.60f))); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(Style.WindowPadding.x * 0.40f, static_cast(Style.WindowPadding.y * 0.60f))); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(Style.FramePadding.x * 0.40f, static_cast(Style.FramePadding.y * 0.60f))); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(Style.ItemSpacing.x * 0.30f, static_cast(Style.ItemSpacing.y * 0.60f))); ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(255, 255, 255, 180)); bool IsPressed = false; - for (int32 i = 0; i < Values.Num(); ++i) + for (int32 i = 0; i < InValues.Num(); ++i) { - int32 ButtonValue = Values[i]; + int32 ButtonValue = InValues[i]; const auto Text = StringCast(*FString::Printf(TEXT("%d"), ButtonValue)); - if (MultiChoiceButton(Text.Get(), ButtonValue == Value, Size)) + if (MultiChoiceButton(Text.Get(), ButtonValue == InCurrentValue, InSize)) { IsPressed = true; - Value = ButtonValue; + InCurrentValue = ButtonValue; } - if (i < Values.Num() - 1) + if (InInline && i < InValues.Num() - 1) { ImGui::SameLine(); } @@ -632,27 +775,42 @@ bool FCogWindowWidgets::MultiChoiceButtonsInt(TArray& Values, int32& Valu } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::MultiChoiceButtonsFloat(TArray& Values, float& Value, const ImVec2& Size) +FString FCogWidgets::RemoveFirstZero(const FString& InText) +{ + return InText.Replace(TEXT("0."), TEXT(".")); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FString FCogWidgets::FormatSmallFloat(float InValue) +{ + return RemoveFirstZero(FString::Printf(TEXT("%g"), InValue)); +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogWidgets::MultiChoiceButtonsFloat(TArray& InValues, float& InValue, const ImVec2& InSize, bool InInline, float InTolerance) { ImGuiStyle& Style = ImGui::GetStyle(); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(Style.WindowPadding.x * 0.40f, (float)(int)(Style.WindowPadding.y * 0.60f))); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(Style.FramePadding.x * 0.40f, (float)(int)(Style.FramePadding.y * 0.60f))); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(Style.ItemSpacing.x * 0.30f, (float)(int)(Style.ItemSpacing.y * 0.60f))); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(Style.WindowPadding.x * 0.40f, static_cast(Style.WindowPadding.y * 0.60f))); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(Style.FramePadding.x * 0.40f, static_cast(Style.FramePadding.y * 0.60f))); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(Style.ItemSpacing.x * 0.30f, static_cast(Style.ItemSpacing.y * 0.60f))); ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(255, 255, 255, 180)); bool IsPressed = false; - for (int32 i = 0; i < Values.Num(); ++i) + for (int32 i = 0; i < InValues.Num(); ++i) { - float ButtonValue = Values[i]; + float ButtonValue = InValues[i]; - const auto Text = StringCast(*FString::Printf(TEXT("%g"), ButtonValue).Replace(TEXT("0."), TEXT("."))); - if (MultiChoiceButton(Text.Get(), ButtonValue == Value, Size)) + const auto Text = StringCast(*FormatSmallFloat(ButtonValue)); + + ImGui::PushID(i); + if (MultiChoiceButton(Text.Get(), FMath::IsNearlyEqual(ButtonValue, InValue, InTolerance) , InSize)) { IsPressed = true; - Value = ButtonValue; + InValue = ButtonValue; } + ImGui::PopID(); - if (i < Values.Num() - 1) + if (InInline && i < InValues.Num() - 1) { ImGui::SameLine(); } @@ -665,41 +823,37 @@ bool FCogWindowWidgets::MultiChoiceButtonsFloat(TArray& Values, float& Va } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::ComboCollisionChannel(const char* Label, ECollisionChannel& Channel) +bool FCogWidgets::ComboTraceChannel(const char* Label, ECollisionChannel& Channel) { FColor ChannelColors[ECC_MAX]; FCogDebug::GetDebugChannelColors(ChannelColors); - FName SelectedChannelName; const UCollisionProfile* CollisionProfile = UCollisionProfile::Get(); - if (CollisionProfile != nullptr) - { - SelectedChannelName = CollisionProfile->ReturnChannelNameFromContainerIndex(Channel); - } + if (CollisionProfile == nullptr) + { return false; } + const FName SelectedChannelName = CollisionProfile->ReturnChannelNameFromContainerIndex(Channel); + bool Result = false; if (ImGui::BeginCombo(Label, TCHAR_TO_ANSI(*SelectedChannelName.ToString()), ImGuiComboFlags_HeightLarge)) { - for (int32 ChannelIndex = 0; ChannelIndex < (int32)ECC_OverlapAll_Deprecated; ++ChannelIndex) + for (int32 ChannelIndex = 0; ChannelIndex < static_cast(ECC_OverlapAll_Deprecated); ++ChannelIndex) { - FColor Color = ChannelColors[ChannelIndex]; - if (Color == FColor::Transparent) - { - continue; - } - + if (CollisionProfile->ConvertToTraceType(static_cast(ChannelIndex)) == TraceTypeQuery_MAX) + { continue; } + ImGui::PushID(ChannelIndex); - const FName ChannelName = CollisionProfile->ReturnChannelNameFromContainerIndex(ChannelIndex); - + FColor Color = ChannelColors[ChannelIndex]; FCogImguiHelper::ColorEdit4("Color", Color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel); ImGui::SameLine(); + const FName ChannelName = CollisionProfile->ReturnChannelNameFromContainerIndex(ChannelIndex); if (ChannelName.IsValid()) { if (ImGui::Selectable(TCHAR_TO_ANSI(*ChannelName.ToString()))) { - Channel = (ECollisionChannel)ChannelIndex; + Channel = static_cast(ChannelIndex); Result = true; } } @@ -713,26 +867,26 @@ bool FCogWindowWidgets::ComboCollisionChannel(const char* Label, ECollisionChann } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::CollisionProfileChannel(const UCollisionProfile& CollisionProfile, const int32 ChannelIndex, FColor& ChannelColor, int32& Channels) +bool FCogWidgets::CollisionProfileChannel(const UCollisionProfile& InCollisionProfile, const int32 InChannelIndex, FColor& InChannelColor, int32& InChannels) { bool Result = false; - FCogImguiHelper::ColorEdit4("Color", ChannelColor, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel); + FCogImguiHelper::ColorEdit4("Color", InChannelColor, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel); ImGui::SameLine(); - bool IsCollisionActive = (Channels & ECC_TO_BITFIELD(ChannelIndex)) > 0; - const FName ChannelName = CollisionProfile.ReturnChannelNameFromContainerIndex(ChannelIndex); + bool IsCollisionActive = (InChannels & ECC_TO_BITFIELD(InChannelIndex)) > 0; + const FName ChannelName = InCollisionProfile.ReturnChannelNameFromContainerIndex(InChannelIndex); if (ImGui::Checkbox(TCHAR_TO_ANSI(*ChannelName.ToString()), &IsCollisionActive)) { Result = true; if (IsCollisionActive) { - Channels |= ECC_TO_BITFIELD(ChannelIndex); + InChannels |= ECC_TO_BITFIELD(InChannelIndex); } else { - Channels &= ~ECC_TO_BITFIELD(ChannelIndex); + InChannels &= ~ECC_TO_BITFIELD(InChannelIndex); } } @@ -740,29 +894,36 @@ bool FCogWindowWidgets::CollisionProfileChannel(const UCollisionProfile& Collisi } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::CollisionProfileChannels(int32& Channels) +bool FCogWidgets::CollisionProfileChannels(int32& OutChannels) +{ + bool Result = false; + ImGui::SeparatorText("Trace Type"); + Result |= CollisionTraceChannels(OutChannels); + ImGui::SeparatorText("Object Type"); + Result |= CollisionObjectTypeChannels(OutChannels); + return Result; +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogWidgets::CollisionTraceChannels(int32& OutChannels) { const UCollisionProfile* CollisionProfile = UCollisionProfile::Get(); if (CollisionProfile == nullptr) - { - return false; - } - + { return false; } + FColor ChannelColors[ECC_MAX]; FCogDebug::GetDebugChannelColors(ChannelColors); bool Result = false; - for (int32 ChannelIndex = 0; ChannelIndex < (int32)ECC_OverlapAll_Deprecated; ++ChannelIndex) + for (int32 ChannelIndex = 0; ChannelIndex < static_cast(ECC_OverlapAll_Deprecated); ++ChannelIndex) { - FColor Color = ChannelColors[ChannelIndex]; - if (Color == FColor::Transparent) - { - continue; - } - + if (CollisionProfile->ConvertToTraceType(static_cast(ChannelIndex)) == TraceTypeQuery_MAX) + { continue; } + ImGui::PushID(ChannelIndex); - Result |= CollisionProfileChannel(*CollisionProfile, ChannelIndex, Color, Channels); + FColor Color = ChannelColors[ChannelIndex]; + Result |= CollisionProfileChannel(*CollisionProfile, ChannelIndex, Color, OutChannels); ImGui::PopID(); } @@ -770,7 +931,33 @@ bool FCogWindowWidgets::CollisionProfileChannels(int32& Channels) } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::ActorsListWithFilters(AActor*& NewSelection, const UWorld& World, const TArray>& ActorClasses, int32& SelectedActorClassIndex, ImGuiTextFilter* Filter, const APawn* LocalPlayerPawn, const FCogWindowActorContextMenuFunction& ContextMenuFunction) +bool FCogWidgets::CollisionObjectTypeChannels(int32& OutChannels) +{ + const UCollisionProfile* CollisionProfile = UCollisionProfile::Get(); + if (CollisionProfile == nullptr) + { return false; } + + FColor ChannelColors[ECC_MAX]; + FCogDebug::GetDebugChannelColors(ChannelColors); + + bool Result = false; + + for (int32 ChannelIndex = 0; ChannelIndex < static_cast(ECC_OverlapAll_Deprecated); ++ChannelIndex) + { + if (CollisionProfile->ConvertToObjectType(static_cast(ChannelIndex)) == TraceTypeQuery_MAX) + { continue; } + + ImGui::PushID(ChannelIndex); + FColor Color = ChannelColors[ChannelIndex]; + Result |= CollisionProfileChannel(*CollisionProfile, ChannelIndex, Color, OutChannels); + ImGui::PopID(); + } + + return Result; +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogWidgets::ActorsListWithFilters(AActor*& NewSelection, const UWorld& World, const TArray>& ActorClasses, int32& SelectedActorClassIndex, ImGuiTextFilter* Filter, const APawn* LocalPlayerPawn, const FCogWindowActorContextMenuFunction& ContextMenuFunction) { TSubclassOf SelectedClass = AActor::StaticClass(); if (ActorClasses.IsValidIndex(SelectedActorClassIndex)) @@ -806,7 +993,7 @@ bool FCogWindowWidgets::ActorsListWithFilters(AActor*& NewSelection, const UWorl if (Filter != nullptr) { ImGui::SameLine(); - SearchBar(*Filter); + SearchBar("##Filter", *Filter); AddSeparator = true; } @@ -826,7 +1013,7 @@ bool FCogWindowWidgets::ActorsListWithFilters(AActor*& NewSelection, const UWorl } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::ActorsList(AActor*& NewSelection, const UWorld& World, const TSubclassOf ActorClass, const ImGuiTextFilter* Filter, const APawn* LocalPlayerPawn, const FCogWindowActorContextMenuFunction& ContextMenuFunction) +bool FCogWidgets::ActorsList(AActor*& NewSelection, const UWorld& World, const TSubclassOf& ActorClass, const ImGuiTextFilter* Filter, const APawn* LocalPlayerPawn, const FCogWindowActorContextMenuFunction& ContextMenuFunction) { TArray Actors; for (TActorIterator It(&World, ActorClass); It; ++It) @@ -836,7 +1023,7 @@ bool FCogWindowWidgets::ActorsList(AActor*& NewSelection, const UWorld& World, c bool AddActor = true; if (Filter != nullptr && Filter->IsActive()) { - const auto ActorName = StringCast(*FCogWindowHelper::GetActorName(*Actor)); + const auto ActorName = StringCast(*FCogHelper::GetActorName(*Actor)); if (Filter != nullptr && Filter->PassFilter(ActorName.Get()) == false) { AddActor = false; @@ -867,7 +1054,7 @@ bool FCogWindowWidgets::ActorsList(AActor*& NewSelection, const UWorld& World, c ImGui::PushStyleColor(ImGuiCol_Text, Actor == LocalPlayerPawn ? IM_COL32(255, 255, 0, 255) : IM_COL32(255, 255, 255, 255)); const bool bIsSelected = Actor == FCogDebug::GetSelection(); - if (ImGui::Selectable(TCHAR_TO_ANSI(*FCogWindowHelper::GetActorName(*Actor)), bIsSelected)) + if (ImGui::Selectable(TCHAR_TO_ANSI(*FCogHelper::GetActorName(*Actor)), bIsSelected)) { //FCogDebug::SetSelection(&World, Actor); NewSelection = Actor; @@ -898,17 +1085,16 @@ bool FCogWindowWidgets::ActorsList(AActor*& NewSelection, const UWorld& World, c } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::MenuActorsCombo(const char* StrID, AActor*& NewSelection, const UWorld& World, TSubclassOf ActorClass, const FCogWindowActorContextMenuFunction& ContextMenuFunction) +bool FCogWidgets::MenuActorsCombo(const char* StrID, AActor*& NewSelection, const UWorld& World, const TSubclassOf& ActorClass, const FCogWindowActorContextMenuFunction& ContextMenuFunction) { int32 SelectedActorClassIndex = 0; const TArray ActorClasses = { ActorClass }; - AActor* Actor = nullptr; return MenuActorsCombo(StrID, NewSelection, World, ActorClasses, SelectedActorClassIndex, nullptr, nullptr, ContextMenuFunction); } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::MenuActorsCombo(const char* StrID, AActor*& NewSelection, const UWorld& World, const TArray>& ActorClasses, int32& SelectedActorClassIndex, ImGuiTextFilter* Filter, const APawn* LocalPlayerPawn, const FCogWindowActorContextMenuFunction& ContextMenuFunction) +bool FCogWidgets::MenuActorsCombo(const char* StrID, AActor*& NewSelection, const UWorld& World, const TArray>& ActorClasses, int32& SelectedActorClassIndex, ImGuiTextFilter* Filter, const APawn* LocalPlayerPawn, const FCogWindowActorContextMenuFunction& ContextMenuFunction) { bool Result = false; ImGui::PushID(StrID); @@ -936,7 +1122,7 @@ bool FCogWindowWidgets::MenuActorsCombo(const char* StrID, AActor*& NewSelection Selection = nullptr; } - const FString CurrentSelectionName = FCogWindowHelper::GetActorName(Selection); + const FString CurrentSelectionName = FCogHelper::GetActorName(Selection); if (ImGui::Button(TCHAR_TO_ANSI(*CurrentSelectionName), ImVec2(Width, 0.0f))) { ImGui::OpenPopup("ActorListPopup"); @@ -980,7 +1166,7 @@ bool FCogWindowWidgets::MenuActorsCombo(const char* StrID, AActor*& NewSelection } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::ActorContextMenu(AActor& Selection, const FCogWindowActorContextMenuFunction& ContextMenuFunction) +void FCogWidgets::ActorContextMenu(AActor& Selection, const FCogWindowActorContextMenuFunction& ContextMenuFunction) { if (ContextMenuFunction == nullptr) { @@ -996,7 +1182,7 @@ void FCogWindowWidgets::ActorContextMenu(AActor& Selection, const FCogWindowActo } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::ActorFrame(const AActor& Actor) +void FCogWidgets::ActorFrame(const AActor& Actor) { const APlayerController* PlayerController = Actor.GetWorld()->GetFirstPlayerController(); if (PlayerController == nullptr) @@ -1046,19 +1232,19 @@ void FCogWindowWidgets::ActorFrame(const AActor& Actor) } FVector2D ScreenPosMin, ScreenPosMax; - if (FCogWindowHelper::ComputeBoundingBoxScreenPosition(PlayerController, BoxOrigin, BoxExtent, ScreenPosMin, ScreenPosMax)) + if (FCogHelper::ComputeBoundingBoxScreenPosition(PlayerController, BoxOrigin, BoxExtent, ScreenPosMin, ScreenPosMax)) { const ImU32 Color = (&Actor == FCogDebug::GetSelection()) ? IM_COL32(255, 255, 255, 255) : IM_COL32(255, 255, 255, 128); if (ScreenPosMin != ScreenPosMax) { DrawList->AddRect(FCogImguiHelper::ToImVec2(ScreenPosMin) + Viewport->Pos, FCogImguiHelper::ToImVec2(ScreenPosMax) + Viewport->Pos, Color, 0.0f, 0, 1.0f); } - AddTextWithShadow(DrawList, FCogImguiHelper::ToImVec2(ScreenPosMin + FVector2D(0, -14.0f)) + Viewport->Pos, Color, TCHAR_TO_ANSI(*FCogWindowHelper::GetActorName(Actor))); + AddTextWithShadow(DrawList, FCogImguiHelper::ToImVec2(ScreenPosMin + FVector2D(0, -14.0f)) + Viewport->Pos, Color, TCHAR_TO_ANSI(*FCogHelper::GetActorName(Actor))); } } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::SmallButton(const char* Text, const ImVec4& Color) +void FCogWidgets::SmallButton(const char* Text, const ImVec4& Color) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(Color.x, Color.y, Color.z, Color.w * 0.6f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(Color.x, Color.y, Color.z, Color.w * 0.8f)); @@ -1068,27 +1254,43 @@ void FCogWindowWidgets::SmallButton(const char* Text, const ImVec4& Color) } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::InputText(const char* Text, FString& Value) +bool FCogWidgets::InputText(const char* Text, FString& Value, ImGuiInputTextFlags InFlags, ImGuiInputTextCallback InCallback, void* InUserData) { - static char Buffer[256] = ""; - ImStrncpy(Buffer, TCHAR_TO_ANSI(*Value), IM_ARRAYSIZE(Buffer)); + static char ValueBuffer[256] = ""; + ImStrncpy(ValueBuffer, TCHAR_TO_ANSI(*Value), IM_ARRAYSIZE(ValueBuffer)); - bool result = ImGui::InputText(Text, Buffer, IM_ARRAYSIZE(Buffer)); + bool result = ImGui::InputText(Text, ValueBuffer, IM_ARRAYSIZE(ValueBuffer), InFlags, InCallback, InUserData); if (result) { - Value = FString(Buffer); + Value = FString(ValueBuffer); } return result; } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::BeginRightAlign(const char* Id) +bool FCogWidgets::InputTextWithHint(const char* InText, const char* InHint, FString& InValue, ImGuiInputTextFlags InFlags, ImGuiInputTextCallback InCallback, void* InUserData) +{ + static char ValueBuffer[256] = ""; + ImStrncpy(ValueBuffer, TCHAR_TO_ANSI(*InValue), IM_ARRAYSIZE(ValueBuffer)); + + bool result = ImGui::InputTextWithHint(InText, InHint, ValueBuffer, IM_ARRAYSIZE(ValueBuffer), InFlags, InCallback, InUserData); + if (result) + { + InValue = FString(ValueBuffer); + } + + return result; +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogWidgets::BeginRightAlign(const char* Id) { if (ImGui::BeginTable(Id, 2, ImGuiTableFlags_SizingFixedFit, ImVec2(-1, 0))) { ImGui::TableSetupColumn("a", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TableNextColumn(); return true; @@ -1097,13 +1299,13 @@ bool FCogWindowWidgets::BeginRightAlign(const char* Id) } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::EndRightAlign() +void FCogWidgets::EndRightAlign() { ImGui::EndTable(); } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindowWidgets::MenuItemShortcut(const char* Id, const FString& Text) +void FCogWidgets::MenuItemShortcut(const char* Id, const FString& Text) { ImGui::SameLine(); if (BeginRightAlign(Id)) @@ -1118,7 +1320,7 @@ void FCogWindowWidgets::MenuItemShortcut(const char* Id, const FString& Text) } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::BrowseToAssetButton(const UObject* InAsset, const ImVec2& InSize) +bool FCogWidgets::BrowseToAssetButton(const UObject* InAsset, const ImVec2& InSize) { #if WITH_EDITOR @@ -1145,7 +1347,25 @@ bool FCogWindowWidgets::BrowseToAssetButton(const UObject* InAsset, const ImVec2 } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::BrowseToObjectAssetButton(const UObject* InObject, const ImVec2& InSize) +bool FCogWidgets::BrowseToAssetButton(const FAssetData& InAssetData, const ImVec2& InSize) +{ +#if WITH_EDITOR + + const bool result = ImGui::Button("Browse To Asset", InSize); + if (result) + { + IAssetTools::Get().SyncBrowserToAssets({ InAssetData }); + } + + return result; + +#else + return false; +#endif +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogWidgets::BrowseToObjectAssetButton(const UObject* InObject, const ImVec2& InSize) { #if WITH_EDITOR @@ -1164,7 +1384,7 @@ bool FCogWindowWidgets::BrowseToObjectAssetButton(const UObject* InObject, const } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::OpenAssetButton(const UObject* InAsset, const ImVec2& InSize) +bool FCogWidgets::OpenAssetButton(const UObject* InAsset, const ImVec2& InSize) { #if WITH_EDITOR @@ -1195,7 +1415,7 @@ bool FCogWindowWidgets::OpenAssetButton(const UObject* InAsset, const ImVec2& In } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogWindowWidgets::OpenObjectAssetButton(const UObject* InObject, const ImVec2& InSize) +bool FCogWidgets::OpenObjectAssetButton(const UObject* InObject, const ImVec2& InSize) { #if WITH_EDITOR @@ -1213,7 +1433,202 @@ bool FCogWindowWidgets::OpenObjectAssetButton(const UObject* InObject, const ImV } - +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWidgets::FloatArray(const char* InLabel, TArray& InArray, int32 InMaxEntries, const ImVec2& Size) +{ + ScalarArray(InLabel, ImGuiDataType_Float, InArray, InMaxEntries, Size); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWidgets::IntArray(const char* InLabel, TArray& InArray, int32 InMaxEntries, const ImVec2& Size) +{ + ScalarArray(InLabel, ImGuiDataType_S32, InArray, InMaxEntries, Size); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWidgets::RenderCloseButton(const ImVec2& InPos) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Tweak 1: Shrink hit-testing area if button covers an abnormally large proportion of the visible region. That's in order to facilitate moving the window away. (#3825) + // This may better be applied as a general hit-rect reduction mechanism for all widgets to ensure the area to move window is always accessible? + const ImRect bb(InPos, InPos + ImVec2(g.FontSize, g.FontSize)); + + ImU32 cross_col = ImGui::GetColorU32(ImGuiCol_Text); + ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); + float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; + window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f); + window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f); +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogWidgets::PickButton(const char* InLabel, const ImVec2& InSize, ImGuiButtonFlags InFlags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiID id = window->GetID(InLabel); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + InSize); + const float default_size = ImGui::GetFrameHeight(); + ImGui::ItemSize(InSize, (InSize.y >= default_size) ? g.Style.FramePadding.y : -1.0f); + if (!ImGui::ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, InFlags); + + // Render + const ImU32 bg_col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + const ImU32 text_col = ImGui::GetColorU32(ImGuiCol_Text); + ImGui::RenderNavCursor(bb, id); + ImGui::RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding); + + const ImVec2 center = bb.GetCenter(); + const float radius = window->DrawList->_Data->FontSize; + window->DrawList->AddCircle(center, radius * 0.4f, text_col, 0, 1); + window->DrawList->AddCircleFilled(center, radius * 0.15f, text_col); + + return pressed; +} + +//-------------------------------------------------------------------------------------------------------------------------- +ImVec2 FCogWidgets::ComputeScreenCornerLocation(const FVector2f& InAlignment, const FIntVector2& InPadding) +{ + return ComputeScreenCornerLocation(FCogImguiHelper::ToImVec2(InAlignment), FCogImguiHelper::ToImVec2(InPadding)); +} + +//-------------------------------------------------------------------------------------------------------------------------- +ImVec2 FCogWidgets::ComputeScreenCornerLocation(const ImVec2& InAlignment, const ImVec2& InPadding) +{ + const ImGuiViewport* Viewport = ImGui::GetMainViewport(); + if (Viewport == nullptr) + { return ImVec2(0, 0); } + + // +Padding for left, 0 for center, -Padding for left + // +Padding for top, 0 for center, -Padding for bottom + const ImVec2 Offset = (InAlignment * 2 - ImVec2(1.0f, 1.0f)) * InPadding; + + ImVec2 Position = Viewport->WorkPos + (InAlignment * Viewport->WorkSize) - Offset; + return Position; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FString FCogWidgets::GetStringAfterCharacter(const FString& InString, const TCHAR InChar) +{ + int32 Index = 0; + if (InString.FindChar(InChar, Index)) + { + return InString.RightChop(Index + 1); + } + + return FString(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FString FCogWidgets::FormatConfigName(const FString& InConfigName) +{ + return FName::NameToDisplayString(GetStringAfterCharacter(InConfigName, '_'), false); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FString FCogWidgets::FormatShortcutName(const FString& InShortcutName) +{ + return FName::NameToDisplayString(InShortcutName.Replace(TEXT("Shortcut_"), TEXT("")), false); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWidgets::TextInputChordProperty(UObject& InConfig, const FProperty& InInputChordProperty) +{ + const FInputChord* InputChord = InInputChordProperty.ContainerPtrToValuePtr(&InConfig); + if (InputChord == nullptr) + { return; } + + const auto Name = StringCast(*FormatShortcutName(InInputChordProperty.GetName())); + const auto Shortcut = StringCast(*FCogImguiInputHelper::InputChordToString(*InputChord)); + + ImGui::Text("%s", Name.Get()); + ImGui::SameLine(); + if (BeginRightAlign(Name.Get())) + { + ImGui::TextDisabled("%s", Shortcut.Get()); + EndRightAlign(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogWidgets::InputChordProperty(UObject& InConfig, const FProperty& InInputChordProperty) +{ + FInputChord* InputChord = InInputChordProperty.ContainerPtrToValuePtr(&InConfig); + if (InputChord == nullptr) + { return false; } + + const auto Name = StringCast(*FormatShortcutName(InInputChordProperty.GetName())); + + return FCogWidgets::InputChord(Name.Get(), *InputChord); +} + + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogWidgets::IsConfigContainingInputChords(const UObject& InConfig) +{ + for (TFieldIterator It(InConfig.GetClass()); It; ++It) + { + if (const FStructProperty* StructProperty = CastField(*It)) + { + if (StructProperty->Struct == FInputChord::StaticStruct()) + { + return true; + } + } + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogWidgets::AllInputChordsOfConfig(UObject& InConfig, FProperty** InModifiedProperty) +{ + bool HasChanged = false; + TArray Properties; + for (TFieldIterator It(InConfig.GetClass()); It; ++It) + { + if (FStructProperty* StructProperty = CastField(*It)) + { + if (StructProperty->Struct == FInputChord::StaticStruct()) + { + if (FCogWidgets::InputChordProperty(InConfig, *StructProperty)) + { + HasChanged = true; + if (InModifiedProperty != nullptr) + { + *InModifiedProperty = StructProperty; + } + } + } + } + } + + return HasChanged; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWidgets::TextOfAllInputChordsOfConfig(UObject& InConfig) +{ + TArray Properties; + for (TFieldIterator It(InConfig.GetClass()); It; ++It) + { + if (const FStructProperty* StructProperty = CastField(*It)) + { + if (StructProperty->Struct == FInputChord::StaticStruct()) + { + TextInputChordProperty(InConfig, *StructProperty); + } + } + } +} diff --git a/Plugins/Cog/Source/CogWindow/Private/CogWindow.cpp b/Plugins/Cog/Source/Cog/Private/CogWindow.cpp similarity index 57% rename from Plugins/Cog/Source/CogWindow/Private/CogWindow.cpp rename to Plugins/Cog/Source/Cog/Private/CogWindow.cpp index 045f886..7ffe71f 100644 --- a/Plugins/Cog/Source/CogWindow/Private/CogWindow.cpp +++ b/Plugins/Cog/Source/Cog/Private/CogWindow.cpp @@ -2,13 +2,27 @@ #include "CogDebug.h" #include "CogWindow_Settings.h" -#include "CogWindowManager.h" +#include "CogSubsystem.h" +#include "CogWidgets.h" #include "Engine/World.h" #include "imgui_internal.h" #include "GameFramework/Pawn.h" #include "GameFramework/PlayerController.h" #include "Engine/LocalPlayer.h" +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWindow::Initialize() +{ + ensure(bIsInitialized == false); + bIsInitialized = true; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWindow::Shutdown() +{ + bIsInitialized = false; +} + //-------------------------------------------------------------------------------------------------------------------------- void FCogWindow::SetFullName(const FString& InFullName) { @@ -52,59 +66,64 @@ bool FCogWindow::CheckEditorVisibility() return true; } +//-------------------------------------------------------------------------------------------------------------------------- + void FCogWindow::RenderMainMenuWidget() +{ +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWindow::RenderContextMenu() +{ + RenderSettings(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWindow::RenderSettings() +{ + if (bHasMenu) + { + ImGui::Checkbox("Show Menu", &bShowMenu); + } + + if (ImGui::Button("Reset Settings", ImVec2(ImGui::GetContentRegionAvail().x, 0))) + { + ResetConfig(); + } +} + //-------------------------------------------------------------------------------------------------------------------------- void FCogWindow::Render(float DeltaTime) { ImGuiWindowFlags WindowFlags = 0; - PreRender(WindowFlags); - - const FString WindowTitle = GetTitle() + "##" + Name; if (bHasMenu && bShowMenu) { WindowFlags |= ImGuiWindowFlags_MenuBar; } - if (bNoPadding) + PreBegin(WindowFlags); + + const FString WindowTitle = GetTitle() + "##" + Name; + const bool IsOpen = ImGui::Begin(StringCast(*WindowTitle).Get(), &bIsVisible, WindowFlags); + + PostBegin(); + + if (IsOpen) { - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - } - - if (ImGui::Begin(TCHAR_TO_ANSI(*WindowTitle), &bIsVisible, WindowFlags)) - { - if (bNoPadding) - { - ImGui::PopStyleVar(1); - } - - if (ImGui::BeginPopupContextWindow()) - { - if (bHasMenu) - { - ImGui::Checkbox("Show Menu", &bShowMenu); - } - - if (ImGui::Button("Reset Settings")) - { - ResetConfig(); - } - - ImGui::EndPopup(); - } - RenderContent(); - } - else - { - if (bNoPadding) + + if (bUseCustomContextMenu == false) { - ImGui::PopStyleVar(1); + if (ImGui::BeginPopupContextWindow()) + { + RenderContextMenu(); + ImGui::EndPopup(); + } } } - ImGui::End(); - - PostRender(); + + PostEnd(); } //-------------------------------------------------------------------------------------------------------------------------- @@ -127,19 +146,19 @@ void FCogWindow::GameTick(float DeltaTime) //-------------------------------------------------------------------------------------------------------------------------- void FCogWindow::SetSelection(AActor* NewSelection) { - if (CurrentSelection == NewSelection) - { - return; - } + AActor* OldActor = GetSelection(); + FCogDebug::SetSelection(NewSelection); - AActor* OldActor = CurrentSelection.Get(); - - CurrentSelection = NewSelection; OnSelectionChanged(OldActor, NewSelection); } //-------------------------------------------------------------------------------------------------------------------------- +AActor* FCogWindow::GetSelection() const +{ + return FCogDebug::GetSelection(); +} +//-------------------------------------------------------------------------------------------------------------------------- void FCogWindow::SetIsVisible(const bool Value) { if (bIsVisible == Value) @@ -189,13 +208,32 @@ ULocalPlayer* FCogWindow::GetLocalPlayer() const } //-------------------------------------------------------------------------------------------------------------------------- -UCogCommonConfig* FCogWindow::GetConfig(const TSubclassOf ConfigClass) const +UCogCommonConfig* FCogWindow::GetConfig(const TSubclassOf& InConfigClass, bool InResetConfigOnRequest) const { - return GetOwner()->GetConfig(ConfigClass); + UCogCommonConfig* Config = GetOwner()->GetConfig(InConfigClass); + + if (Config != nullptr && InResetConfigOnRequest) + { + ConfigsToResetOnRequest.AddUnique(Config); + } + + return Config; } //-------------------------------------------------------------------------------------------------------------------------- -const UObject* FCogWindow::GetAsset(const TSubclassOf AssetClass) const +void FCogWindow::ResetConfig() +{ + for (auto& Config : ConfigsToResetOnRequest) + { + if (Config != nullptr) + { + Config->Reset(); + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +const UObject* FCogWindow::GetAsset(const TSubclassOf& AssetClass) const { return GetOwner()->GetAsset(AssetClass); } @@ -205,3 +243,25 @@ UWorld* FCogWindow::GetWorld() const { return Owner->GetWorld(); } + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogWindow::IsWindowRenderedInMainMenu() +{ + return Owner->IsRenderingMainMenu(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +float FCogWindow::GetDpiScale() const +{ + return GetOwner()->GetContext().GetDpiScale(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWindow::RenderConfigShortcuts(UCogCommonConfig& InConfig) const +{ + FProperty* InModifiedProperty = nullptr; + if (FCogWidgets::AllInputChordsOfConfig(InConfig, &InModifiedProperty)) + { + GetOwner()->RebindShortcut(InConfig, *InModifiedProperty); + } +} \ No newline at end of file diff --git a/Plugins/Cog/Source/Cog/Private/CogWindow_Layouts.cpp b/Plugins/Cog/Source/Cog/Private/CogWindow_Layouts.cpp new file mode 100644 index 0000000..8650952 --- /dev/null +++ b/Plugins/Cog/Source/Cog/Private/CogWindow_Layouts.cpp @@ -0,0 +1,66 @@ +#include "CogWindow_Layouts.h" + +#include "CogImguiInputHelper.h" +#include "CogSubsystem.h" +#include "CogWindow_Settings.h" +#include "InputCoreTypes.h" + +//-------------------------------------------------------------------------------------------------------------------------- +FCogWindow_Layouts::FCogWindow_Layouts() +{ + bShowInMainMenu = false; + bHasMenu = false; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWindow_Layouts::RenderContent() +{ + const UPlayerInput* PlayerInput = FCogImguiInputHelper::GetPlayerInput(*GetWorld()); + if (PlayerInput == nullptr) + { + return; + } + + if (ImGui::MenuItem("Reset Window Layout")) + { + GetOwner()->ResetLayout(); + } + + ImGui::Separator(); + + UCogWindowConfig_Settings* Settings = GetOwner()->GetSettings(); + RenderLoadLayoutMenuItem(1, Settings->Shortcut_LoadLayout1); + RenderLoadLayoutMenuItem(2, Settings->Shortcut_LoadLayout2); + RenderLoadLayoutMenuItem(3, Settings->Shortcut_LoadLayout3); + RenderLoadLayoutMenuItem(4, Settings->Shortcut_LoadLayout4); + + ImGui::Separator(); + RenderSaveLayoutMenuItem(1, Settings->Shortcut_SaveLayout1); + RenderSaveLayoutMenuItem(2, Settings->Shortcut_SaveLayout2); + RenderSaveLayoutMenuItem(3, Settings->Shortcut_SaveLayout3); + RenderSaveLayoutMenuItem(4, Settings->Shortcut_SaveLayout4); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWindow_Layouts::RenderLoadLayoutMenuItem(int InLayoutIndex, const FInputChord& InInputChord) +{ + const auto Shortcut = StringCast(*FCogImguiInputHelper::InputChordToString(InInputChord)); + const auto Text = StringCast(*FString::Printf(TEXT("Load Layout %d"), InLayoutIndex)); + + if (ImGui::MenuItem(Text.Get(), Shortcut.Get())) + { + GetOwner()->LoadLayout(InLayoutIndex + 1); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWindow_Layouts::RenderSaveLayoutMenuItem(int InLayoutIndex, const FInputChord& InInputChord) +{ + const auto Shortcut = StringCast(*FCogImguiInputHelper::InputChordToString(InInputChord)); + const auto Text = StringCast(*FString::Printf(TEXT("Save Layout %d"), InLayoutIndex)); + + if (ImGui::MenuItem(Text.Get(), Shortcut.Get())) + { + GetOwner()->SaveLayout(InLayoutIndex + 1); + } +} \ No newline at end of file diff --git a/Plugins/Cog/Source/CogWindow/Private/CogWindow_Settings.cpp b/Plugins/Cog/Source/Cog/Private/CogWindow_Settings.cpp similarity index 50% rename from Plugins/Cog/Source/CogWindow/Private/CogWindow_Settings.cpp rename to Plugins/Cog/Source/Cog/Private/CogWindow_Settings.cpp index 10ee950..3940697 100644 --- a/Plugins/Cog/Source/CogWindow/Private/CogWindow_Settings.cpp +++ b/Plugins/Cog/Source/Cog/Private/CogWindow_Settings.cpp @@ -2,18 +2,24 @@ #include "CogImguiHelper.h" #include "CogImguiInputHelper.h" -#include "CogWindowManager.h" -#include "CogWindowWidgets.h" -#include "imgui.h" +#include "CogSubsystem.h" +#include "CogWidgets.h" #include "imgui.h" +#include "imgui_internal.h" #include "InputCoreTypes.h" +//-------------------------------------------------------------------------------------------------------------------------- +FCogWindow_Settings::FCogWindow_Settings() +{ + bShowInMainMenu = false; + bHasMenu = false; +} + //-------------------------------------------------------------------------------------------------------------------------- void FCogWindow_Settings::Initialize() { Super::Initialize(); - bHasMenu = false; Config = GetConfig(); @@ -38,6 +44,10 @@ void FCogWindow_Settings::PreSaveConfig() Super::PreSaveConfig(); ImGuiIO& IO = ImGui::GetIO(); + + if (Config == nullptr) + { return; } + Config->bNavEnableKeyboard = IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard; //Config->bNavEnableGamepad = IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad; //Config->bNavNoCaptureInput = IO.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard; @@ -49,14 +59,6 @@ void FCogWindow_Settings::PreSaveConfig() Config->bShareMouseWithGameplay = Context.GetShareMouseWithGameplay(); } -//-------------------------------------------------------------------------------------------------------------------------- -void FCogWindow_Settings::ResetConfig() -{ - Super::ResetConfig(); - - Config->Reset(); -} - //-------------------------------------------------------------------------------------------------------------------------- void FCogWindow_Settings::RenderContent() { @@ -77,8 +79,8 @@ void FCogWindow_Settings::RenderContent() { Context.SetEnableInput(bEnableInput); } - ImGui::SetItemTooltip("Enable ImGui inputs. When enabled the ImGui menu is shown and inputs are forwarded to ImGui."); - FCogWindowWidgets::MenuItemShortcut("EnableInputShortcut", FCogImguiInputHelper::CommandToString(PlayerInput, UCogWindowManager::ToggleInputCommand)); + FCogWidgets::ItemTooltipWrappedText("Enable ImGui inputs. When enabled the ImGui menu is shown and inputs are forwarded to ImGui."); + FCogWidgets::MenuItemShortcut("EnableInputShortcut", FCogImguiInputHelper::InputChordToString(Config->Shortcut_ToggleImguiInput)); //------------------------------------------------------------------------------------------- bool bShareKeyboard = Context.GetShareKeyboard(); @@ -86,7 +88,7 @@ void FCogWindow_Settings::RenderContent() { Context.SetShareKeyboard(bShareKeyboard); } - ImGui::SetItemTooltip("Forward the keyboard inputs to the game when ImGui does not need them."); + FCogWidgets::ItemTooltipWrappedText("Forward the keyboard inputs to the game when ImGui does not need them."); //------------------------------------------------------------------------------------------- bool bShareMouse = Context.GetShareMouse(); @@ -94,7 +96,7 @@ void FCogWindow_Settings::RenderContent() { Context.SetShareMouse(bShareMouse); } - ImGui::SetItemTooltip("Forward mouse inputs to the game when ImGui does not need them."); + FCogWidgets::ItemTooltipWrappedText("Forward mouse inputs to the game when ImGui does not need them."); //------------------------------------------------------------------------------------------- if (bShareMouse == false) @@ -107,7 +109,7 @@ void FCogWindow_Settings::RenderContent() { Context.SetShareMouseWithGameplay(bShareMouseWithGameplay); } - ImGui::SetItemTooltip("When disabled, mouse inputs are only forwarded to game menus. " + FCogWidgets::ItemTooltipWrappedText("When disabled, mouse inputs are only forwarded to game menus. " "When enabled, mouse inputs are also forwarded to the gameplay. Note that this mode: \n" " - Force the cursor to be visible.\n" " - Prevent the interaction of Cog's transform gizmos.\n" @@ -120,13 +122,23 @@ void FCogWindow_Settings::RenderContent() //------------------------------------------------------------------------------------------- ImGui::CheckboxFlags("Keyboard Navigation", &IO.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard); - ImGui::SetItemTooltip("Use the keyboard to navigate in ImGui windows with the following keys : Tab, Directional Arrows, Space, Enter."); + FCogWidgets::ItemTooltipWrappedText("Use the keyboard to navigate in ImGui windows with the following keys : Tab, Directional Arrows, Space, Enter."); + + //------------------------------------------------------------------------------------------- + //ImGui::CheckboxFlags("Gamepad Navigation", &IO.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad); + //FCogWidgets::ItemTooltipWrappedText("Use the gamepad to navigate in ImGui windows."); + + //------------------------------------------------------------------------------------------- + ImGui::Checkbox("Disable Conflicting Commands", &Config->bDisableConflictingCommands); + FCogWidgets::ItemTooltipWrappedText("Disable the existing Unreal command shortcuts mapped to same shortcuts Cog is using. Typically, if the F1 shortcut is used to toggle Inputs, the Unreal wireframe command will get disabled."); + + //------------------------------------------------------------------------------------------- + ImGui::Checkbox("Disable shortcuts when ImGui want text input", &Config->bDisableShortcutsWhenImGuiWantTextInput); + FCogWidgets::ItemTooltipWrappedText("Disable Cog's shortcuts (ToggleInput, ToggleSelectionMode, LoadLayout, ...) when ImGui want text input." + " This can be required if the shortcuts are mapped by keys generating a text input (letters, or Backspace for example)." + " This is not required if the shortcuts are set to keys such as F1 or F2."); } - - //------------------------------------------------------------------------------------------- - //ImGui::CheckboxFlags("Gamepad Navigation", &IO.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad); - //ImGui::SetItemTooltip("Use the gamepad to navigate in ImGui windows."); - + //------------------------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Window", ImGuiTreeNodeFlags_DefaultOpen)) { @@ -134,22 +146,26 @@ void FCogWindow_Settings::RenderContent() { FCogImguiHelper::SetFlags(IO.ConfigFlags, ImGuiConfigFlags_ViewportsEnable, Config->bEnableViewports); } - ImGui::SetItemTooltip("Enable moving ImGui windows outside of the main viewport."); + FCogWidgets::ItemTooltipWrappedText("Enable moving ImGui windows outside of the main viewport."); //------------------------------------------------------------------------------------------- ImGui::Checkbox("Compact Mode", &Config->bCompactMode); - ImGui::SetItemTooltip("Enable compact mode."); + FCogWidgets::ItemTooltipWrappedText("Enable compact mode."); + + //------------------------------------------------------------------------------------------- + ImGui::Checkbox("Transparent Mode", &Config->bTransparentMode); + FCogWidgets::ItemTooltipWrappedText("Enable transparent mode."); //------------------------------------------------------------------------------------------- ImGui::Checkbox("Show Windows In Main Menu", &Config->bShowWindowsInMainMenu); - ImGui::SetItemTooltip("Show the content of the windows when hovering the window menu item."); + FCogWidgets::ItemTooltipWrappedText("Show the content of the windows when hovering the window menu item."); //------------------------------------------------------------------------------------------- ImGui::Checkbox("Show Help", &Config->bShowHelp); - ImGui::SetItemTooltip("Show windows help on the window menu items."); + FCogWidgets::ItemTooltipWrappedText("Show windows help on the window menu items."); //------------------------------------------------------------------------------------------- - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::SliderFloat("DPI Scale", &Config->DPIScale, 0.5f, 2.0f, "%.1f"); if (ImGui::IsItemDeactivatedAfterEdit()) { @@ -164,12 +180,108 @@ void FCogWindow_Settings::RenderContent() } //------------------------------------------------------------------------------------------- - if (ImGui::CollapsingHeader("Config")) + if (ImGui::CollapsingHeader("Widgets (?)", ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::Button("Reset All Windows Config", ImVec2(-1.0f, 0.0f))) + FCogWidgets::ItemTooltipWrappedText("Widgets appear in the main menu bar."); + + ImGui::Checkbox("Show Widget Borders", &Config->ShowWidgetBorders); + FCogWidgets::ItemTooltipWrappedText("Should a border be visible between widgets."); + + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::ComboboxEnum("Widgets Alignment", Config->WidgetAlignment); + FCogWidgets::ItemTooltipWrappedText("How the widgets should be aligned in the main menu bar."); + + if (ImGui::BeginChild("Widgets", ImVec2(0, ImGui::GetFontSize() * 10), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY, ImGuiWindowFlags_MenuBar)) + { + if (ImGui::BeginMenuBar()) + { + ImGui::TextUnformatted("Widgets visibility and ordering"); + ImGui::SameLine(); + FCogWidgets::HelpMarker("Drag and drop the widget names to reorder them."); + ImGui::EndMenuBar(); + } + + TArray& Widgets = GetOwner()->Widgets; + for (int32 i = 0; i < Widgets.Num(); ++i) + { + FCogWindow* Window = Widgets[i]; + + ImGui::PushID(i); + + bool Visible = Window->GetIsWidgetVisible(); + if (ImGui::Checkbox("##Visibility", &Visible)) + { + Window->SetIsWidgetVisible(Visible); + } + + ImGui::SameLine(); + ImGui::Selectable(TCHAR_TO_ANSI(*Window->GetName()), false, ImGuiSelectableFlags_SpanAvailWidth); + { + Window->SetIsWidgetVisible(Visible); + } + if (ImGui::IsItemHovered()) + { + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); + } + + if (ImGui::IsItemActive() && ImGui::IsItemHovered() == false) + { + const int iNext = i + (ImGui::GetMouseDragDelta(0).y < 0.f ? -1 : 1); + if (iNext >= 0 && iNext < Widgets.Num()) + { + Widgets[i] = Widgets[iNext]; + Widgets[iNext] = Window; + ImGui::ResetMouseDragDelta(); + } + } + + ImGui::PopID(); + } + } + ImGui::EndChild(); + } + + //------------------------------------------------------------------------------------------- + if (ImGui::CollapsingHeader("Shortcuts", ImGuiTreeNodeFlags_DefaultOpen)) + { + for (TObjectPtr SomeConfig : GetOwner()->GetConfigs()) + { + if (SomeConfig == nullptr) + { continue; } + + if (FCogWidgets::IsConfigContainingInputChords(*SomeConfig)) + { + auto ConfigName = StringCast(*FCogWidgets::FormatConfigName(SomeConfig->GetClass()->GetName())); + ImGui::SeparatorText(ConfigName.Get()); + + FProperty* InModifiedProperty = nullptr; + if (FCogWidgets::AllInputChordsOfConfig(*SomeConfig, &InModifiedProperty)) + { + GetOwner()->RebindShortcut(*SomeConfig, *InModifiedProperty); + } + } + } + } + + //------------------------------------------------------------------------------------------- + if (ImGui::CollapsingHeader("Settings", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (ImGui::Button("Save All Settings", ImVec2(ImGui::GetContentRegionAvail().x, 0.0f))) + { + GetOwner()->SaveAllSettings(); + } + + // if (ImGui::Button("Reload All Settings", ImVec2(ImGui::GetContentRegionAvail().x, 0.0f))) + // { + // GetOwner()->ReloadAllSettings(); + // } + + FCogWidgets::PushButtonBackColor(ImVec4(1.0f, 0.0f, 0.0f, 1)); + if (ImGui::Button("Reset All Settings", ImVec2(ImGui::GetContentRegionAvail().x, 0.0f))) { GetOwner()->ResetAllWindowsConfig(); } + FCogWidgets::PopButtonBackColor(); } } diff --git a/Plugins/Cog/Source/CogWindow/Private/CogWindow_Spacing.cpp b/Plugins/Cog/Source/Cog/Private/CogWindow_Spacing.cpp similarity index 55% rename from Plugins/Cog/Source/CogWindow/Private/CogWindow_Spacing.cpp rename to Plugins/Cog/Source/Cog/Private/CogWindow_Spacing.cpp index 68974e4..029731a 100644 --- a/Plugins/Cog/Source/CogWindow/Private/CogWindow_Spacing.cpp +++ b/Plugins/Cog/Source/Cog/Private/CogWindow_Spacing.cpp @@ -1,13 +1,19 @@ #include "CogWindow_Spacing.h" //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindow_Spacing::PreRender(ImGuiWindowFlags& WindowFlags) +FCogWindow_Spacing::FCogWindow_Spacing() +{ + bShowInMainMenu = false; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogWindow_Spacing::PreBegin(ImGuiWindowFlags& WindowFlags) { ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); } //-------------------------------------------------------------------------------------------------------------------------- -void FCogWindow_Spacing::PostRender() +void FCogWindow_Spacing::PostBegin() { ImGui::PopStyleColor(1); } diff --git a/Plugins/Cog/Source/CogWindow/Public/CogWindowConsoleCommandManager.h b/Plugins/Cog/Source/Cog/Public/CogConsoleCommandManager.h similarity index 89% rename from Plugins/Cog/Source/CogWindow/Public/CogWindowConsoleCommandManager.h rename to Plugins/Cog/Source/Cog/Public/CogConsoleCommandManager.h index c15a759..a6d2736 100644 --- a/Plugins/Cog/Source/CogWindow/Public/CogWindowConsoleCommandManager.h +++ b/Plugins/Cog/Source/Cog/Public/CogConsoleCommandManager.h @@ -2,7 +2,6 @@ #include "CoreMinimal.h" #include "HAL/IConsoleManager.h" -#include "Templates/Function.h" class UWorld; @@ -11,7 +10,7 @@ DECLARE_DELEGATE_TwoParams(FCogWindowConsoleCommandDelegate, const TArray + static const T* GetFirstAssetByClass(); + + static const UObject* GetFirstAssetByClass(const TSubclassOf& AssetClass); + + template + static FProperty* FindProperty(TCLass* Instance, TMember TCLass::*PointerToMember); + + static bool IsTraceChannelHidden(const UCollisionProfile& InCollisionProfile, ECollisionChannel InCollisionChannel); +}; + +//---------------------------------------------------------------------------------------------------------------------- +template +const T* FCogHelper::GetFirstAssetByClass() +{ + return Cast(GetFirstAssetByClass(T::StaticClass())); +} + +//---------------------------------------------------------------------------------------------------------------------- +template +FProperty* FCogHelper::FindProperty(TCLass* Instance, TMember TCLass::*PointerToMember) +{ + for (TFieldIterator It(Instance->GetClass()); It; ++It) + { + FProperty* Property = *It; + + if (Property == nullptr) + { continue; } + + const void* MemberAddress = &(Instance->*PointerToMember); + const void* PropertyAddress = Property->ContainerPtrToValuePtr(Instance); + + if (MemberAddress == PropertyAddress) + { return Property; } + } + + return nullptr; +} \ No newline at end of file diff --git a/Plugins/Cog/Source/Cog/Public/CogModule.h b/Plugins/Cog/Source/Cog/Public/CogModule.h new file mode 100644 index 0000000..712cd8d --- /dev/null +++ b/Plugins/Cog/Source/Cog/Public/CogModule.h @@ -0,0 +1,16 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class COG_API FCogModule : public IModuleInterface +{ +public: + + static inline FCogModule& Get() { return FModuleManager::LoadModuleChecked("Cog"); } + + virtual void StartupModule() override; + + virtual void ShutdownModule() override; + +}; diff --git a/Plugins/Cog/Source/Cog/Public/CogPluginSubsystem.h b/Plugins/Cog/Source/Cog/Public/CogPluginSubsystem.h new file mode 100644 index 0000000..f6d8119 --- /dev/null +++ b/Plugins/Cog/Source/Cog/Public/CogPluginSubsystem.h @@ -0,0 +1,15 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Engine/GameInstance.h" +#include "CogPluginSubsystem.generated.h" + +UCLASS(Abstract) +class COG_API UCogPluginSubsystem : public UGameInstanceSubsystem +{ + GENERATED_BODY() + +public: + + virtual void OnPlayerControllerSet(APlayerController* InController) {} +}; diff --git a/Plugins/Cog/Source/CogWindow/Public/CogWindowManager.h b/Plugins/Cog/Source/Cog/Public/CogSubsystem.h similarity index 50% rename from Plugins/Cog/Source/CogWindow/Public/CogWindowManager.h rename to Plugins/Cog/Source/Cog/Public/CogSubsystem.h index a0695e1..401eefc 100644 --- a/Plugins/Cog/Source/CogWindow/Public/CogWindowManager.h +++ b/Plugins/Cog/Source/Cog/Public/CogSubsystem.h @@ -1,9 +1,13 @@ #pragma once #include "CoreMinimal.h" +#include "CogHelper.h" #include "CogImguiContext.h" +#include "CogWindow_Settings.h" #include "imgui.h" -#include "CogWindowManager.generated.h" +#include "Subsystems/WorldSubsystem.h" + +#include "CogSubsystem.generated.h" class UCogCommonConfig; class FCogWindow; @@ -17,31 +21,30 @@ struct ImGuiSettingsHandler; struct ImGuiTextBuffer; struct FKey; -UCLASS(Config = Cog) -class COGWINDOW_API UCogWindowManager : public UObject +//-------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class COG_API UCogSubsystem : public UTickableWorldSubsystem { GENERATED_BODY() public: + virtual bool ShouldCreateSubsystem(UObject* Outer) const override; - UCogWindowManager(); + virtual TStatId GetStatId() const override; - virtual void PostInitProperties() override; + virtual void PostInitialize() override; - virtual void Shutdown(); + virtual void Deinitialize() override; - virtual void SortMainMenu(); + virtual void Tick(float DeltaTime) override; - virtual void Render(float DeltaTime); - - virtual void Tick(float DeltaTime); - - - virtual void AddWindow(FCogWindow* Window, const FString& Name, bool AddToMainMenu = true); + virtual void AddWindow(FCogWindow* Window, const FString& Name); template - T* AddWindow(const FString& Name, bool AddToMainMenu = true); - + T* AddWindow(const FString& Name); + + virtual void SortMainMenu(); + virtual FCogWindow* FindWindowByID(ImGuiID ID); virtual void CloseAllWindows(); @@ -52,34 +55,43 @@ public: virtual void SaveLayout(int32 LayoutIndex); - virtual bool GetHideAllWindows() const { return bIsSelectionModeActive; } - virtual void SetActivateSelectionMode(bool Value); + virtual bool GetActivateSelectionMode() const; + virtual void ResetAllWindowsConfig(); - virtual bool RegisterDefaultCommandBindings(); - - const FCogWindow_Settings* GetSettingsWindow() const { return SettingsWindow; } + UCogWindowConfig_Settings* GetSettings() const { return Settings.Get(); } - UCogCommonConfig* GetConfig(const TSubclassOf ConfigClass); + UCogCommonConfig* GetConfig(const TSubclassOf& ConfigClass); template T* GetConfig(); - const UObject* GetAsset(const TSubclassOf AssetClass) const; + const UObject* GetAsset(const TSubclassOf& AssetClass) const; template T* GetAsset(); + FInputActionHandlerSignature& AddShortcut(const UObject& InInstance, const FProperty& InProperty); + + template + FInputActionHandlerSignature& AddShortcut(TCLass* InInstance, TMember TCLass::*InPointerToMember); + + void RebindShortcut(const UCogCommonConfig& InConfig, const FProperty& InProperty); + const FCogImguiContext& GetContext() const { return Context; } FCogImguiContext& GetContext() { return Context; } + bool IsRenderingMainMenu() const { return bIsRenderingInMainMenu; } + static void AddCommand(UPlayerInput* PlayerInput, const FString& Command, const FKey& Key); static void SortCommands(UPlayerInput* PlayerInput); + TArray>& GetConfigs() const { return Configs; }; + protected: friend class FCogWindow_Layouts; @@ -92,10 +104,29 @@ protected: TArray SubMenus; }; - virtual void InitializeInternal(); + struct FCogShortcut + { + FName PropertyName; + + TWeakObjectPtr Config; + + FInputActionHandlerSignature Delegate; + + FInputChord InputChord; + }; + + virtual void Render(float DeltaTime); + + virtual void TryInitialize(UWorld& World); + + virtual void UpdatePlayerControllers(UWorld& World); + + virtual void InitializeWindow(FCogWindow* Window); + + virtual void Shutdown(); virtual void RenderMainMenu(); - + virtual FMenu* AddMenu(const FString& Name); virtual void RenderOptionMenu(FMenu& Menu); @@ -104,10 +135,24 @@ protected: virtual void RenderMenuItemHelp(FCogWindow& Window); + void SetLocalPlayerController(APlayerController& PlayerController); + virtual void ToggleInputMode(); virtual void DisableInputMode(); + virtual void TryDisableCommandsConflictingWithShortcuts(UPlayerInput* PlayerInput); + + virtual void RequestDisableCommandsConflictingWithShortcuts(); + + virtual bool BindShortcut(FCogShortcut& InShortcut) const; + + virtual void RenderWidgets(); + + virtual void SaveAllSettings(); + + virtual void ReloadAllSettings(); + static void SettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*); static void SettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*); @@ -118,6 +163,8 @@ protected: static void SettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf); + static FString EnableCommand; + static FString ToggleInputCommand; static FString DisableInputCommand; @@ -129,13 +176,19 @@ protected: static FString ResetLayoutCommand; UPROPERTY() - mutable TArray Configs; + TWeakObjectPtr CurrentWorld; + + UPROPERTY() + mutable TArray> Configs; UPROPERTY() - mutable TArray Assets; + mutable TArray> Assets; - UPROPERTY(Config) - bool bRegisterDefaultCommands = true; + TArray> ServerPlayerControllers; + + TWeakObjectPtr LocalPlayerController; + + TWeakObjectPtr InputComponent; FCogImguiContext Context; @@ -143,11 +196,15 @@ protected: TArray Widgets; + TArray Shortcuts; + int32 WidgetsOrderIndex = 0; TArray SpaceWindows; FCogWindow_Settings* SettingsWindow = nullptr; + + TWeakObjectPtr Settings; FCogWindow_Layouts* LayoutsWindow = nullptr; @@ -156,32 +213,56 @@ protected: int32 LayoutToLoad = -1; int32 SelectionModeActiveCounter = 0; + + bool bIsInputEnabledBeforeEnteringSelectionMode = false; bool bIsSelectionModeActive = false; - bool IsInitialized = false; + bool bIsInitialized = false; + + bool bIsRenderingInMainMenu = false; + + int32 NumExecBindingsChecked = 0; + + FInputActionHandlerSignature InvalidShortcutDelegate; }; //-------------------------------------------------------------------------------------------------------------------------- template -T* UCogWindowManager::AddWindow(const FString& Name, bool AddToMainMenu) +T* UCogSubsystem::AddWindow(const FString& Name) { T* Window = new T(); - AddWindow(Window, Name, AddToMainMenu); + AddWindow(Window, Name); return Window; } //-------------------------------------------------------------------------------------------------------------------------- template -T* UCogWindowManager::GetConfig() +T* UCogSubsystem::GetConfig() { static_assert(TPointerIsConvertibleFromTo::Value); - return Cast(&GetConfig(T::StaticClass())); + return Cast(GetConfig(T::StaticClass())); } //-------------------------------------------------------------------------------------------------------------------------- template -T* UCogWindowManager::GetAsset() +T* UCogSubsystem::GetAsset() { return Cast(GetAsset(T::StaticClass())); -} \ No newline at end of file +} + +//-------------------------------------------------------------------------------------------------------------------------- +template +FInputActionHandlerSignature& UCogSubsystem::AddShortcut(TCLass* InInstance, TMember TCLass::* InPointerToMember) +{ + if (InInstance == nullptr) + { return InvalidShortcutDelegate; } + + const FProperty* Property = FCogHelper::FindProperty(InInstance, InPointerToMember); + if (Property == nullptr) + { return InvalidShortcutDelegate; } + + return AddShortcut(*InInstance, *Property); +} + + diff --git a/Plugins/Cog/Source/Cog/Public/CogWidgets.h b/Plugins/Cog/Source/Cog/Public/CogWidgets.h new file mode 100644 index 0000000..4fd4daa --- /dev/null +++ b/Plugins/Cog/Source/Cog/Public/CogWidgets.h @@ -0,0 +1,286 @@ +#pragma once + +#include "CoreMinimal.h" +#include "imgui.h" +#include "UObject/ReflectedTypeAccessors.h" + +#include + +#include "CogHelper.h" + +class AActor; +class APawn; +class FEnumProperty; +class UCollisionProfile; +class UEnum; +class UObject; +enum class ECheckBoxState : uint8; +enum ECollisionChannel : int; +struct FKeyBind; + +using FCogWindowActorContextMenuFunction = TFunction; + +//-------------------------------------------------------------------------------------------------------------------------- +class COG_API FCogWidgets +{ +public: + + static bool BeginTableTooltip(); + + static void EndTableTooltip(); + + static bool ItemTooltipWrappedText(const char* InText); + + static bool BeginItemTooltipWrappedText(); + + static void EndItemTooltipWrappedText(); + + static bool BeginItemTableTooltip(); + + static void EndItemTableTooltip(); + + static void ThinSeparatorText(const char* Label); + + static bool DarkCollapsingHeader(const char* InLabel, ImGuiTreeNodeFlags InFlags); + + static void ProgressBarCentered(float Fraction, const ImVec2& Size, const char* Overlay); + + static bool ToggleMenuButton(bool* Value, const char* Text, const ImVec4& TrueColor); + + static bool ToggleButton(bool* Value, const char* Text, const ImVec4& TrueColor, const ImVec4& FalseColor, const ImVec2& Size = ImVec2(0, 0)); + + static bool ToggleButton(bool* Value, const char* TextTrue, const char* TextFalse, const ImVec4& TrueColor, const ImVec4& FalseColor, const ImVec2& Size = ImVec2(0, 0)); + + static bool MultiChoiceButton(const char* Label, bool IsSelected, const ImVec2& Size = ImVec2(0, 0)); + + static bool MultiChoiceButtonsInt(TArray& Values, int32& Value, const ImVec2& Size = ImVec2(0, 0), bool InInline = true); + + static bool MultiChoiceButtonsFloat(TArray& InValues, float& InValue, const ImVec2& InSize = ImVec2(0, 0), bool InInline = true, float InTolerance = UE_SMALL_NUMBER); + + static void SliderWithReset(const char* Name, float* Value, float Min, float Max, const float& ResetValue, const char* Format); + + static void HelpMarker(const char* Text); + + static void PushStyleCompact(); + + static void PopStyleCompact(); + + static void AddTextWithShadow(ImDrawList* DrawList, const ImVec2& Position, ImU32 Color, const char* TextBegin, const char* TextEnd = nullptr); + + static bool SearchBar(const char* InLabel, ImGuiTextFilter& InFilter, float InWidth = -1.0f); + + static void PushButtonBackColor(const ImVec4& Color); + + static void PopButtonBackColor(); + static void PushFrameBackColor(const ImVec4& Color); + static void PushSliderBackColor(const ImVec4& Color); + + static void PushBackColor(const ImVec4& Color); + static void PopSliderBackColor(); + static void PopFrameBackColor(); + + static void PopBackColor(); + + static float GetShortWidth(); + + static void SetNextItemToShortWidth(); + + static float GetFontWidth(); + + template + static bool ComboboxEnum(const char* Label, const EnumType CurrentValue, EnumType& NewValue); + + template + static bool ComboboxEnum(const char* Label, EnumType& Value); + + static bool ComboboxEnum(const char* Label, const UEnum* Enum, int64 CurrentValue, int64& NewValue); + + + static bool ComboboxEnum(const char* Label, const UObject* Object, const char* FieldName, uint8* PointerToEnumValue); + + static bool ComboboxEnum(const char* Label, const FEnumProperty* EnumProperty, uint8* PointerToEnumValue); + + static bool CheckBoxState(const char* Label, ECheckBoxState& State, bool ShowTooltip = true); + + static bool InputChord(const char* Label, FInputChord& InInputChord); + + static bool InputChord(FInputChord& InInputChord); + + static bool Key(FKey& InKey); + + static bool KeyBind(FKeyBind& InKeyBind); + + static bool ButtonWithTooltip(const char* Text, const char* Tooltip); + + static bool DeleteArrayItemButton(); + + static bool ComboTraceChannel(const char* Label, ECollisionChannel& Channel); + + static bool CollisionProfileChannels(int32& OutChannels); + + static bool CollisionTraceChannels(int32& OutChannels); + + static bool CollisionObjectTypeChannels(int32& OutChannels); + + static bool CollisionProfileChannel(const UCollisionProfile& InCollisionProfile, int32 InChannelIndex, FColor& InChannelColor, int32& InChannels); + + static bool MenuActorsCombo(const char* StrID, AActor*& NewSelection, const UWorld& World, const TSubclassOf& ActorClass, const FCogWindowActorContextMenuFunction& ContextMenuFunction = nullptr); + + static bool MenuActorsCombo(const char* StrID, AActor*& NewSelection, const UWorld& World, const TArray>& ActorClasses, int32& SelectedActorClassIndex, ImGuiTextFilter* Filter, const APawn* LocalPlayerPawn, const FCogWindowActorContextMenuFunction& ContextMenuFunction = nullptr); + + static bool ActorsListWithFilters(AActor*& NewSelection, const UWorld& World, const TArray>& ActorClasses, int32& SelectedActorClassIndex, ImGuiTextFilter* Filter, const APawn* LocalPlayerPawn, const FCogWindowActorContextMenuFunction& ContextMenuFunction = nullptr); + + static bool ActorsList(AActor*& NewSelection, const UWorld& World, const TSubclassOf& ActorClass, const ImGuiTextFilter* Filter = nullptr, const APawn* LocalPlayerPawn = nullptr, const FCogWindowActorContextMenuFunction& ContextMenuFunction = nullptr); + + static void ActorContextMenu(AActor& Selection, const FCogWindowActorContextMenuFunction& ContextMenuFunction); + + static void ActorFrame(const AActor& Actor); + + static void SmallButton(const char* Text, const ImVec4& Color); + + static bool InputText(const char* Text, FString& Value, ImGuiInputTextFlags InFlags = 0, ImGuiInputTextCallback InCallback = nullptr, void* InUserData = nullptr); + + static bool InputTextWithHint(const char* InText, const char* InHint, FString& InValue, ImGuiInputTextFlags InFlags = 0, ImGuiInputTextCallback InCallback = nullptr, void* InUserData = nullptr); + + static bool BeginRightAlign(const char* Id); + + static void EndRightAlign(); + + static void MenuItemShortcut(const char* Id, const FString& Text); + + template + static void InputChordProperty(TCLass* InConfig, TMember TCLass::* InInputChordPointerToMember); + + template + static void TextInputChordProperty(TCLass* InConfig, TMember TCLass::* InInputChordPointerToMember); + + static bool BrowseToAssetButton(const UObject* InAsset, const ImVec2& InSize = ImVec2(0, 0)); + + static bool BrowseToAssetButton(const FAssetData& InAssetData, const ImVec2& InSize = ImVec2(0, 0)); + + static bool BrowseToObjectAssetButton(const UObject* InObject, const ImVec2& InSize = ImVec2(0, 0)); + + static bool OpenAssetButton(const UObject* InAsset, const ImVec2& InSize = ImVec2(0, 0)); + + static bool OpenObjectAssetButton(const UObject* InObject, const ImVec2& InSize = ImVec2(0, 0)); + + static void RenderCloseButton(const ImVec2& InPos); + + static bool PickButton(const char* InLabel, const ImVec2& InSize, ImGuiButtonFlags InFlags = ImGuiButtonFlags_None); + + static FString RemoveFirstZero(const FString& InText); + + static FString FormatSmallFloat(float InValue); + + static void FloatArray(const char* InLabel, TArray& InArray, int32 InMaxEntries = 0, const ImVec2& Size = ImVec2(0, 0)); + + static void IntArray(const char* InLabel, TArray& InArray, int32 InMaxEntries = 0, const ImVec2& Size = ImVec2(0, 0)); + + template + static bool ScalarArray(const char* InLabel, ImGuiDataType InDataType, TArray& InArray, int32 InMaxEntries = 0, const ImVec2& Size = ImVec2(0, 0)); + + static ImVec2 ComputeScreenCornerLocation(const FVector2f& InAlignment, const FIntVector2& InPadding); + + static ImVec2 ComputeScreenCornerLocation(const ImVec2& InAlignment, const ImVec2& InPadding); + + static FString GetStringAfterCharacter(const FString& InString, TCHAR InChar); + + static FString FormatConfigName(const FString& InConfigName); + + static FString FormatShortcutName(const FString& InShortcutName); + + static void TextInputChordProperty(UObject& InConfig, const FProperty& InInputChordProperty); + + static bool InputChordProperty(UObject& InConfig, const FProperty& InInputChordProperty); + + static bool IsConfigContainingInputChords(const UObject& InConfig); + + static bool AllInputChordsOfConfig(UObject& InConfig, FProperty** InModifiedProperty = nullptr); + + static void TextOfAllInputChordsOfConfig(UObject& InConfig); +}; + +template +bool FCogWidgets::ComboboxEnum(const char* Label, const EnumType CurrentValue, EnumType& NewValue) +{ + int64 NewValueInt; + if (ComboboxEnum(Label, StaticEnum(), static_cast(CurrentValue), NewValueInt)) + { + NewValue = static_cast(NewValueInt); + return true; + } + + return false; +} + +template +bool FCogWidgets::ComboboxEnum(const char* Label, EnumType& Value) +{ + return ComboboxEnum(Label, Value, Value); +} + +template + bool FCogWidgets::ScalarArray(const char* InLabel, ImGuiDataType InDataType, TArray& InArray, int32 InMaxEntries, const ImVec2& Size) +{ + bool Result = false; + ImGui::PushID(InLabel); + + if (ImGui::BeginChild("##Entries", Size, ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY, ImGuiWindowFlags_MenuBar)) + { + if (ImGui::BeginMenuBar()) + { + ImGui::TextUnformatted(InLabel); + + int32 NumEntries = InArray.Num(); + SetNextItemToShortWidth(); + if (ImGui::SliderInt("##Size", &NumEntries, 0, InMaxEntries)) + { + InArray.SetNum(NumEntries); + Result = true; + } + ImGui::EndMenuBar(); + } + + for (int32 i = 0; i < InArray.Num(); i++) + { + ImGui::PushID(i); + ImGui::SetNextItemWidth(-1); + if (ImGui::InputScalar("##Entry", InDataType, &InArray[i])) + { + Result = true; + } + ImGui::PopID(); + } + } + ImGui::EndChild(); + + ImGui::PopID(); + + return Result; +} + +template +void FCogWidgets::InputChordProperty(TCLass* InConfig, TMember TCLass::* InInputChordPointerToMember) +{ + if (InConfig == nullptr) + { return; } + + FProperty* Property = FCogHelper::FindProperty(InConfig, InInputChordPointerToMember); + if (Property == nullptr) + { return; } + + InputChordProperty(*InConfig, *Property); +} + +template +void FCogWidgets::TextInputChordProperty(TCLass* InConfig, TMember TCLass::* InInputChordPointerToMember) +{ + if (InConfig == nullptr) + { return; } + + FProperty* Property = FCogHelper::FindProperty(InConfig, InInputChordPointerToMember); + if (Property == nullptr) + { return; } + + TextInputChordProperty(*InConfig, *Property); +} \ No newline at end of file diff --git a/Plugins/Cog/Source/CogWindow/Public/CogWindow.h b/Plugins/Cog/Source/Cog/Public/CogWindow.h similarity index 65% rename from Plugins/Cog/Source/CogWindow/Public/CogWindow.h rename to Plugins/Cog/Source/Cog/Public/CogWindow.h index 78b0611..e844aea 100644 --- a/Plugins/Cog/Source/CogWindow/Public/CogWindow.h +++ b/Plugins/Cog/Source/Cog/Public/CogWindow.h @@ -7,24 +7,25 @@ #include "UObject/ReflectedTypeAccessors.h" #include "UObject/WeakObjectPtrTemplates.h" +struct FCogDebugContext; class AActor; class APawn; class APlayerController; -class UCogWindowManager; +class UCogSubsystem; class ULocalPlayer; class UWorld; -class COGWINDOW_API FCogWindow +class COG_API FCogWindow { public: virtual ~FCogWindow() {} - virtual void Initialize() {} + virtual void Initialize(); - virtual void Shutdown() {} + virtual void Shutdown(); - virtual void ResetConfig() {} + virtual void ResetConfig(); virtual void PreSaveConfig() {} @@ -34,15 +35,16 @@ public: /** Called every frame with a valid imgui context even if the window is hidden. */ virtual void RenderTick(float DeltaTime); - /** Called every frame without a valid imgui context (outside of the imgui NewFrame/EndFrame) even if the window is hidden. */ + /** Called every frame without a valid imgui context (outside the imgui NewFrame/EndFrame) even if the window is hidden. */ virtual void GameTick(float DeltaTime); /** */ - virtual float GetMainMenuWidgetWidth(int32 SubWidgetIndex, float MaxWidth) { return -1.0f; } + virtual void RenderMainMenuWidget(); - /** */ - virtual void RenderMainMenuWidget(int32 SubWidgetIndex, float Width) {} + virtual void RenderSettings(); + virtual void BindInputs(UInputComponent* InputComponent) {} + ImGuiID GetID() const { return ID; } /** The full name of the window, that contains the path in the main menu. For example "Gameplay.Character.Effect" */ @@ -53,7 +55,7 @@ public: /** The short name of the window. "Effect" if the window full name is "Gameplay.Character.Effect" */ const FString& GetName() const { return Name; } - AActor* GetSelection() const { return CurrentSelection.Get(); } + AActor* GetSelection() const; void SetSelection(AActor* Actor); @@ -71,23 +73,27 @@ public: void SetWidgetOrderIndex(int32 Value) { WidgetOrderIndex = Value; } - void SetOwner(UCogWindowManager* InOwner) { Owner = InOwner; } + void SetOwner(UCogSubsystem* InOwner) { Owner = InOwner; } - UCogWindowManager* GetOwner() const { return Owner; } + UCogSubsystem* GetOwner() const { return Owner; } + + float GetDpiScale() const; + template - T* GetConfig() const { return Cast(GetConfig(T::StaticClass())); } + T* GetConfig(bool InResetConfigOnRequest = true) const { return Cast(GetConfig(T::StaticClass(), InResetConfigOnRequest)); } - UCogCommonConfig* GetConfig(const TSubclassOf ConfigClass) const; + UCogCommonConfig* GetConfig(const TSubclassOf& InConfigClass, bool InResetConfigOnRequest = true) const; template const T* GetAsset() const { return Cast(GetAsset(T::StaticClass())); } - const UObject* GetAsset(const TSubclassOf AssetClass) const; + const UObject* GetAsset(const TSubclassOf& AssetClass) const; + protected: - friend class UCogWindowManager; + friend class UCogSubsystem; virtual const FString& GetTitle() const { return Title; } @@ -95,30 +101,36 @@ protected: virtual void RenderHelp(); - virtual void PreRender(ImGuiWindowFlags& WindowFlags) {} + virtual void PreBegin(ImGuiWindowFlags& WindowFlags) {} - virtual void PostRender() {} + virtual void PostBegin() {} + + virtual void PostEnd() {} virtual void RenderContent() {} virtual bool CheckEditorVisibility(); + virtual void RenderContextMenu(); + virtual void OnWindowVisibilityChanged(bool NewVisibility) { } virtual void OnSelectionChanged(AActor* OldSelection, AActor* NewSelection) {} + virtual bool IsWindowRenderedInMainMenu(); + + virtual void RenderConfigShortcuts(UCogCommonConfig& InConfig) const; + APawn* GetLocalPlayerPawn() const; APlayerController* GetLocalPlayerController() const; ULocalPlayer* GetLocalPlayer() const; -protected: - + bool bIsInitialized = false; + bool bShowMenu = true; - bool bNoPadding = false; - bool bHasMenu = false; bool bIsVisible = false; @@ -127,20 +139,22 @@ protected: bool bIsWidgetVisible = false; + bool bShowInMainMenu = true; + + bool bUseCustomContextMenu = false; + int32 WidgetOrderIndex = -1; - ImGuiID ID; + ImGuiID ID = 0; FString FullName; FString Name; FString Title; + + UCogSubsystem* Owner = nullptr; - UCogWindowManager* Owner = nullptr; - - TWeakObjectPtr CurrentSelection; - - TWeakObjectPtr OverridenSelection; + mutable TArray> ConfigsToResetOnRequest; }; diff --git a/Plugins/Cog/Source/Cog/Public/CogWindow_Layouts.h b/Plugins/Cog/Source/Cog/Public/CogWindow_Layouts.h new file mode 100644 index 0000000..00f93d2 --- /dev/null +++ b/Plugins/Cog/Source/Cog/Public/CogWindow_Layouts.h @@ -0,0 +1,23 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CogWindow.h" + +class UPlayerInput; + +class COG_API FCogWindow_Layouts : public FCogWindow +{ + typedef FCogWindow Super; + +public: + + FCogWindow_Layouts(); + +protected: + + virtual void RenderContent() override; + + virtual void RenderLoadLayoutMenuItem(int InLayoutIndex, const FInputChord& InInputChord); + + virtual void RenderSaveLayoutMenuItem(int InLayoutIndex, const FInputChord& InInputChord); +}; diff --git a/Plugins/Cog/Source/Cog/Public/CogWindow_Settings.h b/Plugins/Cog/Source/Cog/Public/CogWindow_Settings.h new file mode 100644 index 0000000..e287f13 --- /dev/null +++ b/Plugins/Cog/Source/Cog/Public/CogWindow_Settings.h @@ -0,0 +1,165 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CogCommonConfig.h" +#include "CogWindow.h" +#include "CogWindow_Settings.generated.h" + +class UCogEngineConfig_Settings; + +//-------------------------------------------------------------------------------------------------------------------------- +class COG_API FCogWindow_Settings : public FCogWindow +{ + typedef FCogWindow Super; + +public: + + FCogWindow_Settings(); + + virtual void Initialize() override; + + virtual void RenderTick(float DeltaTime) override; + + const UCogWindowConfig_Settings* GetSettingsConfig() const { return Config; } + + void SetDPIScale(float Value) const; + +protected: + + virtual void RenderContent() override; + + virtual void PreSaveConfig() override; + + TObjectPtr Config = nullptr; +}; + +//-------------------------------------------------------------------------------------------------------------------------- +UENUM() +enum class ECogWidgetAlignment +{ + Left = 0, + Center = 1, + Right = 2, + Manual = 3 +}; + +//-------------------------------------------------------------------------------------------------------------------------- +UCLASS(Config = Cog) +class UCogWindowConfig_Settings : public UCogCommonConfig +{ + GENERATED_BODY() + +public: + + UPROPERTY(Config) + float DPIScale = 1.0f; + + UPROPERTY(Config) + bool bEnableViewports = false; + + UPROPERTY(Config) + bool bCompactMode = false; + + UPROPERTY(Config) + bool bTransparentMode = false; + + UPROPERTY(Config) + bool bShowHelp = true; + + UPROPERTY(Config) + bool bShowWindowsInMainMenu = true; + + UPROPERTY(Config) + bool bEnableInput = false; + + UPROPERTY(Config) + bool bShareMouse = false; + + UPROPERTY(Config) + bool bShareMouseWithGameplay = false; + + UPROPERTY(Config) + bool bShareKeyboard = false; + + UPROPERTY(Config) + bool bNavEnableKeyboard = false; + + UPROPERTY(Config) + bool bDisableConflictingCommands = true; + + UPROPERTY(Config) + bool bDisableShortcutsWhenImGuiWantTextInput = false; + + UPROPERTY(Config) + ECogWidgetAlignment WidgetAlignment = ECogWidgetAlignment::Right; + + UPROPERTY(Config) + bool ShowWidgetBorders = false; + + UPROPERTY(Config) + FInputChord Shortcut_ToggleImguiInput = FInputChord(EKeys::F1); + + UPROPERTY(Config) + FInputChord Shortcut_LoadLayout1 = FInputChord(EKeys::F2); + + UPROPERTY(Config) + FInputChord Shortcut_LoadLayout2 = FInputChord(EKeys::F3); + + UPROPERTY(Config) + FInputChord Shortcut_LoadLayout3 = FInputChord(EKeys::F4); + + UPROPERTY(Config) + FInputChord Shortcut_LoadLayout4 = FInputChord(); + + UPROPERTY(Config) + FInputChord Shortcut_SaveLayout1 = FInputChord(); + + UPROPERTY(Config) + FInputChord Shortcut_SaveLayout2 = FInputChord(); + + UPROPERTY(Config) + FInputChord Shortcut_SaveLayout3 = FInputChord(); + + UPROPERTY(Config) + FInputChord Shortcut_SaveLayout4 = FInputChord(); + + UPROPERTY(Config) + FInputChord Shortcut_ResetLayout = FInputChord(); + + //UPROPERTY(Config) + //bool bNavEnableGamepad = false; + + //UPROPERTY(Config) + //bool bNavNoCaptureInput = true; + + virtual void Reset() override + { + Super::Reset(); + + DPIScale = 1.0f; + bEnableViewports = false; + bCompactMode = false; + bTransparentMode = false; + bShowHelp = true; + bShowWindowsInMainMenu = true; + bEnableInput = false; + bShareMouse = false; + bShareMouseWithGameplay = false; + bShareKeyboard = false; + bNavEnableKeyboard = false; + bDisableConflictingCommands = true; + bDisableShortcutsWhenImGuiWantTextInput = false; + WidgetAlignment = ECogWidgetAlignment::Right; + ShowWidgetBorders = false; + Shortcut_ToggleImguiInput = FInputChord(EKeys::F1); + Shortcut_LoadLayout1 = FInputChord(EKeys::F2); + Shortcut_LoadLayout2 = FInputChord(EKeys::F3); + Shortcut_LoadLayout3 = FInputChord(EKeys::F4); + Shortcut_LoadLayout4 = FInputChord(); + Shortcut_SaveLayout1 = FInputChord(); + Shortcut_SaveLayout2 = FInputChord(); + Shortcut_SaveLayout3 = FInputChord(); + Shortcut_SaveLayout4 = FInputChord(); + Shortcut_ResetLayout = FInputChord(); + } +}; \ No newline at end of file diff --git a/Plugins/Cog/Source/Cog/Public/CogWindow_Spacing.h b/Plugins/Cog/Source/Cog/Public/CogWindow_Spacing.h new file mode 100644 index 0000000..3a27f78 --- /dev/null +++ b/Plugins/Cog/Source/Cog/Public/CogWindow_Spacing.h @@ -0,0 +1,17 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CogWindow.h" + +class COG_API FCogWindow_Spacing : public FCogWindow +{ + typedef FCogWindow Super; + +public: + + FCogWindow_Spacing(); + + virtual void PreBegin(ImGuiWindowFlags& WindowFlags) override; + + virtual void PostBegin() override; +}; diff --git a/Plugins/Cog/Source/CogCommon/Private/CogCommonLogCategory.cpp b/Plugins/Cog/Source/CogCommon/Private/CogCommonLogCategory.cpp new file mode 100644 index 0000000..f196b5a --- /dev/null +++ b/Plugins/Cog/Source/CogCommon/Private/CogCommonLogCategory.cpp @@ -0,0 +1,3 @@ +#include "CogCommonLogCategory.h" + +DEFINE_LOG_CATEGORY(LogCogNotify); diff --git a/Plugins/Cog/Source/CogCommon/Public/CogCommon.h b/Plugins/Cog/Source/CogCommon/Public/CogCommon.h index 2a5eaf7..6b32441 100644 --- a/Plugins/Cog/Source/CogCommon/Public/CogCommon.h +++ b/Plugins/Cog/Source/CogCommon/Public/CogCommon.h @@ -2,6 +2,7 @@ #include "CoreMinimal.h" #include "Templates/IsArrayOrRefOfType.h" +#include "CogCommonLogCategory.h" #ifndef ENABLE_COG #define ENABLE_COG !UE_BUILD_SHIPPING @@ -17,6 +18,37 @@ //-------------------------------------------------------------------------------------------------------------------------- #define COG_LOG_ACTIVE_FOR_OBJECT(Object) (FCogDebug::IsDebugActiveForObject(Object)) +//-------------------------------------------------------------------------------------------------------------------------- +#define COG_NOTIFY(Format, ...) \ +{ \ + static_assert(TIsArrayOrRefOfType::Value, "Formatting string must be a TCHAR array."); \ + FMsg::Logf_Internal(nullptr, 0, LogCogNotify.GetCategoryName(), ELogVerbosity::Log, Format, ##__VA_ARGS__); \ +} + +//-------------------------------------------------------------------------------------------------------------------------- +#define COG_NOTIFY_WARNING(Format, ...) \ +{ \ + static_assert(TIsArrayOrRefOfType::Value, "Formatting string must be a TCHAR array."); \ + FMsg::Logf_Internal(nullptr, 0, LogCogNotify.GetCategoryName(), ELogVerbosity::Warning, Format, ##__VA_ARGS__); \ +} + +//-------------------------------------------------------------------------------------------------------------------------- +#define COG_NOTIFY_ERROR(Format, ...) \ +{ \ + static_assert(TIsArrayOrRefOfType::Value, "Formatting string must be a TCHAR array."); \ + FMsg::Logf_Internal(nullptr, 0, LogCogNotify.GetCategoryName(), ELogVerbosity::Error, Format, ##__VA_ARGS__); \ +} + +//-------------------------------------------------------------------------------------------------------------------------- +#define COG_NOTIFY_VERBOSITY(Verbosity, Format, ...) \ +{ \ + static_assert(TIsArrayOrRefOfType::Value, "Formatting string must be a TCHAR array."); \ + if (LogCogNotify.IsSuppressed(Verbosity) == false) \ + { \ + FMsg::Logf_Internal(nullptr, 0, LogCogNotify.GetCategoryName(), Verbosity, Format, ##__VA_ARGS__); \ + } \ +} + //-------------------------------------------------------------------------------------------------------------------------- #define COG_LOG(LogCategory, Verbosity, Format, ...) \ { \ @@ -50,10 +82,13 @@ #else //ENABLE_COG -#define IF_COG(expr) (0) -#define COG_LOG_CATEGORY FNoLoggingCategory -#define COG_LOG_ABILITY(...) (0) - +#define IF_COG(expr) (0) +#define COG_LOG_CATEGORY FNoLoggingCategory +#define COG_LOG_ABILITY(...) (0) +#define COG_NOTIFY(Format, ...) (0) +#define COG_NOTIFY_WARNING(Format, ...) (0) +#define COG_NOTIFY_ERROR(Format, ...) (0) +#define COG_NOTIFY_VERBOSITY(Verbosity, Format, ...) (0) #define COG_LOG_ACTIVE_FOR_OBJECT(Object) (0) #define COG_LOG(LogCategory, Verbosity, Format, ...) (0) #define COG_LOG_FUNC(LogCategory, Verbosity, Format, ...) (0) diff --git a/Plugins/Cog/Source/CogCommon/Public/CogCommonConfig.h b/Plugins/Cog/Source/CogCommon/Public/CogCommonConfig.h index d555f89..8f262ec 100644 --- a/Plugins/Cog/Source/CogCommon/Public/CogCommonConfig.h +++ b/Plugins/Cog/Source/CogCommon/Public/CogCommonConfig.h @@ -12,7 +12,6 @@ public: UCogCommonConfig() { - Reset(); } virtual void Reset() diff --git a/Plugins/Cog/Source/CogCommon/Public/CogCommonLogCategory.h b/Plugins/Cog/Source/CogCommon/Public/CogCommonLogCategory.h new file mode 100644 index 0000000..f8cd39d --- /dev/null +++ b/Plugins/Cog/Source/CogCommon/Public/CogCommonLogCategory.h @@ -0,0 +1,5 @@ +#pragma once + +#include "Logging/LogMacros.h" + +COGCOMMON_API DECLARE_LOG_CATEGORY_EXTERN(LogCogNotify, All, All); diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebug.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebug.cpp index ca83704..b03424e 100644 --- a/Plugins/Cog/Source/CogDebug/Private/CogDebug.cpp +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebug.cpp @@ -5,91 +5,33 @@ #include "CogDebugReplicator.h" #include "Engine/Engine.h" #include "Engine/World.h" -#include "imgui.h" #include "Kismet/KismetMathLibrary.h" #include "Misc/EngineVersionComparison.h" //-------------------------------------------------------------------------------------------------------------------------- -TWeakObjectPtr FCogDebug::Selection[] = {}; + TMap FCogDebug::DebugContexts; FCogDebugSettings FCogDebug::Settings = FCogDebugSettings(); //-------------------------------------------------------------------------------------------------------------------------- -void FCogDebug::Reset() +FCogDebugContext& FCogDebug::Get(const int32 InPieId) { - Settings = FCogDebugSettings(); -} - -//-------------------------------------------------------------------------------------------------------------------------- -bool FCogDebug::IsDebugActiveForObject(const UObject* WorldContextObject) -{ - const UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); - if (World == nullptr) + if (InPieId == INDEX_NONE) { - return true; - } - - if (World->GetNetMode() == NM_DedicatedServer) - { - return true; - } - - const bool Result = IsDebugActiveForObject_Internal(WorldContextObject, Selection[GetPieSessionId()].Get(), Settings.bIsFilteringBySelection); - - return Result; -} - -//-------------------------------------------------------------------------------------------------------------------------- -bool FCogDebug::IsReplicatedDebugActiveForObject(const UObject* WorldContextObject, const AActor* ServerSelection, bool IsServerFilteringBySelection) -{ - return IsDebugActiveForObject_Internal(WorldContextObject, ServerSelection, IsServerFilteringBySelection); -} - -//-------------------------------------------------------------------------------------------------------------------------- -bool FCogDebug::IsDebugActiveForObject_Internal(const UObject* WorldContextObject, const AActor* InSelection, bool InIsFilteringBySelection) -{ - if (InIsFilteringBySelection == false) - { - return true; - } - - if (WorldContextObject == nullptr) - { - return true; - } - - const AActor* SelectionPtr = InSelection; - if (SelectionPtr == nullptr) - { - return true; - } - - const UObject* Outer = WorldContextObject; - for (;;) - { - if (SelectionPtr == Outer) + if (FCogDebugContext* Context = DebugContexts.Find(1)) { - return true; + return *Context; } - - if (Cast(Outer)) - { - return false; - } - - const UObject* NewOuter = Outer->GetOuter(); - if (NewOuter == Outer || NewOuter == nullptr) - { - return true; - } - - Outer = NewOuter; } + + FCogDebugContext& Context = DebugContexts.FindOrAdd(InPieId); + return Context; } //-------------------------------------------------------------------------------------------------------------------------- -AActor* FCogDebug::GetSelection() +FCogDebugContext& FCogDebug::Get() { - return Selection[GetPieSessionId()].Get(); + const int32 PieId = GetPieSessionId(); + return Get(PieId); } //-------------------------------------------------------------------------------------------------------------------------- @@ -108,31 +50,91 @@ int32 FCogDebug::GetPieSessionId() //-------------------------------------------------------------------------------------------------------------------------- -void FCogDebug::SetSelection(const UWorld* World, AActor* Value) +void FCogDebug::Reset() { - Selection[GetPieSessionId()] = Value; + Settings = FCogDebugSettings(); +} - ReplicateSelection(World, Value); +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogDebug::IsDebugActiveForObject(const UObject* WorldContextObject) +{ + const UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + if (World == nullptr) + { return true; } + + if (World->GetNetMode() == NM_DedicatedServer) + { return true; } + + const bool Result = IsDebugActiveForObject_Internal(WorldContextObject, GetSelection(), Settings.bIsFilteringBySelection); + return Result; +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogDebug::IsReplicatedDebugActiveForObject(const UObject* WorldContextObject, const AActor* ServerSelection, bool IsServerFilteringBySelection) +{ + return IsDebugActiveForObject_Internal(WorldContextObject, ServerSelection, IsServerFilteringBySelection); +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogDebug::IsDebugActiveForObject_Internal(const UObject* WorldContextObject, const AActor* InSelection, bool InIsFilteringBySelection) +{ + if (InIsFilteringBySelection == false) + { return true; } + + if (WorldContextObject == nullptr) + { return true; } + + const AActor* SelectionPtr = InSelection; + if (SelectionPtr == nullptr) + { return true; } + + const UObject* Outer = WorldContextObject; + for (;;) + { + if (SelectionPtr == Outer) + { return true; } + + if (Cast(Outer)) + { return false; } + + const UObject* NewOuter = Outer->GetOuter(); + if (NewOuter == Outer || NewOuter == nullptr) + { return true; } + + Outer = NewOuter; + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +AActor* FCogDebug::GetSelection() +{ + return Get().Selection.Get(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugTracker& FCogDebug::GetTracker() +{ + return Get().Tracker; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebug::SetSelection(AActor* InValue) +{ + Get().Selection = InValue; } //-------------------------------------------------------------------------------------------------------------------------- void FCogDebug::ReplicateSelection(const UWorld* World, AActor* Value) { if (World == nullptr) - { - return; - } + { return; } ACogDebugReplicator* Replicator = ACogDebugReplicator::GetLocalReplicator(*World); if (Replicator == nullptr) - { - return; - } + { return; } if (Replicator->HasAuthority()) - { - return; - } + { return; } Replicator->Server_SetSelection(Value, Settings.ReplicateSelection); } @@ -144,7 +146,7 @@ bool FCogDebug::GetIsFilteringBySelection() } //-------------------------------------------------------------------------------------------------------------------------- -void FCogDebug::SetIsFilteringBySelection(UWorld* World, bool Value) +void FCogDebug::SetIsFilteringBySelection(const UWorld* World, bool Value) { Settings.bIsFilteringBySelection = Value; @@ -173,13 +175,9 @@ float FCogDebug::GetDebugDuration(bool bPersistent) float FCogDebug::GetDebugTextDuration(bool bPersistent) { if (bPersistent) - { - return Settings.Persistent ? 100 : Settings.Duration; - } - else - { - return 0.0f; - } + { return Settings.Persistent ? 100 : Settings.Duration; } + + return 0.0f; } //-------------------------------------------------------------------------------------------------------------------------- @@ -191,7 +189,7 @@ int FCogDebug::GetDebugSegments() //-------------------------------------------------------------------------------------------------------------------------- int FCogDebug::GetCircleSegments() { - return (Settings.Segments * 2) + 2; // because DrawDebugCircle does Segments = FMath::Max((Segments - 2) / 2, 4) for some reason + return (Settings.Segments * 2) + 2; // because DrawDebugCircle do: Segments = FMath::Max((Segments - 2) / 2, 4) for some reason } //-------------------------------------------------------------------------------------------------------------------------- @@ -216,14 +214,12 @@ uint8 FCogDebug::GetDebugDepthPriority(float InDepthPriority) FColor FCogDebug::ModulateDebugColor(const UWorld* World, const FColor& Color, bool bPersistent) { if (bPersistent == false) - { - return Color; - } + { return Color; } switch (Settings.RecolorMode) { case ECogDebugRecolorMode::None: - { + { return Color; } @@ -252,7 +248,7 @@ FColor FCogDebug::ModulateDebugColor(const UWorld* World, const FColor& Color, b case ECogDebugRecolorMode::HueOverFrames: { const FLinearColor BaseColor(Color); - const float Factor = (Settings.RecolorFrameCycle > 0) ? (GFrameCounter % Settings.RecolorFrameCycle) / (float)Settings.RecolorFrameCycle : 0.0f; + const float Factor = (Settings.RecolorFrameCycle > 0) ? (GFrameCounter % Settings.RecolorFrameCycle) / static_cast(Settings.RecolorFrameCycle) : 0.0f; const FLinearColor NewColor(Factor * 360.0f, 1.0f, 1.0f); const FLinearColor BlendColor = BaseColor * (1.0f - Settings.RecolorIntensity) + NewColor.HSVToLinearRGB() * Settings.RecolorIntensity; return BlendColor.ToFColor(true); @@ -286,9 +282,7 @@ bool FCogDebug::IsSecondarySkeletonBone(FName BoneName) for (const FString& Wildcard : Settings.SecondaryBoneWildcards) { if (BoneString.MatchesWildcard(Wildcard)) - { - return true; - } + { return true; } } return false; @@ -305,12 +299,6 @@ void FCogDebug::GetDebugChannelColors(FColor ChannelColors[ECC_MAX]) ChannelColors[ECC_PhysicsBody] = Settings.ChannelColorPhysicsBody; ChannelColors[ECC_Vehicle] = Settings.ChannelColorVehicle; ChannelColors[ECC_Destructible] = Settings.ChannelColorDestructible; - ChannelColors[ECC_EngineTraceChannel1] = Settings.ChannelColorEngineTraceChannel1; - ChannelColors[ECC_EngineTraceChannel2] = Settings.ChannelColorEngineTraceChannel2; - ChannelColors[ECC_EngineTraceChannel3] = Settings.ChannelColorEngineTraceChannel3; - ChannelColors[ECC_EngineTraceChannel4] = Settings.ChannelColorEngineTraceChannel4; - ChannelColors[ECC_EngineTraceChannel5] = Settings.ChannelColorEngineTraceChannel5; - ChannelColors[ECC_EngineTraceChannel6] = Settings.ChannelColorEngineTraceChannel6; ChannelColors[ECC_GameTraceChannel1] = Settings.ChannelColorGameTraceChannel1; ChannelColors[ECC_GameTraceChannel2] = Settings.ChannelColorGameTraceChannel2; ChannelColors[ECC_GameTraceChannel3] = Settings.ChannelColorGameTraceChannel3; @@ -368,4 +356,40 @@ void FCogDebug::GetDebugDrawSweepSettings(FCogDebugDrawSweepParams& Params) GetDebugDrawLineTraceSettings(Params); Params.DrawHitShapes = Settings.CollisionQueryDrawHitShapes; -} \ No newline at end of file +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebug::Plot(const UObject* WorldContextObject, const FCogDebugTrackId& InTrackId, const float Value) +{ + Get().Tracker.Plot(WorldContextObject, InTrackId, Value); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebug::StartEvent(const UObject* WorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugEventId& InEventId, bool IsInstant, const int32 Row, const FColor& Color) +{ + return Get().Tracker.StartEvent(WorldContextObject, InTrackId, InEventId, IsInstant, Row, Color); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebug::InstantEvent(const UObject* WorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugTrackId& InEventId, const int32 Row, const FColor& Color) +{ + return Get().Tracker.InstantEvent(WorldContextObject, InTrackId, InEventId, Row, Color); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebug::StartEvent(const UObject* WorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugTrackId& InEventId, const int32 Row, const FColor& Color) +{ + return Get().Tracker.StartEvent(WorldContextObject, InTrackId, InEventId, Row, Color); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebug::StopEvent(const UObject* WorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugTrackId& InEventId) +{ + return Get().Tracker.StopEvent(WorldContextObject, InTrackId, InEventId); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebug::ToggleEvent(const UObject* WorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugTrackId& InEventId, const bool ToggleValue, const int32 Row, const FColor& Color) +{ + return Get().Tracker.ToggleEvent(WorldContextObject, InTrackId, InEventId, ToggleValue, Row, Color); +} diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugDraw.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugDraw.cpp index 1e32374..a78e10f 100644 --- a/Plugins/Cog/Source/CogDebug/Private/CogDebugDraw.cpp +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebugDraw.cpp @@ -546,7 +546,7 @@ void FCogDebugDraw::Points(const FLogCategoryBase& LogCategory, const UObject* W int32 Index = 0; for (const FVector& Point : Points) { - const FLinearColor Color = FLinearColor::LerpUsingHSV(FLinearColor(StartColor), FLinearColor(EndColor), Points.Num() <= 1 ? 0.0f : Index / (float)(Points.Num() - 1)); + const FLinearColor Color = FLinearColor::LerpUsingHSV(FLinearColor(StartColor), FLinearColor(EndColor), Points.Num() <= 1 ? 0.0f : Index / static_cast(Points.Num() - 1)); Sphere(LogCategory, WorldContextObject, Point, Radius, Color.ToFColor(true), Persistent, DepthPriority); Index++; } @@ -577,7 +577,7 @@ void FCogDebugDraw::Path(const FLogCategoryBase& LogCategory, const UObject* Wor int32 Index = 0; for (const FVector& Position : Points) { - const FLinearColor LinearColor = FLinearColor::LerpUsingHSV(FLinearColor(StartColor), FLinearColor(EndColor), Points.Num() <= 1 ? 0.0f : Index / (float)(Points.Num() - 1)); + const FLinearColor LinearColor = FLinearColor::LerpUsingHSV(FLinearColor(StartColor), FLinearColor(EndColor), Points.Num() <= 1 ? 0.0f : Index / static_cast(Points.Num() - 1)); FColor Color = LinearColor.ToFColor(true); Point(LogCategory, WorldContextObject, Position, PointSize, Color, Persistent, DepthPriority); @@ -622,7 +622,6 @@ void FCogDebugDraw::Skeleton(const FLogCategoryBase& LogCategory, const USkeleta const FTransform Transform = ComponentSpaceTransforms[BoneIndex] * WorldTransform; const FVector BoneLocation = Transform.GetLocation(); - const FRotator BoneRotation = FRotator(Transform.GetRotation()); const int32 ParentIndex = ReferenceSkeleton.GetParentIndex(BoneIndex); FVector ParentLocation; @@ -703,7 +702,7 @@ void FCogDebugDraw::Sweep(const FLogCategoryBase& LogCategory, const UObject* Wo } //-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugDraw::Overlap(const FLogCategoryBase& LogCategory, const UObject* WorldContextObject, const FCollisionShape& Shape, const FVector& Location, const FQuat& Rotation, TArray& OverlapResults, const FCogDebugDrawOverlapParams& Settings) +void FCogDebugDraw::Overlap(const FLogCategoryBase& LogCategory, const UObject* WorldContextObject, const FCollisionShape& Shape, const FVector& Location, const FQuat& Rotation, const bool HasHits, TArray& OverlapResults, const FCogDebugDrawOverlapParams& Settings) { if (FCogDebugLog::IsLogCategoryActive(LogCategory) == false) { return; } @@ -712,7 +711,7 @@ void FCogDebugDraw::Overlap(const FLogCategoryBase& LogCategory, const UObject* if (World == nullptr) { return; } - FCogDebugDrawHelper::DrawOverlap(World, Shape, Location, Rotation, OverlapResults, Settings); + FCogDebugDrawHelper::DrawOverlap(World, Shape, Location, Rotation, HasHits, OverlapResults, Settings); const FColor Color = OverlapResults.Num() > 0 ? Settings.HitColor diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugDrawHelper.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugDrawHelper.cpp index 56f4bb1..7dc0d4e 100644 --- a/Plugins/Cog/Source/CogDebug/Private/CogDebugDrawHelper.cpp +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebugDrawHelper.cpp @@ -78,7 +78,7 @@ void FCogDebugDrawHelper::DrawArc( } float CurrentAngle = AngleStartRad; - const float AngleStep = (AngleEndRad - AngleStartRad) / float(Segments); + const float AngleStep = (AngleEndRad - AngleStartRad) / static_cast(Segments); FVector PrevVertex = Center + OuterRadius * (AxisZ * FMath::Sin(CurrentAngle) + AxisY * FMath::Cos(CurrentAngle)); int32 Count = Segments; while (Count--) @@ -94,7 +94,6 @@ void FCogDebugDrawHelper::DrawArc( CurrentAngle = AngleStartRad; PrevVertex = Center + InnerRadius * (AxisZ * FMath::Sin(CurrentAngle) + AxisY * FMath::Cos(CurrentAngle)); - Count = Segments; while (Segments--) { CurrentAngle += AngleStep; @@ -189,8 +188,8 @@ void FCogDebugDrawHelper::DrawFrustum( const float HozHalfAngleInRadians = FMath::DegreesToRadians(Angle * 0.5f); - float HozLength = 0.0f; - float VertLength = 0.0f; + float HozLength; + float VertLength; if (Angle > 0.0f) { @@ -398,7 +397,7 @@ void FCogDebugDrawHelper::DrawLineTrace( const FVector& Start, const FVector& End, const bool HasHits, - TArray& HitResults, + const TArray& HitResults, const FCogDebugDrawLineTraceParams& Settings ) { @@ -460,11 +459,12 @@ void FCogDebugDrawHelper::DrawOverlap( const FCollisionShape& Shape, const FVector& Location, const FQuat& Rotation, - TArray& OverlapResults, + const bool HasHits, + const TArray& OverlapResults, const FCogDebugDrawOverlapParams& Settings ) { - const FColor Color = OverlapResults.Num() > 0 ? Settings.HitColor : Settings.NoHitColor; + const FColor Color = HasHits ? Settings.HitColor : Settings.NoHitColor; DrawShape(World, Shape, Location, Rotation, FVector::OneVector, Color, Settings.Persistent, Settings.LifeTime, Settings.DepthPriority, Settings.Thickness); if (Settings.DrawHitPrimitives) diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugDrawImGui.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugDrawImGui.cpp index d26b4dd..8c8a498 100644 --- a/Plugins/Cog/Source/CogDebug/Private/CogDebugDrawImGui.cpp +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebugDrawImGui.cpp @@ -3,6 +3,7 @@ #include "imgui_internal.h" //-------------------------------------------------------------------------------------------------------------------------- +float FCogDebugDrawImGui::Time = 0; TArray FCogDebugDrawImGui::Lines; TArray FCogDebugDrawImGui::Triangles; TArray FCogDebugDrawImGui::TrianglesFilled; @@ -22,7 +23,7 @@ void FCogDebugDrawImGui::AddLine(const ImVec2& P1, const ImVec2& P2, ImU32 Color Line.P2 = P2; Line.Color = Color; Line.Thickness = Thickness; - Line.Time = ImGui::GetCurrentContext()->Time; + Line.Time = Time; Line.Duration = Duration; Line.FadeColor = FadeColor; Lines.Add_GetRef(Line); @@ -37,7 +38,7 @@ void FCogDebugDrawImGui::AddRect(const ImVec2& Min, const ImVec2& Max, ImU32 Col Rectangle.Color = Color; Rectangle.Rounding = Rounding; Rectangle.Thickness = Thickness; - Rectangle.Time = ImGui::GetCurrentContext()->Time; + Rectangle.Time = Time; Rectangle.Duration = Duration; Rectangle.FadeColor = FadeColor; Rectangles.Add_GetRef(Rectangle); @@ -52,7 +53,7 @@ void FCogDebugDrawImGui::AddRectFilled(const ImVec2& Min, const ImVec2& Max, ImU Rectangle.Color = Color; Rectangle.Rounding = Rounding; Rectangle.Thickness = 0.0f; - Rectangle.Time = ImGui::GetCurrentContext()->Time; + Rectangle.Time = Time; Rectangle.Duration = Duration; Rectangle.FadeColor = FadeColor; RectanglesFilled.Add_GetRef(Rectangle); @@ -68,7 +69,7 @@ void FCogDebugDrawImGui::AddQuad(const ImVec2& P1, const ImVec2& P2, const ImVec Quad.P4 = P4; Quad.Color = Color; Quad.Thickness = Thickness; - Quad.Time = ImGui::GetCurrentContext()->Time; + Quad.Time = Time; Quad.Duration = Duration; Quad.FadeColor = FadeColor; Quads.Add_GetRef(Quad); @@ -84,7 +85,7 @@ void FCogDebugDrawImGui::AddQuadFilled(const ImVec2& P1, const ImVec2& P2, const Quad.P4 = P4; Quad.Color = Color; Quad.Thickness = 0.0f; - Quad.Time = ImGui::GetCurrentContext()->Time; + Quad.Time = Time; Quad.Duration = Duration; Quad.FadeColor = FadeColor; QuadsFilled.Add_GetRef(Quad); @@ -99,7 +100,7 @@ void FCogDebugDrawImGui::AddTriangle(const ImVec2& P1, const ImVec2& P2, const I Triangle.P3 = P3; Triangle.Color = Color; Triangle.Thickness = Thickness; - Triangle.Time = ImGui::GetCurrentContext()->Time; + Triangle.Time = Time; Triangle.Duration = Duration; Triangle.FadeColor = FadeColor; Triangles.Add_GetRef(Triangle); @@ -114,7 +115,7 @@ void FCogDebugDrawImGui::AddTriangleFilled(const ImVec2& P1, const ImVec2& P2, c Triangle.P3 = P3; Triangle.Color = Color; Triangle.Thickness = 0.0f; - Triangle.Time = ImGui::GetCurrentContext()->Time; + Triangle.Time = Time; Triangle.Duration = Duration; Triangle.FadeColor = FadeColor; TrianglesFilled.Add_GetRef(Triangle); @@ -123,13 +124,14 @@ void FCogDebugDrawImGui::AddTriangleFilled(const ImVec2& P1, const ImVec2& P2, c //-------------------------------------------------------------------------------------------------------------------------- void FCogDebugDrawImGui::AddCircle(const ImVec2& Center, float Radius, ImU32 Color, int Segments /*= 0*/, float Thickness /*= 1.0f*/, float Duration /*= 0.0f*/, bool FadeColor /*= false*/) { + FCircle Circle; Circle.Center = Center; Circle.Radius = Radius > 0.0f ? Radius : 1.0f; Circle.Color = Color; Circle.Segments = Segments; Circle.Thickness = Thickness; - Circle.Time = ImGui::GetCurrentContext()->Time; + Circle.Time = Time; Circle.Duration = Duration; Circle.FadeColor = FadeColor; Circles.Add_GetRef(Circle); @@ -144,7 +146,7 @@ void FCogDebugDrawImGui::AddCircleFilled(const ImVec2& Center, float Radius, ImU Circle.Color = Color; Circle.Segments = Segments; Circle.Thickness = 0.0f; - Circle.Time = ImGui::GetCurrentContext()->Time; + Circle.Time = Time; Circle.Duration = Duration; Circle.FadeColor = FadeColor; CirclesFilled.Add_GetRef(Circle); @@ -157,7 +159,7 @@ void FCogDebugDrawImGui::AddText(const ImVec2& Pos, const FString& Text, ImU32 C TextElement.Pos = Pos; TextElement.Text = Text; TextElement.Color = Color; - TextElement.Time = ImGui::GetCurrentContext()->Time; + TextElement.Time = Time; TextElement.Duration = Duration; TextElement.FadeColor = FadeColor; Texts.Add_GetRef(TextElement); @@ -169,7 +171,7 @@ void FCogDebugDrawImGui::AddText(const ImVec2& Pos, const FString& Text, ImU32 C ShadowTextElement.Text = Text; const float Alpha = ImGui::ColorConvertU32ToFloat4(Color).w; // Keep original Alpha and set to black ShadowTextElement.Color = ImGui::ColorConvertFloat4ToU32(ImVec4(0, 0, 0, Alpha)); - ShadowTextElement.Time = ImGui::GetCurrentContext()->Time; + ShadowTextElement.Time = Time; ShadowTextElement.Duration = Duration; ShadowTextElement.FadeColor = FadeColor; Texts.Add_GetRef(ShadowTextElement); @@ -181,7 +183,7 @@ void FCogDebugDrawImGui::AddText(const ImVec2& Pos, const FString& Text, ImU32 C void FCogDebugDrawImGui::Draw() { ImDrawList* DrawList = ImGui::GetBackgroundDrawList(); - double Time = ImGui::GetCurrentContext()->Time; + Time = ImGui::GetCurrentContext()->Time; DrawShapes(Lines, [DrawList](const FLine& Line, const ImColor Color) { DrawList->AddLine(Line.P1, Line.P2, Color, Line.Thickness); }); DrawShapes(Rectangles, [DrawList](const FRectangle& Rectangle, const ImColor Color) { DrawList->AddRect(Rectangle.Min, Rectangle.Max, Color, Rectangle.Rounding, Rectangle.Thickness); }); diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugEvent.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugEvent.cpp new file mode 100644 index 0000000..a7b5ebc --- /dev/null +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebugEvent.cpp @@ -0,0 +1,77 @@ +#include "CogDebugEvent.h" + +#include "CogDebugTrack.h" +#include "CogDebugTracker.h" + +//-------------------------------------------------------------------------------------------------------------------------- +float FCogDebugEvent::GetActualEndTime(const UWorld& World) const +{ + if (EndTime != 0.0f) + { return EndTime; } + + const float WorldTime = World.GetTimeSeconds(); + return WorldTime; +} + +//-------------------------------------------------------------------------------------------------------------------------- +uint64 FCogDebugEvent::GetActualEndFrame() const +{ + const float ActualEndFame = EndFrame != 0.0f ? EndFrame : GFrameCounter; + return ActualEndFame; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebugEvent::AddParam(const FCogDebugEventParamId& InParamId, bool InValue) +{ + if (Track == nullptr || Track->Owner == nullptr || Track->Owner->IsVisible == false) + { return *this; } + + return AddParam(InParamId, FString::Printf(TEXT("%s"), InValue ? TEXT("True") : TEXT("False"))); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebugEvent::AddParam(const FCogDebugEventParamId& InParamId, int InValue) +{ + if (Track == nullptr || Track->Owner == nullptr || Track->Owner->IsVisible == false) + { return *this; } + + return AddParam(InParamId, FString::Printf(TEXT("%d"), InValue)); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebugEvent::AddParam(const FCogDebugEventParamId& InParamId, float InValue) +{ + if (Track == nullptr || Track->Owner == nullptr || Track->Owner->IsVisible == false) + { return *this; } + + return AddParam(InParamId, FString::Printf(TEXT("%0.2f"), InValue)); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebugEvent::AddParam(const FCogDebugEventParamId& InParamId, FName InValue) +{ + if (Track == nullptr || Track->Owner == nullptr || Track->Owner->IsVisible == false) + { return *this; } + + return AddParam(InParamId, InValue.ToString()); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebugEvent::AddParam(const FCogDebugEventParamId& InParamId, const FString& InValue) +{ + if (Track == nullptr || Track->Owner == nullptr || Track->Owner->IsVisible == false) + { return *this; } + + if (InParamId == "Name") + { + DisplayName = InValue; + } + else + { + FCogDebugEventParams& Param = Params.AddDefaulted_GetRef(); + Param.Name = InParamId; + Param.Value = InValue; + } + + return *this; +} diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugEventTrack.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugEventTrack.cpp new file mode 100644 index 0000000..f555069 --- /dev/null +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebugEventTrack.cpp @@ -0,0 +1,144 @@ +#include "CogDebugEventTrack.h" + +#include "CogDebugTracker.h" +#include "CogDebugHelper.h" +#include "CogImguiHelper.h" +#include "Engine/Engine.h" +#include "Engine/World.h" + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebugEventTrack::AddEvent( + const FString& OwnerName, + const bool IsInstant, + const FName EventId, + const int32 Row, + const FColor& Color) +{ + if (Events.Max() < 200) + { + Events.Reserve(200); + } + + //---------------------------- + // Stop if it already exist. + //---------------------------- + StopEvent(EventId); + + FCogDebugEvent* Event; + int32 AddedIndex; + + if (Events.Num() < Events.Max()) + { + Event = &Events.AddDefaulted_GetRef(); + AddedIndex = Events.Num() - 1; + } + else + { + Event = &Events[EventOffset]; + AddedIndex = EventOffset; + EventOffset = (EventOffset + 1) % Events.Num(); + } + + Event->Id = EventId; + Event->OwnerName = OwnerName; + Event->DisplayName = EventId.ToString(); + Event->StartTime = Time; + Event->EndTime = IsInstant ? Time : 0.0f; + Event->StartFrame = Frame; + Event->EndFrame = IsInstant ? Frame : 0.0f; + Event->Row = (Row == FCogDebugTracker::AutoRow) ? Owner->FindFreeViewRow(GraphIndex) : Row; + + if (IsInstant == false) + { + Owner->OccupyViewRow(GraphIndex, Event->Row); + } + + MaxRow = FMath::Max(Event->Row, MaxRow); + + const FColor BorderColor = FCogDebugHelper::GetAutoColor(EventId, Color).WithAlpha(200); + const FColor FillColor = BorderColor.WithAlpha(100); + Event->BorderColor = FCogImguiHelper::ToImColor(BorderColor); + Event->FillColor = FCogImguiHelper::ToImColor(FillColor); + + Owner->LastAddedEventTrackId = Id; + Owner->LastAddedEventIndex = AddedIndex; + + return *Event; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebugEventTrack::StopEvent(const FCogDebugEventId EventId) +{ + FCogDebugEvent* Event = FindLastEventByName(EventId); + if (Event == nullptr) + { + return Owner->DefaultEvent; + } + + if (Event->EndTime == 0.0f) + { + Event->EndTime = Time; + Event->EndFrame = Frame; + + Owner->FreeViewRow(GraphIndex, Event->Row); + } + + return *Event; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent* FCogDebugEventTrack::GetLastEvent() +{ + if (Events.Num() == 0) + { + return nullptr; + } + + int32 Index = Events.Num() - 1; + if (EventOffset != 0) + { + Index = (Index + EventOffset) % Events.Num(); + } + + return &Events[Index]; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent* FCogDebugEventTrack::FindLastEventByName(FCogDebugEventId EventId) +{ + for (int32 i = Events.Num() - 1; i >= 0; --i) + { + //-------------------------------------------------- + // The array cycle so we must offset the index + //-------------------------------------------------- + int32 Index = i; + if (EventOffset != 0) + { + Index = (i + EventOffset) % Events.Num(); + } + + if (Events[Index].Id == EventId) + { + return &Events[Index]; + } + } + + return nullptr; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugEventTrack::Clear() +{ + FCogDebugTrack::Clear(); + + Owner->ResetLastAddedEvent(); + + MaxRow = 0; + + if (Events.Num() > 0) + { + Events.Empty(); + Events.Shrink(); + EventOffset = 0; + } +} diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugGizmo.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugGizmo.cpp index 1d172ed..7e79ccc 100644 --- a/Plugins/Cog/Source/CogDebug/Private/CogDebugGizmo.cpp +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebugGizmo.cpp @@ -3,12 +3,12 @@ #include "CogDebug.h" #include "CogDebugDrawHelper.h" #include "CogImguiHelper.h" +#include "DrawDebugHelpers.h" +#include "imgui.h" #include "Components/PrimitiveComponent.h" #include "Components/SceneComponent.h" -#include "DrawDebugHelpers.h" #include "Engine/World.h" #include "GameFramework/PlayerController.h" -#include "imgui.h" #include "Kismet/GameplayStatics.h" //-------------------------------------------------------------------------------------------------------------------------- @@ -74,11 +74,11 @@ float ScreenDistanceToArc(const APlayerController& InPlayerController, const FVe const FVector AxisZ = Matrix.GetScaledAxis(EAxis::Z); float CurrentAngle = AngleStartRad; - const float AngleStep = (AngleEndRad - AngleStartRad) / float(NumSegments); + const float AngleStep = (AngleEndRad - AngleStartRad) / static_cast(NumSegments); FVector P0 = Center + Radius * (AxisZ * FMath::Sin(CurrentAngle) + AxisY * FMath::Cos(CurrentAngle)); - FVector2D ScreenP0; + FVector2D ScreenP0; UGameplayStatics::ProjectWorldToScreen(&InPlayerController, P0, ScreenP0); float MinDistanceSqr = FLT_MAX; @@ -163,14 +163,14 @@ void DrawGizmoText(const ImVec2& Position, ImU32 Color, const char* Text) bool RenderComponent(const char* Label, double* Value, double Reset) { ImGui::TableNextColumn(); - ImGui::PushItemWidth(-1); + ImGui::PushItemWidth(-1); bool Result = FCogImguiHelper::DragDouble(Label, Value, 0.1f, 0.0f, 0.0f, "%.1f"); - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) - { + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) + { *Value = Reset; Result = true; - } - ImGui::PopItemWidth(); + } + ImGui::PopItemWidth(); return Result; } @@ -240,7 +240,7 @@ bool FCogDebug_Gizmo::Draw(const char* Id, const APlayerController& InPlayerCont const float GizmoScale = Settings.GizmoScale * ScaleToKeepGizmoScreenSizeConstant; const FQuat RotX = Settings.GizmoUseLocalSpace ? InOutTransform.GetRotation() : FQuat(FVector(0.0f, 0.0f, 1.0f), 0.0f); - const FQuat RotY = RotX * FQuat(FVector(0.0f, 0.0f,-1.0f), UE_HALF_PI); + const FQuat RotY = RotX * FQuat(FVector(0.0f, 0.0f, -1.0f), UE_HALF_PI); const FQuat RotZ = RotX * FQuat(FVector(0.0f, 1.0f, 0.0f), UE_HALF_PI); const FVector UnitAxisX = Settings.GizmoUseLocalSpace ? RotX.GetAxisX() : FVector::XAxisVector; @@ -255,11 +255,11 @@ bool FCogDebug_Gizmo::Draw(const char* Id, const APlayerController& InPlayerCont const ImVec2 ImMousePos = ImGui::GetMousePos() - Viewport->Pos; const FVector2D MousePos = FCogImguiHelper::ToFVector2D(ImMousePos); - const FColor GizmoAxisColorsZLow[] = { Settings.GizmoAxisColorsZLowX, Settings.GizmoAxisColorsZLowY, Settings.GizmoAxisColorsZLowZ, Settings.GizmoAxisColorsZLowW }; - const FColor GizmoAxisColorsZHigh[] = { Settings.GizmoAxisColorsZHighX, Settings.GizmoAxisColorsZHighY, Settings.GizmoAxisColorsZHighZ, Settings.GizmoAxisColorsZHighW }; - const FColor GizmoAxisColorsSelection[] = { Settings.GizmoAxisColorsSelectionX, Settings.GizmoAxisColorsSelectionY, Settings.GizmoAxisColorsSelectionZ, Settings.GizmoAxisColorsSelectionW }; + const FColor GizmoAxisColorsZLow[] = {Settings.GizmoAxisColorsZLowX, Settings.GizmoAxisColorsZLowY, Settings.GizmoAxisColorsZLowZ, Settings.GizmoAxisColorsZLowW}; + const FColor GizmoAxisColorsZHigh[] = {Settings.GizmoAxisColorsZHighX, Settings.GizmoAxisColorsZHighY, Settings.GizmoAxisColorsZHighZ, Settings.GizmoAxisColorsZHighW}; + const FColor GizmoAxisColorsSelection[] = {Settings.GizmoAxisColorsSelectionX, Settings.GizmoAxisColorsSelectionY, Settings.GizmoAxisColorsSelectionZ, Settings.GizmoAxisColorsSelectionW}; - FCogDebug_GizmoElement GizmoElements[(uint8)ECogDebug_GizmoElementType::MAX]; + FCogDebug_GizmoElement GizmoElements[static_cast(ECogDebug_GizmoElementType::MAX)]; for (FCogDebug_GizmoElement& GizmoElement : GizmoElements) { GizmoElement.Type = ECogDebug_GizmoType::MAX; @@ -267,35 +267,35 @@ bool FCogDebug_Gizmo::Draw(const char* Id, const APlayerController& InPlayerCont if (EnumHasAnyFlags(Flags, ECogDebug_GizmoFlags::NoTranslationAxis) == false) { - GizmoElements[(uint8)ECogDebug_GizmoElementType::MoveX] = { ECogDebug_GizmoType::MoveAxis, ECogDebug_GizmoAxis::X, FVector::XAxisVector, UnitAxisX, RotX, GizmoCenter + UnitAxisX * Settings.GizmoTranslationAxisLength * GizmoScale }; - GizmoElements[(uint8)ECogDebug_GizmoElementType::MoveY] = { ECogDebug_GizmoType::MoveAxis, ECogDebug_GizmoAxis::Y, FVector::YAxisVector, UnitAxisY, RotY, GizmoCenter + UnitAxisY * Settings.GizmoTranslationAxisLength * GizmoScale }; - GizmoElements[(uint8)ECogDebug_GizmoElementType::MoveZ] = { ECogDebug_GizmoType::MoveAxis, ECogDebug_GizmoAxis::Z, FVector::ZAxisVector, UnitAxisZ, RotZ, GizmoCenter + UnitAxisZ * Settings.GizmoTranslationAxisLength * GizmoScale }; + GizmoElements[static_cast(ECogDebug_GizmoElementType::MoveX)] = {ECogDebug_GizmoType::MoveAxis, ECogDebug_GizmoAxis::X, FVector::XAxisVector, UnitAxisX, RotX, GizmoCenter + UnitAxisX * Settings.GizmoTranslationAxisLength * GizmoScale}; + GizmoElements[static_cast(ECogDebug_GizmoElementType::MoveY)] = {ECogDebug_GizmoType::MoveAxis, ECogDebug_GizmoAxis::Y, FVector::YAxisVector, UnitAxisY, RotY, GizmoCenter + UnitAxisY * Settings.GizmoTranslationAxisLength * GizmoScale}; + GizmoElements[static_cast(ECogDebug_GizmoElementType::MoveZ)] = {ECogDebug_GizmoType::MoveAxis, ECogDebug_GizmoAxis::Z, FVector::ZAxisVector, UnitAxisZ, RotZ, GizmoCenter + UnitAxisZ * Settings.GizmoTranslationAxisLength * GizmoScale}; } if (EnumHasAnyFlags(Flags, ECogDebug_GizmoFlags::NoTranslationPlane) == false) { - GizmoElements[(uint8)ECogDebug_GizmoElementType::MoveXY] = { ECogDebug_GizmoType::MovePlane, ECogDebug_GizmoAxis::Z, FVector::ZAxisVector, UnitAxisZ, RotZ, GizmoCenter + ((UnitAxisX + UnitAxisY) * Settings.GizmoTranslationPlaneOffset * GizmoScale) }; - GizmoElements[(uint8)ECogDebug_GizmoElementType::MoveXZ] = { ECogDebug_GizmoType::MovePlane, ECogDebug_GizmoAxis::Y, FVector::YAxisVector, UnitAxisY, RotY, GizmoCenter + ((UnitAxisX + UnitAxisZ) * Settings.GizmoTranslationPlaneOffset * GizmoScale) }; - GizmoElements[(uint8)ECogDebug_GizmoElementType::MoveYZ] = { ECogDebug_GizmoType::MovePlane, ECogDebug_GizmoAxis::X, FVector::XAxisVector, UnitAxisX, RotX, GizmoCenter + ((UnitAxisY + UnitAxisZ) * Settings.GizmoTranslationPlaneOffset * GizmoScale) }; + GizmoElements[static_cast(ECogDebug_GizmoElementType::MoveXY)] = {ECogDebug_GizmoType::MovePlane, ECogDebug_GizmoAxis::Z, FVector::ZAxisVector, UnitAxisZ, RotZ, GizmoCenter + ((UnitAxisX + UnitAxisY) * Settings.GizmoTranslationPlaneOffset * GizmoScale)}; + GizmoElements[static_cast(ECogDebug_GizmoElementType::MoveXZ)] = {ECogDebug_GizmoType::MovePlane, ECogDebug_GizmoAxis::Y, FVector::YAxisVector, UnitAxisY, RotY, GizmoCenter + ((UnitAxisX + UnitAxisZ) * Settings.GizmoTranslationPlaneOffset * GizmoScale)}; + GizmoElements[static_cast(ECogDebug_GizmoElementType::MoveYZ)] = {ECogDebug_GizmoType::MovePlane, ECogDebug_GizmoAxis::X, FVector::XAxisVector, UnitAxisX, RotX, GizmoCenter + ((UnitAxisY + UnitAxisZ) * Settings.GizmoTranslationPlaneOffset * GizmoScale)}; } if (EnumHasAnyFlags(Flags, ECogDebug_GizmoFlags::NoRotation) == false) { - GizmoElements[(uint8)ECogDebug_GizmoElementType::RotateX] = { ECogDebug_GizmoType::Rotate, ECogDebug_GizmoAxis::X, FVector::XAxisVector, UnitAxisX, RotX, FVector::ZeroVector }; - GizmoElements[(uint8)ECogDebug_GizmoElementType::RotateY] = { ECogDebug_GizmoType::Rotate, ECogDebug_GizmoAxis::Y, FVector::YAxisVector, UnitAxisY, RotY, FVector::ZeroVector }; - GizmoElements[(uint8)ECogDebug_GizmoElementType::RotateZ] = { ECogDebug_GizmoType::Rotate, ECogDebug_GizmoAxis::Z, FVector::ZAxisVector, UnitAxisZ, RotZ, FVector::ZeroVector }; + GizmoElements[static_cast(ECogDebug_GizmoElementType::RotateX)] = {ECogDebug_GizmoType::Rotate, ECogDebug_GizmoAxis::X, FVector::XAxisVector, UnitAxisX, RotX, FVector::ZeroVector}; + GizmoElements[static_cast(ECogDebug_GizmoElementType::RotateY)] = {ECogDebug_GizmoType::Rotate, ECogDebug_GizmoAxis::Y, FVector::YAxisVector, UnitAxisY, RotY, FVector::ZeroVector}; + GizmoElements[static_cast(ECogDebug_GizmoElementType::RotateZ)] = {ECogDebug_GizmoType::Rotate, ECogDebug_GizmoAxis::Z, FVector::ZAxisVector, UnitAxisZ, RotZ, FVector::ZeroVector}; } if (EnumHasAnyFlags(Flags, ECogDebug_GizmoFlags::NoScaleUniform) == false) { - GizmoElements[(uint8)ECogDebug_GizmoElementType::ScaleXYZ] = { ECogDebug_GizmoType::ScaleUniform, ECogDebug_GizmoAxis::MAX, FVector::OneVector, FVector::OneVector, RotX, GizmoCenter }; + GizmoElements[static_cast(ECogDebug_GizmoElementType::ScaleXYZ)] = {ECogDebug_GizmoType::ScaleUniform, ECogDebug_GizmoAxis::MAX, FVector::OneVector, FVector::OneVector, RotX, GizmoCenter}; } if (EnumHasAnyFlags(Flags, ECogDebug_GizmoFlags::NoScaleAxis) == false) { - GizmoElements[(uint8)ECogDebug_GizmoElementType::ScaleX] = { ECogDebug_GizmoType::ScaleAxis, ECogDebug_GizmoAxis::X, FVector::XAxisVector, UnitAxisX, RotX, GizmoCenter + UnitAxisX * Settings.GizmoScaleBoxOffset * GizmoScale }; - GizmoElements[(uint8)ECogDebug_GizmoElementType::ScaleY] = { ECogDebug_GizmoType::ScaleAxis, ECogDebug_GizmoAxis::Y, FVector::YAxisVector, UnitAxisY, RotY, GizmoCenter + UnitAxisY * Settings.GizmoScaleBoxOffset * GizmoScale }; - GizmoElements[(uint8)ECogDebug_GizmoElementType::ScaleZ] = { ECogDebug_GizmoType::ScaleAxis, ECogDebug_GizmoAxis::Z, FVector::ZAxisVector, UnitAxisZ, RotZ, GizmoCenter + UnitAxisZ * Settings.GizmoScaleBoxOffset * GizmoScale }; + GizmoElements[static_cast(ECogDebug_GizmoElementType::ScaleX)] = {ECogDebug_GizmoType::ScaleAxis, ECogDebug_GizmoAxis::X, FVector::XAxisVector, UnitAxisX, RotX, GizmoCenter + UnitAxisX * Settings.GizmoScaleBoxOffset * GizmoScale}; + GizmoElements[static_cast(ECogDebug_GizmoElementType::ScaleY)] = {ECogDebug_GizmoType::ScaleAxis, ECogDebug_GizmoAxis::Y, FVector::YAxisVector, UnitAxisY, RotY, GizmoCenter + UnitAxisY * Settings.GizmoScaleBoxOffset * GizmoScale}; + GizmoElements[static_cast(ECogDebug_GizmoElementType::ScaleZ)] = {ECogDebug_GizmoType::ScaleAxis, ECogDebug_GizmoAxis::Z, FVector::ZAxisVector, UnitAxisZ, RotZ, GizmoCenter + UnitAxisZ * Settings.GizmoScaleBoxOffset * GizmoScale}; } ECogDebug_GizmoElementType HoveredElementType = ECogDebug_GizmoElementType::MAX; @@ -306,7 +306,7 @@ bool FCogDebug_Gizmo::Draw(const char* Id, const APlayerController& InPlayerCont else if (IO.WantCaptureMouse == false) { float MinDistanceToMouse = FLT_MAX; - for (uint8 i = (uint8)ECogDebug_GizmoElementType::MoveX; i < (uint8)ECogDebug_GizmoElementType::MAX; ++i) + for (uint8 i = static_cast(ECogDebug_GizmoElementType::MoveX); i < static_cast(ECogDebug_GizmoElementType::MAX); ++i) { FCogDebug_GizmoElement& Elm = GizmoElements[i]; float DistanceToMouse = FLT_MAX; @@ -340,22 +340,22 @@ bool FCogDebug_Gizmo::Draw(const char* Id, const APlayerController& InPlayerCont break; } - default:; + default: ; } if (DistanceToMouse < Settings.GizmoCursorSelectionThreshold && DistanceToMouse < MinDistanceToMouse) { - HoveredElementType = (ECogDebug_GizmoElementType)i; + HoveredElementType = static_cast(i); MinDistanceToMouse = DistanceToMouse; } } } - for (uint8 i = (uint8)ECogDebug_GizmoElementType::MoveX; i < (uint8)ECogDebug_GizmoElementType::MAX; ++i) + for (uint8 i = static_cast(ECogDebug_GizmoElementType::MoveX); i < static_cast(ECogDebug_GizmoElementType::MAX); ++i) { const FCogDebug_GizmoElement& Elm = GizmoElements[i]; - const bool IsClosestToMouse = i == (uint8)HoveredElementType; - const uint8 AxisIndex = (uint8)Elm.AxisType; + const bool IsClosestToMouse = i == static_cast(HoveredElementType); + const uint8 AxisIndex = static_cast(Elm.AxisType); const FColor ZLowColor = IsClosestToMouse ? GizmoAxisColorsSelection[AxisIndex] : GizmoAxisColorsZLow[AxisIndex]; const FColor ZHighColor = IsClosestToMouse ? GizmoAxisColorsSelection[AxisIndex] : GizmoAxisColorsZHigh[AxisIndex]; @@ -394,7 +394,7 @@ bool FCogDebug_Gizmo::Draw(const char* Id, const APlayerController& InPlayerCont break; } - default: + default: break; } } @@ -409,7 +409,7 @@ bool FCogDebug_Gizmo::Draw(const char* Id, const APlayerController& InPlayerCont const FRotationTranslationMatrix Matrix(FRotator(90.0f, 0, 0), GroundHit.ImpactPoint); FCogDebugDrawHelper::DrawArc(World, Matrix, Settings.GizmoGroundRaycastCircleRadius, Settings.GizmoGroundRaycastCircleRadius, 0.0f, 360.0f, 24, Settings.GizmoGroundRaycastCircleColor, false, 0.0f, Settings.GizmoZLow, ThicknessZLow); } - DrawDebugLine(World, GizmoCenter, Bottom, Settings.GizmoGroundRaycastColor, false, 0.0f, Settings.GizmoZLow, ThicknessZLow); + DrawDebugLine(World, GizmoCenter, Bottom, Settings.GizmoGroundRaycastColor, false, 0.0f, Settings.GizmoZLow, ThicknessZLow); } if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) @@ -418,18 +418,18 @@ bool FCogDebug_Gizmo::Draw(const char* Id, const APlayerController& InPlayerCont } else if (DraggedElementType != ECogDebug_GizmoElementType::MAX) { - if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) + if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { InOutTransform = InitialTransform; DraggedElementType = ECogDebug_GizmoElementType::MAX; } else if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, Settings.GizmoCursorDraggingThreshold)) { - const FCogDebug_GizmoElement& DraggedElement = GizmoElements[(uint8)DraggedElementType]; + const FCogDebug_GizmoElement& DraggedElement = GizmoElements[static_cast(DraggedElementType)]; switch (DraggedElement.Type) { - case ECogDebug_GizmoType::MoveAxis: + case ECogDebug_GizmoType::MoveAxis: { const FVector CursorOnLine = GetMouseCursorOnLine(InPlayerController, InitialTransform.GetTranslation(), DraggedElement.Direction, MousePos - CursorOffset); const float Delta = FVector::DotProduct(DraggedElement.Direction, CursorOnLine - InitialTransform.GetTranslation()); @@ -472,8 +472,8 @@ bool FCogDebug_Gizmo::Draw(const char* Id, const APlayerController& InPlayerCont DrawGizmoText(FCogImguiHelper::ToImVec2(Center2D), FCogImguiHelper::ToImU32(Settings.GizmoTextColor), StringCast(*Text).Get()); //DrawDebugPoint(World, InitialTransform.GetTranslation(), 5.0f, FColor::White); - //DrawDebugLine(World, InitialTransform.GetTranslation(), InitialTransform.GetTranslation() + WorldDeltaU, FColor::White); - //DrawDebugLine(World, InitialTransform.GetTranslation() + WorldDeltaU, InitialTransform.GetTranslation() + WorldDeltaU + WorldDeltaV, FColor::White); + //DrawDebugLine(World, InitialTransform.GetTranslation(), InitialTransform.GetTranslation() + WorldDeltaU, FColor::White); + //DrawDebugLine(World, InitialTransform.GetTranslation() + WorldDeltaU, InitialTransform.GetTranslation() + WorldDeltaU + WorldDeltaV, FColor::White); break; } @@ -485,7 +485,7 @@ bool FCogDebug_Gizmo::Draw(const char* Id, const APlayerController& InPlayerCont const float NormalizedAngle = FRotator::NormalizeAxis(DragAmount * Settings.GizmoRotationSpeed); const float SnappedAngle = FMath::GridSnap(NormalizedAngle, Settings.GizmoRotationSnapEnable ? Settings.GizmoRotationSnapValue : 0.0f); const FQuat RotDelta(-DraggedElement.Axis, FMath::DegreesToRadians(SnappedAngle)); - const FQuat NewRot = (Settings.GizmoUseLocalSpace) ? InitialTransform.GetRotation() * RotDelta : RotDelta * InitialTransform.GetRotation(); + const FQuat NewRot = (Settings.GizmoUseLocalSpace) ? InitialTransform.GetRotation() * RotDelta : RotDelta * InitialTransform.GetRotation(); InOutTransform.SetRotation(NewRot); Result = true; @@ -526,84 +526,136 @@ bool FCogDebug_Gizmo::Draw(const char* Id, const APlayerController& InPlayerCont break; } - default: + default: break; } - } } else if (HoveredElementType != ECogDebug_GizmoElementType::MAX) { - if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { - DraggedElementType = HoveredElementType; + DraggedElementType = HoveredElementType; CursorOffset = MousePos - Center2D; InitialTransform = InOutTransform; } - //else if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) - //{ - // ImGui::OpenPopup(Id); - //} + else if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) + { + if (Settings.GizmoSupportContextMenu) + { + ImGui::OpenPopup(Id); + } + } } - //if (ImGui::BeginPopup(Id)) - //{ - // FVector Translation = InOutTransform.GetTranslation(); - // FRotator Rotation = InOutTransform.GetRotation().Rotator(); - // FVector Scale = InOutTransform.GetScale3D(); + if (Settings.GizmoSupportContextMenu) + { + if (ImGui::BeginPopup(Id)) + { + FVector Translation = InOutTransform.GetTranslation(); + FRotator Rotation = InOutTransform.GetRotation().Rotator(); + FVector Scale = InOutTransform.GetScale3D(); - // ImGui::Checkbox("Local Space", &Settings.GizmoUseLocalSpace); + ImGui::Checkbox("Local Space", &Settings.GizmoUseLocalSpace); - // ImGui::Separator(); + ImGui::Separator(); - // ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(1.0f, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(1.0f, 1.0f)); - // if (ImGui::BeginTable("Pools", 6, ImGuiTableFlags_SizingFixedFit)) - // { - // ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 5); - // ImGui::TableSetupColumn("X", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 4); - // ImGui::TableSetupColumn("Y", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 4); - // ImGui::TableSetupColumn("Z", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 4); - // ImGui::TableSetupColumn("SnapEnable", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 4); - // ImGui::TableSetupColumn("Snap", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 3); + if (ImGui::BeginTable("Pools", 6, ImGuiTableFlags_SizingFixedFit)) + { + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 5); + ImGui::TableSetupColumn("X", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 4); + ImGui::TableSetupColumn("Y", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 4); + ImGui::TableSetupColumn("Z", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 4); + ImGui::TableSetupColumn("SnapEnable", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 4); + ImGui::TableSetupColumn("Snap", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 3); - // ImGui::PushID("Location"); - // ImGui::TableNextRow(); - // ImGui::TableNextColumn(); - // ImGui::Text("Location"); - // if (RenderComponent("##X", &Translation.X, 0.0)) { InOutTransform.SetTranslation(Translation); } - // if (RenderComponent("##Y", &Translation.Y, 0.0)) { InOutTransform.SetTranslation(Translation); } - // if (RenderComponent("##Z", &Translation.Z, 0.0)) { InOutTransform.SetTranslation(Translation); } - // RenderSnap(&Settings.GizmoTranslationSnapEnable, &Settings.GizmoTranslationSnapValue); - // ImGui::PopID(); + ImGui::PushID("Location"); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Location"); + + bool Translate = false; + Translate |= RenderComponent("##X", &Translation.X, 0.0); + Translate |= RenderComponent("##Y", &Translation.Y, 0.0); + Translate |= RenderComponent("##Z", &Translation.Z, 0.0); + if (Translate) + { + if (Settings.GizmoTranslationSnapEnable) + { + Translation.X = FMath::GridSnap(Translation.X, Settings.GizmoTranslationSnapValue); + Translation.Y = FMath::GridSnap(Translation.Y, Settings.GizmoTranslationSnapValue); + Translation.Z = FMath::GridSnap(Translation.Z, Settings.GizmoTranslationSnapValue); + } + + InOutTransform.SetTranslation(Translation); + Result = true; + } + + RenderSnap(&Settings.GizmoTranslationSnapEnable, &Settings.GizmoTranslationSnapValue); + ImGui::PopID(); - // ImGui::PushID("Rotation"); - // ImGui::TableNextRow(); - // ImGui::TableNextColumn(); - // ImGui::Text("Rotation"); - // if (RenderComponent("##X", &Rotation.Yaw, 0.0)) { InOutTransform.SetRotation(Rotation.Quaternion()); } - // if (RenderComponent("##Y", &Rotation.Pitch, 0.0)) { InOutTransform.SetRotation(Rotation.Quaternion()); } - // if (RenderComponent("##Z", &Rotation.Roll, 0.0)) { InOutTransform.SetRotation(Rotation.Quaternion()); } - // RenderSnap(&Settings.GizmoRotationSnapEnable, &Settings.GizmoRotationSnapValue); - // ImGui::PopID(); + ImGui::PushID("Rotation"); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Rotation"); - // ImGui::PushID("Scale"); - // ImGui::TableNextRow(); - // ImGui::TableNextColumn(); - // ImGui::Text("Scale"); - // if (RenderComponent("##X", &Scale.X, 0.0)) { InOutTransform.SetScale3D(Scale); } - // if (RenderComponent("##Y", &Scale.Y, 0.0)) { InOutTransform.SetScale3D(Scale); } - // if (RenderComponent("##Z", &Scale.Z, 0.0)) { InOutTransform.SetScale3D(Scale); } - // RenderSnap(&Settings.GizmoScaleSnapEnable, &Settings.GizmoScaleSnapValue); - // ImGui::PopID(); + bool Rotate = false; + Rotate |= RenderComponent("##X", &Rotation.Yaw, 0.0); + Rotate |= RenderComponent("##Y", &Rotation.Pitch, 0.0); + Rotate |= RenderComponent("##Z", &Rotation.Roll, 0.0); - // ImGui::EndTable(); - // } + if (Rotate) + { + if (Settings.GizmoRotationSnapEnable) + { + Rotation.Yaw = FMath::GridSnap(Rotation.Yaw, Settings.GizmoRotationSnapValue); + Rotation.Pitch = FMath::GridSnap(Rotation.Pitch, Settings.GizmoRotationSnapValue); + Rotation.Roll = FMath::GridSnap(Rotation.Roll, Settings.GizmoRotationSnapValue); + } + + InOutTransform.SetRotation(Rotation.Quaternion()); + Result = true; + } + + RenderSnap(&Settings.GizmoRotationSnapEnable, &Settings.GizmoRotationSnapValue); + ImGui::PopID(); - // ImGui::PopStyleVar(); + ImGui::PushID("Scale"); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Scale"); - // ImGui::EndPopup(); - //} + bool Rescale = false; + Rescale |= RenderComponent("##X", &Scale.X, 1.0); + Rescale |= RenderComponent("##Y", &Scale.Y, 1.0); + Rescale |= RenderComponent("##Z", &Scale.Z, 1.0); + + if (Rescale) + { + if (Settings.GizmoScaleSnapEnable) + { + Scale.X = FMath::GridSnap(Scale.X, Settings.GizmoScaleSnapValue); + Scale.Y = FMath::GridSnap(Scale.Y, Settings.GizmoScaleSnapValue); + Scale.Z = FMath::GridSnap(Scale.Z, Settings.GizmoScaleSnapValue); + } + + InOutTransform.SetScale3D(Scale); + Result = true; + } + + RenderSnap(&Settings.GizmoScaleSnapEnable, &Settings.GizmoScaleSnapValue); + ImGui::PopID(); + + ImGui::EndTable(); + } + + ImGui::PopStyleVar(); + + ImGui::EndPopup(); + } + } return Result; } diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugHelper.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugHelper.cpp index 29213bc..693337d 100644 --- a/Plugins/Cog/Source/CogDebug/Private/CogDebugHelper.cpp +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebugHelper.cpp @@ -12,8 +12,8 @@ FColor FCogDebugHelper::GetAutoColor(FName Name, const FColor& UserColor) const uint32 Hash = GetTypeHash(Name.ToString()); FMath::RandInit(Hash); - const uint8 Hue = (uint8)(FMath::FRand() * 255); - const uint8 Saturation = 255; + const uint8 Hue = static_cast(FMath::FRand() * 255); + constexpr uint8 Saturation = 255; const uint8 Value = FMath::Rand() > 0.5f ? 200 : 255; return FLinearColor::MakeFromHSV8(Hue, Saturation, Value).ToFColor(true); @@ -33,9 +33,8 @@ const char* FCogDebugHelper::VerbosityToString(ELogVerbosity::Type Verbosity) case ELogVerbosity::Log: return "Log"; case ELogVerbosity::Verbose: return "Verbose"; case ELogVerbosity::VeryVerbose: return "Very Verbose"; + default: return "None"; } - - return "None"; } //-------------------------------------------------------------------------------------------------------------------------- diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugLog.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugLog.cpp index 8e2f424..ef25a0b 100644 --- a/Plugins/Cog/Source/CogDebug/Private/CogDebugLog.cpp +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebugLog.cpp @@ -97,16 +97,16 @@ ELogVerbosity::Type FCogDebugLog::GetServerVerbosity(const FName CategoryName) } //-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugLog::SetServerVerbosity(UWorld& World, const FName CategoryName, ELogVerbosity::Type Verbosity) +void FCogDebugLog::SetServerVerbosity(const UWorld& World, const FName CategoryName, ELogVerbosity::Type Verbosity) { if (ACogDebugReplicator* Replicator = ACogDebugReplicator::GetLocalReplicator(World)) { - Replicator->Server_SetCategoryVerbosity(CategoryName, (ECogLogVerbosity)Verbosity); + Replicator->Server_SetCategoryVerbosity(CategoryName, static_cast(Verbosity)); } } //-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugLog::SetServerVerbosityActive(UWorld& World, const FName CategoryName, const bool Value) +void FCogDebugLog::SetServerVerbosityActive(const UWorld& World, const FName CategoryName, const bool Value) { SetServerVerbosity(World, CategoryName, Value ? ELogVerbosity::Verbose : ELogVerbosity::Warning); } diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugLogBlueprint.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugLogBlueprint.cpp index ed45fb2..3f90a7c 100644 --- a/Plugins/Cog/Source/CogDebug/Private/CogDebugLogBlueprint.cpp +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebugLogBlueprint.cpp @@ -30,6 +30,7 @@ void UCogDebugLogBlueprint::Log(const UObject* WorldContextObject, const FCogLog } //-------------------------------------------------------------------------------------------------------------------------- +// ReSharper disable once CppPassValueParameterByConstReference bool UCogDebugLogBlueprint::IsLogActive(const UObject* WorldContextObject, const FCogLogCategory LogCategory) { #if ENABLE_COG diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugPlot.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugPlot.cpp deleted file mode 100644 index fb34a22..0000000 --- a/Plugins/Cog/Source/CogDebug/Private/CogDebugPlot.cpp +++ /dev/null @@ -1,560 +0,0 @@ -#include "CogDebugPlot.h" - -#include "CogDebug.h" -#include "CogDebugHelper.h" -#include "CogImguiHelper.h" -#include "Engine/Engine.h" -#include "Engine/World.h" - -FCogDebugPlotEvent FCogDebugPlot::DefaultEvent; -TArray FCogDebugPlot::Plots; -TArray FCogDebugPlot::Events; -bool FCogDebugPlot::IsVisible = false; -bool FCogDebugPlot::Pause = false; -FName FCogDebugPlot::LastAddedEventPlotName = NAME_None; -int32 FCogDebugPlot::LastAddedEventIndex = INDEX_NONE; -TMap> FCogDebugPlot::OccupationMap; - -//-------------------------------------------------------------------------------------------------------------------------- -// FCogPlotEvent -//-------------------------------------------------------------------------------------------------------------------------- -float FCogDebugPlotEvent::GetActualEndTime(const FCogDebugPlotEntry& Plot) const -{ - const UWorld* World = Plot.World.Get(); - const float WorldTime = World != nullptr ? World->GetTimeSeconds() : 0.0f; - const float ActualEndTime = EndTime != 0.0f ? EndTime : WorldTime; - return ActualEndTime; -} - -//-------------------------------------------------------------------------------------------------------------------------- -uint64 FCogDebugPlotEvent::GetActualEndFrame(const FCogDebugPlotEntry& Plot) const -{ - const float ActualEndFame = EndFrame != 0.0f ? EndFrame : GFrameCounter; - return ActualEndFame; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlotEvent::AddParam(const FName Name, bool Value) -{ - if (FCogDebugPlot::IsVisible) - { - AddParam(Name, FString::Printf(TEXT("%s"), Value ? TEXT("True") : TEXT("False"))); - } - - return *this; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlotEvent::AddParam(const FName Name, int Value) -{ - if (FCogDebugPlot::IsVisible) - { - AddParam(Name, FString::Printf(TEXT("%d"), Value)); - } - - return *this; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlotEvent::AddParam(const FName Name, float Value) -{ - if (FCogDebugPlot::IsVisible) - { - AddParam(Name, FString::Printf(TEXT("%0.2f"), Value)); - } - - return *this; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlotEvent::AddParam(const FName Name, FName Value) -{ - if (FCogDebugPlot::IsVisible) - { - AddParam(Name, Value.ToString()); - } - - return *this; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlotEvent::AddParam(const FName Name, const FString& Value) -{ - if (FCogDebugPlot::IsVisible) - { - - if (Name == "Name") - { - DisplayName = Value; - } - else - { - FCogDebugPlotEventParams& Param = Params.AddDefaulted_GetRef(); - Param.Name = Name; - Param.Value = Value; - } - } - - return *this; -} - - -//-------------------------------------------------------------------------------------------------------------------------- -// FCogPlotEntry -//-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlotEntry::AddPoint(float X, float Y) -{ - if (Values.Capacity == 0) - { - Values.reserve(2000); - } - - if (Values.size() < Values.Capacity) - { - Values.push_back(ImVec2(X, Y)); - } - else - { - Values[ValueOffset] = ImVec2(X, Y); - ValueOffset = (ValueOffset + 1) % Values.size(); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlotEntry::AddEvent( - const FString& OwnerName, - const bool IsInstant, - const FName EventId, - const int32 Row, - const FColor& Color) -{ - if (Events.Max() < 200) - { - Events.Reserve(200); - } - - //---------------------------- - // Stop if it already exist. - //---------------------------- - StopEvent(EventId); - - FCogDebugPlotEvent* Event = nullptr; - - int32 AddedIndex = 0; - if (Events.Num() < Events.Max()) - { - Event = &Events.AddDefaulted_GetRef(); - AddedIndex = Events.Num() - 1; - } - else - { - Event = &Events[EventOffset]; - AddedIndex = EventOffset; - EventOffset = (EventOffset + 1) % Events.Num(); - } - - Event->Id = EventId; - Event->OwnerName = OwnerName; - Event->DisplayName = EventId.ToString(); - Event->StartTime = Time; - Event->EndTime = IsInstant ? Time : 0.0f; - Event->StartFrame = Frame; - Event->EndFrame = IsInstant ? Frame : 0.0f; - Event->Row = (Row == FCogDebugPlot::AutoRow) ? FCogDebugPlot::FindFreeGraphRow(GraphIndex) : Row; - - if (IsInstant == false) - { - FCogDebugPlot::OccupyGraphRow(GraphIndex, Event->Row); - } - - MaxRow = FMath::Max(Event->Row, MaxRow); - - const FColor BorderColor = FCogDebugHelper::GetAutoColor(EventId, Color).WithAlpha(200); - const FColor FillColor = BorderColor.WithAlpha(100); - Event->BorderColor = FCogImguiHelper::ToImColor(BorderColor); - Event->FillColor = FCogImguiHelper::ToImColor(FillColor); - - FCogDebugPlot::LastAddedEventPlotName = Name; - FCogDebugPlot::LastAddedEventIndex = AddedIndex; - - return *Event; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlotEntry::StopEvent(const FName EventId) -{ - FCogDebugPlotEvent* Event = FindLastEventByName(EventId); - if (Event == nullptr) - { - return FCogDebugPlot::DefaultEvent; - } - - if (Event->EndTime == 0.0f) - { - Event->EndTime = Time; - Event->EndFrame = Frame; - - FCogDebugPlot::FreeGraphRow(GraphIndex, Event->Row); - } - - return *Event; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent* FCogDebugPlotEntry::GetLastEvent() -{ - if (Events.Num() == 0) - { - return nullptr; - } - - int32 Index = Events.Num() - 1; - if (EventOffset != 0) - { - Index = (Index + EventOffset) % Events.Num(); - } - - return &Events[Index]; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent* FCogDebugPlotEntry::FindLastEventByName(FName EventId) -{ - for (int32 i = Events.Num() - 1; i >= 0; --i) - { - //-------------------------------------------------- - // The array cycle so we must offset the index - //-------------------------------------------------- - int32 Index = i; - if (EventOffset != 0) - { - Index = (i + EventOffset) % Events.Num(); - } - - if (Events[Index].Id == EventId) - { - return &Events[Index]; - } - } - - return nullptr; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlotEntry::AssignGraphAndAxis(int32 InGraph, ImAxis InYAxis) -{ - GraphIndex = InGraph; - YAxis = InYAxis; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlotEntry::ResetGraphAndAxis() -{ - GraphIndex = INDEX_NONE; - YAxis = ImAxis_COUNT; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlotEntry::Clear() -{ - FCogDebugPlot::ResetLastAddedEvent(); - - MaxRow = 0; - - if (Values.size() > 0) - { - Values.shrink(0); - ValueOffset = 0; - } - - if (Events.Num() > 0) - { - Events.Empty(); - Events.Shrink(); - EventOffset = 0; - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -bool FCogDebugPlotEntry::FindValue(float x, float& y) const -{ - y = 0.0f; - - bool FoundAfter = false; - bool FoundBefore = false; - - for (int32 i = Values.size() - 1; i >= 0; --i) - { - //-------------------------------------------------- - // The array cycle so we must offset the index - //-------------------------------------------------- - int32 Index = i; - if (ValueOffset != 0) - { - Index = (i + ValueOffset) % Values.size(); - } - - const ImVec2 Point = Values[Index]; - if (Point.x > x) - { - FoundAfter = true; - } - - if (Point.x < x) - { - FoundBefore = true; - } - - if (FoundAfter && FoundBefore) - { - y = Point.y; - return true; - } - } - - return false; -} - -//-------------------------------------------------------------------------------------------------------------------------- -// FCogPlot -//-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlot::Reset() -{ - Plots.Empty(); - Events.Empty(); - OccupationMap.Empty(); - Pause = false; - ResetLastAddedEvent(); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlot::Clear() -{ - for (FCogDebugPlotEntry& Entry : Plots) - { - Entry.Clear(); - } - - for (FCogDebugPlotEntry& Entry : Events) - { - Entry.Clear(); - } - - OccupationMap.Empty(); - ResetLastAddedEvent(); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlot::ResetLastAddedEvent() -{ - LastAddedEventPlotName = NAME_None; - LastAddedEventIndex = INDEX_NONE; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent* FCogDebugPlot::GetLastAddedEvent() -{ - FCogDebugPlotEntry* Plot = FindEntry(true, LastAddedEventPlotName); - if (Plot == nullptr) - { - return nullptr; - } - - return Plot->GetLastEvent(); -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEntry* FCogDebugPlot::FindEntry(const FName Name) -{ - if (FCogDebugPlotEntry* Event = Events.FindByPredicate([Name](const FCogDebugPlotEntry& P) { return P.Name == Name; })) - { - return Event; - } - - if (FCogDebugPlotEntry* Plot = Plots.FindByPredicate([Name](const FCogDebugPlotEntry& P) { return P.Name == Name; })) - { - return Plot; - } - - return nullptr; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEntry* FCogDebugPlot::FindEntry(bool IsEvent, const FName Name) -{ - TArray* Entries = IsEvent ? &Events : &Plots; - FCogDebugPlotEntry* Entry = Entries->FindByPredicate([Name](const FCogDebugPlotEntry& P) { return P.Name == Name; }); - return Entry; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEntry* FCogDebugPlot::RegisterPlot(const UObject* WorldContextObject, const FName PlotName, bool IsEventPlot) -{ - //---------------------------------------------------------- - // When not visible, we don't go further for performances. - //---------------------------------------------------------- - if (IsVisible == false) - { - return nullptr; - } - - const UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull); - if (World == nullptr) - { - return nullptr; - } - - if (FCogDebug::IsDebugActiveForObject(WorldContextObject) == false) - { - return nullptr; - } - - FCogDebugPlotEntry* EntryPtr = FindEntry(IsEventPlot, PlotName); - if (EntryPtr == nullptr) - { - TArray* Entries = IsEventPlot ? &Events : &Plots; - EntryPtr = &Entries->AddDefaulted_GetRef(); - EntryPtr->Name = PlotName; - EntryPtr->IsEventPlot = IsEventPlot; - Entries->Sort([](const FCogDebugPlotEntry& A, const FCogDebugPlotEntry& B) { return A.Name.ToString().Compare(B.Name.ToString()) < 0; }); - } - - //if (EntryPtr->YAxis == ImAxis_COUNT) - //{ - // return nullptr; - //} - - const float Time = World->GetTimeSeconds(); - if (Time < EntryPtr->Time) - { - EntryPtr->Clear(); - } - - EntryPtr->World = World; - EntryPtr->Time = World->GetTimeSeconds(); - EntryPtr->Frame = GFrameCounter; - - return EntryPtr; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlot::PlotValue(const UObject* WorldContextObject, const FName PlotName, const float Value) -{ - FCogDebugPlotEntry* Plot = RegisterPlot(WorldContextObject, PlotName, false); - if (Plot == nullptr) - { - return; - } - - Plot->AddPoint(Plot->Time, Value); -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlot::PlotEvent(const UObject* WorldContextObject, const FName PlotName, const FName EventId, bool IsInstant, const int32 Row, const FColor& Color) -{ - FCogDebugPlotEntry* Plot = RegisterPlot(WorldContextObject, PlotName, true); - if (Plot == nullptr) - { - ResetLastAddedEvent(); - return DefaultEvent; - } - - FCogDebugPlotEvent& Event = Plot->AddEvent(GetNameSafe(WorldContextObject), IsInstant, EventId, Row, Color); - return Event; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlot::PlotEventInstant(const UObject* WorldContextObject, const FName PlotName, const FName EventId, const int32 Row, const FColor& Color) -{ - FCogDebugPlotEvent& Event = PlotEvent(WorldContextObject, PlotName, EventId, true, Row, Color); - return Event; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlot::PlotEventStart(const UObject* WorldContextObject, const FName PlotName, const FName EventId, const int32 Row, const FColor& Color) -{ - FCogDebugPlotEvent& Event = PlotEvent(WorldContextObject, PlotName, EventId, false, Row, Color); - return Event; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlot::PlotEventStop(const UObject* WorldContextObject, const FName PlotName, const FName EventId) -{ - FCogDebugPlotEntry* Plot = RegisterPlot(WorldContextObject, PlotName, true); - if (Plot == nullptr) - { - return DefaultEvent; - } - - FCogDebugPlotEvent& Event = Plot->StopEvent(EventId); - return Event; -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogDebugPlotEvent& FCogDebugPlot::PlotEventToggle(const UObject* WorldContextObject, const FName PlotName, const FName EventId, const bool ToggleValue, const int32 Row, const FColor& Color) -{ - if (ToggleValue) - { - return PlotEventStart(WorldContextObject, PlotName, EventId, Row, Color); - } - else - { - return PlotEventStop(WorldContextObject, PlotName, EventId); - } -} - - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlot::OccupyGraphRow(const int32 InGraphIndex, const int32 InRow) -{ - TMap& GraphOccupation = OccupationMap.FindOrAdd(InGraphIndex); - - if (int32* RowOccupation = GraphOccupation.Find(InRow)) - { - (*RowOccupation)++; - } - else - { - GraphOccupation.Add(InRow, 1); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogDebugPlot::FreeGraphRow(const int32 InGraphIndex, const int32 Row) -{ - TMap* GraphOccupation = OccupationMap.Find(InGraphIndex); - if (GraphOccupation == nullptr) - { return; } - - int32* RowOccupation = GraphOccupation->Find(Row); - if (RowOccupation == nullptr) - { return; } - - (*RowOccupation)--; -} - -//-------------------------------------------------------------------------------------------------------------------------- -int32 FCogDebugPlot::FindFreeGraphRow(const int32 InGraphIndex) -{ - constexpr int32 MaxRows = 100; - - int32 FreeRow = 0; - - TMap* GraphOccupation = OccupationMap.Find(InGraphIndex); - if (GraphOccupation == nullptr) - { - return FreeRow; - } - - for (; FreeRow < MaxRows; ++FreeRow) - { - const int32* Occupation = GraphOccupation->Find(FreeRow); - if (Occupation == nullptr || *Occupation == 0) - { - break; - } - } - - return FreeRow; -} - diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugPlotBlueprint.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugPlotBlueprint.cpp index 2b61343..861853a 100644 --- a/Plugins/Cog/Source/CogDebug/Private/CogDebugPlotBlueprint.cpp +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebugPlotBlueprint.cpp @@ -1,12 +1,12 @@ #include "CogDebugPlotBlueprint.h" #include "CogCommon.h" -#include "CogDebugPlot.h" +#include "CogDebugTracker.h" //-------------------------------------------------------------------------------------------------------------------------- void UCogDebugPlotBlueprint::Plot(const UObject* Owner, const FName Name, const float Value) { #if ENABLE_COG - FCogDebugPlot::PlotValue(Owner, Name, Value); + FCogDebug::Plot(Owner, Name, Value); #endif //ENABLE_COG } diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugReplicator.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugReplicator.cpp index a2a95d2..8314410 100644 --- a/Plugins/Cog/Source/CogDebug/Private/CogDebugReplicator.cpp +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebugReplicator.cpp @@ -1,12 +1,10 @@ #include "CogDebugReplicator.h" #include "CogDebug.h" -#include "CogDebugDraw.h" #include "CogDebugLog.h" #include "EngineUtils.h" #include "GameFramework/PlayerController.h" #include "GameFramework/WorldSettings.h" -#include "Net/Core/PushModel/PushModel.h" #include "Net/UnrealNetwork.h" //-------------------------------------------------------------------------------------------------------------------------- @@ -27,17 +25,18 @@ ACogDebugReplicator* ACogDebugReplicator::Spawn(APlayerController* Controller) //-------------------------------------------------------------------------------------------------------------------------- ACogDebugReplicator* ACogDebugReplicator::GetLocalReplicator(const UWorld& World) { - for (TActorIterator It(&World, StaticClass()); It; ++It) + const TActorIterator It(&World, StaticClass()); + if (It) { ACogDebugReplicator* Replicator = *It; - return Replicator; + return Replicator; } - + return nullptr; } //-------------------------------------------------------------------------------------------------------------------------- -void ACogDebugReplicator::GetRemoteReplicators(UWorld& World, TArray& Replicators) +void ACogDebugReplicator::GetRemoteReplicators(const UWorld& World, TArray& Replicators) { for (TActorIterator It(&World, ACogDebugReplicator::StaticClass()); It; ++It) { @@ -104,7 +103,7 @@ void ACogDebugReplicator::TickActor(float DeltaTime, enum ELevelTick TickType, F #if !UE_BUILD_SHIPPING Super::TickActor(DeltaTime, TickType, ThisTickFunction); - if (OwnerPlayerController) + if (OwnerPlayerController.IsValid()) { if (GetWorld()->GetNetMode() == NM_Client) { @@ -128,7 +127,7 @@ void ACogDebugReplicator::Server_SetCategoryVerbosity_Implementation(FName LogCa { if (const FCogDebugLogCategoryInfo* LogCategoryInfo = FCogDebugLog::FindLogCategoryInfo(LogCategoryName)) { - LogCategoryInfo->LogCategory->SetVerbosity((ELogVerbosity::Type)Verbosity); + LogCategoryInfo->LogCategory->SetVerbosity(static_cast(Verbosity)); TArray CategoriesData; CategoriesData.Add({ LogCategoryName, Verbosity }); @@ -148,7 +147,7 @@ void ACogDebugReplicator::NetMulticast_SendCategoriesVerbosity_Implementation(co { for (const FCogServerCategoryData& Category : Categories) { - FCogDebugLog::OnServerVerbosityChanged(Category.LogCategoryName, (ELogVerbosity::Type)Category.Verbosity); + FCogDebugLog::OnServerVerbosityChanged(Category.LogCategoryName, static_cast(Category.Verbosity)); } } @@ -164,7 +163,7 @@ void ACogDebugReplicator::Client_SendCategoriesVerbosity_Implementation(const TA { for (const FCogServerCategoryData& Category : Categories) { - FCogDebugLog::OnServerVerbosityChanged(Category.LogCategoryName, (ELogVerbosity::Type)Category.Verbosity); + FCogDebugLog::OnServerVerbosityChanged(Category.LogCategoryName, static_cast(Category.Verbosity)); } } @@ -188,7 +187,7 @@ void ACogDebugReplicator::Server_RequestAllCategoriesVerbosity_Implementation() CategoriesData.Add( { CategoryInfo.LogCategory->GetCategoryName(), - (ECogLogVerbosity)CategoryInfo.LogCategory->GetVerbosity() + static_cast(CategoryInfo.LogCategory->GetVerbosity()) }); } } @@ -218,11 +217,11 @@ void ACogDebugReplicator::Server_SetSelection_Implementation(AActor* Value, bool if (ForceSelection) { - FCogDebug::SetSelection(GetWorld(), Value); + FCogDebug::SetSelection(Value); } else { - FCogDebug::SetSelection(GetWorld(), nullptr); + FCogDebug::SetSelection(nullptr); } @@ -248,14 +247,14 @@ public: //-------------------------------------------------------------------------------------------------------------------------- // FCogReplicatorNetPack //-------------------------------------------------------------------------------------------------------------------------- -bool FCogReplicatorNetPack::NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms) +bool FCogReplicatorNetPack::NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParams) { - if (DeltaParms.bUpdateUnmappedObjects || Owner == nullptr) + if (DeltaParams.bUpdateUnmappedObjects || Owner == nullptr) { return true; } - if (DeltaParms.Writer) + if (DeltaParams.Writer) { const bool bIsOwnerClient = !Owner->bHasAuthority; if (bIsOwnerClient) @@ -263,10 +262,10 @@ bool FCogReplicatorNetPack::NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms return false; } - const FCogReplicatorNetState* OldState = static_cast(DeltaParms.OldState); + const FCogReplicatorNetState* OldState = static_cast(DeltaParams.OldState); FCogReplicatorNetState* NewState = new FCogReplicatorNetState(); - check(DeltaParms.NewState); - *DeltaParms.NewState = TSharedPtr(NewState); + check(DeltaParams.NewState); + *DeltaParams.NewState = TSharedPtr(NewState); //------------------------------------------------------------------------------------------------------------------ // Find delta to replicate @@ -289,7 +288,7 @@ bool FCogReplicatorNetPack::NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms const bool bMissingOldState = (OldState == nullptr); const uint8 ShouldUpdateShapes = bMissingOldState || (OldState->ShapesRepCounter != NewState->ShapesRepCounter); - FBitWriter& Writer = *DeltaParms.Writer; + FBitWriter& Writer = *DeltaParams.Writer; Writer.WriteBit(ShouldUpdateShapes); if (ShouldUpdateShapes) { @@ -297,12 +296,12 @@ bool FCogReplicatorNetPack::NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms } } } - else if (DeltaParms.Reader) + else if (DeltaParams.Reader) { //------------------------------------------------------------------------------------------------------------------ // Read //------------------------------------------------------------------------------------------------------------------ - FBitReader& Reader = *DeltaParms.Reader; + FBitReader& Reader = *DeltaParams.Reader; const uint8 ShouldUpdateShapes = Reader.ReadBit(); if (ShouldUpdateShapes) { diff --git a/Plugins/Cog/Source/CogDebug/Private/CogDebugTracker.cpp b/Plugins/Cog/Source/CogDebug/Private/CogDebugTracker.cpp new file mode 100644 index 0000000..dc58710 --- /dev/null +++ b/Plugins/Cog/Source/CogDebug/Private/CogDebugTracker.cpp @@ -0,0 +1,360 @@ +#include "CogDebugTracker.h" + +#include "CogDebug.h" +#include "CogDebugEventTrack.h" +#include "CogDebugPlotTrack.h" +#include "Engine/Engine.h" +#include "Engine/World.h" + +int32 FCogDebugTracker::NumRecordedValues = 2000; +FCogDebugEvent FCogDebugTracker::DefaultEvent; + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugTracker::Reset() +{ + Values.Empty(); + Events.Empty(); + OccupationMap.Empty(); + Pause = false; + ResetLastAddedEvent(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugTracker::Clear() +{ + for (auto& kv : Values) + { + kv.Value.Clear(); + } + + for (auto& kv : Events) + { + kv.Value.Clear(); + } + + OccupationMap.Empty(); + ResetLastAddedEvent(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogDebugTracker::CanCreateTrack(const UObject* WorldContextObject, const UWorld*& World) const +{ + //---------------------------------------------------------- + // When not visible, we don't go further for performances. + //---------------------------------------------------------- + if (IsVisible == false) + { + return false; + } + + World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull); + if (World == nullptr) + { + return false; + } + + if (FCogDebug::IsDebugActiveForObject(WorldContextObject) == false) + { + return false; + } + + return true; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugTracker::InitializeTrack(FCogDebugTrack& OutTrack, const UWorld* InWorld, const FCogDebugTrackId& InTrackId) +{ + const float Time = InWorld->GetTimeSeconds(); + if (Time < OutTrack.Time) + { + OutTrack.Clear(); + } + + OutTrack.Id = InTrackId; + OutTrack.Time = InWorld->GetTimeSeconds(); + OutTrack.Frame = GFrameCounter; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugTracker::OccupyViewRow(const int32 InViewIndex, const int32 InRow) +{ + TMap& GraphOccupation = OccupationMap.FindOrAdd(InViewIndex); + + if (int32* RowOccupation = GraphOccupation.Find(InRow)) + { + (*RowOccupation)++; + } + else + { + GraphOccupation.Add(InRow, 1); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugTracker::FreeViewRow(const int32 InViewIndex, const int32 Row) +{ + TMap* GraphOccupation = OccupationMap.Find(InViewIndex); + if (GraphOccupation == nullptr) + { return; } + + int32* RowOccupation = GraphOccupation->Find(Row); + if (RowOccupation == nullptr) + { return; } + + (*RowOccupation)--; +} + +//-------------------------------------------------------------------------------------------------------------------------- +int32 FCogDebugTracker::FindFreeViewRow(const int32 InViewIndex) +{ + constexpr int32 MaxRows = 100; + + int32 FreeRow = 0; + + TMap* GraphOccupation = OccupationMap.Find(InViewIndex); + if (GraphOccupation == nullptr) + { + return FreeRow; + } + + for (; FreeRow < MaxRows; ++FreeRow) + { + const int32* Occupation = GraphOccupation->Find(FreeRow); + if (Occupation == nullptr || *Occupation == 0) + { + break; + } + } + + return FreeRow; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugTrack* FCogDebugTracker::FindTrack(const FCogDebugTrackId& InTrackId) +{ + FCogDebugTrack* Track = Events.Find(InTrackId); + if (Track != nullptr) + { + return Track; + } + + Track = Values.Find(InTrackId); + if (Track != nullptr) + { + return Track; + } + + return nullptr; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugTracker::SetNumRecordedValues(const int32 InValue) +{ + NumRecordedValues = InValue; + + for (auto& kv : Values) + { + kv.Value.SetNumPlots(InValue); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugPlotTrack::SetNumPlots(const int32 Value) +{ + Values.reserve(Value); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugPlotTrack::Plot(float X, float Y) +{ + if (Values.Capacity == 0) + { + Values.reserve(FCogDebugTracker::NumRecordedValues); + } + + if (Values.size() < Values.Capacity) + { + Values.push_back(ImVec2(X, Y)); + } + else + { + Values[ValueOffset] = ImVec2(X, Y); + ValueOffset = (ValueOffset + 1) % Values.size(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugPlotTrack::Clear() +{ + FCogDebugTrack::Clear(); + + if (Values.size() > 0) + { + Values.shrink(0); + ValueOffset = 0; + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogDebugPlotTrack::FindValue(float InX, float& OutY) const +{ + OutY = 0.0f; + + bool FoundAfter = false; + bool FoundBefore = false; + + for (int32 i = Values.size() - 1; i >= 0; --i) + { + //-------------------------------------------------- + // The array cycle so we must offset the index + //-------------------------------------------------- + int32 Index = i; + if (ValueOffset != 0) + { + Index = (i + ValueOffset) % Values.size(); + } + + const ImVec2 Point = Values[Index]; + if (Point.x > InX) + { + FoundAfter = true; + } + + if (Point.x < InX) + { + FoundBefore = true; + } + + if (FoundAfter && FoundBefore) + { + OutY = Point.y; + return true; + } + } + + return false; +} + +//-------------------------------------------------------------------------------------------------------------------------- +// Plot Track Creation +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugPlotTrack* FCogDebugTracker::GetOrCreatePlotTrack(const UObject* InWorldContextObject, const FCogDebugTrackId& InTrackId) +{ + const UWorld* World; + if (CanCreateTrack(InWorldContextObject, World) == false) + { + return nullptr; + } + + FCogDebugPlotTrack& Track = Values.FindOrAdd(InTrackId); + Track.Owner = this; + Track.Type = ECogDebugTrackType::Value; + + InitializeTrack(Track, World, InTrackId); + + return &Track; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugTracker::Plot(const UObject* InWorldContextObject, const FName InTrackId, const float Value) +{ + if (Pause && RecordValuesWhenPause == false) + { return; } + + FCogDebugPlotTrack* Track = GetOrCreatePlotTrack(InWorldContextObject, InTrackId); + if (Track == nullptr) + { return; } + + Track->Plot(Track->Time, Value); +} + +//-------------------------------------------------------------------------------------------------------------------------- +// Event Track Creation +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebugTracker::StartEvent(const UObject* InWorldContextObject, const FCogDebugEventId& InTrackId, const FCogDebugEventId& InEventId, bool IsInstant, const int32 Row, const FColor& Color) +{ + FCogDebugEventTrack* Track = GetOrCreateEventTrack(InWorldContextObject, InTrackId); + if (Track == nullptr) + { + ResetLastAddedEvent(); + return DefaultEvent; + } + + FCogDebugEvent& Event = Track->AddEvent(GetNameSafe(InWorldContextObject), IsInstant, InEventId, Row, Color); + return Event; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebugTracker::InstantEvent(const UObject* InWorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugEventId& InEventId, const int32 Row, const FColor& Color) +{ + FCogDebugEvent& Event = StartEvent(InWorldContextObject, InTrackId, InEventId, true, Row, Color); + return Event; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebugTracker::StartEvent(const UObject* InWorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugEventId& InEventId, const int32 Row, const FColor& Color) +{ + FCogDebugEvent& Event = StartEvent(InWorldContextObject, InTrackId, InEventId, false, Row, Color); + return Event; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebugTracker::StopEvent(const UObject* InWorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugEventId& InEventId) +{ + FCogDebugEventTrack* EventHistory = GetOrCreateEventTrack(InWorldContextObject, InTrackId); + if (EventHistory == nullptr) + { + return DefaultEvent; + } + + FCogDebugEvent& Event = EventHistory->StopEvent(InEventId); + return Event; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent& FCogDebugTracker::ToggleEvent(const UObject* InWorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugEventId& InEventId, const bool ToggleValue, const int32 Row, const FColor& Color) +{ + if (ToggleValue) + { + return StartEvent(InWorldContextObject, InTrackId, InEventId, Row, Color); + } + else + { + return StopEvent(InWorldContextObject, InTrackId, InEventId); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEventTrack* FCogDebugTracker::GetOrCreateEventTrack(const UObject* InWorldContextObject, const FCogDebugEventId& InTrackId) +{ + const UWorld* World; + if (CanCreateTrack(InWorldContextObject, World) == false) + { return nullptr; } + + FCogDebugEventTrack& Track = Events.FindOrAdd(InTrackId); + Track.Type = ECogDebugTrackType::Event; + Track.Owner = this; + + InitializeTrack(Track, World, InTrackId); + + return &Track; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogDebugTracker::ResetLastAddedEvent() +{ + LastAddedEventTrackId = NAME_None; + LastAddedEventIndex = INDEX_NONE; +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogDebugEvent* FCogDebugTracker::GetLastAddedEvent() +{ + FCogDebugEventTrack* EventHistory = Events.Find(LastAddedEventTrackId); + if (EventHistory == nullptr) + { + return nullptr; + } + + return EventHistory->GetLastEvent(); +} diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebug.h b/Plugins/Cog/Source/CogDebug/Public/CogDebug.h index 7a4b517..613d474 100644 --- a/Plugins/Cog/Source/CogDebug/Public/CogDebug.h +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebug.h @@ -1,9 +1,11 @@ #pragma once #include "CoreMinimal.h" +#include "CogDebugEvent.h" +#include "CogDebugSettings.h" +#include "CogDebugTracker.h" #include "Engine/EngineTypes.h" #include "UObject/WeakObjectPtrTemplates.h" -#include "CogDebug.generated.h" class AActor; class UObject; @@ -12,387 +14,30 @@ struct FCogDebugDrawLineTraceParams; struct FCogDebugDrawOverlapParams; struct FCogDebugDrawSweepParams; -UENUM() -enum class ECogDebugRecolorMode : uint8 +//-------------------------------------------------------------------------------------------------------------------------- +struct FCogDebugContext { - None, - Color, - HueOverTime, - HueOverFrames, -}; - -USTRUCT() -struct FCogDebugSettings -{ - GENERATED_BODY() - - UPROPERTY(Config) - bool bIsFilteringBySelection = true; - - UPROPERTY(Config) - bool ReplicateSelection = true; - - UPROPERTY(Config) - bool Persistent = false; - - UPROPERTY(Config) - bool TextShadow = true; - - UPROPERTY(Config) - bool Fade2D = true; - - UPROPERTY(Config) - float Duration = 3.0f; - - UPROPERTY(Config) - int DepthPriority = 0; - - UPROPERTY(Config) - int Segments = 12; - - UPROPERTY(Config) - float Thickness = 0.0f; - - UPROPERTY(Config) - float ServerThickness = 2.0f; - - UPROPERTY(Config) - float ServerColorMultiplier = 0.8f; - - UPROPERTY(Config) - float ArrowSize = 10.0f; - - UPROPERTY(Config) - float AxesScale = 1.0f; - - UPROPERTY(Config) - ECogDebugRecolorMode RecolorMode = ECogDebugRecolorMode::None; - - UPROPERTY(Config) - float RecolorIntensity = 0.0f; - - UPROPERTY(Config) - FColor RecolorColor = FColor(255, 0, 0, 255); - - UPROPERTY(Config) - float RecolorTimeSpeed = 2.0f; - - UPROPERTY(Config) - int32 RecolorFrameCycle = 6; - - UPROPERTY(Config) - float TextSize = 1.0f; - - UPROPERTY(Config) - bool ActorNameUseLabel = true; - - UPROPERTY(Config) - float GizmoScale = 1.0f; - - UPROPERTY(Config) - bool GizmoUseLocalSpace = false; - - UPROPERTY(Config) - int GizmoZLow = 0; - - UPROPERTY(Config) - int GizmoZHigh = 100; - - UPROPERTY(Config) - float GizmoThicknessZLow = 1.0f; - - UPROPERTY(Config) - float GizmoThicknessZHigh = 0.0f; - - UPROPERTY(Config) - float GizmoCursorDraggingThreshold = 4.0f; - - UPROPERTY(Config) - float GizmoCursorSelectionThreshold = 10.0f; - - UPROPERTY(Config) - float GizmoTranslationAxisLength = 80.0f; - - UPROPERTY(Config) - bool GizmoTranslationSnapEnable = false; - - UPROPERTY(Config) - float GizmoTranslationSnapValue = 10.0f; - - UPROPERTY(Config) - float GizmoTranslationPlaneOffset = 18.0f; - - UPROPERTY(Config) - float GizmoTranslationPlaneExtent = 5.0f; - - UPROPERTY(Config) - bool GizmoRotationSnapEnable = false; - - UPROPERTY(Config) - float GizmoRotationSnapValue = 10.0f; - - UPROPERTY(Config) - float GizmoRotationSpeed = 1.0f; - - UPROPERTY(Config) - float GizmoRotationRadius = 40.0f; - - UPROPERTY(Config) - int GizmoRotationSegments = 8; - - UPROPERTY(Config) - bool GizmoScaleSnapEnable = false; - - UPROPERTY(Config) - float GizmoScaleSnapValue = 1.0f; - - UPROPERTY(Config) - float GizmoScaleBoxOffset = 85.0f; - - UPROPERTY(Config) - float GizmoScaleBoxExtent = 5.0f; - - UPROPERTY(Config) - float GizmoScaleSpeed = 0.01f; - - UPROPERTY(Config) - float GizmoScaleMin = 0.001f; - - UPROPERTY(Config) - float GizmoGroundRaycastLength = 100000.0f; - - UPROPERTY(Config) - TEnumAsByte GizmoGroundRaycastChannel = ECollisionChannel::ECC_WorldStatic; - - UPROPERTY(Config) - float GizmoGroundRaycastCircleRadius = 5.0f; - - UPROPERTY(Config) - FColor GizmoAxisColorsZHighX = FColor(255, 50, 50, 255); - - UPROPERTY(Config) - FColor GizmoAxisColorsZHighY = FColor(50, 255, 50, 255); - - UPROPERTY(Config) - FColor GizmoAxisColorsZHighZ = FColor(50, 50, 255, 255); - - UPROPERTY(Config) - FColor GizmoAxisColorsZHighW = FColor(255, 255, 255, 255); - - UPROPERTY(Config) - FColor GizmoAxisColorsZLowX = FColor(128, 0, 0, 255); - - UPROPERTY(Config) - FColor GizmoAxisColorsZLowY = FColor(0, 128, 0, 255); - - UPROPERTY(Config) - FColor GizmoAxisColorsZLowZ = FColor(0, 0, 128, 255); - - UPROPERTY(Config) - FColor GizmoAxisColorsZLowW = FColor(128, 128, 128, 255); - - UPROPERTY(Config) - FColor GizmoAxisColorsSelectionX = FColor(255, 255, 0, 255); - - UPROPERTY(Config) - FColor GizmoAxisColorsSelectionY = FColor(255, 255, 0, 255); - - UPROPERTY(Config) - FColor GizmoAxisColorsSelectionZ = FColor(255, 255, 0, 255); - - UPROPERTY(Config) - FColor GizmoAxisColorsSelectionW = FColor(255, 255, 0, 255); - - UPROPERTY(Config) - FColor GizmoGroundRaycastColor = FColor(128, 128, 128, 255); - - UPROPERTY(Config) - FColor GizmoGroundRaycastCircleColor = FColor(128, 128, 128, 255); - - UPROPERTY(Config) - FColor GizmoTextColor = FColor(255, 255, 255, 255); - - UPROPERTY(Config) - FColor CollisionQueryHitColor = FColor::Green; - - UPROPERTY(Config) - FColor CollisionQueryNoHitColor = FColor::Red; - - UPROPERTY(Config) - bool CollisionQueryDrawHitPrimitives = true; - - UPROPERTY(Config) - bool CollisionQueryDrawHitPrimitiveActorsName = false; - - UPROPERTY(Config) - bool CollisionQueryHitPrimitiveActorsNameShadow = true; - - UPROPERTY(Config) - float CollisionQueryHitPrimitiveActorsNameSize = 1.0f; - - UPROPERTY(Config) - bool CollisionQueryDrawHitLocation = true; - - UPROPERTY(Config) - bool CollisionQueryDrawHitImpactPoints = true; - - UPROPERTY(Config) - bool CollisionQueryDrawHitNormals = true; - - UPROPERTY(Config) - bool CollisionQueryDrawHitImpactNormals = true; - - UPROPERTY(Config) - float CollisionQueryHitPointSize = 5.0f; - - UPROPERTY(Config) - FColor CollisionQueryNormalColor = FColor::Yellow; - - UPROPERTY(Config) - FColor CollisionQueryImpactNormalColor = FColor::Cyan; - - UPROPERTY(Config) - bool CollisionQueryDrawHitShapes = true; - - UPROPERTY(Config) - FColor ChannelColorWorldStatic = FColor(255, 0, 0, 5); - - UPROPERTY(Config) - FColor ChannelColorWorldDynamic = FColor(255, 0, 188, 5); - - UPROPERTY(Config) - FColor ChannelColorPawn = FColor(105, 0, 255, 5); - - UPROPERTY(Config) - FColor ChannelColorVisibility = FColor(0, 15, 255, 5); - - UPROPERTY(Config) - FColor ChannelColorCamera = FColor(0, 105, 255, 5); - - UPROPERTY(Config) - FColor ChannelColorPhysicsBody = FColor(0, 255, 208, 5); - - UPROPERTY(Config) - FColor ChannelColorVehicle = FColor(52, 255, 0, 5); - - UPROPERTY(Config) - FColor ChannelColorDestructible = FColor(255, 255, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorEngineTraceChannel1 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorEngineTraceChannel2 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorEngineTraceChannel3 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorEngineTraceChannel4 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorEngineTraceChannel5 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorEngineTraceChannel6 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel1 = FColor(255, 105, 0, 5); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel2 = FColor(255, 30, 0, 5); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel3 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel4 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel5 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel6 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel7 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel8 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel9 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel10 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel11 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel12 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel13 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel14 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel15 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel16 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel17 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - FColor ChannelColorGameTraceChannel18 = FColor(0, 0, 0, 0); - - UPROPERTY(Config) - TArray SecondaryBoneWildcards = { - "interaction", - "center_of_mass", - "ik_*", - "index_*", - "middle_*", - "pinky_*", - "ring_*", - "thumb_*", - "wrist_*", - "*_bck_*", - "*_fwd_*", - "*_in_*", - "*_out_*", - "*_pec_*", - "*_scap_*", - "*_bicep_*", - "*_tricep_*", - "*ankle*", - "*knee*", - "*corrective*", - "*twist*", - "*latissimus*", - }; + FCogDebugTracker Tracker; + + TWeakObjectPtr Selection; }; +//-------------------------------------------------------------------------------------------------------------------------- struct COGDEBUG_API FCogDebug { -public: - - //---------------------------------------------------------------------------------------------------------------------- static bool IsDebugActiveForObject(const UObject* WorldContextObject); static bool IsReplicatedDebugActiveForObject(const UObject* WorldContextObject, const AActor* ServerSelection, bool IsServerFilteringBySelection); static AActor* GetSelection(); - static void SetSelection(const UWorld* World, AActor* Value); + static void SetSelection(AActor* InValue); static bool GetIsFilteringBySelection(); - static void SetIsFilteringBySelection(UWorld* World, bool Value); + static void SetIsFilteringBySelection(const UWorld* World, bool Value); + + static FCogDebugTracker& GetTracker(); static bool GetDebugPersistent(bool bPersistent); @@ -426,17 +71,32 @@ public: static void GetDebugDrawSweepSettings(FCogDebugDrawSweepParams& Params); + static FCogDebugContext& Get(int32 InPieId); + + static FCogDebugContext& Get(); + + static int32 GetPieSessionId(); + + static void Plot(const UObject* WorldContextObject, const FCogDebugTrackId& InTrackId, const float Value); + + static FCogDebugEvent& StartEvent(const UObject* WorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugEventId& InEventId, bool IsInstant, const int32 Row = -1, const FColor& Color = FColor::Transparent); + + static FCogDebugEvent& InstantEvent(const UObject* WorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugEventId& InEventId, const int32 Row = -1, const FColor& Color = FColor::Transparent); + + static FCogDebugEvent& StartEvent(const UObject* WorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugEventId& InEventId, const int32 Row = -1, const FColor& Color = FColor::Transparent); + + static FCogDebugEvent& StopEvent(const UObject* WorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugEventId& InEventId); + + static FCogDebugEvent& ToggleEvent(const UObject* WorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugEventId& InEventId, const bool ToggleValue, const int32 Row = -1, const FColor& Color = FColor::Transparent); + static FCogDebugSettings Settings; private: - static int32 GetPieSessionId(); - static void ReplicateSelection(const UWorld* World, AActor* Value); static bool IsDebugActiveForObject_Internal(const UObject* WorldContextObject, const AActor* InSelection, bool InIsFilteringBySelection); - static constexpr uint32 MaxPie = 16; - - static TWeakObjectPtr Selection[MaxPie]; + static TMap DebugContexts; }; + diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugDraw.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugDraw.h index 7ac28f5..5016a91 100644 --- a/Plugins/Cog/Source/CogDebug/Public/CogDebugDraw.h +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugDraw.h @@ -59,7 +59,7 @@ struct COGDEBUG_API FCogDebugDraw static void Sweep(const FLogCategoryBase& LogCategory, const UObject* WorldContextObject, const FCollisionShape& Shape, const FVector& Start, const FVector& End, const FQuat& Rotation, const bool HasHits, TArray& HitResults, const FCogDebugDrawSweepParams& Settings); - static void Overlap(const FLogCategoryBase& LogCategory, const UObject* WorldContextObject, const FCollisionShape& Shape, const FVector& Location, const FQuat& Rotation, TArray& OverlapResults, const FCogDebugDrawOverlapParams& Settings); + static void Overlap(const FLogCategoryBase& LogCategory, const UObject* WorldContextObject, const FCollisionShape& Shape, const FVector& Location, const FQuat& Rotation, const bool HasHits, TArray& OverlapResults, const FCogDebugDrawOverlapParams& Settings); static void ReplicateShape(const UObject* WorldContextObject, const FCogDebugShape& Shape); diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugDrawBlueprint.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugDrawBlueprint.h index 3c28f4d..ef86725 100644 --- a/Plugins/Cog/Source/CogDebug/Public/CogDebugDrawBlueprint.h +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugDrawBlueprint.h @@ -1,7 +1,7 @@ +// ReSharper disable CppUEBlueprintCallableFunctionUnused #pragma once #include "CoreMinimal.h" -#include "Kismet/KismetSystemLibrary.h" #include "CogDebugDrawBlueprint.generated.h" //-------------------------------------------------------------------------------------------------------------------------- diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugDrawHelper.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugDrawHelper.h index 02c7e01..28b8f76 100644 --- a/Plugins/Cog/Source/CogDebug/Public/CogDebugDrawHelper.h +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugDrawHelper.h @@ -64,13 +64,13 @@ public: static void DrawPrimitiveComponent(const UPrimitiveComponent& PrimitiveComponent, const int32 BodyIndex, const FColor& Color, const bool Persistent, const float LifeTime, const uint8 DepthPriority, const float Thickness, const bool DrawName = true, const bool DrawNameShadow = true, const float DrawNameSize = 1.0f); - static void DrawOverlap(const UWorld* World, const FCollisionShape& Shape, const FVector& Location, const FQuat& Rotation, TArray& OverlapResults, const FCogDebugDrawOverlapParams& Settings); + static void DrawOverlap(const UWorld* World, const FCollisionShape& Shape, const FVector& Location, const FQuat& Rotation, const bool HasHits, const TArray& OverlapResults, const FCogDebugDrawOverlapParams& Settings); static void DrawHitResult(const UWorld* World, const FHitResult& HitResult, const FCogDebugDrawLineTraceParams& Settings); static void DrawHitResults(const UWorld* World, const TArray& HitResults, const FCogDebugDrawLineTraceParams& Settings); - static void DrawLineTrace(const UWorld* World, const FVector& Start, const FVector& End, const bool HasHits, TArray& HitResults, const FCogDebugDrawLineTraceParams& Settings); + static void DrawLineTrace(const UWorld* World, const FVector& Start, const FVector& End, const bool HasHits, const TArray& HitResults, const FCogDebugDrawLineTraceParams& Settings); static void DrawSweep(const UWorld* World, const FCollisionShape& Shape, const FVector& Start, const FVector& End, const FQuat& Rotation, const bool HasHits, TArray& HitResults, const FCogDebugDrawSweepParams& Settings); }; diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugDrawImGui.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugDrawImGui.h index ce81d0c..86e03f5 100644 --- a/Plugins/Cog/Source/CogDebug/Public/CogDebugDrawImGui.h +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugDrawImGui.h @@ -77,6 +77,7 @@ private: }; //---------------------------------------------------------------------------------------------------------------------- + static float Time; static TArray Lines; static TArray Triangles; static TArray TrianglesFilled; @@ -92,9 +93,6 @@ private: template static void DrawShapes(TArray& Shapes, TDrawFunction DrawFunction) { - ImDrawList* ImDrawList = ImGui::GetBackgroundDrawList(); - const double Time = ImGui::GetCurrentContext()->Time; - for (int32 i = 0; i < Shapes.Num(); i++) { const TShape& Shape = Shapes[i]; diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugEvent.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugEvent.h new file mode 100644 index 0000000..60c8ee0 --- /dev/null +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugEvent.h @@ -0,0 +1,57 @@ +#pragma once + +#include "CoreMinimal.h" +#include "imgui.h" + +typedef FName FCogDebugEventId; +typedef FName FCogDebugEventParamId; +struct FCogDebugTrack; + +//-------------------------------------------------------------------------------------------------------------------------- +struct COGDEBUG_API FCogDebugEventParams +{ + FCogDebugEventParamId Name; + FString Value; +}; + +//-------------------------------------------------------------------------------------------------------------------------- +struct COGDEBUG_API FCogDebugEvent +{ + float GetActualEndTime(const UWorld& World) const; + + uint64 GetActualEndFrame() const; + + FCogDebugEvent& AddParam(const FCogDebugEventParamId& InParamId, bool InValue); + + FCogDebugEvent& AddParam(const FCogDebugEventParamId& InParamId, int InValue); + + FCogDebugEvent& AddParam(const FCogDebugEventParamId& InParamId, float InValue); + + FCogDebugEvent& AddParam(const FCogDebugEventParamId& InParamId, FName InValue); + + FCogDebugEvent& AddParam(const FCogDebugEventParamId& InParamId, const FString& InValue); + + FCogDebugTrack* Track = nullptr; + + FCogDebugEventParamId Id; + + float StartTime = 0.0f; + + float EndTime = 0.0f; + + uint64 StartFrame = 0; + + uint64 EndFrame = 0; + + ImU32 BorderColor; + + ImU32 FillColor; + + int32 Row; + + FString OwnerName; + + FString DisplayName; + + TArray Params; +}; diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugEventTrack.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugEventTrack.h new file mode 100644 index 0000000..cc5cd18 --- /dev/null +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugEventTrack.h @@ -0,0 +1,24 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CogDebugEvent.h" +#include "CogDebugTrack.h" + +struct COGDEBUG_API FCogDebugEventTrack : FCogDebugTrack +{ + FCogDebugEvent& AddEvent(const FString& OwnerName, bool IsInstant, const FCogDebugEventId EventId, const int32 Row, const FColor& Color); + + FCogDebugEvent& StopEvent(const FCogDebugEventId EventId); + + FCogDebugEvent* GetLastEvent(); + + FCogDebugEvent* FindLastEventByName(FCogDebugEventId EventId); + + virtual void Clear() override; + + int32 EventOffset = 0; + + int32 MaxRow = 1; + + TArray Events; +}; diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugLog.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugLog.h index c589767..d2be2ba 100644 --- a/Plugins/Cog/Source/CogDebug/Public/CogDebugLog.h +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugLog.h @@ -46,13 +46,13 @@ struct COGDEBUG_API FCogDebugLog static TMap& GetLogCategories() { return LogCategories; } - static void SetServerVerbosityActive(UWorld& World, FName CategoryName, bool Value); + static void SetServerVerbosityActive(const UWorld& World, FName CategoryName, bool Value); static bool IsServerVerbosityActive(FName CategoryName); static ELogVerbosity::Type GetServerVerbosity(FName CategoryName); - static void SetServerVerbosity(UWorld& World, FName CategoryName, ELogVerbosity::Type Verbosity); + static void SetServerVerbosity(const UWorld& World, FName CategoryName, ELogVerbosity::Type Verbosity); static void OnServerVerbosityChanged(FName CategoryName, ELogVerbosity::Type Verbosity); diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugModule.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugModule.h index 28e8417..c1aee7f 100644 --- a/Plugins/Cog/Source/CogDebug/Public/CogDebugModule.h +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugModule.h @@ -10,7 +10,7 @@ class COGDEBUG_API FCogDebugModule : public IModuleInterface { public: - static inline FCogDebugModule& Get() { return FModuleManager::LoadModuleChecked("CogDebug"); } + static FCogDebugModule& Get() { return FModuleManager::LoadModuleChecked("CogDebug"); } virtual void StartupModule() override; diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugPlot.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugPlot.h deleted file mode 100644 index 40556ca..0000000 --- a/Plugins/Cog/Source/CogDebug/Public/CogDebugPlot.h +++ /dev/null @@ -1,165 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "CogCommon.h" -#include "imgui.h" -#include "implot.h" - -#ifdef ENABLE_COG - -struct FCogDebugPlotEntry; - -//-------------------------------------------------------------------------------------------------------------------------- -struct COGDEBUG_API FCogDebugPlotEventParams -{ - FName Name; - FString Value; -}; - -//-------------------------------------------------------------------------------------------------------------------------- -struct COGDEBUG_API FCogDebugPlotEvent -{ - float GetActualEndTime(const FCogDebugPlotEntry& Plot) const; - - uint64 GetActualEndFrame(const FCogDebugPlotEntry& Plot) const; - - FCogDebugPlotEvent& AddParam(const FName Name, bool Value); - - FCogDebugPlotEvent& AddParam(const FName Name, int Value); - - FCogDebugPlotEvent& AddParam(const FName Name, float Value); - - FCogDebugPlotEvent& AddParam(const FName Name, FName Value); - - FCogDebugPlotEvent& AddParam(const FName Name, const FString& Value); - - FName Id; - float StartTime = 0.0f; - float EndTime = 0.0f; - uint64 StartFrame = 0; - uint64 EndFrame = 0; - ImU32 BorderColor; - ImU32 FillColor; - int32 Row; - FString OwnerName; - FString DisplayName; - TArray Params; -}; - -//-------------------------------------------------------------------------------------------------------------------------- -struct COGDEBUG_API FCogDebugPlotEntry -{ - void AssignGraphAndAxis(int32 AssignedRow, ImAxis CurrentYAxis); - - void AddPoint(float X, float Y); - - bool FindValue(float Time, float& Value) const; - - void ResetGraphAndAxis(); - - void Clear(); - - FCogDebugPlotEvent& AddEvent(const FString& OwnerName, bool IsInstant, const FName EventId, const int32 Row, const FColor& Color); - - FCogDebugPlotEvent& StopEvent(const FName EventId); - - FCogDebugPlotEvent* GetLastEvent(); - - FCogDebugPlotEvent* FindLastEventByName(FName EventId); - - FName Name; - - bool IsEventPlot = false; - - int32 GraphIndex = INDEX_NONE; - - ImAxis YAxis = ImAxis_COUNT; - - float Time = 0; - - uint64 Frame = 0; - - TWeakObjectPtr World; - - //-------------------------- - // Values - //-------------------------- - int32 ValueOffset = 0; - - ImVector Values; - - bool ShowValuesMarkers = false; - - //-------------------------- - // Events - //-------------------------- - int32 EventOffset = 0; - - TArray Events; - - int32 MaxRow = 1; - -}; - -//-------------------------------------------------------------------------------------------------------------------------- - -class COGDEBUG_API FCogDebugPlot -{ -public: - static constexpr int32 AutoRow = -1; - - static void PlotValue(const UObject* WorldContextObject, const FName PlotName, const float Value); - - static FCogDebugPlotEvent& PlotEvent(const UObject* WorldContextObject, const FName PlotName, const FName EventId, bool IsInstant, const int32 Row = AutoRow, const FColor& Color = FColor::Transparent); - - static FCogDebugPlotEvent& PlotEventInstant(const UObject* WorldContextObject, const FName PlotName, const FName EventId, const int32 Row = AutoRow, const FColor& Color = FColor::Transparent); - - static FCogDebugPlotEvent& PlotEventStart(const UObject* WorldContextObject, const FName PlotName, const FName EventId, const int32 Row = AutoRow, const FColor& Color = FColor::Transparent); - - static FCogDebugPlotEvent& PlotEventStop(const UObject* WorldContextObject, const FName PlotName, const FName EventId); - - static FCogDebugPlotEvent& PlotEventToggle(const UObject* WorldContextObject, const FName PlotName, const FName EventId, const bool ToggleValue, const int32 Row = AutoRow, const FColor& Color = FColor::Transparent); - - static void Reset(); - - static void Clear(); - - static FCogDebugPlotEntry* FindEntry(const FName Name); - - static FCogDebugPlotEntry* FindEntry(bool IsEvent, const FName Name); - - static TArray Plots; - - static TArray Events; - - static bool IsVisible; - - static bool Pause; - -private: - friend struct FCogDebugPlotEntry; - - static void ResetLastAddedEvent(); - - static FCogDebugPlotEntry* RegisterPlot(const UObject* Owner, const FName PlotName, bool IsEventPlot); - - static FCogDebugPlotEvent* GetLastAddedEvent(); - - static void OccupyGraphRow(const int32 InGraphIndex, const int32 InRow); - - static void FreeGraphRow(const int32 InGraphIndex, const int32 InRow); - - static int32 FindFreeGraphRow(const int32 InGraphIndex); - - static FName LastAddedEventPlotName; - - static int32 LastAddedEventIndex; - - static FCogDebugPlotEvent DefaultEvent; - - // graph index to row index to number of objects occupying the row - static TMap> OccupationMap; -}; - -#endif //ENABLE_COG - diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugPlotTrack.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugPlotTrack.h new file mode 100644 index 0000000..c1509f8 --- /dev/null +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugPlotTrack.h @@ -0,0 +1,22 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CogDebugTrack.h" + +//-------------------------------------------------------------------------------------------------------------------------- +struct COGDEBUG_API FCogDebugPlotTrack : FCogDebugTrack +{ + virtual void Clear() override; + + void Plot(float X, float Y); + + bool FindValue(float Time, float& Value) const; + + void SetNumPlots(const int32 Value); + + int32 ValueOffset = 0; + + ImVector Values; + + bool ShowValuesMarkers = false; +}; diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugPluginSubsystem.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugPluginSubsystem.h new file mode 100644 index 0000000..b700dbb --- /dev/null +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugPluginSubsystem.h @@ -0,0 +1,15 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Engine/GameInstance.h" +#include "CogDebugPluginSubsystem.generated.h" + +UCLASS(Abstract) +class COGDEBUG_API UCogDebugPluginSubsystem : public UGameInstanceSubsystem +{ + GENERATED_BODY() + +public: + + virtual void OnPlayerControllerReady(APlayerController* InController) {} +}; diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugReplicator.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugReplicator.h index 8ddcc1a..60d459c 100644 --- a/Plugins/Cog/Source/CogDebug/Public/CogDebugReplicator.h +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugReplicator.h @@ -32,9 +32,9 @@ struct FCogReplicatorNetPack { GENERATED_USTRUCT_BODY() - ACogDebugReplicator* Owner = nullptr; + TObjectPtr Owner; - bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms); + bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParams); private: @@ -63,7 +63,7 @@ public: static ACogDebugReplicator* GetLocalReplicator(const UWorld& World); - static void GetRemoteReplicators(UWorld& World, TArray& Replicators); + static void GetRemoteReplicators(const UWorld& World, TArray& Replicators); virtual void BeginPlay() override; @@ -98,7 +98,7 @@ public: protected: friend FCogReplicatorNetPack; - TObjectPtr OwnerPlayerController; + TWeakObjectPtr OwnerPlayerController; uint32 bHasAuthority : 1; diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugSettings.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugSettings.h new file mode 100644 index 0000000..9a53406 --- /dev/null +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugSettings.h @@ -0,0 +1,360 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Engine/EngineTypes.h" +#include "CogDebugSettings.generated.h" + +class AActor; +class UObject; +class UWorld; + + +UENUM() +enum class ECogDebugRecolorMode : uint8 +{ + None, + Color, + HueOverTime, + HueOverFrames, +}; + +USTRUCT() +struct FCogDebugSettings +{ + GENERATED_BODY() + + UPROPERTY(Config) + bool bIsFilteringBySelection = true; + + UPROPERTY(Config) + bool ReplicateSelection = true; + + UPROPERTY(Config) + bool Persistent = false; + + UPROPERTY(Config) + bool TextShadow = true; + + UPROPERTY(Config) + bool Fade2D = true; + + UPROPERTY(Config) + float Duration = 3.0f; + + UPROPERTY(Config) + int DepthPriority = 0; + + UPROPERTY(Config) + int Segments = 12; + + UPROPERTY(Config) + float Thickness = 0.0f; + + UPROPERTY(Config) + float ServerThickness = 2.0f; + + UPROPERTY(Config) + float ServerColorMultiplier = 0.8f; + + UPROPERTY(Config) + float ArrowSize = 10.0f; + + UPROPERTY(Config) + float AxesScale = 1.0f; + + UPROPERTY(Config) + ECogDebugRecolorMode RecolorMode = ECogDebugRecolorMode::None; + + UPROPERTY(Config) + float RecolorIntensity = 0.0f; + + UPROPERTY(Config) + FColor RecolorColor = FColor(255, 0, 0, 255); + + UPROPERTY(Config) + float RecolorTimeSpeed = 2.0f; + + UPROPERTY(Config) + int32 RecolorFrameCycle = 6; + + UPROPERTY(Config) + float TextSize = 1.0f; + + UPROPERTY(Config) + bool ActorNameUseLabel = true; + + UPROPERTY(Config) + float GizmoScale = 1.0f; + + UPROPERTY(Config) + bool GizmoSupportContextMenu = true; + + UPROPERTY(Config) + bool GizmoUseLocalSpace = false; + + UPROPERTY(Config) + int GizmoZLow = 0; + + UPROPERTY(Config) + int GizmoZHigh = 100; + + UPROPERTY(Config) + float GizmoThicknessZLow = 1.0f; + + UPROPERTY(Config) + float GizmoThicknessZHigh = 0.0f; + + UPROPERTY(Config) + float GizmoCursorDraggingThreshold = 4.0f; + + UPROPERTY(Config) + float GizmoCursorSelectionThreshold = 10.0f; + + UPROPERTY(Config) + float GizmoTranslationAxisLength = 80.0f; + + UPROPERTY(Config) + bool GizmoTranslationSnapEnable = false; + + UPROPERTY(Config) + float GizmoTranslationSnapValue = 10.0f; + + UPROPERTY(Config) + float GizmoTranslationPlaneOffset = 18.0f; + + UPROPERTY(Config) + float GizmoTranslationPlaneExtent = 5.0f; + + UPROPERTY(Config) + bool GizmoRotationSnapEnable = false; + + UPROPERTY(Config) + float GizmoRotationSnapValue = 10.0f; + + UPROPERTY(Config) + float GizmoRotationSpeed = 1.0f; + + UPROPERTY(Config) + float GizmoRotationRadius = 40.0f; + + UPROPERTY(Config) + int GizmoRotationSegments = 8; + + UPROPERTY(Config) + bool GizmoScaleSnapEnable = false; + + UPROPERTY(Config) + float GizmoScaleSnapValue = 1.0f; + + UPROPERTY(Config) + float GizmoScaleBoxOffset = 85.0f; + + UPROPERTY(Config) + float GizmoScaleBoxExtent = 5.0f; + + UPROPERTY(Config) + float GizmoScaleSpeed = 0.01f; + + UPROPERTY(Config) + float GizmoScaleMin = 0.001f; + + UPROPERTY(Config) + float GizmoGroundRaycastLength = 100000.0f; + + UPROPERTY(Config) + TEnumAsByte GizmoGroundRaycastChannel = ECollisionChannel::ECC_WorldStatic; + + UPROPERTY(Config) + float GizmoGroundRaycastCircleRadius = 5.0f; + + UPROPERTY(Config) + FColor GizmoAxisColorsZHighX = FColor(255, 50, 50, 255); + + UPROPERTY(Config) + FColor GizmoAxisColorsZHighY = FColor(50, 255, 50, 255); + + UPROPERTY(Config) + FColor GizmoAxisColorsZHighZ = FColor(50, 50, 255, 255); + + UPROPERTY(Config) + FColor GizmoAxisColorsZHighW = FColor(255, 255, 255, 255); + + UPROPERTY(Config) + FColor GizmoAxisColorsZLowX = FColor(128, 0, 0, 255); + + UPROPERTY(Config) + FColor GizmoAxisColorsZLowY = FColor(0, 128, 0, 255); + + UPROPERTY(Config) + FColor GizmoAxisColorsZLowZ = FColor(0, 0, 128, 255); + + UPROPERTY(Config) + FColor GizmoAxisColorsZLowW = FColor(128, 128, 128, 255); + + UPROPERTY(Config) + FColor GizmoAxisColorsSelectionX = FColor(255, 255, 0, 255); + + UPROPERTY(Config) + FColor GizmoAxisColorsSelectionY = FColor(255, 255, 0, 255); + + UPROPERTY(Config) + FColor GizmoAxisColorsSelectionZ = FColor(255, 255, 0, 255); + + UPROPERTY(Config) + FColor GizmoAxisColorsSelectionW = FColor(255, 255, 0, 255); + + UPROPERTY(Config) + FColor GizmoGroundRaycastColor = FColor(128, 128, 128, 255); + + UPROPERTY(Config) + FColor GizmoGroundRaycastCircleColor = FColor(128, 128, 128, 255); + + UPROPERTY(Config) + FColor GizmoTextColor = FColor(255, 255, 255, 255); + + UPROPERTY(Config) + FColor CollisionQueryHitColor = FColor::Green; + + UPROPERTY(Config) + FColor CollisionQueryNoHitColor = FColor::Red; + + UPROPERTY(Config) + bool CollisionQueryDrawHitPrimitives = true; + + UPROPERTY(Config) + bool CollisionQueryDrawHitPrimitiveActorsName = false; + + UPROPERTY(Config) + bool CollisionQueryHitPrimitiveActorsNameShadow = true; + + UPROPERTY(Config) + float CollisionQueryHitPrimitiveActorsNameSize = 1.0f; + + UPROPERTY(Config) + bool CollisionQueryDrawHitLocation = true; + + UPROPERTY(Config) + bool CollisionQueryDrawHitImpactPoints = true; + + UPROPERTY(Config) + bool CollisionQueryDrawHitNormals = true; + + UPROPERTY(Config) + bool CollisionQueryDrawHitImpactNormals = true; + + UPROPERTY(Config) + float CollisionQueryHitPointSize = 5.0f; + + UPROPERTY(Config) + FColor CollisionQueryNormalColor = FColor::Yellow; + + UPROPERTY(Config) + FColor CollisionQueryImpactNormalColor = FColor::Cyan; + + UPROPERTY(Config) + bool CollisionQueryDrawHitShapes = true; + + UPROPERTY(Config) + FColor ChannelColorWorldStatic = FColor(255, 0, 0, 5); + + UPROPERTY(Config) + FColor ChannelColorWorldDynamic = FColor(255, 0, 188, 5); + + UPROPERTY(Config) + FColor ChannelColorPawn = FColor(105, 0, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorVisibility = FColor(0, 15, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorCamera = FColor(0, 105, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorPhysicsBody = FColor(0, 255, 208, 5); + + UPROPERTY(Config) + FColor ChannelColorVehicle = FColor(52, 255, 0, 5); + + UPROPERTY(Config) + FColor ChannelColorDestructible = FColor(255, 255, 0, 0); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel1 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel2 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel3 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel4 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel5 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel6 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel7 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel8 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel9 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel10 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel11 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel12 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel13 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel14 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel15 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel16 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel17 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + FColor ChannelColorGameTraceChannel18 = FColor(255, 255, 255, 5); + + UPROPERTY(Config) + TArray SecondaryBoneWildcards = { + "interaction", + "center_of_mass", + "ik_*", + "index_*", + "middle_*", + "pinky_*", + "ring_*", + "thumb_*", + "wrist_*", + "*_bck_*", + "*_fwd_*", + "*_in_*", + "*_out_*", + "*_pec_*", + "*_scap_*", + "*_bicep_*", + "*_tricep_*", + "*ankle*", + "*knee*", + "*corrective*", + "*twist*", + "*latissimus*", + }; +}; diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugShape.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugShape.h index de3a1aa..e69b60d 100644 --- a/Plugins/Cog/Source/CogDebug/Public/CogDebugShape.h +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugShape.h @@ -30,7 +30,7 @@ struct COGDEBUG_API FCogDebugShape { ECogDebugShape Type = ECogDebugShape::Invalid; TArray ShapeData; - FColor Color; + FColor Color = FColor::White; bool bPersistent = false; float Thickness = 0.0f; uint8 DepthPriority = 0; diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugSubsystem.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugSubsystem.h new file mode 100644 index 0000000..bf06177 --- /dev/null +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugSubsystem.h @@ -0,0 +1,22 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CogDebugReplicator.h" +#include "CogDebugPluginSubsystem.h" +#include "CogDebugSubsystem.generated.h" + +UCLASS() +class COGDEBUG_API UCogDebugSubsystem : public UCogDebugPluginSubsystem +{ + GENERATED_BODY() + +public: + + virtual void OnPlayerControllerReady(APlayerController* InController) override + { + if (InController != nullptr) + { + ACogDebugReplicator::Spawn(InController); + } + } +}; diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugTrack.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugTrack.h new file mode 100644 index 0000000..faca687 --- /dev/null +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugTrack.h @@ -0,0 +1,34 @@ +#pragma once + +#include "CoreMinimal.h" + +struct FCogDebugTracker; + +typedef FName FCogDebugTrackId; + +//-------------------------------------------------------------------------------------------------------------------------- +enum class ECogDebugTrackType +{ + Value, + Event, +}; + +//-------------------------------------------------------------------------------------------------------------------------- +struct COGDEBUG_API FCogDebugTrack +{ + virtual ~FCogDebugTrack() {} + + virtual void Clear() {} + + FCogDebugTrackId Id; + + float Time = 0; + + uint64 Frame = 0; + + int32 GraphIndex = 0; + + ECogDebugTrackType Type = ECogDebugTrackType::Value; + + FCogDebugTracker* Owner = nullptr; +}; diff --git a/Plugins/Cog/Source/CogDebug/Public/CogDebugTracker.h b/Plugins/Cog/Source/CogDebug/Public/CogDebugTracker.h new file mode 100644 index 0000000..a4b00f4 --- /dev/null +++ b/Plugins/Cog/Source/CogDebug/Public/CogDebugTracker.h @@ -0,0 +1,82 @@ +#pragma once + +#include "CoreMinimal.h" + +#include "CogDebugTrack.h" +#include "CogDebugEvent.h" +#include "CogDebugPlotTrack.h" +#include "CogDebugEventTrack.h" + +struct COGDEBUG_API FCogDebugTracker +{ + static constexpr int32 AutoRow = -1; + + static constexpr int32 MaxNumViews = 5; + + static constexpr int32 MaxNumTrackPerView = 10; + + void Plot(const UObject* InWorldContextObject, const FCogDebugTrackId InTrackId, const float Value); + + FCogDebugEvent& InstantEvent(const UObject* InWorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugEventId& InEventId, const int32 Row = AutoRow, const FColor& Color = FColor::Transparent); + + FCogDebugEvent& StartEvent(const UObject* InWorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugEventId& InEventId, bool IsInstant, const int32 Row = AutoRow, const FColor& Color = FColor::Transparent); + + FCogDebugEvent& StartEvent(const UObject* InWorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugEventId& InEventId, const int32 Row = AutoRow, const FColor& Color = FColor::Transparent); + + FCogDebugEvent& StopEvent(const UObject* InWorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugEventId& InEventId); + + FCogDebugEvent& ToggleEvent(const UObject* InWorldContextObject, const FCogDebugTrackId& InTrackId, const FCogDebugEventId& InEventId, const bool ToggleValue, const int32 Row = AutoRow, const FColor& Color = FColor::Transparent); + + FCogDebugTrack* FindTrack(const FCogDebugTrackId& InTrackId); + + void SetNumRecordedValues(int32 InValue); + + void Reset(); + + void Clear(); + + TMap Values; + + TMap Events; + + bool IsVisible = false; + + bool Pause = false; + + bool RecordValuesWhenPause = true; + +private: + friend struct FCogDebugEvent; + friend struct FCogDebugTrack; + friend struct FCogDebugEventTrack; + friend struct FCogDebugPlotTrack; + + void ResetLastAddedEvent(); + + FCogDebugPlotTrack* GetOrCreatePlotTrack(const UObject* InWorldContextObject, const FCogDebugTrackId& InTrackId); + + FCogDebugEventTrack* GetOrCreateEventTrack(const UObject* InWorldContextObject, const FCogDebugTrackId& InTrackId); + + bool CanCreateTrack(const UObject* WorldContextObject, const UWorld*& World) const; + + static void InitializeTrack(FCogDebugTrack& OutTrack, const UWorld* InWorld, const FCogDebugTrackId& InTrackId); + + FCogDebugEvent* GetLastAddedEvent(); + + void OccupyViewRow(const int32 InViewIndex, const int32 InRow); + + void FreeViewRow(const int32 InViewIndex, const int32 InRow); + + int32 FindFreeViewRow(const int32 InViewIndex); + + static int32 NumRecordedValues; + + static FCogDebugEvent DefaultEvent; + + FCogDebugTrackId LastAddedEventTrackId = NAME_None; + + int32 LastAddedEventIndex = INDEX_NONE; + + // view index to row index to number of objects occupying the row + TMap> OccupationMap; +}; diff --git a/Plugins/Cog/Source/CogDebugEditor/Private/CogDebugEditorModule.cpp b/Plugins/Cog/Source/CogDebugEditor/Private/CogDebugEditorModule.cpp index 25a2e03..3dcd6e8 100644 --- a/Plugins/Cog/Source/CogDebugEditor/Private/CogDebugEditorModule.cpp +++ b/Plugins/Cog/Source/CogDebugEditor/Private/CogDebugEditorModule.cpp @@ -18,7 +18,7 @@ private: /** Pin factory for abilities graph; Cached so it can be unregistered */ TSharedPtr GraphPanelPinFactory; - EAssetTypeCategories::Type AssetCategory; + EAssetTypeCategories::Type AssetCategory = EAssetTypeCategories::None; }; IMPLEMENT_MODULE(FCogDebugEditorModule, CogDebugEditor); diff --git a/Plugins/Cog/Source/CogDebugEditor/Private/CogDebugLogCategoryDetails.cpp b/Plugins/Cog/Source/CogDebugEditor/Private/CogDebugLogCategoryDetails.cpp index bf4e93b..a9b5abc 100644 --- a/Plugins/Cog/Source/CogDebugEditor/Private/CogDebugLogCategoryDetails.cpp +++ b/Plugins/Cog/Source/CogDebugEditor/Private/CogDebugLogCategoryDetails.cpp @@ -64,7 +64,7 @@ void FCogLogCategoryDetails::CustomizeChildren(TSharedRef Struc } //-------------------------------------------------------------------------------------------------------------------------- -void FCogLogCategoryDetails::OnLogCategoryChanged(FName SelectedName) +void FCogLogCategoryDetails::OnLogCategoryChanged(const FName SelectedName) const { if (NameProperty.IsValid()) { diff --git a/Plugins/Cog/Source/CogDebugEditor/Private/SCogDebugLogCategoryWidget.cpp b/Plugins/Cog/Source/CogDebugEditor/Private/SCogDebugLogCategoryWidget.cpp index 9853b55..1e94a1c 100644 --- a/Plugins/Cog/Source/CogDebugEditor/Private/SCogDebugLogCategoryWidget.cpp +++ b/Plugins/Cog/Source/CogDebugEditor/Private/SCogDebugLogCategoryWidget.cpp @@ -123,7 +123,7 @@ public: */ void Construct(const FArguments& InArgs); - virtual ~SLogCategoryListWidget(); + virtual ~SLogCategoryListWidget() override; private: typedef TTextFilter FLogCategoryTextFilter; @@ -132,10 +132,10 @@ private: void OnFilterTextChanged(const FText& InFilterText); /** Creates the row widget when called by Slate when an item appears on the list. */ - TSharedRef< ITableRow > OnGenerateRowForLogCategoryViewer(TSharedPtr Item, const TSharedRef< STableViewBase >& OwnerTable); + TSharedRef< ITableRow > OnGenerateRowForLogCategoryViewer(TSharedPtr Item, const TSharedRef< STableViewBase >& OwnerTable) const; /** Called by Slate when an item is selected from the tree/list. */ - void OnLogCategorySelectionChanged(TSharedPtr Item, ESelectInfo::Type SelectInfo); + void OnLogCategorySelectionChanged(TSharedPtr Item, ESelectInfo::Type SelectInfo) const; /** Updates the list of items in the dropdown menu */ TSharedPtr UpdatePropertyOptions(); @@ -146,7 +146,7 @@ private: /** The search box */ TSharedPtr SearchBoxPtr; - /** Holds the Slate List widget which holds the LogCategorys for the LogCategory Viewer. */ + /** Holds the Slate List widget which holds the LogCategory for the LogCategory Viewer. */ TSharedPtr >> LogCategoryList; /** Array of items that can be selected in the dropdown menu */ @@ -229,7 +229,7 @@ void SLogCategoryListWidget::Construct(const FArguments& InArgs) } //-------------------------------------------------------------------------------------------------------------------------- -TSharedRef SLogCategoryListWidget::OnGenerateRowForLogCategoryViewer(TSharedPtr Item, const TSharedRef< STableViewBase >& OwnerTable) +TSharedRef SLogCategoryListWidget::OnGenerateRowForLogCategoryViewer(TSharedPtr Item, const TSharedRef< STableViewBase >& OwnerTable) const { TSharedRef< SLogCategoryItem > ReturnRow = SNew(SLogCategoryItem, OwnerTable) .HighlightText(SearchBoxPtr->GetText()) @@ -273,7 +273,7 @@ void SLogCategoryListWidget::OnFilterTextChanged(const FText& InFilterText) } //-------------------------------------------------------------------------------------------------------------------------- -void SLogCategoryListWidget::OnLogCategorySelectionChanged(TSharedPtr Item, ESelectInfo::Type SelectInfo) +void SLogCategoryListWidget::OnLogCategorySelectionChanged(TSharedPtr Item, ESelectInfo::Type SelectInfo) const { OnLogCategoryPicked.ExecuteIfBound(Item->Name); } diff --git a/Plugins/Cog/Source/CogDebugEditor/Public/CogDebugEditorModule.h b/Plugins/Cog/Source/CogDebugEditor/Public/CogDebugEditorModule.h index 325d98e..80fdd3f 100644 --- a/Plugins/Cog/Source/CogDebugEditor/Public/CogDebugEditorModule.h +++ b/Plugins/Cog/Source/CogDebugEditor/Public/CogDebugEditorModule.h @@ -1,4 +1,3 @@ - #pragma once #include "Modules/ModuleInterface.h" @@ -9,7 +8,7 @@ class ICogDebugEditorModule : public IModuleInterface public: - static inline ICogDebugEditorModule& Get() { return FModuleManager::LoadModuleChecked("CogDebugEditor"); } + static ICogDebugEditorModule& Get() { return FModuleManager::LoadModuleChecked("CogDebugEditor"); } - static inline bool IsAvailable() { return FModuleManager::Get().IsModuleLoaded("CogDebugEditor"); } + static bool IsAvailable() { return FModuleManager::Get().IsModuleLoaded("CogDebugEditor"); } }; diff --git a/Plugins/Cog/Source/CogDebugEditor/Public/CogDebugGraphPanelPinFactory.h b/Plugins/Cog/Source/CogDebugEditor/Public/CogDebugGraphPanelPinFactory.h index 720c5fb..e985172 100644 --- a/Plugins/Cog/Source/CogDebugEditor/Public/CogDebugGraphPanelPinFactory.h +++ b/Plugins/Cog/Source/CogDebugEditor/Public/CogDebugGraphPanelPinFactory.h @@ -17,6 +17,6 @@ class FCogGraphPanelPinFactory : public FGraphPanelPinFactory { return SNew(SCogLogCategoryGraphPin, InPin); } - return NULL; + return nullptr; } }; diff --git a/Plugins/Cog/Source/CogDebugEditor/Public/CogDebugLogCategoryDetails.h b/Plugins/Cog/Source/CogDebugEditor/Public/CogDebugLogCategoryDetails.h index 9370525..db8f475 100644 --- a/Plugins/Cog/Source/CogDebugEditor/Public/CogDebugLogCategoryDetails.h +++ b/Plugins/Cog/Source/CogDebugEditor/Public/CogDebugLogCategoryDetails.h @@ -20,5 +20,5 @@ private: TSharedPtr NameProperty; TArray> PropertyOptions; - void OnLogCategoryChanged(FName SelectedName); + void OnLogCategoryChanged(FName SelectedName) const; }; diff --git a/Plugins/Cog/Source/CogEngine/CogEngine.Build.cs b/Plugins/Cog/Source/CogEngine/CogEngine.Build.cs index 360db8c..078030c 100644 --- a/Plugins/Cog/Source/CogEngine/CogEngine.Build.cs +++ b/Plugins/Cog/Source/CogEngine/CogEngine.Build.cs @@ -15,16 +15,18 @@ public class CogEngine : ModuleRules PrivateDependencyModuleNames.AddRange( new [] { + "ApplicationCore", "CogCommon", "CogImgui", - "CogWindow", + "Cog", "Core", "CoreUObject", "Engine", "InputCore", "NetCore", "Slate", - "SlateCore", + "SlateCore", + "BuildSettings", }); if (Target.bBuildEditor) diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineCollisionTester.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineCollisionTester.cpp index c81c475..78c4fdd 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineCollisionTester.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineCollisionTester.cpp @@ -14,10 +14,10 @@ ACogEngineCollisionTester::ACogEngineCollisionTester(const FObjectInitializer& O PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.bStartWithTickEnabled = true; - StartComponent = CreateDefaultSubobject(TEXT("Start")); + StartComponent = CreateDefaultSubobject(TEXT("Start")); RootComponent = StartComponent; - EndComponent = CreateDefaultSubobject(TEXT("End")); + EndComponent = CreateDefaultSubobject(TEXT("End")); EndComponent->SetupAttachment(RootComponent); EndComponent->SetRelativeLocation(FVector(1000, 0, 0)); } @@ -29,7 +29,7 @@ bool ACogEngineCollisionTester::ShouldTickIfViewportsOnly() const { return true; } - + return false; } @@ -42,11 +42,13 @@ void ACogEngineCollisionTester::Tick(float DeltaSeconds) //-------------------------------------------------------------------------------------------------------------------------- void ACogEngineCollisionTester::Query() const { + const UWorld* World = GetWorld(); + FVector QueryStart = StartComponent->GetComponentLocation(); FVector QueryEnd = EndComponent->GetComponentLocation(); FQuat QueryRotation = StartComponent->GetComponentQuat(); bool HasHits = false; - + static const FName TraceTag(TEXT("FCogWindow_Collision")); const FCollisionQueryParams QueryParams(TraceTag, SCENE_QUERY_STAT_ONLY(CogHitDetection), TraceComplex); @@ -58,243 +60,284 @@ void ACogEngineCollisionTester::Query() const { switch (Shape) { - case ECogEngine_CollisionQueryShape::Sphere: QueryShape.SetSphere(ShapeExtent.X); break; - case ECogEngine_CollisionQueryShape::Capsule: QueryShape.SetCapsule(ShapeExtent.X, ShapeExtent.Z); break; - case ECogEngine_CollisionQueryShape::Box: QueryShape.SetBox(FVector3f(ShapeExtent)); break; + case ECogEngine_CollisionQueryShape::Sphere: QueryShape.SetSphere(ShapeExtent.X); + break; + case ECogEngine_CollisionQueryShape::Capsule: QueryShape.SetCapsule(ShapeExtent.X, ShapeExtent.Z); + break; + case ECogEngine_CollisionQueryShape::Box: QueryShape.SetBox(FVector3f(ShapeExtent)); + break; } } switch (Type) { - case ECogEngine_CollisionQueryType::Overlap: - { - TArray Overlaps; - switch (By) - { - case ECogEngine_CollisionQueryBy::Channel: - { - HasHits = GetWorld()->OverlapMultiByChannel(Overlaps, QueryStart, QueryRotation, Channel, QueryShape, QueryParams); - break; - } + case ECogEngine_CollisionQueryType::Overlap: + { + TArray Overlaps; + switch (By) + { + case ECogEngine_CollisionQueryBy::Channel: + { + switch (OverlapMode) + { + case ECogEngine_CollisionQueryOverlapMode::AnyTest: + HasHits = World->OverlapAnyTestByChannel(QueryStart, QueryRotation, TraceChannel, QueryShape, QueryParams); + break; - case ECogEngine_CollisionQueryBy::ObjectType: - { - FCollisionObjectQueryParams QueryObjectParams; - QueryObjectParams.ObjectTypesToQuery = ObjectTypesToQuery; - HasHits = GetWorld()->OverlapMultiByObjectType(Overlaps, QueryStart, QueryRotation, QueryObjectParams, QueryShape, QueryParams); - break; - } + case ECogEngine_CollisionQueryOverlapMode::BlockingTest: + HasHits = World->OverlapBlockingTestByChannel(QueryStart, QueryRotation, TraceChannel, QueryShape, QueryParams); + break; - case ECogEngine_CollisionQueryBy::Profile: - { - HasHits = GetWorld()->OverlapMultiByProfile(Overlaps, QueryStart, QueryRotation, ProfileName, QueryShape, QueryParams); - break; - } - } + case ECogEngine_CollisionQueryOverlapMode::Multi: + HasHits = World->OverlapMultiByChannel(Overlaps, QueryStart, QueryRotation, TraceChannel, QueryShape, QueryParams); + break; + } + break; + } - FCogDebugDrawOverlapParams DrawParams; - FCogDebug::GetDebugDrawOverlapSettings(DrawParams); - FCogDebugDrawHelper::DrawOverlap(GetWorld(), QueryShape, QueryStart, QueryRotation, Overlaps, DrawParams); - break; - } + case ECogEngine_CollisionQueryBy::ObjectType: + { + FCollisionObjectQueryParams QueryObjectParams; + QueryObjectParams.ObjectTypesToQuery = ObjectTypesToQuery; - case ECogEngine_CollisionQueryType::LineTrace: - { - TArray Hits; - switch (By) - { - case ECogEngine_CollisionQueryBy::Channel: - { - switch (Mode) - { - case ECogEngine_CollisionQueryMode::Single: - { - FHitResult Hit; - HasHits = GetWorld()->LineTraceSingleByChannel(Hit, QueryStart, QueryEnd, Channel, QueryParams); - if (HasHits) - { - Hits.Add(Hit); - } - break; - } - case ECogEngine_CollisionQueryMode::Multi: - { - HasHits = GetWorld()->LineTraceMultiByChannel(Hits, QueryStart, QueryEnd, Channel, QueryParams); - break; - } - case ECogEngine_CollisionQueryMode::Test: - { - HasHits = GetWorld()->LineTraceTestByChannel(QueryStart, QueryEnd, Channel, QueryParams); - break; - } - } + switch (OverlapMode) + { + case ECogEngine_CollisionQueryOverlapMode::AnyTest: + HasHits = World->OverlapAnyTestByObjectType(QueryStart, QueryRotation, QueryObjectParams, QueryShape, QueryParams); + break; - break; - } + case ECogEngine_CollisionQueryOverlapMode::BlockingTest: + break; - case ECogEngine_CollisionQueryBy::ObjectType: - { - FCollisionObjectQueryParams QueryObjectParams; - QueryObjectParams.ObjectTypesToQuery = ObjectTypesToQuery; + case ECogEngine_CollisionQueryOverlapMode::Multi: + HasHits = World->OverlapMultiByObjectType(Overlaps, QueryStart, QueryRotation, QueryObjectParams, QueryShape, QueryParams); + break; + } + break; + } - switch (Mode) - { - case ECogEngine_CollisionQueryMode::Single: - { - FHitResult Hit; - HasHits = GetWorld()->LineTraceSingleByObjectType(Hit, QueryStart, QueryEnd, QueryObjectParams, QueryParams); - if (HasHits) - { - Hits.Add(Hit); - } - break; - } - case ECogEngine_CollisionQueryMode::Multi: - { - HasHits = GetWorld()->LineTraceMultiByObjectType(Hits, QueryStart, QueryEnd, QueryObjectParams, QueryParams); - break; - } - case ECogEngine_CollisionQueryMode::Test: - { - HasHits = GetWorld()->LineTraceTestByObjectType(QueryStart, QueryEnd, QueryObjectParams, QueryParams); - break; - } - } - break; - } + case ECogEngine_CollisionQueryBy::Profile: + { + switch (OverlapMode) + { + case ECogEngine_CollisionQueryOverlapMode::AnyTest: + HasHits = World->OverlapAnyTestByProfile(QueryStart, QueryRotation, ProfileName, QueryShape, QueryParams); + break; - case ECogEngine_CollisionQueryBy::Profile: - { - switch (Mode) - { - case ECogEngine_CollisionQueryMode::Single: - { - FHitResult Hit; - HasHits = GetWorld()->LineTraceSingleByProfile(Hit, QueryStart, QueryEnd, ProfileName, QueryParams); - if (HasHits) - { - Hits.Add(Hit); - } - break; - } - case ECogEngine_CollisionQueryMode::Multi: - { - HasHits = GetWorld()->LineTraceMultiByProfile(Hits, QueryStart, QueryEnd, ProfileName, QueryParams); - break; - } - case ECogEngine_CollisionQueryMode::Test: - { - HasHits = GetWorld()->LineTraceTestByProfile(QueryStart, QueryEnd, ProfileName, QueryParams); - break; - } - } - break; - } - } + case ECogEngine_CollisionQueryOverlapMode::BlockingTest: + HasHits = World->OverlapBlockingTestByProfile(QueryStart, QueryRotation, ProfileName, QueryShape, QueryParams); + break; - FCogDebugDrawLineTraceParams DrawParams; - FCogDebug::GetDebugDrawLineTraceSettings(DrawParams); - FCogDebugDrawHelper::DrawLineTrace(GetWorld(), QueryStart, QueryEnd, HasHits, Hits, DrawParams); - break; - } + case ECogEngine_CollisionQueryOverlapMode::Multi: + HasHits = World->OverlapMultiByProfile(Overlaps, QueryStart, QueryRotation, ProfileName, QueryShape, QueryParams); + break; + } + break; + } + } - case ECogEngine_CollisionQueryType::Sweep: - { - TArray Hits; - switch (By) - { - case ECogEngine_CollisionQueryBy::Channel: - { - switch (Mode) - { - case ECogEngine_CollisionQueryMode::Single: - { - FHitResult Hit; - HasHits = GetWorld()->SweepSingleByChannel(Hit, QueryStart, QueryEnd, QueryRotation, Channel, QueryShape, QueryParams); - if (HasHits) - { - Hits.Add(Hit); - } - break; - } - case ECogEngine_CollisionQueryMode::Multi: - { - HasHits = GetWorld()->SweepMultiByChannel(Hits, QueryStart, QueryEnd, QueryRotation, Channel, QueryShape, QueryParams); - break; - } - case ECogEngine_CollisionQueryMode::Test: - { - HasHits = GetWorld()->SweepTestByChannel(QueryStart, QueryEnd, QueryRotation, Channel, QueryShape, QueryParams); - break; - } - } - break; - } + FCogDebugDrawOverlapParams DrawParams; + FCogDebug::GetDebugDrawOverlapSettings(DrawParams); + FCogDebugDrawHelper::DrawOverlap(World, QueryShape, QueryStart, QueryRotation, HasHits, Overlaps, DrawParams); + break; + } - case ECogEngine_CollisionQueryBy::ObjectType: - { - FCollisionObjectQueryParams QueryObjectParams; - QueryObjectParams.ObjectTypesToQuery = ObjectTypesToQuery; + case ECogEngine_CollisionQueryType::LineTrace: + { + TArray Hits; + switch (By) + { + case ECogEngine_CollisionQueryBy::Channel: + { + switch (TraceMode) + { + case ECogEngine_CollisionQueryTraceMode::Single: + { + FHitResult Hit; + HasHits = World->LineTraceSingleByChannel(Hit, QueryStart, QueryEnd, TraceChannel, QueryParams); + if (HasHits) + { + Hits.Add(Hit); + } + break; + } + case ECogEngine_CollisionQueryTraceMode::Multi: + { + HasHits = World->LineTraceMultiByChannel(Hits, QueryStart, QueryEnd, TraceChannel, QueryParams); + break; + } + case ECogEngine_CollisionQueryTraceMode::Test: + { + HasHits = World->LineTraceTestByChannel(QueryStart, QueryEnd, TraceChannel, QueryParams); + break; + } + } + break; + } - switch (Mode) - { - case ECogEngine_CollisionQueryMode::Single: - { - FHitResult Hit; - HasHits = GetWorld()->SweepSingleByObjectType(Hit, QueryStart, QueryEnd, QueryRotation, QueryObjectParams, QueryShape, QueryParams); - if (HasHits) - { - Hits.Add(Hit); - } - break; - } - case ECogEngine_CollisionQueryMode::Multi: - { - HasHits = GetWorld()->SweepMultiByObjectType(Hits, QueryStart, QueryEnd, QueryRotation, QueryObjectParams, QueryShape, QueryParams); - break; - } - case ECogEngine_CollisionQueryMode::Test: - { - HasHits = GetWorld()->SweepTestByObjectType(QueryStart, QueryEnd, QueryRotation, QueryObjectParams, QueryShape, QueryParams); - break; - } - } - break; - } + case ECogEngine_CollisionQueryBy::ObjectType: + { + FCollisionObjectQueryParams QueryObjectParams; + QueryObjectParams.ObjectTypesToQuery = ObjectTypesToQuery; - case ECogEngine_CollisionQueryBy::Profile: - { - switch (Mode) - { - case ECogEngine_CollisionQueryMode::Single: - { - FHitResult Hit; - HasHits = GetWorld()->SweepSingleByProfile(Hit, QueryStart, QueryEnd, QueryRotation, ProfileName, QueryShape, QueryParams); - if (HasHits) - { - Hits.Add(Hit); - } - break; - } - case ECogEngine_CollisionQueryMode::Multi: - { - HasHits = GetWorld()->SweepMultiByProfile(Hits, QueryStart, QueryEnd, QueryRotation, ProfileName, QueryShape, QueryParams); - break; - } - case ECogEngine_CollisionQueryMode::Test: - { - HasHits = GetWorld()->SweepTestByProfile(QueryStart, QueryEnd, QueryRotation, ProfileName, QueryShape, QueryParams); - break; - } - } - break; - } - } + switch (TraceMode) + { + case ECogEngine_CollisionQueryTraceMode::Single: + { + FHitResult Hit; + HasHits = World->LineTraceSingleByObjectType(Hit, QueryStart, QueryEnd, QueryObjectParams, QueryParams); + if (HasHits) + { + Hits.Add(Hit); + } + break; + } + case ECogEngine_CollisionQueryTraceMode::Multi: + { + HasHits = World->LineTraceMultiByObjectType(Hits, QueryStart, QueryEnd, QueryObjectParams, QueryParams); + break; + } + case ECogEngine_CollisionQueryTraceMode::Test: + { + HasHits = World->LineTraceTestByObjectType(QueryStart, QueryEnd, QueryObjectParams, QueryParams); + break; + } + } + break; + } - FCogDebugDrawSweepParams DrawParams; - FCogDebug::GetDebugDrawSweepSettings(DrawParams); - FCogDebugDrawHelper::DrawSweep(GetWorld(), QueryShape, QueryStart, QueryEnd, QueryRotation, HasHits, Hits, DrawParams); - break; - } + case ECogEngine_CollisionQueryBy::Profile: + { + switch (TraceMode) + { + case ECogEngine_CollisionQueryTraceMode::Single: + { + FHitResult Hit; + HasHits = World->LineTraceSingleByProfile(Hit, QueryStart, QueryEnd, ProfileName, QueryParams); + if (HasHits) + { + Hits.Add(Hit); + } + break; + } + case ECogEngine_CollisionQueryTraceMode::Multi: + { + HasHits = World->LineTraceMultiByProfile(Hits, QueryStart, QueryEnd, ProfileName, QueryParams); + break; + } + case ECogEngine_CollisionQueryTraceMode::Test: + { + HasHits = World->LineTraceTestByProfile(QueryStart, QueryEnd, ProfileName, QueryParams); + break; + } + } + break; + } + } + + FCogDebugDrawLineTraceParams DrawParams; + FCogDebug::GetDebugDrawLineTraceSettings(DrawParams); + FCogDebugDrawHelper::DrawLineTrace(World, QueryStart, QueryEnd, HasHits, Hits, DrawParams); + break; + } + + case ECogEngine_CollisionQueryType::Sweep: + { + TArray Hits; + switch (By) + { + case ECogEngine_CollisionQueryBy::Channel: + { + switch (TraceMode) + { + case ECogEngine_CollisionQueryTraceMode::Single: + { + FHitResult Hit; + HasHits = World->SweepSingleByChannel(Hit, QueryStart, QueryEnd, QueryRotation, TraceChannel, QueryShape, QueryParams); + if (HasHits) + { + Hits.Add(Hit); + } + break; + } + case ECogEngine_CollisionQueryTraceMode::Multi: + { + HasHits = World->SweepMultiByChannel(Hits, QueryStart, QueryEnd, QueryRotation, TraceChannel, QueryShape, QueryParams); + break; + } + case ECogEngine_CollisionQueryTraceMode::Test: + { + HasHits = World->SweepTestByChannel(QueryStart, QueryEnd, QueryRotation, TraceChannel, QueryShape, QueryParams); + break; + } + } + break; + } + + case ECogEngine_CollisionQueryBy::ObjectType: + { + FCollisionObjectQueryParams QueryObjectParams; + QueryObjectParams.ObjectTypesToQuery = ObjectTypesToQuery; + + switch (TraceMode) + { + case ECogEngine_CollisionQueryTraceMode::Single: + { + FHitResult Hit; + HasHits = World->SweepSingleByObjectType(Hit, QueryStart, QueryEnd, QueryRotation, QueryObjectParams, QueryShape, QueryParams); + if (HasHits) + { + Hits.Add(Hit); + } + break; + } + case ECogEngine_CollisionQueryTraceMode::Multi: + { + HasHits = World->SweepMultiByObjectType(Hits, QueryStart, QueryEnd, QueryRotation, QueryObjectParams, QueryShape, QueryParams); + break; + } + case ECogEngine_CollisionQueryTraceMode::Test: + { + HasHits = World->SweepTestByObjectType(QueryStart, QueryEnd, QueryRotation, QueryObjectParams, QueryShape, QueryParams); + break; + } + } + break; + } + + case ECogEngine_CollisionQueryBy::Profile: + { + switch (TraceMode) + { + case ECogEngine_CollisionQueryTraceMode::Single: + { + FHitResult Hit; + HasHits = World->SweepSingleByProfile(Hit, QueryStart, QueryEnd, QueryRotation, ProfileName, QueryShape, QueryParams); + if (HasHits) + { + Hits.Add(Hit); + } + break; + } + case ECogEngine_CollisionQueryTraceMode::Multi: + { + HasHits = World->SweepMultiByProfile(Hits, QueryStart, QueryEnd, QueryRotation, ProfileName, QueryShape, QueryParams); + break; + } + case ECogEngine_CollisionQueryTraceMode::Test: + { + HasHits = World->SweepTestByProfile(QueryStart, QueryEnd, QueryRotation, ProfileName, QueryShape, QueryParams); + break; + } + } + break; + } + } + + FCogDebugDrawSweepParams DrawParams; + FCogDebug::GetDebugDrawSweepSettings(DrawParams); + FCogDebugDrawHelper::DrawSweep(World, QueryShape, QueryStart, QueryEnd, QueryRotation, HasHits, Hits, DrawParams); + break; + } } } diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineDataAsset.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineDataAsset.cpp index d5e3ecf..eb47385 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineDataAsset.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineDataAsset.cpp @@ -1,12 +1,12 @@ #include "CogEngineDataAsset.h" //-------------------------------------------------------------------------------------------------------------------------- -void UCogEngineCheat_Execution::Execute_Implementation(const AActor* Instigator, const TArray& Targets) const +void UCogEngineCheat_Execution::Execute_Implementation(const UObject* WorldContextObject, const AActor* Instigator, const TArray& Targets) const { } //-------------------------------------------------------------------------------------------------------------------------- -ECogEngineCheat_ActiveState UCogEngineCheat_Execution::IsActiveOnTargets_Implementation(const TArray& Targets) const +ECogEngineCheat_ActiveState UCogEngineCheat_Execution::IsActiveOnTargets_Implementation(const UObject* WorldContextObject, const TArray& Targets) const { return ECogEngineCheat_ActiveState::Inactive; } diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineHelper.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineHelper.cpp index d663d48..5532b0d 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineHelper.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineHelper.cpp @@ -1,8 +1,7 @@ #include "CogEngineHelper.h" #include "CogEngineReplicator.h" -#include "CogWindowHelper.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "imgui.h" #include "GameFramework/Actor.h" #include "GameFramework/Pawn.h" @@ -15,10 +14,10 @@ //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineHelper::ActorContextMenu(AActor& Actor) { - FCogWindowWidgets::ThinSeparatorText("Object"); + FCogWidgets::ThinSeparatorText("Object"); #if WITH_EDITOR - FCogWindowWidgets::OpenObjectAssetButton(&Actor, ImVec2(-1, 0)); + FCogWidgets::OpenObjectAssetButton(&Actor, ImVec2(-1, 0)); #endif if (ImGui::Button("Delete", ImVec2(-1, 0))) @@ -31,7 +30,7 @@ void FCogEngineHelper::ActorContextMenu(AActor& Actor) if (APawn* Pawn = Cast(&Actor)) { - FCogWindowWidgets::ThinSeparatorText("Pawn"); + FCogWidgets::ThinSeparatorText("Pawn"); if (ImGui::Button("Possess", ImVec2(-1, 0))) { @@ -55,4 +54,17 @@ void FCogEngineHelper::ActorContextMenu(AActor& Actor) } } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineHelper::RenderConfigureMessage(const TWeakObjectPtr InAsset) +{ + if (InAsset == nullptr) + { + ImGui::Text("Create a DataAsset child of '%s' to configure. ", StringCast(*UCogEngineDataAsset::StaticClass()->GetName()).Get()); + } + else + { + ImGui::Text("Can be configured in the '%s' DataAsset. ", StringCast(*GetNameSafe(InAsset.Get())).Get()); + } } \ No newline at end of file diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineReplicator.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineReplicator.cpp index 404ff8f..220d5d3 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineReplicator.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineReplicator.cpp @@ -3,6 +3,7 @@ #include "CogCommon.h" #include "CogCommonPossessorInterface.h" #include "CogEngineDataAsset.h" +#include "CogSubsystem.h" #include "Engine/EngineTypes.h" #include "Engine/World.h" #include "EngineUtils.h" @@ -32,12 +33,13 @@ ACogEngineReplicator* ACogEngineReplicator::Spawn(APlayerController* Controller) //-------------------------------------------------------------------------------------------------------------------------- ACogEngineReplicator* ACogEngineReplicator::GetLocalReplicator(const UWorld& World) { - for (TActorIterator It(&World, StaticClass()); It; ++It) + const TActorIterator It(&World, StaticClass()); + if (It) { ACogEngineReplicator* Replicator = *It; return Replicator; } - + return nullptr; } @@ -226,20 +228,24 @@ void ACogEngineReplicator::Server_DeleteActor_Implementation(AActor* Actor) void ACogEngineReplicator::Server_ApplyCheat_Implementation(const AActor* CheatInstigator, const TArray& Targets, const FCogEngineCheat& Cheat) const { if (Cheat.Execution == nullptr) - { - return; - } + { return; } - Cheat.Execution->Execute(CheatInstigator, Targets); + if (GetWorld() == nullptr) + { return; } + + Cheat.Execution->Execute(GetWorld(), CheatInstigator, Targets); } //-------------------------------------------------------------------------------------------------------------------------- -ECogEngineCheat_ActiveState ACogEngineReplicator::IsCheatActiveOnTargets(const TArray& Targets, const FCogEngineCheat& Cheat) +ECogEngineCheat_ActiveState ACogEngineReplicator::IsCheatActiveOnTargets(const TArray& Targets, const FCogEngineCheat& Cheat) const { + if (GetWorld() == nullptr) + { return ECogEngineCheat_ActiveState::Inactive; } + if (Cheat.Execution == nullptr) { return ECogEngineCheat_ActiveState::Inactive; } - return Cheat.Execution->IsActiveOnTargets(Targets); + return Cheat.Execution->IsActiveOnTargets(GetWorld(), Targets); } diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Audio.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Audio.cpp index 5db3d27..3914499 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Audio.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Audio.cpp @@ -5,9 +5,7 @@ //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Audio::RenderHelp() { - ImGui::Text( - "This window displays audio settings. " - ); + ImGui::Text("This window displays audio settings."); } //-------------------------------------------------------------------------------------------------------------------------- diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_BuildInfo.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_BuildInfo.cpp new file mode 100644 index 0000000..18a0391 --- /dev/null +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_BuildInfo.cpp @@ -0,0 +1,126 @@ +#include "CogEngineWindow_BuildInfo.h" + +#include "CogImguiHelper.h" +#include "imgui.h" +#include "BuildSettings.h" +#include "GenericPlatform/GenericPlatformMisc.h" + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_BuildInfo::Initialize() +{ + FCogWindow::Initialize(); + + Config = GetConfig(); + + BuildText(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_BuildInfo::RenderHelp() +{ + ImGui::Text( + "This window can be used to display the build information such as the build version, changelist, date, target, and so on." + ); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_BuildInfo::RenderTick(float DeltaTime) +{ + FCogWindow::RenderTick(DeltaTime); + + if (FApp::GetBuildTargetType() == EBuildTargetType::Editor) + { + if (Config->ShowInEditor == false) + { return; } + } + else + { + if (Config->ShowInPackage == false) + { return;} + } + + const auto TextStr = StringCast(*Text); + ImDrawList* DrawList = Config->ShowInForeground ? ImGui::GetForegroundDrawList() : ImGui::GetBackgroundDrawList(); + const ImVec2 WindowPadding = ImGui::GetStyle().WindowPadding; + const ImVec2 TextSize = ImGui::CalcTextSize(TextStr.Get(), nullptr, false); + const ImVec2 RectSize = TextSize + WindowPadding * 2; + const ImVec2 Pos = FCogWidgets::ComputeScreenCornerLocation(Config->Alignment, Config->Padding); + const ImVec2 AlignedPos = Pos - (FCogImguiHelper::ToImVec2(Config->Alignment) * RectSize); + + DrawList->AddRectFilled(AlignedPos, AlignedPos + RectSize, FCogImguiHelper::ToImU32(Config->BackgroundColor), Config->Rounding); + DrawList->AddRect(AlignedPos, AlignedPos + RectSize, FCogImguiHelper::ToImU32(Config->BorderColor), Config->Rounding); + DrawList->AddText(AlignedPos + WindowPadding, FCogImguiHelper::ToImU32(Config->TextColor), TextStr.Get()); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_BuildInfo::RenderContent() +{ + Super::RenderContent(); + + FCogWidgets::ThinSeparatorText("Build Properties"); + + if (ImGui::BeginChild("Settings", ImVec2(-1, 100 * GetDpiScale()), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY)) + { + if (ImGui::Checkbox("Branch Name", &Config->ShowBranchName)) { BuildText(); } + if (ImGui::Checkbox("Build Date", &Config->ShowBuildDate)) { BuildText(); } + if (ImGui::Checkbox("Build Configuration", &Config->ShowBuildConfiguration)) { BuildText(); } + if (ImGui::Checkbox("Build User", &Config->ShowBuildUser)) { BuildText(); } + if (ImGui::Checkbox("Build Machine", &Config->ShowBuildMachine)) { BuildText(); } + if (ImGui::Checkbox("Build Target Type", &Config->ShowBuildTargetType)) { BuildText(); } + if (ImGui::Checkbox("Current Change list", &Config->ShowCurrentChangelist)) { BuildText(); } + if (ImGui::Checkbox("Compatible Change list", &Config->ShowCompatibleChangelist)) { BuildText(); } + } + ImGui::EndChild(); + + FCogWidgets::ThinSeparatorText("Display"); + + ImGui::Checkbox("Show In Editor", &Config->ShowInEditor); + ImGui::Checkbox("Show In Package", &Config->ShowInPackage); + ImGui::Checkbox("Show In Foreground", &Config->ShowInForeground); + + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderFloat2("Alignment", &Config->Alignment.X, 0, 1.0f, "%.2f"); + + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderInt2("Padding", &Config->Padding.X, 0, 100); + + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderInt("Rounding", &Config->Rounding, 0, 12); + + if (FCogWidgets::InputText("Separator", Config->Separator)) + { + BuildText(); + } + + constexpr ImGuiColorEditFlags ColorEditFlags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf; + FCogImguiHelper::ColorEdit4("Background Color", Config->BackgroundColor, ColorEditFlags); + FCogImguiHelper::ColorEdit4("Border Color", Config->BorderColor, ColorEditFlags); + FCogImguiHelper::ColorEdit4("Text Color", Config->TextColor, ColorEditFlags); + + ImGui::Separator(); + + if (ImGui::Button("Reset Settings", ImVec2(-1, 0))) + { + ResetConfig(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_BuildInfo::BuildText() +{ + FStringBuilderBase S; + bool AddSeparator = false; + + if (Config->ShowBranchName) { S.Append(BuildSettings::GetBranchName()); S.Append(Config->Separator); } + if (Config->ShowBuildDate) { S.Append(BuildSettings::GetBuildDate()); S.Append(Config->Separator); } + if (Config->ShowBuildConfiguration) { S.Append(LexToString(FApp::GetBuildConfiguration())); S.Append(Config->Separator); } + if (Config->ShowBuildTargetType) { S.Append(LexToString(FApp::GetBuildTargetType())); S.Append(Config->Separator); } + if (Config->ShowBuildUser) { S.Append(BuildSettings::GetBuildUser()); S.Append(Config->Separator); } + if (Config->ShowBuildMachine) { S.Append(BuildSettings::GetBuildMachine()); S.Append(Config->Separator); } + if (Config->ShowCurrentChangelist) { S.Appendf(TEXT("%d"), BuildSettings::GetCurrentChangelist()); S.Append(Config->Separator); } + if (Config->ShowCompatibleChangelist) { S.Appendf(TEXT("%d"),BuildSettings::GetCompatibleChangelist()); S.Append(Config->Separator); } + + S.RemoveSuffix(Config->Separator.Len()); + + Text = FString(S); +} \ No newline at end of file diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Cheats.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Cheats.cpp index 2f00585..0398cae 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Cheats.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Cheats.cpp @@ -3,9 +3,10 @@ #include "CogEngineDataAsset.h" #include "CogEngineReplicator.h" #include "CogCommonAllegianceActorInterface.h" +#include "CogEngineHelper.h" #include "CogImguiHelper.h" -#include "CogWindowConsoleCommandManager.h" -#include "CogWindowWidgets.h" +#include "CogConsoleCommandManager.h" +#include "CogWidgets.h" #include "EngineUtils.h" #include "GameFramework/Character.h" #include "imgui.h" @@ -16,13 +17,13 @@ void FCogEngineWindow_Cheats::RenderHelp() { ImGui::Text( "This window can be used to apply cheats to the selected actor (by default). " - "The cheats can be configured in the '%s' data asset. " "When clicking a cheat button, press:\n" " [CTRL] to apply the cheat to controlled actor\n" " [ALT] to apply the cheat to the allies of the selected actor\n" " [SHIFT] to apply the cheat to the enemies of the selected actor\n" - , TCHAR_TO_ANSI(*GetNameSafe(Asset.Get())) ); + + FCogEngineHelper::RenderConfigureMessage(Asset); } //-------------------------------------------------------------------------------------------------------------------------- @@ -35,26 +36,34 @@ void FCogEngineWindow_Cheats::Initialize() Asset = GetAsset(); Config = GetConfig(); - FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand( + FCogConsoleCommandManager::RegisterWorldConsoleCommand( TEXT("Cog.Cheat"), TEXT("Apply a cheat to the selection. Cog.Cheat -Allies -Enemies -Controlled"), GetWorld(), - FCogWindowConsoleCommandDelegate::CreateLambda([this](const TArray& InArgs, UWorld* InWorld) + FCogWindowConsoleCommandDelegate::CreateLambda([this](const TArray& InArgs, const UWorld* InWorld) { if (InArgs.Num() > 0) { - if (const FCogEngineCheat* cheat = FindCheatByName(InArgs[0], false)) - { - const bool ApplyToEnemies = InArgs.Contains("-Enemies"); - const bool ApplyToAllies = InArgs.Contains("-Allies"); - const bool ApplyToControlled = InArgs.Contains("-Controlled"); - - RequestCheat(GetLocalPlayerPawn(), GetSelection(), *cheat, ApplyToEnemies, ApplyToAllies, ApplyToControlled); - } - else + const FCogEngineCheat* Cheat = FindCheatByName(InArgs[0], false); + if (Cheat == nullptr) { UE_LOG(LogCogImGui, Warning, TEXT("Cog.Cheat %s | Cheat not found"), *InArgs[0]); + return; } + + ACogEngineReplicator* Replicator = ACogEngineReplicator::GetLocalReplicator(*InWorld); + if (Replicator == nullptr) + { + UE_LOG(LogCogImGui, Warning, TEXT("Cog.Cheat %s | Replicator not found"), *InArgs[0]); + return; + } + + const bool ApplyToEnemies = InArgs.Contains("-Enemies"); + const bool ApplyToAllies = InArgs.Contains("-Allies"); + const bool ApplyToControlled = InArgs.Contains("-Controlled"); + + AActor* Selection = GetSelection(); + RequestCheat(*Replicator, GetLocalPlayerPawn(), Selection, *Cheat, ApplyToEnemies, ApplyToAllies, ApplyToControlled); } })); @@ -64,12 +73,12 @@ void FCogEngineWindow_Cheats::Initialize() for (const FCogEngineCheatCategory& CheatCategory : Asset->CheatCategories) { - for (const FCogEngineCheat& Cheat : CheatCategory.PersistentEffects) + for (const FCogEngineCheat& Cheat : CheatCategory.PersistentCheats) { UpdateCheatColor(Cheat); } - for (const FCogEngineCheat& Cheat : CheatCategory.InstantEffects) + for (const FCogEngineCheat& Cheat : CheatCategory.InstantCheats) { UpdateCheatColor(Cheat); } @@ -90,14 +99,6 @@ void FCogEngineWindow_Cheats::UpdateCheatColor(const FCogEngineCheat& Cheat) con } } -//-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Cheats::ResetConfig() -{ - Super::ResetConfig(); - - Config->Reset(); -} - //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Cheats::GameTick(float DeltaTime) { @@ -187,6 +188,13 @@ void FCogEngineWindow_Cheats::RenderContent() return; } + ACogEngineReplicator* Replicator = ACogEngineReplicator::GetLocalReplicator(*GetWorld()); + if (Replicator == nullptr) + { + ImGui::TextDisabled("No Replicator"); + return; + } + if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("Options")) @@ -243,7 +251,7 @@ void FCogEngineWindow_Cheats::RenderContent() ImGui::EndMenu(); } - FCogWindowWidgets::SearchBar(Filter); + FCogWidgets::SearchBar("##Filter", Filter); ImGui::EndMenuBar(); } @@ -266,7 +274,7 @@ void FCogEngineWindow_Cheats::RenderContent() bool Open = true; if (Config->bGroupByCategories) { - Open = FCogWindowWidgets::DarkCollapsingHeader(CategoryStr.Get(), ImGuiTreeNodeFlags_DefaultOpen); + Open = FCogWidgets::DarkCollapsingHeader(CategoryStr.Get(), ImGuiTreeNodeFlags_DefaultOpen); if (Open && Config->bUseTwoColumns) { @@ -284,9 +292,9 @@ void FCogEngineWindow_Cheats::RenderContent() } int Index = 0; - for (const FCogEngineCheat& Cheat : CheatCategory.PersistentEffects) + for (const FCogEngineCheat& Cheat : CheatCategory.PersistentCheats) { - AddCheat(Index, ControlledActor, SelectedActor, Cheat, true); + AddCheat(*Replicator, Index, ControlledActor, SelectedActor, Cheat, true); Index++; } @@ -298,10 +306,10 @@ void FCogEngineWindow_Cheats::RenderContent() //---------------------------------------------------------------------------- if (SelectedActor == ControlledActor) { - for (const FCogEngineCheat& Cheat : CheatCategory.PersistentEffects) + for (const FCogEngineCheat& Cheat : CheatCategory.PersistentCheats) { TArray Targets = { SelectedActor }; - if (ACogEngineReplicator::IsCheatActiveOnTargets(Targets, Cheat) == ECogEngineCheat_ActiveState::Active) + if (Replicator->IsCheatActiveOnTargets(Targets, Cheat) == ECogEngineCheat_ActiveState::Active) { Config->AppliedCheats.AddUnique(Cheat.Name); } @@ -315,9 +323,9 @@ void FCogEngineWindow_Cheats::RenderContent() ImGui::TableNextColumn(); Index = 0; - for (const FCogEngineCheat& Cheat : CheatCategory.InstantEffects) + for (const FCogEngineCheat& Cheat : CheatCategory.InstantCheats) { - AddCheat(Index, ControlledActor, SelectedActor, Cheat, false); + AddCheat(*Replicator, Index, ControlledActor, SelectedActor, Cheat, false); Index++; } @@ -335,7 +343,7 @@ void FCogEngineWindow_Cheats::RenderContent() } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogEngineWindow_Cheats::AddCheat(const int32 Index, AActor* ControlledActor, AActor* SelectedActor, const FCogEngineCheat& Cheat, bool IsPersistent) +bool FCogEngineWindow_Cheats::AddCheat(ACogEngineReplicator& Replicator, const int32 Index, AActor* ControlledActor, AActor* SelectedActor, const FCogEngineCheat& Cheat, bool IsPersistent) { const auto CheatName = StringCast(*Cheat.Name); @@ -344,7 +352,7 @@ bool FCogEngineWindow_Cheats::AddCheat(const int32 Index, AActor* ControlledActo ImGui::PushID(Index); - FCogWindowWidgets::PushBackColor(FCogImguiHelper::ToImVec4(Cheat.CustomColor)); + FCogWidgets::PushBackColor(FCogImguiHelper::ToImVec4(Cheat.CustomColor)); const bool IsShiftDown = (ImGui::GetCurrentContext()->IO.KeyMods & ImGuiMod_Shift) != 0; const bool IsAltDown = (ImGui::GetCurrentContext()->IO.KeyMods & ImGuiMod_Alt) != 0; @@ -354,10 +362,10 @@ bool FCogEngineWindow_Cheats::AddCheat(const int32 Index, AActor* ControlledActo if (IsPersistent) { TArray Targets = { SelectedActor }; - bool isEnabled = ACogEngineReplicator::IsCheatActiveOnTargets(Targets, Cheat) == ECogEngineCheat_ActiveState::Active; + bool isEnabled = Replicator.IsCheatActiveOnTargets(Targets, Cheat) == ECogEngineCheat_ActiveState::Active; if (ImGui::Checkbox(CheatName.Get(), &isEnabled)) { - RequestCheat(ControlledActor, SelectedActor, Cheat, IsShiftDown, IsAltDown, IsControlDown); + RequestCheat(Replicator, ControlledActor, SelectedActor, Cheat, IsShiftDown, IsAltDown, IsControlDown); bIsPressed = true; } } @@ -365,7 +373,7 @@ bool FCogEngineWindow_Cheats::AddCheat(const int32 Index, AActor* ControlledActo { if (ImGui::Button(CheatName.Get(), ImVec2(-1, 0))) { - RequestCheat(ControlledActor, SelectedActor, Cheat, IsShiftDown, IsAltDown, IsControlDown); + RequestCheat(Replicator, ControlledActor, SelectedActor, Cheat, IsShiftDown, IsAltDown, IsControlDown); bIsPressed = true; } } @@ -373,14 +381,14 @@ bool FCogEngineWindow_Cheats::AddCheat(const int32 Index, AActor* ControlledActo if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); - ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, IsShiftDown || IsAltDown || IsControlDown ? 0.5f : 1.0f), "On Selection"); - ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, IsShiftDown ? 1.0f : 0.5f), "On Enemies [SHIFT]"); - ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, IsAltDown ? 1.0f : 0.5f), "On Allies [ALT]"); - ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, IsControlDown ? 1.0f : 0.5f), "On Controlled [CTRL]"); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, IsShiftDown || IsAltDown || IsControlDown ? 0.5f : 1.0f), "Selection"); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, IsShiftDown ? 1.0f : 0.5f), "Enemies [SHIFT]"); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, IsAltDown ? 1.0f : 0.5f), "Allies [ALT]"); + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, IsControlDown ? 1.0f : 0.5f), "Controlled [CTRL]"); ImGui::EndTooltip(); } - FCogWindowWidgets::PopBackColor(); + FCogWidgets::PopBackColor(); ImGui::PopID(); @@ -388,7 +396,7 @@ bool FCogEngineWindow_Cheats::AddCheat(const int32 Index, AActor* ControlledActo } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Cheats::RequestCheat(AActor* ControlledActor, AActor* SelectedActor, const FCogEngineCheat& Cheat, bool ApplyToEnemies, bool ApplyToAllies, bool ApplyToControlled) +void FCogEngineWindow_Cheats::RequestCheat(ACogEngineReplicator& Replicator, AActor* ControlledActor, AActor* SelectedActor, const FCogEngineCheat& Cheat, bool ApplyToEnemies, bool ApplyToAllies, bool ApplyToControlled) { TArray Actors; @@ -424,22 +432,18 @@ void FCogEngineWindow_Cheats::RequestCheat(AActor* ControlledActor, AActor* Sele Actors.Add(SelectedActor); } - if (ACogEngineReplicator* Replicator = ACogEngineReplicator::GetLocalReplicator(*GetWorld())) - { - Replicator->Server_ApplyCheat(ControlledActor, Actors, Cheat); - } - else - { - UE_LOG(LogCogImGui, Warning, TEXT("FCogAbilityWindow_Cheats::RequestCheat | Replicator not found")); - } + Replicator.Server_ApplyCheat(ControlledActor, Actors, Cheat); } //-------------------------------------------------------------------------------------------------------------------------- const FCogEngineCheat* FCogEngineWindow_Cheats::FindCheatByName(const FString& CheatName, const bool OnlyPersistentCheats) { + if (Asset == nullptr) + { return nullptr; } + for (const FCogEngineCheatCategory& CheatCategory : Asset->CheatCategories) { - for (const FCogEngineCheat& Cheat : CheatCategory.PersistentEffects) + for (const FCogEngineCheat& Cheat : CheatCategory.PersistentCheats) { if (Cheat.Name == CheatName) { @@ -448,19 +452,15 @@ const FCogEngineCheat* FCogEngineWindow_Cheats::FindCheatByName(const FString& C } if (OnlyPersistentCheats) - { - continue; - } + { continue; } - for (const FCogEngineCheat& Cheat : CheatCategory.InstantEffects) + for (const FCogEngineCheat& Cheat : CheatCategory.InstantCheats) { if (Cheat.Name == CheatName) { return &Cheat; } } - - } return nullptr; diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_CollisionTester.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_CollisionTester.cpp index d4abc26..09f3d49 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_CollisionTester.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_CollisionTester.cpp @@ -2,7 +2,7 @@ #include "CogDebug.h" #include "CogImguiHelper.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "Components/PrimitiveComponent.h" #include "Components/SceneComponent.h" #include "Engine/CollisionProfile.h" @@ -25,14 +25,6 @@ void FCogEngineWindow_CollisionTester::RenderHelp() ImGui::Text("This window is used to test a collision query."); } -//-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_CollisionTester::ResetConfig() -{ - Super::ResetConfig(); - - Config->Reset(); -} - //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_CollisionTester::RenderContent() { @@ -55,7 +47,7 @@ void FCogEngineWindow_CollisionTester::RenderContent() NewActor->SetActorLabel(NewActor->GetName().Replace(TEXT("CogEngine"), TEXT(""))); #endif - FCogDebug::SetSelection(GetWorld(), NewActor); + FCogDebug::SetSelection(NewActor); } if (ImGui::BeginItemTooltip()) { @@ -85,9 +77,9 @@ void FCogEngineWindow_CollisionTester::RenderContent() ImGui::SetNextItemWidth(-1); AActor* NewSelection = nullptr; - if (FCogWindowWidgets::MenuActorsCombo("CollisionTesters", NewSelection, *GetWorld(), ACogEngineCollisionTester::StaticClass())) + if (FCogWidgets::MenuActorsCombo("CollisionTesters", NewSelection, *GetWorld(), ACogEngineCollisionTester::StaticClass())) { - FCogDebug::SetSelection(GetWorld(), NewSelection); + FCogDebug::SetSelection(NewSelection); } ImGui::EndMenuBar(); @@ -111,28 +103,41 @@ void FCogEngineWindow_CollisionTester::RenderContent() if (const APlayerController* LocalPlayerController = GetLocalPlayerController()) { StartGizmo.Draw("CollisionTesterStartGizmo", *LocalPlayerController, *CollisionTester->StartComponent); - EndGizmo.Draw("CollisionTesterEndGizmo", *LocalPlayerController, *CollisionTester->EndComponent, ECogDebug_GizmoFlags::NoRotation | ECogDebug_GizmoFlags::NoScale); + + if (CollisionTester->Type != ECogEngine_CollisionQueryType::Overlap) + { + EndGizmo.Draw("CollisionTesterEndGizmo", *LocalPlayerController, *CollisionTester->EndComponent, ECogDebug_GizmoFlags::NoRotation | ECogDebug_GizmoFlags::NoScale); + } } - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::ComboboxEnum("Type", CollisionTester->Type); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::ComboboxEnum("Type", CollisionTester->Type); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::ComboboxEnum("Mode", CollisionTester->Mode); + if (CollisionTester->Type == ECogEngine_CollisionQueryType::Overlap) + { + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::ComboboxEnum("Mode", CollisionTester->OverlapMode); + } + else + { + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::ComboboxEnum("Mode", CollisionTester->TraceMode); + } - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::ComboboxEnum("By", CollisionTester->By); + + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::ComboboxEnum("By", CollisionTester->By); //------------------------------------------------- // Channel //------------------------------------------------- if (CollisionTester->By == ECogEngine_CollisionQueryBy::Channel) { - FCogWindowWidgets::SetNextItemToShortWidth(); - ECollisionChannel Channel = CollisionTester->Channel.GetValue(); - if (FCogWindowWidgets::ComboCollisionChannel("Channel", Channel)) + FCogWidgets::SetNextItemToShortWidth(); + ECollisionChannel Channel = CollisionTester->TraceChannel.GetValue(); + if (FCogWidgets::ComboTraceChannel("Channel", Channel)) { - CollisionTester->Channel = Channel; + CollisionTester->TraceChannel = Channel; } } //------------------------------------------------- @@ -143,7 +148,7 @@ void FCogEngineWindow_CollisionTester::RenderContent() const FCollisionResponseTemplate* SelectedProfile = CollisionProfile->GetProfileByIndex(CollisionTester->ProfileIndex); const FName SelectedProfileName = SelectedProfile != nullptr ? SelectedProfile->Name : FName("Custom"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); if (ImGui::BeginCombo("Profile", TCHAR_TO_ANSI(*SelectedProfileName.ToString()), ImGuiComboFlags_HeightLargest)) { for (int i = 0; i < CollisionProfile->GetNumOfProfiles(); ++i) @@ -153,13 +158,12 @@ void FCogEngineWindow_CollisionTester::RenderContent() { CollisionTester->ProfileIndex = i; CollisionTester->ObjectTypesToQuery = 0; - SelectedProfile = CollisionProfile->GetProfileByIndex(CollisionTester->ProfileIndex); if (Profile->CollisionEnabled != ECollisionEnabled::NoCollision) { for (int j = 0; j < ECC_MAX; ++j) { - const ECollisionResponse Response = Profile->ResponseToChannels.GetResponse((ECollisionChannel)j); + const ECollisionResponse Response = Profile->ResponseToChannels.GetResponse(static_cast(j)); if (Response != ECR_Ignore) { CollisionTester->ObjectTypesToQuery |= ECC_TO_BITFIELD(j); @@ -180,31 +184,31 @@ void FCogEngineWindow_CollisionTester::RenderContent() if (CollisionTester->Type != ECogEngine_CollisionQueryType::LineTrace) { - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::ComboboxEnum("Shape", CollisionTester->Shape); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::ComboboxEnum("Shape", CollisionTester->Shape); switch (CollisionTester->Shape) { case ECogEngine_CollisionQueryShape::Sphere: { - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); FCogImguiHelper::DragDouble("Sphere Radius", &CollisionTester->ShapeExtent.X, 1.0f, 0, FLT_MAX, "%.1f"); break; } case ECogEngine_CollisionQueryShape::Box: { - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); FCogImguiHelper::DragFVector("Box Extent", CollisionTester->ShapeExtent, 1.0f, 0, FLT_MAX, "%.1f"); break; } case ECogEngine_CollisionQueryShape::Capsule: { - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); FCogImguiHelper::DragDouble("Capsule Radius", &CollisionTester->ShapeExtent.X, 1.0f, 0, FLT_MAX, "%.1f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); FCogImguiHelper::DragDouble("Capsule Half Height", &CollisionTester->ShapeExtent.Z, 1.0f, 0, FLT_MAX, "%.1f"); break; } @@ -218,12 +222,12 @@ void FCogEngineWindow_CollisionTester::RenderContent() { ImGui::Separator(); ImGui::BeginDisabled(); - FCogWindowWidgets::CollisionProfileChannels(CollisionTester->ObjectTypesToQuery); + FCogWidgets::CollisionProfileChannels(CollisionTester->ObjectTypesToQuery); ImGui::EndDisabled(); } else if (CollisionTester->By == ECogEngine_CollisionQueryBy::ObjectType) { ImGui::Separator(); - FCogWindowWidgets::CollisionProfileChannels(CollisionTester->ObjectTypesToQuery); + FCogWidgets::CollisionObjectTypeChannels(CollisionTester->ObjectTypesToQuery); } } diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_CollisionViewer.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_CollisionViewer.cpp index 45e7e4a..f5954be 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_CollisionViewer.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_CollisionViewer.cpp @@ -2,9 +2,8 @@ #include "CogDebugDrawHelper.h" #include "CogDebug.h" -#include "CogEngineCollisionTester.h" #include "CogImguiHelper.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "Components/PrimitiveComponent.h" #include "Components/SceneComponent.h" #include "DrawDebugHelpers.h" @@ -32,14 +31,6 @@ void FCogEngineWindow_CollisionViewer::RenderHelp() ); } -//-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_CollisionViewer::ResetConfig() -{ - Super::ResetConfig(); - - Config->Reset(); -} - //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_CollisionViewer::RenderContent() { @@ -128,7 +119,7 @@ void FCogEngineWindow_CollisionViewer::RenderContent() { for (int j = 0; j < ECC_MAX; ++j) { - ECollisionResponse Response = Profile->ResponseToChannels.GetResponse((ECollisionChannel)j); + ECollisionResponse Response = Profile->ResponseToChannels.GetResponse(static_cast(j)); if (Response != ECR_Ignore) { Config->ObjectTypesToQuery |= ECC_TO_BITFIELD(j); @@ -141,7 +132,7 @@ void FCogEngineWindow_CollisionViewer::RenderContent() } ImGui::Separator(); - FCogWindowWidgets::CollisionProfileChannels(Config->ObjectTypesToQuery); + FCogWidgets::CollisionObjectTypeChannels(Config->ObjectTypesToQuery); //------------------------------------------------- // Perform Query @@ -190,6 +181,8 @@ void FCogEngineWindow_CollisionViewer::RenderContent() QueryRadius = Config->QueryThickness; break; } + + default: break; } static const FName TraceTag(TEXT("FCogWindow_Collision")); diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_CommandBindings.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_CommandBindings.cpp index b194c7b..240584a 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_CommandBindings.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_CommandBindings.cpp @@ -1,7 +1,7 @@ #include "CogEngineWindow_CommandBindings.h" -#include "CogWindowManager.h" -#include "CogWindowWidgets.h" +#include "CogSubsystem.h" +#include "CogWidgets.h" #include "GameFramework/PlayerController.h" #include "GameFramework/PlayerInput.h" #include "imgui.h" @@ -35,51 +35,44 @@ void FCogEngineWindow_CommandBindings::RenderContent() int32 Index = 0; int32 IndexToRemove = INDEX_NONE; - if (FCogWindowWidgets::ButtonWithTooltip("Add", "Add a new item in the array")) + if (FCogWidgets::ButtonWithTooltip("Add", "Add a new item in the array")) { PlayerInput->DebugExecBindings.AddDefaulted(); PlayerInput->SaveConfig(); } ImGui::SameLine(); - if (FCogWindowWidgets::ButtonWithTooltip("Sort", "Sort the array")) + if (FCogWidgets::ButtonWithTooltip("Sort", "Sort the array")) { - UCogWindowManager::SortCommands(PlayerInput); + UCogSubsystem::SortCommands(PlayerInput); PlayerInput->SaveConfig(); } ImGui::SameLine(); - if (FCogWindowWidgets::ButtonWithTooltip( - "Register Default Commands", - "Register the default commands used to control Cog:\n\n" - "[Tab] Cog.ToggleInput\n" - "[F1] Cog.LoadLayout 1\n" - "[F2] Cog.LoadLayout 2\n" - "[F3] Cog.LoadLayout 3\n" - "[F4] Cog.LoadLayout 4\n" - "[F5] Cog.ToggleSelectionMode\n" + if (FCogWidgets::ButtonWithTooltip( + "Disable Conflicting Commands", + "Disable the existing Unreal command shortcuts mapped to same shortcuts Cog is using. Typically, if the F1 shortcut is used to toggle Inputs, the Unreal wireframe command will get disabled." )) { - GetOwner()->RegisterDefaultCommandBindings(); + //GetOwner()->OnShortcutsDefined(); } for (FKeyBind& KeyBind : PlayerInput->DebugExecBindings) { ImGui::PushID(Index); - if (FCogWindowWidgets::DeleteArrayItemButton()) + if (FCogWidgets::DeleteArrayItemButton()) { IndexToRemove = Index; } ImGui::SameLine(); - if (FCogWindowWidgets::KeyBind(KeyBind)) + if (FCogWidgets::KeyBind(KeyBind)) { PlayerInput->SaveConfig(); } - ImGui::PopID(); Index++; } diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Console.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Console.cpp new file mode 100644 index 0000000..faba536 --- /dev/null +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Console.cpp @@ -0,0 +1,655 @@ +#include "CogEngineWindow_Console.h" + +#include "CogImguiHelper.h" +#include "CogSubsystem.h" +#include "CogWidgets.h" +#include "imgui.h" +#include "imgui_internal.h" + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::RenderHelp() +{ + ImGui::Text("This window can be used as a replacement of the Unreal console."); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::Initialize() +{ + FCogWindow::Initialize(); + + Config = GetConfig(); + + bHasMenu = true; + bHasWidget = true; + bIsWidgetVisible = true; + SelectedCommandIndex = -1; + + RefreshCommandList(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::PreBegin(ImGuiWindowFlags& WindowFlags) +{ + WindowFlags |= ImGuiWindowFlags_NoScrollbar; + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::PostBegin() +{ + ImGui::PopStyleVar(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::RenderContent() +{ + Super::RenderContent(); + + const APlayerController* PlayerController = GetLocalPlayerController(); + if (PlayerController == nullptr) + { + return; + } + + if (ImGui::BeginMenuBar()) + { + RenderMenu(); + ImGui::EndMenuBar(); + } + + ImGui::Spacing(); + + if (Config->DockInputInMenuBar == false) + { + ImGui::SetNextItemWidth(-1); + RenderInput(); + } + + RenderCommandList(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::RenderTick(float DeltaTime) +{ + if (GetOwner()->GetContext().GetEnableInput() == false) + { + WidgetMode_OpenCommandList = false; + } + + if (WidgetMode_OpenCommandList) + { + bIsWidgetMode = true; + + const ImGuiContext& g = *GImGui; + ImGui::SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.90f); + ImGui::SetNextWindowSize(ImVec2(Config->WidgetWidth, ImGui::GetFontSize() * 30), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(WidgetMode_CommandListPosition, ImGuiCond_Always); + + ImGuiWindowFlags Flags = + ImGuiWindowFlags_NoTitleBar + | ImGuiWindowFlags_NoMove + | ImGuiWindowFlags_NoFocusOnAppearing; // We want the console input text to keep the focus. + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + const bool IsCommandListWindowVisible = ImGui::Begin("ConsoleCommandList", nullptr, Flags); + ImGui::PopStyleVar(); + + if (IsCommandListWindowVisible) + { + ImGui::Spacing(); + RenderCommandList(); + + if (ImGui::BeginPopupContextWindow("ConsoleCommandListPopup")) + { + RenderMenu(); + ImGui::EndPopup(); + } + + const bool IsWindowFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); + if (IsWindowFocused) + { + if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) + { + SelectNextCommand(); + ActivateInputText(); + } + else if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) + { + SelectPreviousCommand(); + ActivateInputText(); + } + else if (ImGui::IsKeyPressed(ImGuiKey_Tab)) + { + SelectNextCommand(); + ActivateInputText(); + } + else if (ImGui::IsKeyPressed(ImGuiKey_Escape)) + { + WidgetMode_OpenCommandList = false; + } + } + + if (IsWindowFocused == false && WidgetMode_IsTextInputActive == false) + { + WidgetMode_OpenCommandList = false; + } + } + ImGui::End(); + + bIsWidgetMode = false; + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::RenderMainMenuWidget() +{ + bIsWidgetMode = true; + + const ImGuiWindow* Window = ImGui::GetCurrentWindow(); + WidgetMode_CommandListPosition = Window->DC.CursorPos; + WidgetMode_CommandListPosition.y += Window->MenuBarHeight; + + ImGui::SetNextItemWidth(Config->WidgetWidth); + + RenderInput(); + WidgetMode_IsTextInputActive = ImGui::IsItemActive(); + + if (Config->FocusWidgetWhenAppearing && ImGui::IsWindowAppearing()) + { + SelectedCommandIndex = -1; + RefreshCommandList(); + ActivateInputText(); + } + + if (ImGui::BeginPopupContextItem()) + { + RenderMenu(); + ImGui::EndPopup(); + } + + bIsWidgetMode = false; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::RenderMenu() +{ + if (ImGui::BeginMenu("Options")) + { + FCogWidgets::ThinSeparatorText("General"); + + ImGui::Checkbox("Show Help", &Config->ShowHelp); + + if (ImGui::Checkbox("Sort Commands", &Config->SortCommands)) + { + RefreshCommandList(); + } + + // if (ImGui::Checkbox("Use Clipper", &Config->UseClipper)) + // { + // RefreshCommandList(); + // } + + FCogWidgets::SetNextItemToShortWidth(); + if (ImGui::SliderInt("Completion Minimum Characters", &Config->CompletionMinimumCharacters, 0, 3)) + { + RefreshCommandList(); + } + + FCogWidgets::SetNextItemToShortWidth(); + if (ImGui::SliderInt("Num History Commands", &Config->NumHistoryCommands, 0, 100)) + { + RefreshCommandList(); + } + + ImGui::ColorEdit4("History Color", &Config->HistoryColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + + FCogWidgets::ThinSeparatorText("Window"); + + if (ImGui::Checkbox("Dock Input in Menu Bar", &Config->DockInputInMenuBar)) + { + RefreshCommandList(); + } + + FCogWidgets::ThinSeparatorText("Widget"); + + if (ImGui::Checkbox("Focus Console Widget When Appearing", &Config->FocusWidgetWhenAppearing)) + { + RefreshCommandList(); + } + + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderInt("Widget Width", &Config->WidgetWidth, 0, 1000); + + ImGui::EndMenu(); + } + + if (bIsWidgetMode == false && Config->DockInputInMenuBar) + { + ImGui::SetNextItemWidth(-1); + RenderInput(); + } + + //ImGui::Text("%d", SelectedCommandIndex); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::RenderInput() +{ + constexpr ImGuiInputTextFlags InputFlags = + ImGuiInputTextFlags_EnterReturnsTrue + | ImGuiInputTextFlags_EscapeClearsAll + | ImGuiInputTextFlags_CallbackCompletion + | ImGuiInputTextFlags_CallbackHistory + | ImGuiInputTextFlags_CallbackEdit + | ImGuiInputTextFlags_CallbackAlways; + + const bool IsEnterPressed = FCogWidgets::InputTextWithHint("##Command", "Command", CurrentUserInput, InputFlags, &OnTextInputCallbackStub, this); + InputTextId = ImGui::GetItemID(); + + if (IsEnterPressed) + { + ExecuteCommand(CurrentUserInput); + ActivateInputText(); + } + + ImGui::SetItemDefaultFocus(); + + //------------------------------------------------------------------------------------------------- + // In Widget mode, do not want to show the command list as soon as the input text has focus, + // but wait for the user to click on the input text (or interact with it). This is because + // we want the text input to always have focus so the user can directly type text if he wants to, + // but if he doesn't the command list should not clutter the screen. + //------------------------------------------------------------------------------------------------- + if (bIsWidgetMode && ImGui::IsItemActive() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + { + WidgetMode_OpenCommandList = true; + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::SelectNextCommand() +{ + SelectedCommandIndex += 1; + bScroll = true; + + if (SelectedCommandIndex >= CommandList.Num()) + { + SelectedCommandIndex = 0; + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::SelectPreviousCommand() +{ + SelectedCommandIndex -= 1; + bScroll = true; + + if (SelectedCommandIndex < 0) + { + SelectedCommandIndex = CommandList.Num() - 1; + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +int FCogEngineWindow_Console::OnTextInputCallback(ImGuiInputTextCallbackData* InData) +{ + bool DoCompletion = false; + if (InData->EventFlag == ImGuiInputTextFlags_CallbackHistory) + { + if (InData->EventKey == ImGuiKey_UpArrow) + { + SelectPreviousCommand(); + } + else if (InData->EventKey == ImGuiKey_DownArrow) + { + SelectNextCommand(); + } + + DoCompletion = true; + } + else if (InData->EventFlag == ImGuiInputTextFlags_CallbackCompletion) + { + SelectNextCommand(); + DoCompletion = true; + } + else if (InData->EventFlag == ImGuiInputTextFlags_CallbackEdit) + { + CurrentUserInput = FString(InData->Buf); + RefreshCommandList(); + + if (bIsWidgetMode) + { + WidgetMode_OpenCommandList = true; + } + } + else if (InData->EventFlag == ImGuiInputTextFlags_CallbackAlways) + { + if (bSetBufferToSelectedCommand) + { + DoCompletion = true; + bSetBufferToSelectedCommand = false; + } + + if (ImGui::IsKeyPressed(ImGuiKey_Tab) && ImGui::IsKeyDown(ImGuiKey_ReservedForModShift)) + { + SelectPreviousCommand(); + DoCompletion = true; + } + } + + if (DoCompletion && CommandList.IsValidIndex(SelectedCommandIndex)) + { + const FString SelectedCommand = CommandList[SelectedCommandIndex]; + const FString CleanupSelectedCommand = SelectedCommand.TrimEnd(); + const auto& CommandStr = StringCast(*CleanupSelectedCommand); + InData->DeleteChars(0, InData->BufTextLen); + InData->InsertChars(0, CommandStr.Get()); + InData->InsertChars(InData->CursorPos, " "); + + if (bIsWidgetMode) + { + WidgetMode_OpenCommandList = true; + } + } + + return 0; +} + +//-------------------------------------------------------------------------------------------------------------------------- +int FCogEngineWindow_Console::OnTextInputCallbackStub(ImGuiInputTextCallbackData* InData) +{ + FCogEngineWindow_Console& ConsoleWindow = *static_cast(InData->UserData); + return ConsoleWindow.OnTextInputCallback(InData); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::RenderCommandList() +{ + const float HelpHeight = Config->ShowHelp ? ImGui::GetFontSize() * 5 : 0.0f; + const float Indent = ImGui::GetFontSize() * 0.5f; + const ImVec2 Size = IsWindowRenderedInMainMenu() ? ImVec2(0, ImGui::GetFontSize() * 20) : ImVec2(0.0f, ImGui::GetContentRegionAvail().y - HelpHeight); + + if (ImGui::BeginChild("Commands", Size, ImGuiChildFlags_None)) + { + ImGui::Indent(Indent); + + //-------------------------------------------------------------------- + // Gather the child window region min max so we can check if the + // selected command is clipped to know if we should scroll. + //-------------------------------------------------------------------- + const float RegionMinY = ImGui::GetItemRectMin().y; + const float RegionMaxY = RegionMinY + ImGui::GetContentRegionAvail().y; + + //-------------------------------------------------------------------- + // Reset the scroll when the command list reappear, otherwise we + // keep the previous scroll which can be confusing. + //-------------------------------------------------------------------- + if (ImGui::IsWindowAppearing()) + { + ImGui::SetScrollHereY(0.0f); + SelectedCommandIndex = -1; + } + + int32 Index = 0; + + //-------------------------------------------------------------------- + // TODO: The Clipper is currently not working correctly + //-------------------------------------------------------------------- + ImGuiListClipper Clipper; + Clipper.Begin(CommandList.Num()); + while (Clipper.Step()) + { + const int32 Start = Config->UseClipper ? Clipper.DisplayStart : 0; + const int32 End = Config->UseClipper ? Clipper.DisplayEnd : CommandList.Num(); + + for (Index = Start; Index < End; Index++) + { + if (CommandList.IsValidIndex(Index)) + { + ImGui::PushID(Index); + const FString& CommandName = CommandList[Index]; + RenderCommand(CommandName, Index, RegionMinY, RegionMaxY); + ImGui::PopID(); + } + } + + if (Config->UseClipper == false) + { break; } + } + Clipper.End(); + + //-------------------------------------------------------------------- + // If any is available, draw an additional command below the clipper + // to be able to scroll when pressing bottom + //-------------------------------------------------------------------- + if (CommandList.IsValidIndex(Index + 1)) + { + const FString& Command = CommandList[Index + 1]; + RenderCommand(Command, Index, RegionMinY, RegionMaxY); + } + + ImGui::Unindent(Indent); + } + ImGui::EndChild(); + + //-------------------------------------------------------------------- + // Render Help + //-------------------------------------------------------------------- + if (Config->ShowHelp) + { + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.2f, 0.2f, 0.2f, 0.5f)); + if (ImGui::BeginChild("Help", ImVec2(0.0f, ImGui::GetContentRegionAvail().y))) + { + ImGui::Spacing(); + ImGui::BeginDisabled(); + ImGui::Indent(Indent); + + if (CommandList.IsValidIndex(SelectedCommandIndex)) + { + const FString SelectedCommand = CommandList[SelectedCommandIndex]; + const FString Help = GetConsoleCommandHelp(SelectedCommand); + const auto& HelpStr = StringCast(*Help); + ImGui::TextWrapped(HelpStr.Get()); + } + + ImGui::Unindent(Indent); + ImGui::EndDisabled(); + } + ImGui::EndChild(); + ImGui::PopStyleColor(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- + IConsoleObject* FCogEngineWindow_Console::GetCommandObjectFromCommandLine(const FString& InCommandLine) +{ + if (InCommandLine.IsEmpty()) + { return nullptr; } + + TArray CommandSplitWithSpaces; + InCommandLine.ParseIntoArrayWS(CommandSplitWithSpaces); + + if (CommandSplitWithSpaces.Num() == 0) + { return nullptr; } + + return IConsoleManager::Get().FindConsoleObject(*CommandSplitWithSpaces[0]); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FString FCogEngineWindow_Console::GetConsoleCommandHelp(const FString& InCommandLine) +{ + if (IConsoleObject* ConsoleObject = GetCommandObjectFromCommandLine(InCommandLine)) + { + return ConsoleObject->GetHelp(); + } + + return FString("Unknown command."); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::RenderCommand(const FString& CommandName, const int32 Index, float RegionMinY, float RegionMaxY) +{ + const auto& CommandNameStr = StringCast(*CommandName); + + bool IsSelected = Index == SelectedCommandIndex; + + // ImGui::Text("%d - ", Index); + // ImGui::SameLine(); + + if (Index < NumHistoryCommands) + { + ImGui::PushStyleColor(ImGuiCol_Text, FCogImguiHelper::ToImVec4(Config->HistoryColor)); + } + + ImGuiSelectableFlags Flags = + ImGuiSelectableFlags_AllowDoubleClick // Double click executes the selected command + | ImGuiSelectableFlags_SelectOnClick; // Need to focus the console text input right away, otherwise the Selectable take back the focus on mouse release + + const bool Pressed = ImGui::Selectable(CommandNameStr.Get(), &IsSelected, Flags); + const int32 IsClippedTop = ImGui::GetItemRectMin().y < RegionMinY; + const int32 IsClippedBottom = ImGui::GetItemRectMax().y > RegionMaxY; + + if (Pressed) + { + SelectedCommandIndex = Index; + + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + { + ExecuteCommand(CommandName); + } + else + { + bSetBufferToSelectedCommand = true; + } + + ActivateInputText(); + } + + if (ImGui::BeginItemTooltip()) + { + const FString Help = GetConsoleCommandHelp(CommandName); + const auto& HelpStr = StringCast(*Help); + + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(HelpStr.Get()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + if (Index < NumHistoryCommands) + { + ImGui::PopStyleColor(); + } + + if (NumHistoryCommands > 0 && Index == NumHistoryCommands - 1) + { + ImGui::Separator(); + } + + if (IsSelected && bScroll) + { + if (IsClippedBottom) + { + ImGui::SetScrollHereY(1.0f); + } + + if (IsClippedTop) + { + ImGui::SetScrollHereY(0.0f); + } + + bScroll = false; + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::RefreshCommandList() +{ + FString CurrentUserInputWithoutArgs = CurrentUserInput; + + //------------------------------------------------------------------------------------------------------ + // Split the user input with spaces tp get the first part of the command so the completion is made on + // "Cog.Cheat" instead of "Cog.Cheat God", as the later would return no results + //------------------------------------------------------------------------------------------------------ + TArray UserInputSplitWithSpaces; + CurrentUserInput.ParseIntoArrayWS(UserInputSplitWithSpaces); + if (UserInputSplitWithSpaces.Num() > 0) + { + CurrentUserInputWithoutArgs = UserInputSplitWithSpaces[0]; + } + + CommandList.Empty(); + + TArray AllHistory; + IConsoleManager::Get().GetConsoleHistory(TEXT(""), AllHistory); + + NumHistoryCommands = 0; + for (int32 i = AllHistory.Num() - 1; i >= 0; i--) + { + FString Command = AllHistory[i]; + if (Command.IsEmpty()) + { continue; } + + if (CurrentUserInput.IsEmpty() == false && Command.Contains(CurrentUserInput) == false) + { continue; } + + if (CommandList.Num() >= Config->NumHistoryCommands) + { break; } + + CommandList.Add(Command); + NumHistoryCommands++; + } + + TArray Commands; + if (CurrentUserInputWithoutArgs.Len() >= Config->CompletionMinimumCharacters) + { + auto OnConsoleObject = [&](const TCHAR *InName, const IConsoleObject* InConsoleObject) + { + if (InConsoleObject->TestFlags(ECVF_Unregistered) || InConsoleObject->TestFlags(ECVF_ReadOnly)) + { return; } + + Commands.Add(InName); + }; + + IConsoleManager::Get().ForEachConsoleObjectThatContains(FConsoleObjectVisitor::CreateLambda(OnConsoleObject), *CurrentUserInputWithoutArgs); + } + + if (Config->SortCommands) + { + Commands.Sort(); + } + + CommandList.Append(Commands); + + //------------------------------------------------------------------------------------- + // Reset to -1 so the next down arrow will select the first entry in history/command + //------------------------------------------------------------------------------------- + SelectedCommandIndex = -1; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::ActivateInputText() const +{ + return ImGui::ActivateItemByID(InputTextId); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::ExecuteCommand(const FString& InCommand) +{ + const FString CleanupCommand = InCommand.TrimEnd(); + if (CleanupCommand.IsEmpty() == false) + { + IConsoleManager::Get().AddConsoleHistoryEntry(TEXT(""), *CleanupCommand); + GEngine->DeferredCommands.Add(CleanupCommand); + } + + if (bIsWidgetMode) + { + WidgetMode_OpenCommandList = false; + } + + CurrentUserInput = FString(); + RefreshCommandList(); +} + + diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_DebugSettings.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_DebugSettings.cpp index d612c10..4b4231f 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_DebugSettings.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_DebugSettings.cpp @@ -2,7 +2,7 @@ #include "CogDebug.h" #include "CogImguiHelper.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "Engine/CollisionProfile.h" //-------------------------------------------------------------------------------------------------------------------------- @@ -27,25 +27,23 @@ void FCogEngineWindow_DebugSettings::Initialize() FCogDebug::SetIsFilteringBySelection(GetWorld(), Config->Data.bIsFilteringBySelection); } -//-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_DebugSettings::ResetConfig() -{ - Super::ResetConfig(); - - Config->Reset(); -} - //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_DebugSettings::PreSaveConfig() { Super::PreSaveConfig(); + if (Config == nullptr) + { return; } + Config->Data = FCogDebug::Settings; } //-------------------------------------------------------------------------------------------------------------------------- void RenderCollisionChannelColor(const UCollisionProfile& CollisionProfile, FColor& Color, ECollisionChannel Channel, ImGuiColorEditFlags ColorEditFlags) { + if (CollisionProfile.ConvertToObjectType(Channel) == TraceTypeQuery_MAX && CollisionProfile.ConvertToTraceType(Channel) == TraceTypeQuery_MAX) + { return; } + const FString ChannelName = CollisionProfile.ReturnChannelNameFromContainerIndex(Channel).ToString(); FCogImguiHelper::ColorEdit4(StringCast(*ChannelName).Get(), Color, ColorEditFlags); } @@ -91,53 +89,53 @@ void FCogEngineWindow_DebugSettings::RenderContent() ImGui::Checkbox("Text Shadow", &Settings.TextShadow); ImGui::SetItemTooltip("Show a shadow below debug text."); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::Checkbox("Fade 2D", &Settings.Fade2D); ImGui::SetItemTooltip("Does the 2D debug is fading out."); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Duration", &Settings.Duration, 0.01f, 0.0f, 100.0f, "%.1f"); ImGui::SetItemTooltip("The duration of debug elements."); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Thickness", &Settings.Thickness, 0.05f, 0.0f, 5.0f, "%.1f"); ImGui::SetItemTooltip("The thickness of debug lines."); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Server Thickness", &Settings.ServerThickness, 0.05f, 0.0f, 5.0f, "%.1f"); ImGui::SetItemTooltip("The thickness the server debug lines."); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Server Color Mult", &Settings.ServerColorMultiplier, 0.01f, 0.0f, 1.0f, "%.1f"); ImGui::SetItemTooltip("The color multiplier applied to the server debug lines."); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragInt("Depth Priority", &Settings.DepthPriority, 0.1f, 0, 100); ImGui::SetItemTooltip("The depth priority of debug elements."); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragInt("Segments", &Settings.Segments, 0.1f, 4, 20.0f); ImGui::SetItemTooltip("The number of segments used for circular shapes."); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Axes Scale", &Settings.AxesScale, 0.1f, 0, 10.0f, "%.1f"); ImGui::SetItemTooltip("The scaling debug axis."); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Arrow Size", &Settings.ArrowSize, 1.0f, 0.0f, 200.0f, "%.0f"); ImGui::SetItemTooltip("The size of debug arrows."); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Text Size", &Settings.TextSize, 0.1f, 0.1f, 5.0f, "%.1f"); ImGui::SetItemTooltip("The size of the debug texts."); } if (ImGui::CollapsingHeader("Recolor", ImGuiTreeNodeFlags_DefaultOpen)) { - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ECogDebugRecolorMode Mode = Settings.RecolorMode; - if (FCogWindowWidgets::ComboboxEnum("Recolor mode", Mode)) + if (FCogWidgets::ComboboxEnum("Recolor mode", Mode)) { Settings.RecolorMode = Mode; } @@ -145,7 +143,7 @@ void FCogEngineWindow_DebugSettings::RenderContent() if (Settings.RecolorMode != ECogDebugRecolorMode::None) { - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Recolor Intensity", &Settings.RecolorIntensity, 0.01f, 0.0f, 1.0f, "%.2f"); ImGui::SetItemTooltip("How much the debug elements color should be changed."); } @@ -156,13 +154,13 @@ void FCogEngineWindow_DebugSettings::RenderContent() } else if (Settings.RecolorMode == ECogDebugRecolorMode::HueOverTime) { - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Recolor Speed", &Settings.RecolorTimeSpeed, 0.1f, 0.0f, 10.0f, "%.1f"); ImGui::SetItemTooltip("The speed of the recolor."); } else if (Settings.RecolorMode == ECogDebugRecolorMode::HueOverFrames) { - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragInt("Recolor Cycle", &Settings.RecolorFrameCycle, 1, 2, 100); ImGui::SetItemTooltip("How many frames are used to perform a full hue cycle."); } @@ -174,92 +172,95 @@ void FCogEngineWindow_DebugSettings::RenderContent() ImGui::Checkbox("Use Local Space", &Settings.GizmoUseLocalSpace); - FCogWindowWidgets::SetNextItemToShortWidth(); - ImGui::DragFloat("Gizmo Scale", &Settings.GizmoScale, 0.1f, 0.1f, 10.0f, "%.1f"); + ImGui::Checkbox("Support Context Menu", &Settings.GizmoSupportContextMenu); + ImGui::SetItemTooltip("Does right clicking on the gizmo displays a context menu ?"); + + FCogWidgets::SetNextItemToShortWidth(); + ImGui::DragFloat("Scale", &Settings.GizmoScale, 0.1f, 0.1f, 10.0f, "%.1f"); ImGui::SetItemTooltip("The scale of the gizmo."); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragInt("Z Low", &Settings.GizmoZLow, 0.5f, 0, 1000); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragInt("Z High", &Settings.GizmoZHigh, 0.5f, 0, 1000); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Thickness Z Low", &Settings.GizmoThicknessZLow, 0.1f, 0.0f, 10.0f, "%.1f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Thickness Z High", &Settings.GizmoThicknessZHigh, 0.1f, 0.0f, 10.0f, "%.1f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Mouse Max Distance", &Settings.GizmoCursorSelectionThreshold, 0.1f, 0.0f, 50.0f, "%.1f"); ImGui::SeparatorText("Translation"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::Checkbox("Translation Snap Enable", &Settings.GizmoTranslationSnapEnable); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Translation Snap", &Settings.GizmoTranslationSnapValue, 0.1f, 0.0f, 1000.0f, "%.1f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Translation Axis Length", &Settings.GizmoTranslationAxisLength, 0.1f, 0.1f, 500.0f, "%.1f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Translation Plane Offset", &Settings.GizmoTranslationPlaneOffset, 0.1f, 0.0f, 500.0f, "%.1f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Translation Plane Extent", &Settings.GizmoTranslationPlaneExtent, 0.1f, 0.0f, 100.0f, "%.1f"); ImGui::SeparatorText("Rotation"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::Checkbox("Rotation Snap Enable", &Settings.GizmoRotationSnapEnable); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Rotation Snap", &Settings.GizmoRotationSnapValue, 0.1f, 0.0f, 360.0f, "%.1f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Rotation Speed", &Settings.GizmoRotationSpeed, 0.01f, 0.01f, 100.0f, "%.2f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Rotation Radius", &Settings.GizmoRotationRadius, 0.1f, 0.1f, 500.0f, "%.1f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragInt("Rotation Segments", &Settings.GizmoRotationSegments, 0.5f, 2, 12); ImGui::SeparatorText("Scale"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::Checkbox("Scale Snap Enable", &Settings.GizmoScaleSnapEnable); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Scale Snap", &Settings.GizmoScaleSnapValue, 0.1f, 0.0f, 10.0f, "%.1f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Scale Box Offset", &Settings.GizmoScaleBoxOffset, 0.0f, 0.0f, 500.0f, "%.1f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Scale Box Extent", &Settings.GizmoScaleBoxExtent, 0.1f, 0.0f, 100.0f, "%.1f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Scale Speed", &Settings.GizmoScaleSpeed, 0.01f, 0.01f, 100.0f, "%.2f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Scale Min", &Settings.GizmoScaleMin, 0.001f, 0.001f, 1.0f, "%.3f"); ImGui::SeparatorText("Ground Raycast"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Ground Raycast Length", &Settings.GizmoGroundRaycastLength, 10.0f, 0.0f, 1000000.0f, "%.0f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ECollisionChannel Channel = Settings.GizmoGroundRaycastChannel.GetValue(); - if (FCogWindowWidgets::ComboCollisionChannel("Channel", Channel)) + if (FCogWidgets::ComboTraceChannel("Channel", Channel)) { Settings.GizmoGroundRaycastChannel = Channel; } - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Ground Raycast Circle Radius", &Settings.GizmoGroundRaycastCircleRadius, 0.1f, 0.1f, 1000.0f, "%.1f"); FCogImguiHelper::ColorEdit4("Ground Raycast Color", Settings.GizmoGroundRaycastColor, ColorEditFlags); @@ -297,12 +298,7 @@ void FCogEngineWindow_DebugSettings::RenderContent() RenderCollisionChannelColor(*CollisionProfile, Settings.ChannelColorPhysicsBody, ECC_PhysicsBody, ColorEditFlags); RenderCollisionChannelColor(*CollisionProfile, Settings.ChannelColorVehicle, ECC_Vehicle, ColorEditFlags); RenderCollisionChannelColor(*CollisionProfile, Settings.ChannelColorDestructible, ECC_Destructible, ColorEditFlags); - RenderCollisionChannelColor(*CollisionProfile, Settings.ChannelColorEngineTraceChannel1, ECC_EngineTraceChannel1, ColorEditFlags); - RenderCollisionChannelColor(*CollisionProfile, Settings.ChannelColorEngineTraceChannel2, ECC_EngineTraceChannel2, ColorEditFlags); - RenderCollisionChannelColor(*CollisionProfile, Settings.ChannelColorEngineTraceChannel3, ECC_EngineTraceChannel3, ColorEditFlags); - RenderCollisionChannelColor(*CollisionProfile, Settings.ChannelColorEngineTraceChannel4, ECC_EngineTraceChannel4, ColorEditFlags); - RenderCollisionChannelColor(*CollisionProfile, Settings.ChannelColorEngineTraceChannel5, ECC_EngineTraceChannel5, ColorEditFlags); - RenderCollisionChannelColor(*CollisionProfile, Settings.ChannelColorEngineTraceChannel6, ECC_EngineTraceChannel6, ColorEditFlags); + RenderCollisionChannelColor(*CollisionProfile, Settings.ChannelColorGameTraceChannel1, ECC_GameTraceChannel1, ColorEditFlags); RenderCollisionChannelColor(*CollisionProfile, Settings.ChannelColorGameTraceChannel2, ECC_GameTraceChannel2, ColorEditFlags); RenderCollisionChannelColor(*CollisionProfile, Settings.ChannelColorGameTraceChannel3, ECC_GameTraceChannel3, ColorEditFlags); @@ -350,11 +346,11 @@ void FCogEngineWindow_DebugSettings::RenderContent() ImGui::Checkbox("Draw Hit Impact Normals", &Settings.CollisionQueryDrawHitImpactNormals); ImGui::SetItemTooltip("Draw the hit impact normal of hit results."); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Primitive Actors Name Size", &Settings.CollisionQueryHitPrimitiveActorsNameSize, 0.1f, 0.5f, 10.0f, "%0.1f"); ImGui::SetItemTooltip("Size of the actor name of the primitives that have been hit."); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Hit Point Size", &Settings.CollisionQueryHitPointSize, 0.5f, 0.0f, 100.0f, "%0.1f"); ImGui::SetItemTooltip("Size of the hit result location and impact point."); diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Inspector.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Inspector.cpp index 1faacd0..6f1c3ae 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Inspector.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Inspector.cpp @@ -1,6 +1,6 @@ #include "CogEngineWindow_Inspector.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "Containers/SortedMap.h" #include "Engine/Engine.h" #include "imgui_internal.h" @@ -57,14 +57,14 @@ void FCogEngineWindow_Inspector::SetInspectedObject(UObject* Value) //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Inspector::AddFavorite(UObject* Object) { - Favorite& Favorite = Favorites.AddDefaulted_GetRef(); + FFavorite& Favorite = Favorites.AddDefaulted_GetRef(); Favorite.Object = Object; } //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Inspector::AddFavorite(UObject* Object, FCogEngineInspectorApplyFunction ApplyFunction) { - Favorite& Favorite = Favorites.AddDefaulted_GetRef(); + FFavorite& Favorite = Favorites.AddDefaulted_GetRef(); Favorite.Object = Object; Favorite.ApplyFunction = ApplyFunction; } @@ -110,7 +110,7 @@ void FCogEngineWindow_Inspector::RenderContent() //-------------------------------------------------------------------------------------------------------------------------- FCogEngineInspectorApplyFunction FCogEngineWindow_Inspector::FindObjectApplyFunction(const UObject* Object) const { - for (const Favorite& Favorite : Favorites) + for (const FFavorite& Favorite : Favorites) { if (Favorite.Object == Object) { @@ -166,23 +166,18 @@ void FCogEngineWindow_Inspector::RenderMenu() ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f)); ImGui::SameLine(); - if (ImGui::Button(InspectedObjectName.Get(), ImVec2(FCogWindowWidgets::GetFontWidth() * 20, 0))) + if (ImGui::Button(InspectedObjectName.Get(), ImVec2(FCogWidgets::GetFontWidth() * 20, 0))) { ImGui::OpenPopup("SelectionPopup"); } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Current Inspected Object: %s", InspectedObjectName.Get()); + ImGui::SetTooltip("%s", InspectedObjectName.Get()); } ImGui::PopStyleVar(1); } - if (ImGui::IsItemHovered()) - { - ImGui::SetTooltip("%s", InspectedObjectName.Get()); - } - ImGui::PopStyleColor(1); ImGui::PopStyleVar(1); @@ -192,7 +187,7 @@ void FCogEngineWindow_Inspector::RenderMenu() ImGui::SetNextWindowPos(Pos + ImVec2(0, ImGui::GetFrameHeight())); if (ImGui::BeginPopup("SelectionPopup")) { - ImGui::BeginChild("Popup", ImVec2(FCogWindowWidgets::GetFontWidth() * 30, FCogWindowWidgets::GetFontWidth() * 40), false); + ImGui::BeginChild("Popup", ImVec2(FCogWidgets::GetFontWidth() * 30, FCogWidgets::GetFontWidth() * 40), false); //----------------------------------- // FAVORITES @@ -206,7 +201,7 @@ void FCogEngineWindow_Inspector::RenderMenu() } ImGui::PushID("Favorites"); - for (Favorite& Favorite : Favorites) + for (FFavorite& Favorite : Favorites) { const TWeakObjectPtr& Object = Favorite.Object; if (ImGui::MenuItem(TCHAR_TO_ANSI(*GetNameSafe(Object.Get())))) @@ -256,7 +251,7 @@ void FCogEngineWindow_Inspector::RenderMenu() //----------------------------------- // Search //----------------------------------- - FCogWindowWidgets::SearchBar(Filter, -FCogWindowWidgets::GetFontWidth() * 9); + FCogWidgets::SearchBar("##Filter", Filter, -FCogWidgets::GetFontWidth() * 9); //----------------------------------- // Options @@ -273,7 +268,7 @@ void FCogEngineWindow_Inspector::RenderMenu() ImGui::Checkbox("Sort by Name", &Config->bSortByName); ImGui::Checkbox("Show Background", &Config->bShowRowBackground); - ImGui::Checkbox("Show Sorders", &Config->bShowBorders); + ImGui::Checkbox("Show Borders", &Config->bShowBorders); #if WITH_EDITORONLY_DATA ImGui::Checkbox("Show Display Name", &Config->bShowDisplayName); ImGui::Checkbox("Show Categories", &Config->bShowCategories); @@ -349,7 +344,7 @@ bool FCogEngineWindow_Inspector::RenderInspector() ImGui::SetNextItemOpen(false); } - if (ImGui::CollapsingHeader(TCHAR_TO_ANSI(*Entry.Key), nullptr, ImGuiTreeNodeFlags_DefaultOpen)) + if (ImGui::CollapsingHeader(TCHAR_TO_UTF8(*Entry.Key), nullptr, ImGuiTreeNodeFlags_DefaultOpen)) { if (RenderBegin()) { @@ -381,7 +376,7 @@ bool FCogEngineWindow_Inspector::RenderInspector() //-------------------------------------------------------------------------------------------------------------------------- bool FCogEngineWindow_Inspector::RenderBegin() { - FCogWindowWidgets::PushStyleCompact(); + FCogWidgets::PushStyleCompact(); ImGuiTableFlags TableFlags = ImGuiTableFlags_Resizable; if ((Config->bShowBorders) != 0) @@ -412,7 +407,7 @@ bool FCogEngineWindow_Inspector::RenderBegin() void FCogEngineWindow_Inspector::RenderEnd() { ImGui::EndTable(); - FCogWindowWidgets::PopStyleCompact(); + FCogWidgets::PopStyleCompact(); } //-------------------------------------------------------------------------------------------------------------------------- @@ -456,7 +451,7 @@ bool FCogEngineWindow_Inspector::RenderPropertyList(TArray& Pr } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogEngineWindow_Inspector::RenderProperty(const FProperty* Property, uint8* PointerToValue, int IndexInArray) +bool FCogEngineWindow_Inspector::RenderProperty(const FProperty* Property, uint8* PointerToValue, int IndexInArray, const char* NameSuffix) { bool HasChanged = false; @@ -473,6 +468,11 @@ bool FCogEngineWindow_Inspector::RenderProperty(const FProperty* Property, uint8 if (IndexInArray != -1) { PropertyName = FString::Printf(TEXT("[%d]"), IndexInArray); + if (NameSuffix != nullptr) + { + PropertyName.Append(" "); + PropertyName.Append(NameSuffix); + } } else { @@ -510,7 +510,7 @@ bool FCogEngineWindow_Inspector::RenderProperty(const FProperty* Property, uint8 ImGui::TableNextColumn(); ImGui::Text("DisplayName:"); ImGui::TableNextColumn(); - ImGui::Text(TCHAR_TO_ANSI(*Property->GetDisplayNameText().ToString())); + ImGui::Text(TCHAR_TO_UTF8(*Property->GetDisplayNameText().ToString())); #endif // WITH_EDITORONLY_DATA ImGui::TableNextRow(); @@ -532,7 +532,7 @@ bool FCogEngineWindow_Inspector::RenderProperty(const FProperty* Property, uint8 ImGui::TableNextColumn(); if (Property->HasMetaData("Tooltip")) { - ImGui::Text(TCHAR_TO_ANSI(*Property->GetToolTipText(false).ToString())); + ImGui::Text(TCHAR_TO_UTF8(*Property->GetToolTipText(false).ToString())); } #endif // WITH_EDITORONLY_DATA @@ -550,7 +550,7 @@ bool FCogEngineWindow_Inspector::RenderProperty(const FProperty* Property, uint8 ImGui::BeginTooltip(); ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::Text(TCHAR_TO_ANSI(*Property->GetToolTipText(false).ToString())); + ImGui::Text(TCHAR_TO_UTF8(*Property->GetToolTipText(false).ToString())); ImGui::Text("Details [CTRL]"); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); @@ -635,6 +635,14 @@ bool FCogEngineWindow_Inspector::RenderProperty(const FProperty* Property, uint8 { HasChanged = RenderArray(ArrayProperty, PointerToValue, ShowChildren); } + else if (const FSetProperty* SetProperty = CastField(Property)) + { + HasChanged = RenderSet(SetProperty, PointerToValue, ShowChildren); + } + else if (const FMapProperty* MapProperty = CastField(Property)) + { + HasChanged = RenderMap(MapProperty, PointerToValue, ShowChildren); + } else if (const FDelegateProperty* DelegateProperty = CastField(Property)) { } @@ -680,7 +688,7 @@ bool FCogEngineWindow_Inspector::RenderByte(const FByteProperty* ByteProperty, u if (ImGui::InputInt("##Byte", &Value)) { HasChanged = true; - ByteProperty->SetPropertyValue(PointerToValue, (uint8)Value); + ByteProperty->SetPropertyValue(PointerToValue, static_cast(Value)); } return HasChanged; @@ -695,7 +703,7 @@ bool FCogEngineWindow_Inspector::RenderInt8(const FInt8Property* Int8Property, u if (ImGui::InputInt("##Int8", &Value)) { HasChanged = true; - Int8Property->SetPropertyValue(PointerToValue, (int8)Value); + Int8Property->SetPropertyValue(PointerToValue, static_cast(Value)); } return HasChanged; @@ -721,11 +729,11 @@ bool FCogEngineWindow_Inspector::RenderInt64(const FInt64Property* Int64Property { bool HasChanged = false; - int Value = (int)Int64Property->GetPropertyValue(PointerToValue); + int Value = static_cast(Int64Property->GetPropertyValue(PointerToValue)); if (ImGui::InputInt("##UInt64", &Value)) { HasChanged = true; - Int64Property->SetPropertyValue(PointerToValue, (uint64)Value); + Int64Property->SetPropertyValue(PointerToValue, static_cast(Value)); } return HasChanged; @@ -736,11 +744,11 @@ bool FCogEngineWindow_Inspector::RenderUInt32(const FUInt32Property* UInt32Prope { bool HasChanged = false; - int Value = (int)UInt32Property->GetPropertyValue(PointerToValue); + int Value = static_cast(UInt32Property->GetPropertyValue(PointerToValue)); if (ImGui::InputInt("##UInt32", &Value)) { HasChanged = true; - UInt32Property->SetPropertyValue(PointerToValue, (uint32)Value); + UInt32Property->SetPropertyValue(PointerToValue, static_cast(Value)); } return HasChanged; @@ -779,7 +787,7 @@ bool FCogEngineWindow_Inspector::RenderDouble(const FDoubleProperty* DoublePrope //-------------------------------------------------------------------------------------------------------------------------- bool FCogEngineWindow_Inspector::RenderEnum(const FEnumProperty* EnumProperty, uint8* PointerToValue) { - return FCogWindowWidgets::ComboboxEnum("##Enum", EnumProperty, PointerToValue); + return FCogWidgets::ComboboxEnum("##Enum", EnumProperty, PointerToValue); } //-------------------------------------------------------------------------------------------------------------------------- @@ -819,7 +827,7 @@ bool FCogEngineWindow_Inspector::RenderText(const FTextProperty* TextProperty, u FString Text; TextProperty->ExportTextItem_Direct(Text, PointerToValue, nullptr, nullptr, PPF_None, nullptr); ImGui::BeginDisabled(); - ImGui::Text("%s", TCHAR_TO_ANSI(*Text)); + ImGui::Text("%s", TCHAR_TO_UTF8(*Text)); ImGui::EndDisabled(); return false; @@ -869,7 +877,7 @@ bool FCogEngineWindow_Inspector::RenderObject(UObject* Object, bool ShowChildren bool FCogEngineWindow_Inspector::RenderStruct(const FStructProperty* StructProperty, uint8* PointerToValue, bool ShowChildren) { ImGui::BeginDisabled(); - ImGui::Text("%s", TCHAR_TO_ANSI(*StructProperty->Struct->GetClass()->GetName())); + ImGui::Text("%s", TCHAR_TO_ANSI(*StructProperty->Struct->GetStructCPPName())); ImGui::EndDisabled(); bool HasChanged = false; @@ -936,7 +944,12 @@ bool FCogEngineWindow_Inspector::RenderArray(const FArrayProperty* ArrayProperty const int32 Num = Helper.Num(); ImGui::BeginDisabled(); - ImGui::Text("%s [%d]", TCHAR_TO_ANSI(*ArrayProperty->Inner->GetClass()->GetName()), Num); + FString ElementPropertyName = ArrayProperty->Inner->GetClass()->GetName(); + if (const FStructProperty* StructProperty = CastField(ArrayProperty->Inner)) + { + ElementPropertyName = StructProperty->Struct->GetStructCPPName(); + } + ImGui::Text("%s [%d]", StringCast(*ElementPropertyName).Get(), Num); ImGui::EndDisabled(); bool HasChanged = false; @@ -955,6 +968,75 @@ bool FCogEngineWindow_Inspector::RenderArray(const FArrayProperty* ArrayProperty return HasChanged; } +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogEngineWindow_Inspector::RenderSet(const FSetProperty* SetProperty, uint8* PointerToValue, bool ShowChildren) +{ + FScriptSetHelper Helper(SetProperty, PointerToValue); + const int32 Num = Helper.Num(); + + ImGui::BeginDisabled(); + FString ElementPropertyName = SetProperty->GetElementProperty()->GetClass()->GetName(); + if (const FStructProperty* StructProperty = CastField(SetProperty->GetElementProperty())) + { + ElementPropertyName = StructProperty->Struct->GetStructCPPName(); + } + ImGui::Text("%s {%d}", StringCast(*ElementPropertyName).Get(), Num); + ImGui::EndDisabled(); + + bool HasChanged = false; + + if (ShowChildren) + { + for (int32 i = 0; i < Num; ++i) + { + ImGui::PushID(i); + HasChanged |= RenderProperty(SetProperty->GetElementProperty(), Helper.GetElementPtr(i), i); + ImGui::PopID(); + } + ImGui::TreePop(); + } + + return HasChanged; +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogEngineWindow_Inspector::RenderMap(const FMapProperty* MapProperty, uint8* PointerToValue, bool ShowChildren) +{ + FScriptMapHelper Helper(MapProperty, PointerToValue); + const int32 Num = Helper.Num(); + + ImGui::BeginDisabled(); + FString KeyPropertyName = MapProperty->GetKeyProperty()->GetClass()->GetName(); + if (const FStructProperty* StructProperty = CastField(MapProperty->GetKeyProperty())) + { + KeyPropertyName = StructProperty->Struct->GetStructCPPName(); + } + FString ValuePropertyName = MapProperty->GetValueProperty()->GetClass()->GetName(); + if (const FStructProperty* StructProperty = CastField(MapProperty->GetValueProperty())) + { + ValuePropertyName = StructProperty->Struct->GetStructCPPName(); + } + ImGui::Text("%s -> %s [%d]", StringCast(*KeyPropertyName).Get(), StringCast(*ValuePropertyName).Get(), Num); + ImGui::EndDisabled(); + + bool HasChanged = false; + + if (ShowChildren) + { + for (int32 i = 0; i < Num; ++i) + { + ImGui::PushID(i); + // @todo: refactor this so it's better? + HasChanged |= RenderProperty(MapProperty->GetKeyProperty(), Helper.GetKeyPtr(i), i, "Key"); + HasChanged |= RenderProperty(MapProperty->GetValueProperty(), Helper.GetValuePtr(i), i, "Value"); + ImGui::PopID(); + } + ImGui::TreePop(); + } + + return HasChanged; +} + //-------------------------------------------------------------------------------------------------------------------------- bool FCogEngineWindow_Inspector::HasPropertyAnyChildren(const FProperty* Property, uint8* PointerToValue) { @@ -963,25 +1045,34 @@ bool FCogEngineWindow_Inspector::HasPropertyAnyChildren(const FProperty* Propert const TFieldIterator It(StructProperty->Struct); return It ? true : false; } - else if (const FArrayProperty* ArrayProperty = CastField(Property)) + + if (const FArrayProperty* ArrayProperty = CastField(Property)) { const FScriptArrayHelper Helper(ArrayProperty, PointerToValue); const int32 Num = Helper.Num(); if (Num == 0) + { return false; } + + return true; + } + + if (const FSetProperty* SetProperty = CastField(Property)) + { + const FScriptSetHelper Helper(SetProperty, PointerToValue); + const int32 Num = Helper.Num(); + if (Num == 0) { return false; } return true; } - else if (const FClassProperty* ClassProperty = CastField(Property)) + + if (const FMapProperty* MapProperty = CastField(Property)) { - return false; - } - else if (const FObjectProperty* ObjectProperty = CastField(Property)) - { - const UObject* ReferencedObject = ObjectProperty->GetObjectPropertyValue(PointerToValue); - if (ReferencedObject == nullptr) + const FScriptMapHelper Helper(MapProperty, PointerToValue); + const int32 Num = Helper.Num(); + if (Num == 0) { return false; } @@ -989,6 +1080,18 @@ bool FCogEngineWindow_Inspector::HasPropertyAnyChildren(const FProperty* Propert return true; } + if (const FClassProperty* ClassProperty = CastField(Property)) + { return false; } + + if (const FObjectProperty* ObjectProperty = CastField(Property)) + { + const UObject* ReferencedObject = ObjectProperty->GetObjectPropertyValue(PointerToValue); + if (ReferencedObject == nullptr) + { return false; } + + return true; + } + return false; } diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Levels.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Levels.cpp new file mode 100644 index 0000000..97b0fcf --- /dev/null +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Levels.cpp @@ -0,0 +1,175 @@ +#include "CogEngineWindow_Levels.h" + +#include "CogImguiHelper.h" +#include "CogWidgets.h" +#include "AssetRegistry/AssetRegistryModule.h" + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Levels::RenderHelp() +{ + ImGui::Text("This window can be used to load levels."); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Levels::Initialize() +{ + Super::Initialize(); + + bHasMenu = true; + Config = GetConfig(); +} + +//-------------------------------------------------------------------------------------------------------------------------- + void FCogEngineWindow_Levels::GetAllLevels(TArray& OutLevels) +{ + const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + const IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); + + FARFilter AssetFilter; + AssetFilter.ClassPaths.Add(UWorld::StaticClass()->GetClassPathName()); + + AssetRegistry.GetAssets(AssetFilter, OutLevels); + + RefreshSorting(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Levels::RefreshSorting() +{ + Levels = UnsortedLevels; + + if (Config->SortByName) + { + if (Config->ShowPath) + { + Levels.Sort([](const auto& InA, const auto& InB) { return InA.PackagePath.Compare(InB.PackagePath) < 0; }); + } + else + { + Levels.Sort([](const auto& InA, const auto& InB) { return InA.AssetName.Compare(InB.AssetName) < 0; }); + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Levels::RenderMenu() +{ + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Options")) + { + if (ImGui::Checkbox("Sort by Name", &Config->SortByName)) + { + RefreshSorting(); + } + + if (ImGui::Checkbox("Show Path", &Config->ShowPath)) + { + RefreshSorting(); + } + + ImGui::EndMenu(); + } + + FCogWidgets::SearchBar("##Filter", Filter); + + ImGui::EndMenuBar(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Levels::RenderContent() +{ + Super::RenderContent(); + + if (HasGatheredLevels == false) + { + GetAllLevels(UnsortedLevels); + RefreshSorting(); + HasGatheredLevels = true; + } + + RenderMenu(); + + const float FooterHeight = ImGui::GetFrameHeightWithSpacing(); + const ImVec2 Size = IsWindowRenderedInMainMenu() + ? ImVec2(0.f, ImGui::GetFontSize() * 10 - FooterHeight) + : ImVec2(0.f, ImGui::GetContentRegionAvail().y - FooterHeight); + + const bool Visible = ImGui::BeginChild("Levels", Size, 0, ImGuiWindowFlags_HorizontalScrollbar); + + if (Visible) + { + for (int32 i = 0; i < Levels.Num(); i++) + { + ImGui::PushID(i); + + const FAssetData& Asset = Levels[i]; + RenderLevel(i, Asset); + + ImGui::PopID(); + } + } + ImGui::EndChild(); + + const bool CanLoad = Levels.IsValidIndex(SelectedIndex); + if (CanLoad == false) + { + ImGui::BeginDisabled(); + } + if (ImGui::Button("Load", ImVec2(-1, 0))) + { + if (Levels.IsValidIndex(SelectedIndex)) + { + LoadLevel(Levels[SelectedIndex]); + } + } + if (CanLoad == false) + { + ImGui::EndDisabled(); + } +} + + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Levels::RenderLevel(int32 InIndex, const FAssetData& InAsset) +{ + const FString Label = Config->ShowPath ? InAsset.PackageName.ToString() : InAsset.AssetName.ToString(); + + const auto LabelStr = StringCast(*Label); + if (Filter.PassFilter(LabelStr.Get()) == false) + { return; } + + if (ImGui::Selectable(LabelStr.Get(), SelectedIndex == InIndex, ImGuiSelectableFlags_AllowDoubleClick)) + { + SelectedIndex = InIndex; + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + { + LoadLevel(InAsset); + } + } + + ImGui::SetItemTooltip(StringCast(*InAsset.PackageName.ToString()).Get()); + + RenderLevelContextMenu(InIndex, InAsset); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Levels::LoadLevel(const FAssetData& InAsset) +{ + APlayerController* LocalPlayerController = GetLocalPlayerController(); + if (LocalPlayerController == nullptr) + { return; } + + LocalPlayerController->ConsoleCommand(FString::Printf(TEXT("Travel %s"), *InAsset.PackageName.ToString())); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Levels::RenderLevelContextMenu(int Index, const FAssetData& Asset) +{ + if (ImGui::BeginPopupContextItem()) + { + FCogWidgets::BrowseToAssetButton(Asset); + ImGui::EndPopup(); + } +} diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_LogCategories.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_LogCategories.cpp index f2d535e..42f1cbb 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_LogCategories.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_LogCategories.cpp @@ -2,7 +2,7 @@ #include "CogDebugHelper.h" #include "CogDebug.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "CogDebugLog.h" #include "DrawDebugHelpers.h" #include "Engine/World.h" @@ -76,12 +76,12 @@ void FCogEngineWindow_LogCategories::RenderContent() bool bIsFilteringBySelection = FCogDebug::GetIsFilteringBySelection(); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 2); - FCogWindowWidgets::PushStyleCompact(); + FCogWidgets::PushStyleCompact(); if (ImGui::Checkbox("Filter", &bIsFilteringBySelection)) { FCogDebug::SetIsFilteringBySelection(GetWorld(), bIsFilteringBySelection); } - FCogWindowWidgets::PopStyleCompact(); + FCogWidgets::PopStyleCompact(); if (ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary)) { @@ -111,8 +111,6 @@ void FCogEngineWindow_LogCategories::RenderContent() const bool IsClient = World->GetNetMode() == NM_Client; - ImGuiStyle& Style = ImGui::GetStyle(); - int Index = 0; for (const auto& Entry : FCogDebugLog::GetLogCategories()) { @@ -221,13 +219,13 @@ void FCogEngineWindow_LogCategories::RenderContent() if (IsClient) { const ELogVerbosity::Type CurrentVerbosity = FCogDebugLog::GetServerVerbosity(CategoryName); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); if (ImGui::BeginCombo("##Server", FCogDebugHelper::VerbosityToString(CurrentVerbosity))) { - for (int32 i = (int32)ELogVerbosity::Error; i <= (int32)ELogVerbosity::VeryVerbose; ++i) + for (int32 i = ELogVerbosity::Error; i <= static_cast(ELogVerbosity::VeryVerbose); ++i) { - const bool IsSelected = i == (int32)CurrentVerbosity; - const ELogVerbosity::Type Verbosity = (ELogVerbosity::Type)i; + const bool IsSelected = i == static_cast(CurrentVerbosity); + const ELogVerbosity::Type Verbosity = static_cast(i); if (ImGui::Selectable(FCogDebugHelper::VerbosityToString(Verbosity), IsSelected)) { @@ -251,13 +249,13 @@ void FCogEngineWindow_LogCategories::RenderContent() { const ELogVerbosity::Type CurrentVerbosity = Category->GetVerbosity(); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); if (ImGui::BeginCombo("##Local", FCogDebugHelper::VerbosityToString(CurrentVerbosity))) { - for (int32 i = (int32)ELogVerbosity::Error; i <= (int32)ELogVerbosity::VeryVerbose; ++i) + for (int32 i = ELogVerbosity::Error; i <= static_cast(ELogVerbosity::VeryVerbose); ++i) { - const bool IsSelected = i == (int32)CurrentVerbosity; - const ELogVerbosity::Type Verbosity = (ELogVerbosity::Type)i; + const bool IsSelected = i == static_cast(CurrentVerbosity); + const ELogVerbosity::Type Verbosity = static_cast(i); if (ImGui::Selectable(FCogDebugHelper::VerbosityToString(Verbosity), IsSelected)) { diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Metrics.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Metrics.cpp index d9d4059..8dac6f0 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Metrics.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Metrics.cpp @@ -1,7 +1,7 @@ #include "CogEngineWindow_Metrics.h" #include "CogDebugMetric.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "imgui.h" #include "Engine/World.h" @@ -27,19 +27,14 @@ void FCogEngineWindow_Metrics::RenderHelp() ); } -//-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Metrics::ResetConfig() -{ - Super::ResetConfig(); - - Config->Reset(); -} - //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Metrics::PreSaveConfig() { Super::PreSaveConfig(); + if (Config == nullptr) + { return; } + Config->MaxDurationSetting = FCogDebugMetric::MaxDurationSetting; Config->RestartDelaySetting = FCogDebugMetric::RestartDelaySetting; } @@ -62,13 +57,13 @@ void FCogEngineWindow_Metrics::RenderContent() { if (ImGui::BeginMenu("Options")) { - FCogWindowWidgets::PushStyleCompact(); + FCogWidgets::PushStyleCompact(); ImGui::DragFloat("Auto Restart Delay", &FCogDebugMetric::RestartDelaySetting, 0.1f, 0.0f, FLT_MAX, "%0.1f"); - FCogWindowWidgets::PopStyleCompact(); + FCogWidgets::PopStyleCompact(); - FCogWindowWidgets::PushStyleCompact(); + FCogWidgets::PushStyleCompact(); ImGui::DragFloat("Max Time", &FCogDebugMetric::MaxDurationSetting, 0.1f, 0.0f, FLT_MAX, "%0.1f"); - FCogWindowWidgets::PopStyleCompact(); + FCogWidgets::PopStyleCompact(); ImGui::EndMenu(); } @@ -108,7 +103,7 @@ void FCogEngineWindow_Metrics::RenderContent() //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Metrics::DrawMetric(FCogDebugMetricEntry& Metric) { - FCogWindowWidgets::PushBackColor(ImVec4(0.8f, 0.8f, 0.8f, 1.0f)); + FCogWidgets::PushBackColor(ImVec4(0.8f, 0.8f, 0.8f, 1.0f)); if (ImGui::BeginTable("MetricTable", 4, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_NoBordersInBodyUntilResize | ImGuiTableFlags_RowBg)) { @@ -128,25 +123,25 @@ void FCogEngineWindow_Metrics::DrawMetric(FCogDebugMetricEntry& Metric) } ImGui::Text("Crits"); - ImGui::SameLine(FCogWindowWidgets::GetFontWidth() * 20); - FCogWindowWidgets::ProgressBarCentered(Metric.Count == 0 ? 0.0f : Metric.Crits / (float)Metric.Count, ImVec2(-1, 0), TCHAR_TO_ANSI(*FString::Printf(TEXT("%d / %d"), Metric.Crits, Metric.Count))); + ImGui::SameLine(FCogWidgets::GetFontWidth() * 20); + FCogWidgets::ProgressBarCentered(Metric.Count == 0 ? 0.0f : Metric.Crits / static_cast(Metric.Count), ImVec2(-1, 0), TCHAR_TO_ANSI(*FString::Printf(TEXT("%d / %d"), Metric.Crits, Metric.Count))); if (FCogDebugMetric::MaxDurationSetting > 0.0f) { ImGui::Text("Timer"); - ImGui::SameLine(FCogWindowWidgets::GetFontWidth() * 20); - FCogWindowWidgets::ProgressBarCentered(Metric.Timer / (float)FCogDebugMetric::MaxDurationSetting, ImVec2(-1, 0), TCHAR_TO_ANSI(*FString::Printf(TEXT("%0.1f / %0.1f"), Metric.Timer, FCogDebugMetric::MaxDurationSetting))); + ImGui::SameLine(FCogWidgets::GetFontWidth() * 20); + FCogWidgets::ProgressBarCentered(Metric.Timer / (float)FCogDebugMetric::MaxDurationSetting, ImVec2(-1, 0), TCHAR_TO_ANSI(*FString::Printf(TEXT("%0.1f / %0.1f"), Metric.Timer, FCogDebugMetric::MaxDurationSetting))); } else { ImGui::Text("Timer"); - ImGui::SameLine(FCogWindowWidgets::GetFontWidth() * 20); + ImGui::SameLine(FCogWidgets::GetFontWidth() * 20); ImGui::Text("%0.1f", Metric.Timer); } ImGui::Spacing(); - FCogWindowWidgets::PopBackColor(); + FCogWidgets::PopBackColor(); if (ImGui::Button("Restart")) { diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_NetEmulation.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_NetEmulation.cpp index 3b8f4c7..6d4e916 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_NetEmulation.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_NetEmulation.cpp @@ -1,7 +1,8 @@ #include "CogEngineWindow_NetEmulation.h" #include "CogEngineWindow_Stats.h" -#include "CogWindowWidgets.h" +#include "CogImguiHelper.h" +#include "CogWidgets.h" #include "Engine/Engine.h" #include "Engine/NetConnection.h" #include "Engine/NetDriver.h" @@ -16,6 +17,25 @@ void FCogEngineWindow_NetEmulation::RenderHelp() ImGui::Text("This window is used to configure the network emulation."); } +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_NetEmulation::Initialize() +{ + Super::Initialize(); + + Config = GetConfig(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_NetEmulation::RenderContextMenu() +{ + Config->RenderColorConfig(); + Config->RenderPingConfig(); + Config->RenderPacketLossConfig(); + + ImGui::Separator(); + FCogWindow::RenderContextMenu(); +} + //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_NetEmulation::RenderContent() { @@ -42,7 +62,7 @@ void FCogEngineWindow_NetEmulation::DrawStats() const float Ping = PlayerState->GetPingInMilliseconds(); ImGui::Text("Ping "); ImGui::SameLine(); - ImGui::TextColored(FCogEngineWindow_Stats::GetPingColor(Ping), "%0.0fms", Ping); + ImGui::TextColored(Config->GetPingColor(Ping), "%0.0fms", Ping); } if (UNetConnection* Connection = PlayerController->GetNetConnection()) @@ -50,12 +70,12 @@ void FCogEngineWindow_NetEmulation::DrawStats() const float OutPacketLost = Connection->GetOutLossPercentage().GetAvgLossPercentage() * 100.0f; ImGui::Text("Packet Loss Out "); ImGui::SameLine(); - ImGui::TextColored(FCogEngineWindow_Stats::GetPacketLossColor(OutPacketLost), "%0.0f%%", OutPacketLost); + ImGui::TextColored(Config->GetPacketLossColor(OutPacketLost), "%0.0f%%", OutPacketLost); const float InPacketLost = Connection->GetInLossPercentage().GetAvgLossPercentage() * 100.0f; ImGui::Text("Packet Loss In "); ImGui::SameLine(); - ImGui::TextColored(FCogEngineWindow_Stats::GetPacketLossColor(InPacketLost), "%0.0f%%", InPacketLost); + ImGui::TextColored(Config->GetPacketLossColor(InPacketLost), "%0.0f%%", InPacketLost); } } @@ -82,7 +102,7 @@ void FCogEngineWindow_NetEmulation::DrawControls() return; } - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); if (ImGui::BeginCombo("Driver", TCHAR_TO_ANSI(*SelectedNetDriver->NetDriver->GetName()))) { int i = 0; @@ -113,7 +133,7 @@ void FCogEngineWindow_NetEmulation::DrawControls() FPacketSimulationSettings Settings = SelectedNetDriver->NetDriver->PacketSimulationSettings; //------------------------------------------------------------------------------------------- - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); if (ImGui::DragInt("Lag Min", &Settings.PktLagMin, 5.0f, 0, INT_MAX, "%d ms")) { SelectedNetDriver->NetDriver->SetPacketSimulationSettings(Settings); @@ -125,7 +145,7 @@ void FCogEngineWindow_NetEmulation::DrawControls() } //------------------------------------------------------------------------------------------- - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); if (ImGui::DragInt("Lag Max", &Settings.PktLagMax, 5.0f, 0, INT_MAX, "%d ms")) { SelectedNetDriver->NetDriver->SetPacketSimulationSettings(Settings); @@ -137,7 +157,7 @@ void FCogEngineWindow_NetEmulation::DrawControls() } - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); if (ImGui::SliderInt("Packet Loss", &Settings.PktLoss, 0, 100, "%d%%")) { SelectedNetDriver->NetDriver->SetPacketSimulationSettings(Settings); @@ -153,7 +173,7 @@ void FCogEngineWindow_NetEmulation::DrawControls() } //------------------------------------------------------------------------------------------- - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); if (ImGui::SliderInt("Packet Order", &Settings.PktOrder, 0, 100, "%d%%")) { SelectedNetDriver->NetDriver->SetPacketSimulationSettings(Settings); @@ -168,7 +188,7 @@ void FCogEngineWindow_NetEmulation::DrawControls() } //------------------------------------------------------------------------------------------- - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); if (ImGui::SliderInt("Packet Dup", &Settings.PktDup, 0, 100, "%d%%")) { SelectedNetDriver->NetDriver->SetPacketSimulationSettings(Settings); @@ -186,7 +206,7 @@ void FCogEngineWindow_NetEmulation::DrawControls() ImGui::Separator(); //------------------------------------------------------------------------------------------- - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); if (ImGui::DragInt("Incoming Lag Min", &Settings.PktIncomingLagMin, 5.0f, 0, INT_MAX, "%d ms")) { SelectedNetDriver->NetDriver->SetPacketSimulationSettings(Settings); @@ -198,7 +218,7 @@ void FCogEngineWindow_NetEmulation::DrawControls() } //------------------------------------------------------------------------------------------- - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); if (ImGui::DragInt("Incoming Lag Max", &Settings.PktIncomingLagMax, 5.0f, 0, INT_MAX, "%d ms")) { SelectedNetDriver->NetDriver->SetPacketSimulationSettings(Settings); @@ -210,7 +230,7 @@ void FCogEngineWindow_NetEmulation::DrawControls() } //------------------------------------------------------------------------------------------- - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); if (ImGui::SliderInt("Incoming Packet Loss", &Settings.PktIncomingLoss, 0, 100, "%d%%")) { SelectedNetDriver->NetDriver->SetPacketSimulationSettings(Settings); diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_NetImGui.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_NetImGui.cpp index 225d22d..b1642e1 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_NetImGui.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_NetImGui.cpp @@ -2,9 +2,9 @@ #include "CogImguiContext.h" #include "CogImguiHelper.h" -#include "CogWindowConsoleCommandManager.h" -#include "CogWindowManager.h" -#include "CogWindowWidgets.h" +#include "CogConsoleCommandManager.h" +#include "CogSubsystem.h" +#include "CogWidgets.h" #include "Engine/EngineBaseTypes.h" #include "Engine/World.h" #include "imgui.h" @@ -12,6 +12,13 @@ #include "Misc/Paths.h" #include "NetImgui_Api.h" +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_NetImgui::RenderHelp() +{ + ImGui::Text("This window manage the connection to the NetImgui server." + "See https://github.com/sammyfreg/netImgui for more info."); +} + //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_NetImgui::Initialize() { @@ -19,7 +26,7 @@ void FCogEngineWindow_NetImgui::Initialize() Config = GetConfig(); - FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand( + FCogConsoleCommandManager::RegisterWorldConsoleCommand( TEXT("Cog.NetImgui.Connect"), TEXT("Connect to NetImgui server"), GetWorld(), @@ -28,7 +35,7 @@ void FCogEngineWindow_NetImgui::Initialize() ConnectTo(); })); - FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand( + FCogConsoleCommandManager::RegisterWorldConsoleCommand( TEXT("Cog.NetImgui.Listen"), TEXT("Listen for NetImgui server connection"), GetWorld(), @@ -37,7 +44,7 @@ void FCogEngineWindow_NetImgui::Initialize() ConnectFrom(); })); - FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand( + FCogConsoleCommandManager::RegisterWorldConsoleCommand( TEXT("Cog.NetImgui.Disconnect"), TEXT("Disconnect from NetImgui server"), GetWorld(), @@ -46,7 +53,7 @@ void FCogEngineWindow_NetImgui::Initialize() Disconnect(); })); - FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand( + FCogConsoleCommandManager::RegisterWorldConsoleCommand( TEXT("Cog.NetImgui.RunServer"), TEXT("Run NetImgui server application"), GetWorld(), @@ -55,7 +62,7 @@ void FCogEngineWindow_NetImgui::Initialize() RunServer(); })); - FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand( + FCogConsoleCommandManager::RegisterWorldConsoleCommand( TEXT("Cog.NetImgui.CloseServer"), TEXT("Close NetImgui server application"), GetWorld(), @@ -76,20 +83,6 @@ void FCogEngineWindow_NetImgui::Shutdown() CloseServer(); } -//-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_NetImgui::ResetConfig() -{ - Super::ResetConfig(); - Config->Reset(); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_NetImgui::RenderHelp() -{ - ImGui::Text("This window manage the connection to the NetImgui server." - "See https://github.com/sammyfreg/netImgui for more info."); -} - //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_NetImgui::RenderTick(float DeltaTime) { @@ -107,7 +100,7 @@ void FCogEngineWindow_NetImgui::RenderTick(float DeltaTime) RunServer(); } - ECogNetImguiAutoConnectionMode AutoConnectMode = ECogNetImguiAutoConnectionMode::NoAutoConnect; + ECogNetImguiAutoConnectionMode AutoConnectMode; switch (GetWorld()->GetNetMode()) { case NM_Client: AutoConnectMode = Config->AutoConnectOnClient; break; @@ -221,38 +214,38 @@ void FCogEngineWindow_NetImgui::RenderContent() { ImGui::SeparatorText("Connection"); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::InputText("Server Address", Config->ServerAddress); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::InputText("Server Address", Config->ServerAddress); ImGui::SetItemTooltip("NetImgui server application address."); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::InputInt("Server Port", &Config->ServerPort); ImGui::SetItemTooltip("Port of the NetImgui Server application to connect to."); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::InputText("Client Name", Config->ClientName); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::InputText("Client Name", Config->ClientName); ImGui::SetItemTooltip("Client name displayed in the server's clients list."); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::InputInt("Client Port", &Config->ClientPort); ImGui::SetItemTooltip("Port this client should wait for connection from server application."); ImGui::SeparatorText("Auto-Connect"); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::ComboboxEnum("Dedicated Server", Config->AutoConnectOnDedicatedServer); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::ComboboxEnum("Dedicated Server", Config->AutoConnectOnDedicatedServer); ImGui::SetItemTooltip("Auto-connect mode to the NetImgui server when launching on dedicated server mode."); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::ComboboxEnum("Listen Server", Config->AutoConnectOnListenServer); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::ComboboxEnum("Listen Server", Config->AutoConnectOnListenServer); ImGui::SetItemTooltip("Auto-connect mode to the NetImgui server when launching on listen server mode."); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::ComboboxEnum("Client", Config->AutoConnectOnClient); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::ComboboxEnum("Client", Config->AutoConnectOnClient); ImGui::SetItemTooltip("Auto-connect mode to the NetImgui server when launching on client mode."); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::ComboboxEnum("Standalone", Config->AutoConnectOnStandalone); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::ComboboxEnum("Standalone", Config->AutoConnectOnStandalone); ImGui::SetItemTooltip("Auto-connect mode to the NetImgui server when launching on standalone mode."); ImGui::SeparatorText("Server App"); @@ -260,19 +253,19 @@ void FCogEngineWindow_NetImgui::RenderContent() ImGui::Checkbox("Auto Run Server", &Config->AutoRunServer); ImGui::SetItemTooltip("Automatically run the NetImgui server executable at startup."); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::InputText("Server Executable", Config->ServerExecutable); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::InputText("Server Executable", Config->ServerExecutable); ImGui::SetItemTooltip("Filename of the NetImgui server executable."); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::InputText("Server Directory", Config->ServerDirectory); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::InputText("Server Directory", Config->ServerDirectory); ImGui::SetItemTooltip("Directory of the NetImgui server executable."); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::InputText("Server Arguments", Config->ServerArguments); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::InputText("Server Arguments", Config->ServerArguments); ImGui::SetItemTooltip("Argument used when launching the NetImgui server executable."); } -#endif // #if NETIMGUI_ENABLED +#endif } //-------------------------------------------------------------------------------------------------------------------------- @@ -292,7 +285,7 @@ FString FCogEngineWindow_NetImgui::GetClientName() const //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_NetImgui::ConnectTo() +void FCogEngineWindow_NetImgui::ConnectTo() const { FCogImGuiContextScope ImGuiContextScope(GetOwner()->GetContext()); @@ -309,7 +302,7 @@ void FCogEngineWindow_NetImgui::ConnectTo() } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_NetImgui::ConnectFrom() +void FCogEngineWindow_NetImgui::ConnectFrom() const { FCogImGuiContextScope ImGuiContextScope(GetOwner()->GetContext()); @@ -325,7 +318,7 @@ void FCogEngineWindow_NetImgui::ConnectFrom() } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_NetImgui::Disconnect() +void FCogEngineWindow_NetImgui::Disconnect() const { FCogImGuiContextScope ImGuiContextScope(GetOwner()->GetContext()); diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Notifications.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Notifications.cpp new file mode 100644 index 0000000..2f3c317 --- /dev/null +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Notifications.cpp @@ -0,0 +1,333 @@ +#include "CogEngineWindow_Notifications.h" + +#include "CogCommon.h" +#include "CogCommonLogCategory.h" +#include "CogImguiHelper.h" +#include "CogWidgets.h" +#include "Engine/Engine.h" +#include "Misc/StringBuilder.h" + +int32 FCogEngineWindow_Notifications::NotificationsId = 0; + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Notifications::Initialize() +{ + Super::Initialize(); + + Config = GetConfig(); + + OutputDevice.Notifications = this; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Notifications::RenderHelp() +{ + ImGui::Text("This window manage the notifications."); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Notifications::Clear() +{ + Notifications.Empty(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Notifications::AddNotification(const TCHAR* InMessage, ELogVerbosity::Type InVerbosity) +{ + FNotification& Notification = Notifications.AddDefaulted_GetRef(); + Notification.Id = FString::Printf(TEXT("###Notify%d"), NotificationsId); + Notification.Time = FDateTime::Now(); + Notification.Verbosity = InVerbosity; + Notification.Message = InMessage; + + NotificationsId++; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Notifications::OnLogReceived(const TCHAR* InMessage, ELogVerbosity::Type InVerbosity, const class FName& InCategory) +{ + if (Config == nullptr) + { return; } + + if (Config->DisableNotifications) + { return; } + + static FName CmdName("Cmd"); + +#if ENABLE_COG + if (InCategory == LogCogNotify.GetCategoryName() + || (InCategory == CmdName && Config->NotifyConsoleCommands) + || (InVerbosity == ELogVerbosity::Warning && Config->NotifyAllWarnings) + || (InVerbosity == ELogVerbosity::Error && Config->NotifyAllErrors)) + { + AddNotification(InMessage, InVerbosity); + } +#endif +} + + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Notifications::RenderTick(float DeltaTime) +{ + Super::RenderTick(DeltaTime); + + if (Config->DisableNotifications) + { return; } + + RenderNotifications(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Notifications::RenderNotifications() +{ + if (Notifications.Num() == 0) + { return; } + + constexpr ImGuiWindowFlags Flags = + ImGuiWindowFlags_NoMove + | ImGuiWindowFlags_NoDecoration + | ImGuiWindowFlags_NoDocking + | ImGuiWindowFlags_AlwaysAutoResize + | ImGuiWindowFlags_NoSavedSettings + | ImGuiWindowFlags_NoFocusOnAppearing + | ImGuiWindowFlags_NoNav + | ImGuiWindowFlags_NoInputs; + + const ImGuiViewport* Viewport = ImGui::GetMainViewport(); + + const float DpiScale = GetDpiScale(); + + ImVec2 WindowPos = FCogWidgets::ComputeScreenCornerLocation(Config->Alignment, Config->Padding); + const ImVec2 WindowPadding = ImGui::GetStyle().WindowPadding; + const ImVec2 ItemSpacing = ImGui::GetStyle().ItemSpacing; + const float MaxHeight = Config->MaxHeight * DpiScale + WindowPadding.y * 2; + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, Config->Rounding); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, Config->ShowBorder); + + const FDateTime Now = FDateTime::Now(); + + for (int32 i = Notifications.Num() - 1; i >= 0; i--) + { + const FNotification& Notification = Notifications[i]; + + const FTimespan Span = Now - Notification.Time; + if (Span > FTimespan::FromSeconds(Config->Duration + Config->FadeOut)) + { + Notifications.RemoveAt(i); + continue; + } + + ImVec4 TextColor, BackColor, BorderColor; + switch (Notification.Verbosity) + { + case ELogVerbosity::Error: + { + TextColor = FCogImguiHelper::ToImVec4(Config->TextErrorColor); + BackColor = FCogImguiHelper::ToImVec4(Config->BackgroundErrorColor); + BorderColor = FCogImguiHelper::ToImVec4(Config->BorderErrorColor); + break; + } + + case ELogVerbosity::Warning: + { + TextColor = FCogImguiHelper::ToImVec4(Config->TextWarningColor); + BackColor = FCogImguiHelper::ToImVec4(Config->BackgroundWarningColor); + BorderColor = FCogImguiHelper::ToImVec4(Config->BorderWarningColor); + break; + } + + default: + { + TextColor = FCogImguiHelper::ToImVec4(Config->TextDefaultColor); + BackColor = FCogImguiHelper::ToImVec4(Config->BackgroundDefaultColor); + BorderColor = FCogImguiHelper::ToImVec4(Config->BorderDefaultColor); + break; + } + } + + const float ElapsedTime = Span.GetTotalSeconds(); + const float Alpha = FMath::GetMappedRangeValueClamped(FVector2d(Config->Duration, Config->Duration + Config->FadeOut), FVector2d(1.0f, 0.0f), ElapsedTime); + + BackColor.w *= Alpha; + TextColor.w *= Alpha; + BorderColor.w *= Alpha; + + ImGui::PushStyleColor(ImGuiCol_WindowBg, BackColor); + ImGui::PushStyleColor(ImGuiCol_Border, BorderColor); + ImGui::PushStyleColor(ImGuiCol_Text, TextColor); + + const auto Message = StringCast(*Notification.Message); + const float WrapWidth = Config->TextWrapping * DpiScale; + + ImGui::SetNextWindowViewport(Viewport->ID); + ImGui::SetNextWindowPos(WindowPos, ImGuiCond_Always, FCogImguiHelper::ToImVec2(Config->Alignment)); + if (Config->UseFixedWidth) + { + ImGui::SetNextWindowSizeConstraints(ImVec2(WrapWidth + WindowPadding.x * 2, 0), ImVec2(WrapWidth + WindowPadding.x * 2, MaxHeight)); + } + + if (ImGui::Begin(StringCast(*Notification.Id).Get(), nullptr, Flags)) + { + ImGui::PushTextWrapPos(WrapWidth); + ImGui::TextUnformatted(Message.Get()); + ImGui::PopTextWrapPos(); + } + + ImGui::PopStyleColor(3); + + //---------------------------------------------------------------------- + // Compute ourself window height otherwise we get a one frame glitch, + // maybe because the real window size is computed the next frame. + //---------------------------------------------------------------------- + const ImVec2 TextSize = ImGui::CalcTextSize(Message.Get(), nullptr, false, WrapWidth); + const float WindowHeight = FMath::Min(MaxHeight, TextSize.y + (WindowPadding.y * 2)); + WindowPos.y += (WindowHeight + ItemSpacing.y) * (Config->Alignment.Y > 0.5f ? -1 : 1); + ImGui::End(); + } + + ImGui::PopStyleVar(2); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Notifications::RenderContent() +{ + Super::RenderContent(); + + if (ImGui::Button("Clear Notifications", ImVec2(-1, 0))) + { + Notifications.Empty(); + } + + FCogWidgets::ThinSeparatorText("Notification Test"); + + if (ImGui::Button("Notify Normal", ImVec2(-1, 0))) + { + COG_NOTIFY(TEXT("A notification test. Frame:%llu"), GFrameCounter); + } + + if (ImGui::Button("Notify Warning", ImVec2(-1, 0))) + { + COG_NOTIFY_WARNING(TEXT("A long long long long long long long long long long long long long long long long long long long long long warning notification test. Frame:%llu"), GFrameCounter); + } + + if (ImGui::Button("Notify Error", ImVec2(-1, 0))) + { + COG_NOTIFY_ERROR(TEXT("An error notification test. Frame:%llu"), GFrameCounter); + } + + RenderSettings(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Notifications::RenderSettings() +{ + FCogWidgets::ThinSeparatorText("Filtering"); + + ImGui::Checkbox("Disable Notifications", &Config->DisableNotifications); + + ImGui::Checkbox("Notify Console Commands", &Config->NotifyConsoleCommands); + + ImGui::Checkbox("Notify All Warnings", &Config->NotifyAllWarnings); + + ImGui::Checkbox("Notify All Errors", &Config->NotifyAllErrors); + + FCogWidgets::ThinSeparatorText("Location & Size"); + + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderFloat2("Alignment", &Config->Alignment.X, 0, 1.0f, "%.2f"); + + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderInt2("Padding", &Config->Padding.X, 0, 100); + + ImGui::Checkbox("Use Fixed Width", &Config->UseFixedWidth); + + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderInt("Text Wrapping", &Config->TextWrapping, 1, 500); + + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderInt("Max Height", &Config->MaxHeight, 0, 500); + + FCogWidgets::ThinSeparatorText("Display"); + + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderFloat("Duration", &Config->Duration, 1, 10, "%0.1f"); + + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderFloat("Fade Out", &Config->FadeOut, 0, 3, "%0.1f"); + + ImGui::Checkbox("Show Border", &Config->ShowBorder); + + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderInt("Rounding", &Config->Rounding, 0, 12); + + FCogWidgets::ThinSeparatorText("Colors"); + + constexpr ImGuiColorEditFlags ColorEditFlags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf; + + FCogImguiHelper::ColorEdit4("##BackDef", Config->BackgroundDefaultColor, ColorEditFlags); + ImGui::SameLine(); + ImGui::SetItemTooltip("Background Default"); + FCogImguiHelper::ColorEdit4("##BackWarn", Config->BackgroundWarningColor, ColorEditFlags); + ImGui::SameLine(); + ImGui::SetItemTooltip("Background Warning"); + FCogImguiHelper::ColorEdit4("##BackError", Config->BackgroundErrorColor, ColorEditFlags); + ImGui::SameLine(); + ImGui::SetItemTooltip("Background Error"); + ImGui::TextUnformatted("Background Color"); + + FCogImguiHelper::ColorEdit4("##BorderDef", Config->BorderDefaultColor, ColorEditFlags); + ImGui::SameLine(); + ImGui::SetItemTooltip("Border Default"); + FCogImguiHelper::ColorEdit4("##BorderWarn", Config->BorderWarningColor, ColorEditFlags); + ImGui::SameLine(); + ImGui::SetItemTooltip("Border Warning"); + FCogImguiHelper::ColorEdit4("##BorderError", Config->BorderErrorColor, ColorEditFlags); + ImGui::SameLine(); + ImGui::SetItemTooltip("Border Error"); + ImGui::TextUnformatted("Border Color"); + + FCogImguiHelper::ColorEdit4("##TextDef", Config->TextDefaultColor, ColorEditFlags); + ImGui::SameLine(); + ImGui::SetItemTooltip("Text Default"); + FCogImguiHelper::ColorEdit4("##TextWarn", Config->TextWarningColor, ColorEditFlags); + ImGui::SameLine(); + ImGui::SetItemTooltip("Text Warning"); + FCogImguiHelper::ColorEdit4("##TextError", Config->TextErrorColor, ColorEditFlags); + ImGui::SameLine(); + ImGui::SetItemTooltip("Text Error"); + ImGui::TextUnformatted("Text Color"); + + ImGui::Separator(); + + if (ImGui::Button("Reset Settings", ImVec2(-1, 0))) + { + ResetConfig(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +// FCogNotificationOutputDevice +//-------------------------------------------------------------------------------------------------------------------------- +FCogNotificationOutputDevice::FCogNotificationOutputDevice() +{ + GLog->AddOutputDevice(this); +} + +//-------------------------------------------------------------------------------------------------------------------------- +FCogNotificationOutputDevice::~FCogNotificationOutputDevice() +{ + if (GLog != nullptr) + { + GLog->RemoveOutputDevice(this); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogNotificationOutputDevice::Serialize(const TCHAR* Message, const ELogVerbosity::Type Verbosity, const FName& Category) +{ + if (Notifications != nullptr) + { + Notifications->OnLogReceived(Message, Verbosity, Category); + } +} \ No newline at end of file diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_OutputLog.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_OutputLog.cpp index 219c087..fcaba01 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_OutputLog.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_OutputLog.cpp @@ -1,8 +1,12 @@ #include "CogEngineWindow_OutputLog.h" +#include "CogCommon.h" +#include "CogCommonLogCategory.h" #include "CogDebugHelper.h" -#include "CogWindowWidgets.h" +#include "CogImguiHelper.h" +#include "CogWidgets.h" #include "Engine/Engine.h" +#include "HAL/PlatformApplicationMisc.h" #include "Misc/StringBuilder.h" //-------------------------------------------------------------------------------------------------------------------------- @@ -25,137 +29,188 @@ void FCogEngineWindow_OutputLog::RenderHelp() ); } -//-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_OutputLog::ResetConfig() -{ - Super::ResetConfig(); - - Config->Reset(); -} - //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_OutputLog::Clear() { TextBuffer.clear(); - LineInfos.Empty(); + LogInfos.Empty(); } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_OutputLog::AddLog(const TCHAR* Message, ELogVerbosity::Type Verbosity, const class FName& Category) +void FCogEngineWindow_OutputLog::AddLog(const TCHAR* InMessage, ELogVerbosity::Type InVerbosity, const class FName& InCategory) { static TAnsiStringBuilder<512> Format; Format.Reset(); - if (Message) + if (InMessage) { - Format.Append(Message); + Format.Append(InMessage); } - FLineInfo& LineInfo = LineInfos.AddDefaulted_GetRef(); - LineInfo.Frame = GFrameCounter % 1000; - LineInfo.Verbosity = Verbosity; - LineInfo.Category = Category; - LineInfo.Start = TextBuffer.size(); + FLogInfo& LogInfo = LogInfos.AddDefaulted_GetRef(); + LogInfo.Frame = GFrameCounter; + LogInfo.Time = Config != nullptr && Config->UseUTCTime ? FDateTime::UtcNow() : FDateTime::Now(); + LogInfo.Verbosity = InVerbosity; + LogInfo.Category = InCategory; + LogInfo.LineStart = TextBuffer.size(); TextBuffer.append(Format.GetData(), Format.GetData() + Format.Len()); - LineInfo.End = TextBuffer.size(); - + LogInfo.LineEnd = TextBuffer.size(); } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_OutputLog::DrawRow(const char* BufferStart, const FLineInfo& LineInfo, bool IsTableShown) const +void FCogEngineWindow_OutputLog::DrawRow(const char* InBufferStart, const FLogInfo& InLogInfo, bool InShowAsTableRow) const { ImU32 Color; - switch (LineInfo.Verbosity) + switch (InLogInfo.Verbosity) { - case ELogVerbosity::Error: Color = IM_COL32(255, 0, 0, 255); break; - case ELogVerbosity::Warning: Color = IM_COL32(255, 200, 0, 255); break; - default: Color = IM_COL32(200, 200, 200, 255); break; + case ELogVerbosity::Error: Color = FCogImguiHelper::ToImColor(Config->ErrorColor); break; + case ELogVerbosity::Warning: Color = FCogImguiHelper::ToImColor(Config->WarningColor); break; + default: Color = FCogImguiHelper::ToImColor(Config->DefaultColor); break; } ImGui::PushStyleColor(ImGuiCol_Text, Color); - if (IsTableShown) + if (InShowAsTableRow) { ImGui::TableNextRow(); - - if (Config->ShowFrame) - { - ImGui::TableNextColumn(); - ImGui::Text("%3d", LineInfo.Frame); - } - - if (Config->ShowCategory) - { - ImGui::TableNextColumn(); - ImGui::Text("%s", TCHAR_TO_ANSI(*LineInfo.Category.ToString())); - } - - if (Config->ShowVerbosity) - { - ImGui::TableNextColumn(); - ImGui::Text("%s", TCHAR_TO_ANSI(ToString(LineInfo.Verbosity))); - } - - ImGui::TableNextColumn(); - const char* LineStart = BufferStart + LineInfo.Start; - const char* LineEnd = BufferStart + LineInfo.End; - ImGui::TextUnformatted(LineStart, LineEnd); } - else + + if (Config->ShowFrame) { - if (Config->ShowFrame) + if (InShowAsTableRow) + { + ImGui::TableNextColumn(); + } + + ImGui::Text("%3d", GetDisplayedFrame(InLogInfo)); + + if (InShowAsTableRow == false) { - ImGui::Text("[%3d] ", LineInfo.Frame); ImGui::SameLine(); } - - if (Config->ShowCategory) - { - ImGui::Text("%s: ", TCHAR_TO_ANSI(*LineInfo.Category.ToString())); - ImGui::SameLine(); - } - - if (Config->ShowVerbosity) - { - ImGui::Text("%s: ", TCHAR_TO_ANSI(ToString(LineInfo.Verbosity))); - ImGui::SameLine(); - } - - const char* LineStart = BufferStart + LineInfo.Start; - const char* LineEnd = BufferStart + LineInfo.End; - ImGui::TextUnformatted(LineStart, LineEnd); } + if (Config->ShowTime) + { + if (InShowAsTableRow) + { + ImGui::TableNextColumn(); + } + + ImGui::TextUnformatted(StringCast(*InLogInfo.Time.ToString()).Get()); + + if (InShowAsTableRow == false) + { + ImGui::SameLine(); + } + } + + if (Config->ShowCategory) + { + if (InShowAsTableRow) + { + ImGui::TableNextColumn(); + } + + ImGui::TextUnformatted(StringCast(*InLogInfo.Category.ToString()).Get()); + + if (InShowAsTableRow == false) + { + ImGui::SameLine(); + } + } + + if (Config->ShowVerbosity) + { + if (InShowAsTableRow) + { + ImGui::TableNextColumn(); + } + + ImGui::TextUnformatted(StringCast(ToString(InLogInfo.Verbosity)).Get()); + + if (InShowAsTableRow == false) + { + ImGui::SameLine(); + } + } + + if (InShowAsTableRow) + { + ImGui::TableNextColumn(); + } + + const char* LineStart = InBufferStart + InLogInfo.LineStart; + const char* LineEnd = InBufferStart + InLogInfo.LineEnd; + ImGui::TextUnformatted(LineStart, LineEnd); + ImGui::PopStyleColor(); } +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_OutputLog::Copy() const +{ + const auto Buffer = StringCast(TextBuffer.c_str()); + const wchar_t* BufferData = Buffer.Get(); + if (BufferData == nullptr) + { return; } + + FStringBuilderBase StringBuilder; + for (const FLogInfo& LogInfo : LogInfos) + { + StringBuilder.Append(FString::Printf(TEXT("[%3d] [%s] [%s] "), GetDisplayedFrame(LogInfo), *LogInfo.Category.ToString(), ToString(LogInfo.Verbosity))); + StringBuilder.Append(BufferData + LogInfo.LineStart, LogInfo.LineEnd - LogInfo.LineStart); + StringBuilder.Append("\n"); + }; + + FPlatformApplicationMisc::ClipboardCopy(StringBuilder.ToString()); +} + +//-------------------------------------------------------------------------------------------------------------------------- +int32 FCogEngineWindow_OutputLog::GetDisplayedFrame(const FCogEngineWindow_OutputLog::FLogInfo& InLogInfo) const +{ + return Config->FrameCycle > 0 ? InLogInfo.Frame % Config->FrameCycle : InLogInfo.Frame; +} + //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_OutputLog::RenderContent() { Super::RenderContent(); - bool ClearPressed = false; - bool CopyPressed = false; - if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("Options")) { if (ImGui::MenuItem("Copy")) { - ImGui::LogToClipboard(); + Copy(); } ImGui::Separator(); ImGui::Checkbox("Auto Scroll", &Config->AutoScroll); ImGui::Checkbox("Show Frame", &Config->ShowFrame); + ImGui::Checkbox("Show Time", &Config->ShowTime); ImGui::Checkbox("Show Category", &Config->ShowCategory); ImGui::Checkbox("Show Verbosity", &Config->ShowVerbosity); ImGui::Checkbox("Show As Table", &Config->ShowAsTable); + ImGui::Separator(); + + constexpr ImGuiColorEditFlags ColorEditFlags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf; + FCogImguiHelper::ColorEdit4("Default Color", Config->DefaultColor, ColorEditFlags); + FCogImguiHelper::ColorEdit4("Warning Color", Config->WarningColor, ColorEditFlags); + FCogImguiHelper::ColorEdit4("Error Color", Config->ErrorColor, ColorEditFlags); + + ImGui::Separator(); + + if (ImGui::Button("Reset Settings", ImVec2(-1, 0))) + { + ResetConfig(); + } + ImGui::EndMenu(); } @@ -169,12 +224,12 @@ void FCogEngineWindow_OutputLog::RenderContent() ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetFontSize() * 9); - if (ImGui::BeginCombo("##Verbosity", FCogDebugHelper::VerbosityToString((ELogVerbosity::Type)Config->VerbosityFilter))) + if (ImGui::BeginCombo("##Verbosity", FCogDebugHelper::VerbosityToString(static_cast(Config->VerbosityFilter)))) { - for (int32 i = ELogVerbosity::Error; i <= (int32)ELogVerbosity::VeryVerbose; ++i) + for (int32 i = ELogVerbosity::Error; i <= static_cast(ELogVerbosity::VeryVerbose); ++i) { const bool IsSelected = i == Config->VerbosityFilter; - const ELogVerbosity::Type Verbosity = (ELogVerbosity::Type)i; + const ELogVerbosity::Type Verbosity = static_cast(i); if (ImGui::Selectable(FCogDebugHelper::VerbosityToString(Verbosity), IsSelected)) { @@ -184,35 +239,36 @@ void FCogEngineWindow_OutputLog::RenderContent() ImGui::EndCombo(); } - FCogWindowWidgets::SearchBar(Filter); + FCogWidgets::SearchBar("##Filter", Filter); ImGui::EndMenuBar(); } int32 ColumnCount = 1; - ColumnCount += (int32)Config->ShowFrame; - ColumnCount += (int32)Config->ShowCategory; - ColumnCount += (int32)Config->ShowVerbosity; + ColumnCount += Config->ShowFrame ? 1 : 0; + ColumnCount += Config->ShowTime ? 1 : 0; + ColumnCount += Config->ShowCategory ? 1 : 0; + ColumnCount += Config->ShowVerbosity ? 1 : 0; bool IsTableShown = false; if (Config->ShowAsTable) { - if (ImGui::BeginTable("LogTable", ColumnCount, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ScrollX)) + if (ImGui::BeginTable("LogTable", ColumnCount, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ScrollX | ImGuiTableFlags_NoHostExtendX)) { IsTableShown = true; if (Config->ShowFrame) { - ImGui::TableSetupColumn("Frame", ImGuiTableColumnFlags_WidthFixed, FCogWindowWidgets::GetFontWidth() * 4); + ImGui::TableSetupColumn("Frame", ImGuiTableColumnFlags_WidthFixed, FCogWidgets::GetFontWidth() * 4); } if (Config->ShowCategory) { - ImGui::TableSetupColumn("Category", ImGuiTableColumnFlags_WidthFixed, FCogWindowWidgets::GetFontWidth() * 10); + ImGui::TableSetupColumn("Category", ImGuiTableColumnFlags_WidthFixed, FCogWidgets::GetFontWidth() * 10); } if (Config->ShowVerbosity) { - ImGui::TableSetupColumn("Verbosity", ImGuiTableColumnFlags_WidthFixed, FCogWindowWidgets::GetFontWidth() * 10); + ImGui::TableSetupColumn("Verbosity", ImGuiTableColumnFlags_WidthFixed, FCogWidgets::GetFontWidth() * 10); } ImGui::TableSetupColumn("Message", ImGuiTableColumnFlags_WidthStretch); @@ -228,11 +284,11 @@ void FCogEngineWindow_OutputLog::RenderContent() if (Filter.IsActive()) { - for (int32 LineIndex = 0; LineIndex < LineInfos.Num(); LineIndex++) + for (int32 LineIndex = 0; LineIndex < LogInfos.Num(); LineIndex++) { - const FLineInfo& LineInfo = LineInfos[LineIndex]; - const char* LineStart = BufferStart + LineInfo.Start; - const char* LineEnd = BufferStart + LineInfo.End; + const FLogInfo& LineInfo = LogInfos[LineIndex]; + const char* LineStart = BufferStart + LineInfo.LineStart; + const char* LineEnd = BufferStart + LineInfo.LineEnd; if (Filter.PassFilter(LineStart, LineEnd)) { DrawRow(BufferStart, LineInfo, IsTableShown); @@ -241,14 +297,12 @@ void FCogEngineWindow_OutputLog::RenderContent() } else if (Config->VerbosityFilter != ELogVerbosity::VeryVerbose) { - for (int32 LineIndex = 0; LineIndex < LineInfos.Num(); LineIndex++) + for (int32 LineIndex = 0; LineIndex < LogInfos.Num(); LineIndex++) { - const FLineInfo& LineInfo = LineInfos[LineIndex]; + const FLogInfo& LineInfo = LogInfos[LineIndex]; - if (LineInfo.Verbosity <= (ELogVerbosity::Type)Config->VerbosityFilter) + if (LineInfo.Verbosity <= static_cast(Config->VerbosityFilter)) { - const char* LineStart = BufferStart + LineInfo.Start; - const char* LineEnd = BufferStart + LineInfo.End; DrawRow(BufferStart, LineInfo, IsTableShown); } } @@ -256,14 +310,14 @@ void FCogEngineWindow_OutputLog::RenderContent() else { ImGuiListClipper Clipper; - Clipper.Begin(LineInfos.Num()); + Clipper.Begin(LogInfos.Num()); while (Clipper.Step()) { for (int32 LineIndex = Clipper.DisplayStart; LineIndex < Clipper.DisplayEnd; LineIndex++) { - if (LineInfos.IsValidIndex(LineIndex)) + if (LogInfos.IsValidIndex(LineIndex)) { - const FLineInfo& LineInfo = LineInfos[LineIndex]; + const FLogInfo& LineInfo = LogInfos[LineIndex]; DrawRow(BufferStart, LineInfo, IsTableShown); } } @@ -292,6 +346,11 @@ void FCogEngineWindow_OutputLog::RenderContent() Clear(); } + if (ImGui::MenuItem("Copy")) + { + Copy(); + } + ImGui::EndPopup(); } } diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Plots.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Plots.cpp index 0399111..ede02ce 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Plots.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Plots.cpp @@ -1,8 +1,10 @@ #include "CogEngineWindow_Plots.h" -#include "CogDebugPlot.h" +#include "CogDebug.h" +#include "CogDebugTracker.h" #include "CogImguiHelper.h" -#include "CogWindowWidgets.h" +#include "CogSubsystem.h" +#include "CogWidgets.h" #include "Engine/World.h" #include "imgui.h" #include "implot_internal.h" @@ -13,11 +15,15 @@ void FCogEngineWindow_Plots::Initialize() Super::Initialize(); bHasMenu = true; - bNoPadding = true; + auto& Tracker = FCogDebug::GetTracker(); + Tracker.Clear(); + Config = GetConfig(); - - FCogDebugPlot::Clear(); + if (Config != nullptr) + { + RefreshPlotSettings(); + } } //-------------------------------------------------------------------------------------------------------------------------- @@ -33,15 +39,28 @@ void FCogEngineWindow_Plots::ResetConfig() { Super::ResetConfig(); - Config->Reset(); + RefreshPlotSettings(); } - //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Plots::RenderTick(float DeltaTime) { Super::RenderTick(DeltaTime); - FCogDebugPlot::IsVisible = GetIsVisible(); + + FCogDebugTracker& Tracker = FCogDebug::GetTracker(); + Tracker.IsVisible = GetIsVisible(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::PreBegin(ImGuiWindowFlags& WindowFlags) +{ + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::PostBegin() +{ + ImGui::PopStyleVar(); } //-------------------------------------------------------------------------------------------------------------------------- @@ -49,24 +68,9 @@ void FCogEngineWindow_Plots::RenderContent() { Super::RenderContent(); - TArray VisiblePlots; - for (FCogDebugPlotEntry& Plot : FCogDebugPlot::Plots) - { - if (Plot.YAxis != ImAxis_COUNT && Plot.GraphIndex != INDEX_NONE) - { - VisiblePlots.Add(&Plot); - } - } + FCogDebugTracker& Tracker = FCogDebug::GetTracker(); - for (FCogDebugPlotEntry& Event : FCogDebugPlot::Events) - { - if (Event.YAxis != ImAxis_COUNT && Event.GraphIndex != INDEX_NONE) - { - VisiblePlots.Add(&Event); - } - } - - RenderMenu(); + RenderMenu(Tracker); if (Config->DockEntries) { @@ -77,16 +81,16 @@ void FCogEngineWindow_Plots::RenderContent() | ImGuiTableFlags_NoPadOuterX)) { - ImGui::TableSetupColumn("PlotsList", ImGuiTableColumnFlags_WidthFixed, FCogWindowWidgets::GetFontWidth() * 20.0f); + ImGui::TableSetupColumn("PlotsList", ImGuiTableColumnFlags_WidthFixed, FCogWidgets::GetFontWidth() * 20.0f); ImGui::TableSetupColumn("Plots", ImGuiTableColumnFlags_WidthStretch, 0.0f); ImGui::TableNextRow(); ImGui::TableNextColumn(); - RenderAllEntriesNames(ImVec2(0, -1)); + RenderAllEntriesNames(Tracker, ImVec2(0, -1)); ImGui::TableNextColumn(); - RenderPlots(VisiblePlots); + RenderPlots(Tracker); ImGui::EndTable(); } @@ -94,14 +98,22 @@ void FCogEngineWindow_Plots::RenderContent() } else { - RenderPlots(VisiblePlots); + RenderPlots(Tracker); } bApplyTimeScale = false; } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Plots::RenderMenu() +void FCogEngineWindow_Plots::RefreshPlotSettings() +{ + FCogDebugTracker& Tracker = FCogDebug::GetTracker(); + Tracker.SetNumRecordedValues(Config->NumRecordedValues); + Tracker.RecordValuesWhenPause = Config->RecordValuesWhenPaused; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::RenderMenu(FCogDebugTracker& InTracker) { if (ImGui::BeginMenuBar()) { @@ -109,91 +121,120 @@ void FCogEngineWindow_Plots::RenderMenu() { if (ImGui::BeginMenu("Entries")) { - RenderAllEntriesNames(ImVec2(ImGui::GetFontSize() * 15, ImGui::GetFontSize() * 20)); + RenderAllEntriesNames(InTracker, ImVec2(ImGui::GetFontSize() * 15, ImGui::GetFontSize() * 20)); ImGui::EndMenu(); } } if (ImGui::BeginMenu("Options")) { - if (ImGui::MenuItem("Reset")) - { - FCogDebugPlot::Pause = false; - FCogDebugPlot::Reset(); - ResetConfig(); - } + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderInt("Num graphs", &Config->NumGraphs, 1, UCogEngineConfig_Plots::MaxNumGraphs); - ImGui::Separator(); - - FCogWindowWidgets::SetNextItemToShortWidth(); - if (ImGui::SliderInt("Num Graphs", &Config->NumGraphs, 1, 5)) + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderInt("Num Y axis", &Config->NumYAxis, 1, 3); + + FCogWidgets::SetNextItemToShortWidth(); + if (ImGui::SliderFloat("Time range", &Config->TimeRange, 1.0f, 100.0f, "%0.0f")) { bApplyTimeScale = true; } - FCogWindowWidgets::SetNextItemToShortWidth(); - ImGui::SliderInt("Num YAxis", &Config->NumYAxis, 0, 3); - - FCogWindowWidgets::SetNextItemToShortWidth(); - if (ImGui::SliderFloat("Time range", &Config->TimeRange, 1.0f, 100.0f, "%0.1f")) + FCogWidgets::SetNextItemToShortWidth(); + if (ImGui::SliderInt("Num recorded values", &Config->NumRecordedValues, 100, 10000)) { - bApplyTimeScale = true; + Config->NumRecordedValues = (Config->NumRecordedValues / 100) * 100; + } + + if (ImGui::IsItemDeactivatedAfterEdit()) + { + RefreshPlotSettings(); } - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderFloat("Auto-fit padding", &Config->AutoFitPadding, 0.0f, 0.2f, "%0.2f"); + + FCogWidgets::SetNextItemToShortWidth(); ImGui::SliderFloat("Drag pause sensitivity", &Config->DragPauseSensitivity, 1.0f, 50.0f, "%0.0f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + ImGui::Checkbox("Record values when paused", &Config->RecordValuesWhenPaused); + if (ImGui::IsItemDeactivatedAfterEdit()) + { + RefreshPlotSettings(); + } + + FCogWidgets::SetNextItemToShortWidth(); ImGui::Checkbox("Show time bar at game time", &Config->ShowTimeBarAtGameTime); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::Checkbox("Show time bar at cursor", &Config->ShowTimeBarAtCursor); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::Checkbox("Show value at cursor", &Config->ShowValueAtCursor); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::Checkbox("Dock entries", &Config->DockEntries); constexpr ImGuiColorEditFlags ColorEditFlags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf; FCogImguiHelper::ColorEdit4("Pause background color", Config->PauseBackgroundColor, ColorEditFlags); ImGui::SetItemTooltip("Background color of the plot when paused."); + ImGui::Separator(); + + if (ImGui::MenuItem("Reset Settings")) + { + InTracker.Pause = false; + InTracker.Reset(); + ResetConfig(); + bApplyTimeScale = true; + } + ImGui::EndMenu(); } if (ImGui::MenuItem("Clear")) { - FCogDebugPlot::Clear(); + InTracker.Clear(); } - FCogWindowWidgets::ToggleMenuButton(&FCogDebugPlot::Pause, "Pause", ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); + FCogWidgets::ToggleMenuButton(&InTracker.Pause, "Pause", ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); ImGui::EndMenuBar(); } } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Plots::RenderEntryName(const int Index, FCogDebugPlotEntry& Entry) +void FCogEngineWindow_Plots::RenderEntryName(FCogDebugTracker& InTracker, const int Index, FCogDebugTrack& Entry) { ImGui::PushID(Index); - const bool IsAssignedToRow = Entry.GraphIndex != INDEX_NONE; - if (ImGui::Selectable(TCHAR_TO_ANSI(*Entry.Name.ToString()), IsAssignedToRow, ImGuiSelectableFlags_AllowDoubleClick)) + bool IsAssignedToGraph = false; + + for (int32 i = 0; i < UCogEngineConfig_Plots::MaxNumGraphs; ++i) { - if (IsAssignedToRow) + FCogEngineConfig_Plots_GraphInfo& GraphInfo = Config->Graphs[i]; + if (GraphInfo.Entries.ContainsByPredicate([Entry](const auto& InEntry) { return InEntry.Name == Entry.Id; })) { - Entry.ResetGraphAndAxis(); + IsAssignedToGraph = true; + break; + } + } + + if (ImGui::Selectable(TCHAR_TO_ANSI(*Entry.Id.ToString()), IsAssignedToGraph, ImGuiSelectableFlags_AllowDoubleClick)) + { + if (IsAssignedToGraph) + { + UnassignToGraphAndAxis(InTracker, Entry.Id); } else { - Entry.AssignGraphAndAxis(0, ImAxis_Y1); + AssignToGraphAndAxis(InTracker, Entry.Id, 0, ImAxis_Y1); } } if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { - const auto EntryName = StringCast(*Entry.Name.ToString()); + const auto EntryName = StringCast(*Entry.Id.ToString()); ImGui::SetDragDropPayload("DragAndDrop", EntryName.Get(), EntryName.Length() + 1); ImGui::Text("%s", EntryName.Get()); ImGui::EndDragDropSource(); @@ -203,52 +244,48 @@ void FCogEngineWindow_Plots::RenderEntryName(const int Index, FCogDebugPlotEntry } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Plots::RenderAllEntriesNames(const ImVec2& InSize) +void FCogEngineWindow_Plots::RenderAllEntriesNames(FCogDebugTracker& InTracker, const ImVec2& InSize) { + const int32 Indent = ImGui::GetFontSize() * 0.5f; + if (ImGui::BeginChild("Entries", InSize)) { - if (Config->DockEntries) - { - ImGui::Indent(6); - } - int Index = 0; - if (FCogWindowWidgets::DarkCollapsingHeader("Events", ImGuiTreeNodeFlags_DefaultOpen)) + if (FCogWidgets::DarkCollapsingHeader("Events", ImGuiTreeNodeFlags_DefaultOpen)) { - if (FCogDebugPlot::Events.IsEmpty()) + ImGui::Indent(Indent); + if (InTracker.Events.IsEmpty()) { ImGui::TextDisabled("No event added yet"); } else { - for (FCogDebugPlotEntry& Event : FCogDebugPlot::Events) + for (auto& kv : InTracker.Events) { - RenderEntryName(Index, Event); + RenderEntryName(InTracker, Index, kv.Value); Index++; } } + ImGui::Unindent(Indent); } - if (FCogWindowWidgets::DarkCollapsingHeader("Plots", ImGuiTreeNodeFlags_DefaultOpen)) + if (FCogWidgets::DarkCollapsingHeader("Plots", ImGuiTreeNodeFlags_DefaultOpen)) { - if (FCogDebugPlot::Plots.IsEmpty()) + ImGui::Indent(Indent); + if (InTracker.Values.IsEmpty()) { ImGui::TextDisabled("No plot added yet"); } else { - for (FCogDebugPlotEntry& Plot : FCogDebugPlot::Plots) + for (auto& kv : InTracker.Values) { - RenderEntryName(Index, Plot); + RenderEntryName(InTracker, Index, kv.Value); Index++; } } - } - - if (Config->DockEntries) - { - ImGui::Unindent(); + ImGui::Unindent(Indent); } } ImGui::EndChild(); @@ -257,17 +294,14 @@ void FCogEngineWindow_Plots::RenderAllEntriesNames(const ImVec2& InSize) { if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) { - if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindEntry(FName((const char*)Payload->Data))) - { - Plot->ResetGraphAndAxis(); - } + UnassignToGraphAndAxis(InTracker, GetDroppedEntryName(Payload)); } ImGui::EndDragDropTarget(); } } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Plots::RenderPlots(const TArray& VisiblePlots) const +void FCogEngineWindow_Plots::RenderPlots(FCogDebugTracker& InTracker) { if (ImGui::BeginChild("Graph", ImVec2(0, -1))) { @@ -275,51 +309,72 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi static float ColRatios[] = { 1 }; static ImPlotSubplotFlags SubplotsFlags = ImPlotSubplotFlags_LinkCols; - const bool PushPlotBgStyle = FCogDebugPlot::Pause; + const bool PushPlotBgStyle = InTracker.Pause; if (PushPlotBgStyle) { ImPlot::PushStyleColor(ImPlotCol_PlotBg, FCogImguiHelper::ToImVec4(Config->PauseBackgroundColor)); } + ImPlot::PushStyleVar(ImPlotStyleVar_FitPadding, ImVec2(0.0f, Config->AutoFitPadding)); + if (ImPlot::BeginSubplots("", Config->NumGraphs, 1, ImVec2(-1, -1), SubplotsFlags, RowRatios, ColRatios)) { - for (int PlotIndex = 0; PlotIndex < Config->NumGraphs; ++PlotIndex) + for (int32 GraphIndex = 0; GraphIndex < Config->NumGraphs && GraphIndex < UCogEngineConfig_Plots::MaxNumGraphs; ++GraphIndex) { + ImGui::PushID(GraphIndex); + + FCogEngineConfig_Plots_GraphInfo& GraphInfo = Config->Graphs[GraphIndex]; + if (ImPlot::BeginPlot("##Plot", ImVec2(-1, 250))) { - ImPlotAxisFlags HasPlotOnAxisY1 = false; - ImPlotAxisFlags HasPlotOnAxisY2 = false; - ImPlotAxisFlags HasPlotOnAxisY3 = false; - - for (const FCogDebugPlotEntry* PlotPtr : VisiblePlots) - { - HasPlotOnAxisY1 |= PlotPtr->YAxis == ImAxis_Y1 && PlotPtr->GraphIndex == PlotIndex; - HasPlotOnAxisY2 |= PlotPtr->YAxis == ImAxis_Y2 && PlotPtr->GraphIndex == PlotIndex; - HasPlotOnAxisY3 |= PlotPtr->YAxis == ImAxis_Y3 && PlotPtr->GraphIndex == PlotIndex; - } - ImPlot::SetupAxis(ImAxis_X1, nullptr, ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines); - if (Config->NumYAxis > 0) - { - ImPlot::SetupAxis(ImAxis_Y1, HasPlotOnAxisY1 ? "" : "[drop here]", (HasPlotOnAxisY1 ? ImPlotAxisFlags_None : (ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines)) | ImPlotAxisFlags_AutoFit); - } - - if (Config->NumYAxis > 1) - { - ImPlot::SetupAxis(ImAxis_Y2, HasPlotOnAxisY2 ? "" : "[drop here]", (HasPlotOnAxisY2 ? ImPlotAxisFlags_None : (ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines)) | ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_Opposite); - } - - if (Config->NumYAxis > 2) - { - ImPlot::SetupAxis(ImAxis_Y3, HasPlotOnAxisY3 ? "" : "[drop here]", (HasPlotOnAxisY3 ? ImPlotAxisFlags_None : (ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines)) | ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_Opposite); - } - //-------------------------------------------------------------------------------------------------- // Set the initial X axis range. After, it is automatically updated to move with the current time. //-------------------------------------------------------------------------------------------------- ImPlot::SetupAxisLimits(ImAxis_X1, 0, Config->TimeRange, ImGuiCond_Appearing); + //-------------------------------------------------------------------------------------------------- + // Setup the Y axis + //-------------------------------------------------------------------------------------------------- + for (int32 YAxisIndex = 0; YAxisIndex <= (ImAxis_Y3 - ImAxis_Y1); ++YAxisIndex) + { + const ImAxis YAxis = ImAxis_Y1 + YAxisIndex; + + bool IsAssigned = false; + int32 YMax = 0; + for (const FCogEngineConfig_Plots_GraphEntryInfo& GraphEntry : GraphInfo.Entries) + { + IsAssigned |= GraphEntry.YAxis == YAxis; + + if (FCogDebugEventTrack* EventHistory = InTracker.Events.Find(GraphEntry.Name)) + { + YMax = FMath::Max(FMath::Max(5, YMax), EventHistory->MaxRow); + } + } + + const bool IsAxisVisible = IsAssigned || (YAxisIndex < Config->NumYAxis); + if (IsAxisVisible) + { + ImPlotAxisFlags Flags = IsAssigned ? ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_AutoFit + : ImPlotAxisFlags_NoTickLabels | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_AutoFit; + if (YAxisIndex > 0) + { + Flags |= ImPlotAxisFlags_Opposite; + } + + ImPlot::SetupAxis(YAxis, IsAssigned || (Config->NumYAxis == 1) ? "" : "[drop here]", Flags); + + //-------------------------------------------------------------------------------- + // Set the Y axis limit for Events. + //-------------------------------------------------------------------------------- + if (YMax > 0) + { + ImPlot::SetupAxisLimits(YAxis, 0, YMax, ImGuiCond_Always); + } + } + } + const ImPlotRange& PlotRange = ImPlot::GetCurrentPlot()->Axes[ImAxis_X1].Range; const float TimeRange = PlotRange.Max - PlotRange.Min; @@ -328,17 +383,18 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi Config->TimeRange = TimeRange; } - const float Time = GetWorld() ? GetWorld()->GetTimeSeconds() : 0.0; + const UWorld* World = GetWorld(); + const float Time = World != nullptr ? World->GetTimeSeconds() : 0.0; //------------------------------------------------------------------ - // Setup all the Z and Y axis limits. Must be done before calling + // Setup all the X axis limits. Must be done before calling // ImPlot::GetPlotPos or ImPlot::GetPlotSize as it calls SetupLock() //------------------------------------------------------------------ { //-------------------------------------------------------------------------------- // Make the time axis move forward automatically, unless the user pauses or zoom. //-------------------------------------------------------------------------------- - if (FCogDebugPlot::Pause == false && ImGui::GetIO().MouseWheel == 0) + if (InTracker.Pause == false && ImGui::GetIO().MouseWheel == 0) { ImPlot::SetupAxisLimits(ImAxis_X1, Time - TimeRange, Time, ImGuiCond_Always); } @@ -347,23 +403,6 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi { ImPlot::SetupAxisLimits(ImAxis_X1, Time - Config->TimeRange, Time, ImGuiCond_Always); } - - //-------------------------------------------------------------------------------- - // Set the Y axis limit for Events. - //-------------------------------------------------------------------------------- - for (const FCogDebugPlotEntry* PlotPtr : VisiblePlots) - { - if (PlotPtr == nullptr) - { continue; } - - if (PlotPtr->GraphIndex != PlotIndex) - { continue; } - - if (PlotPtr->IsEventPlot) - { - ImPlot::SetupAxisLimits(PlotPtr->YAxis, 0, PlotPtr->MaxRow + 2, ImGuiCond_Always); - } - } } const ImVec2 PlotMin = ImPlot::GetPlotPos(); @@ -373,23 +412,27 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi //---------------------------------------------------------------- // Pause the scrolling if the user drag inside //---------------------------------------------------------------- - const ImVec2 Mouse = ImGui::GetMousePos(); - if (Mouse.x > PlotMin.x - && Mouse.y > PlotMin.y - && Mouse.x < PlotMax.x - && Mouse.y < PlotMax.y - && ImGui::GetDragDropPayload() == nullptr) + if (ImGui::IsWindowFocused()) { - const ImVec2 Drag = ImGui::GetMouseDragDelta(0); - if (FMath::Abs(Drag.x) > Config->DragPauseSensitivity) + const ImVec2 Mouse = ImGui::GetMousePos(); + if (Mouse.x > PlotMin.x + && Mouse.y > PlotMin.y + && Mouse.x < PlotMax.x + && Mouse.y < PlotMax.y + && ImGui::GetDragDropPayload() == nullptr) { - FCogDebugPlot::Pause = true; + const ImVec2 Drag = ImGui::GetMouseDragDelta(0); + + if (FMath::Abs(Drag.x) > Config->DragPauseSensitivity) + { + InTracker.Pause = true; + } } } if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - FCogDebugPlot::Pause = false; + InTracker.Pause = false; } //--------------------------------------------------------------------------- @@ -408,7 +451,7 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi PlotDrawList->AddLine(ImVec2(ImGui::GetMousePos().x, PlotTop), ImVec2(ImGui::GetMousePos().x, TimeBarBottom), IM_COL32(128, 128, 128, 64)); } - if (Config->ShowTimeBarAtGameTime && FCogDebugPlot::Pause) + if (Config->ShowTimeBarAtGameTime && InTracker.Pause) { const float TimeBarX = ImPlot::PlotToPixels(Time, 0.0f).x; PlotDrawList->AddLine(ImVec2(TimeBarX, PlotTop), ImVec2(TimeBarX, TimeBarBottom), IM_COL32(255, 255, 255, 64)); @@ -417,43 +460,46 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi } //----------------------------------------------------------- - // Draw all the plots assigned to this row + // Draw all the plots assigned to this graph //----------------------------------------------------------- - for (FCogDebugPlotEntry* PlotPtr : VisiblePlots) + for (FCogEngineConfig_Plots_GraphEntryInfo& Entry : GraphInfo.Entries) { - if (PlotPtr == nullptr) - { continue; } + FCogDebugTrack* Track = InTracker.FindTrack(Entry.Name); + if (Track == nullptr) + { + continue; + } - FCogDebugPlotEntry& Plot = *PlotPtr; - if (Plot.GraphIndex != PlotIndex) - { continue; } - - ImPlot::SetAxis(Plot.YAxis); + ImPlot::SetAxis(Entry.YAxis); ImPlot::SetNextLineStyle(IMPLOT_AUTO_COL); - const auto Label = StringCast(*Plot.Name.ToString()); + const auto Label = StringCast(*Entry.Name.ToString()); //------------------------------------------------------- // Plot Events //------------------------------------------------------- - if (Plot.IsEventPlot) + switch (Track->Type) { - RenderEvents(Plot, Label.Get(), PlotMin, PlotMax); - } - //------------------------------------------------------- - // Plot Values - //------------------------------------------------------- - else if (Plot.Values.empty() == false) - { - RenderValues(Plot, Label.Get()); + case ECogDebugTrackType::Event: + { + RenderEvents(*static_cast(Track), Label.Get(), PlotMin, PlotMax); + break; + } + + case ECogDebugTrackType::Value: + { + RenderValues(*static_cast(Track), Label.Get()); + break; + } } + //------------------------------------------------------- // Allow legend item labels to be drag and drop sources //------------------------------------------------------- if (ImPlot::BeginDragDropSourceItem(Label.Get())) { - const auto EntryName = StringCast(*Plot.Name.ToString()); + const auto EntryName = StringCast(*Entry.Name.ToString()); ImGui::SetDragDropPayload("DragAndDrop", EntryName.Get(), EntryName.Length() + 1); ImGui::TextUnformatted(EntryName.Get()); ImPlot::EndDragDropSource(); @@ -467,10 +513,7 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi { if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) { - if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindEntry(FName((const char*)Payload->Data))) - { - Plot->AssignGraphAndAxis(PlotIndex, ImAxis_Y1); - } + AssignToGraphAndAxis(InTracker, GetDroppedEntryName(Payload), GraphIndex, ImAxis_Y1); } ImPlot::EndDragDropTarget(); } @@ -478,16 +521,14 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi //------------------------------------------------------- // Allow each y-axis to be a drag and drop target //------------------------------------------------------- - for (int y = ImAxis_Y1; y <= ImAxis_Y3; ++y) + for (int32 YAxisIndex = 0; YAxisIndex < Config->NumYAxis; ++YAxisIndex) { - if (ImPlot::BeginDragDropTargetAxis(y)) + const ImAxis YAxis = ImAxis_Y1 + YAxisIndex; + if (ImPlot::BeginDragDropTargetAxis(YAxis)) { if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) { - if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindEntry(FName((const char*)Payload->Data))) - { - Plot->AssignGraphAndAxis(PlotIndex, y); - } + AssignToGraphAndAxis(InTracker, GetDroppedEntryName(Payload), GraphIndex, YAxis); } ImPlot::EndDragDropTarget(); } @@ -500,20 +541,21 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi { if (const ImGuiPayload* Payload = ImGui::AcceptDragDropPayload("DragAndDrop")) { - if (FCogDebugPlotEntry* Plot = FCogDebugPlot::FindEntry(FName((const char*)Payload->Data))) - { - Plot->AssignGraphAndAxis(PlotIndex, ImAxis_Y1); - } + AssignToGraphAndAxis(InTracker, GetDroppedEntryName(Payload), GraphIndex, ImAxis_Y1); } ImPlot::EndDragDropTarget(); } ImPlot::EndPlot(); } + + ImGui::PopID(); } ImPlot::EndSubplots(); } + ImPlot::PopStyleVar(); + if (PushPlotBgStyle) { ImPlot::PopStyleColor(); @@ -523,17 +565,22 @@ void FCogEngineWindow_Plots::RenderPlots(const TArray& Visi } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Plots::RenderValues(FCogDebugPlotEntry& Entry, const char* Label) const +void FCogEngineWindow_Plots::RenderValues(FCogDebugPlotTrack& Timeline, const char* Label) const { + if (Timeline.Values.empty()) + { + return; + } + //---------------------------------------------------------------- // Value at cursor tooltip //---------------------------------------------------------------- if (Config->ShowValueAtCursor && ImPlot::IsPlotHovered()) { float Value; - if (Entry.FindValue(ImPlot::GetPlotMousePos().x, Value)) + if (Timeline.FindValue(ImPlot::GetPlotMousePos().x, Value)) { - if (FCogWindowWidgets::BeginTableTooltip()) + if (FCogWidgets::BeginTableTooltip()) { if (ImGui::BeginTable("Params", 2, ImGuiTableFlags_Borders)) { @@ -544,31 +591,31 @@ void FCogEngineWindow_Plots::RenderValues(FCogDebugPlotEntry& Entry, const char* ImGui::Text("%0.2f", Value); ImGui::EndTable(); } - FCogWindowWidgets::EndTableTooltip(); + FCogWidgets::EndTableTooltip(); } } } - if (Entry.ShowValuesMarkers) + if (Timeline.ShowValuesMarkers) { ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle); } - ImPlot::PlotLine(Label, &Entry.Values[0].x, &Entry.Values[0].y, Entry.Values.size(), ImPlotLineFlags_None, Entry.ValueOffset, 2 * sizeof(float)); + ImPlot::PlotLine(Label, &Timeline.Values[0].x, &Timeline.Values[0].y, Timeline.Values.size(), ImPlotLineFlags_None, Timeline.ValueOffset, 2 * sizeof(float)); if (ImPlot::BeginLegendPopup(Label)) { if (ImGui::Button("Clear")) { - Entry.Clear(); + Timeline.Clear(); } - ImGui::Checkbox("Show Markers", &Entry.ShowValuesMarkers); + ImGui::Checkbox("Show Markers", &Timeline.ShowValuesMarkers); ImPlot::EndLegendPopup(); } } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Plots::RenderEvents(FCogDebugPlotEntry& Entry, const char* Label, const ImVec2& PlotMin, const ImVec2& PlotMax) const +void FCogEngineWindow_Plots::RenderEvents(FCogDebugEventTrack& InTrack, const char* InLabel, const ImVec2& InPlotMin, const ImVec2& InPlotMax) const { const ImVec2 Mouse = ImGui::GetMousePos(); ImDrawList* PlotDrawList = ImPlot::GetPlotDrawList(); @@ -581,15 +628,20 @@ void FCogEngineWindow_Plots::RenderEvents(FCogDebugPlotEntry& Entry, const char* ImVector DummyData; DummyData.push_back(ImVec2(0, 0)); DummyData.push_back(ImVec2(0, 8)); - ImPlot::PlotLine(Label, &DummyData[0].x, &DummyData[0].y, DummyData.size(), Entry.ValueOffset, 2 * sizeof(float)); + ImPlot::PlotLine(InLabel, &DummyData[0].x, &DummyData[0].y, DummyData.size(), InTrack.EventOffset, 2 * sizeof(float)); - const FCogDebugPlotEvent* HoveredEvent = nullptr; + const FCogDebugEvent* HoveredEvent = nullptr; - for (const FCogDebugPlotEvent& Event : Entry.Events) + for (const FCogDebugEvent& Event : InTrack.Events) { - const ImVec2 PosBot = ImPlot::PlotToPixels(ImPlotPoint(Event.StartTime, Event.Row + 0.8f)); - const ImVec2 PosTop = ImPlot::PlotToPixels(ImPlotPoint(Event.StartTime, Event.Row + 0.2f)); - const ImVec2 PosMid(PosBot.x, PosBot.y + (PosTop.y - PosBot.y) * 0.5f); + const ImVec2 PosStartBot = ImPlot::PlotToPixels(ImPlotPoint(Event.StartTime, Event.Row + 0.8f)); + const ImVec2 PosStartTop = ImPlot::PlotToPixels(ImPlotPoint(Event.StartTime, Event.Row + 0.2f)); + const ImVec2 PosMid(PosStartBot.x, PosStartBot.y + (PosStartTop.y - PosStartBot.y) * 0.5f); + const ImVec2 PosEnd = ImPlot::PlotToPixels(ImPlotPoint(Event.GetActualEndTime(*GetWorld()), 0)); + + // Clipping + if (PosStartBot.x > InPlotMax.x || PosEnd.x < InPlotMin.x) + { continue; } const bool IsInstant = Event.StartTime == Event.EndTime; if (IsInstant) @@ -606,15 +658,13 @@ void FCogEngineWindow_Plots::RenderEvents(FCogDebugPlotEntry& Entry, const char* } else { - const float ActualEndTime = Event.GetActualEndTime(Entry); - const ImVec2 PosEnd = ImPlot::PlotToPixels(ImPlotPoint(ActualEndTime, 0)); - const ImVec2 Min = ImVec2(PosBot.x, PosBot.y); - const ImVec2 Max = ImVec2(PosEnd.x, PosTop.y); + const ImVec2 Min = ImVec2(PosStartBot.x, PosStartBot.y); + const ImVec2 Max = ImVec2(PosEnd.x, PosStartTop.y); const ImDrawFlags Flags = Event.EndTime == 0.0f ? ImDrawFlags_RoundCornersLeft : ImDrawFlags_RoundCornersAll; PlotDrawList->AddRect(Min, Max, Event.BorderColor, 6.0f, Flags); PlotDrawList->AddRectFilled(Min, Max, Event.FillColor, 6.0f, Flags); - PlotDrawList->PushClipRect(ImMax(Min, PlotMin), ImMin(Max, PlotMax)); + PlotDrawList->PushClipRect(ImMax(Min, InPlotMin), ImMin(Max, InPlotMax)); PlotDrawList->AddText(ImVec2(PosMid.x + 5, PosMid.y - 7), IM_COL32(255, 255, 255, 255), TCHAR_TO_ANSI(*Event.DisplayName)); PlotDrawList->PopClipRect(); @@ -630,22 +680,22 @@ void FCogEngineWindow_Plots::RenderEvents(FCogDebugPlotEntry& Entry, const char* //------------------------------------------------------- //char Buffer[64]; //ImFormatString(Buffer, 64, "%0.1f %0.1f", Mouse.x, Mouse.y); - //PlotDrawList->AddText(ImVec2(PlotMin.x + 50, PlotMin.y + 100), IM_COL32(255, 255, 255, 255), Buffer); + //PlotDrawList->AddText(ImVec2(InPlotMin.x + 50, InPlotMin.y + 100), IM_COL32(255, 255, 255, 255), Buffer); //------------------------------------------------------- // Hovered event tooltip //------------------------------------------------------- - RenderEventTooltip(HoveredEvent, Entry); + RenderEventTooltip(HoveredEvent, InTrack); ImPlot::PopPlotClipRect(); } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Plots::RenderEventTooltip(const FCogDebugPlotEvent* HoveredEvent, const FCogDebugPlotEntry& Entry) +void FCogEngineWindow_Plots::RenderEventTooltip(const FCogDebugEvent* HoveredEvent, const FCogDebugTrack& Entry) const { if (ImPlot::IsPlotHovered() && HoveredEvent != nullptr) { - if (FCogWindowWidgets::BeginTableTooltip()) + if (FCogWidgets::BeginTableTooltip()) { if (ImGui::BeginTable("Params", 2, ImGuiTableFlags_Borders)) { @@ -672,8 +722,8 @@ void FCogEngineWindow_Plots::RenderEventTooltip(const FCogDebugPlotEvent* Hovere //------------------------ if (HoveredEvent->EndTime != HoveredEvent->StartTime) { - const float ActualEndTime = HoveredEvent->GetActualEndTime(Entry); - const uint64 ActualEndFrame = HoveredEvent->GetActualEndFrame(Entry); + const float ActualEndTime = HoveredEvent->GetActualEndTime(*GetWorld()); + const uint64 ActualEndFrame = HoveredEvent->GetActualEndFrame(); ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -686,9 +736,9 @@ void FCogEngineWindow_Plots::RenderEventTooltip(const FCogDebugPlotEvent* Hovere ImGui::Text("Frames"); ImGui::TableNextColumn(); ImGui::Text("%d [%d-%d]", - (int32)(ActualEndFrame - HoveredEvent->StartFrame), - (int32)(HoveredEvent->StartFrame % 1000), - (int32)(ActualEndFrame % 1000)); + static_cast(ActualEndFrame - HoveredEvent->StartFrame), + static_cast(HoveredEvent->StartFrame % 1000), + static_cast(ActualEndFrame % 1000)); } else { @@ -696,13 +746,13 @@ void FCogEngineWindow_Plots::RenderEventTooltip(const FCogDebugPlotEvent* Hovere ImGui::TableNextColumn(); ImGui::Text("Frame"); ImGui::TableNextColumn(); - ImGui::Text("%d", (int32)(HoveredEvent->StartFrame % 1000)); + ImGui::Text("%d", static_cast(HoveredEvent->StartFrame % 1000)); } //------------------------ // Params //------------------------ - for (FCogDebugPlotEventParams Param : HoveredEvent->Params) + for (FCogDebugEventParams Param : HoveredEvent->Params) { ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -712,7 +762,58 @@ void FCogEngineWindow_Plots::RenderEventTooltip(const FCogDebugPlotEvent* Hovere } ImGui::EndTable(); } - FCogWindowWidgets::EndTableTooltip(); + FCogWidgets::EndTableTooltip(); } } } + + +//-------------------------------------------------------------------------------------------------------------------------- +FName FCogEngineWindow_Plots::GetDroppedEntryName(const ImGuiPayload* Payload) +{ + return FName(static_cast(Payload->Data)); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::AssignToGraphAndAxis(FCogDebugTracker& InTracker, const FName InName, const int32 InGraphIndex, const ImAxis InYAxis) +{ + UnassignToGraphAndAxis(InTracker, InName); + + FCogDebugTrack* History = InTracker.FindTrack(InName); + if (History == nullptr) + { return; } + + History->GraphIndex = InGraphIndex; + + FCogEngineConfig_Plots_GraphInfo& GraphInfo = Config->Graphs[InGraphIndex]; + + FCogEngineConfig_Plots_GraphEntryInfo* CorrespondingEntry = GraphInfo.Entries.FindByPredicate( + [InName](const auto& InEntry) { return InEntry.Name == InName; }); + + if (CorrespondingEntry == nullptr) + { + GraphInfo.Entries.Add({InName, InYAxis}); + } + else + { + CorrespondingEntry->YAxis = InYAxis; + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Plots::UnassignToGraphAndAxis(FCogDebugTracker& InTracker, const FName InName) +{ + const FCogDebugTrack* History = InTracker.FindTrack(InName); + if (History == nullptr) + { return; } + + FCogEngineConfig_Plots_GraphInfo& GraphInfo = Config->Graphs[History->GraphIndex]; + + const int32 Index = GraphInfo.Entries.IndexOfByPredicate([InName](const auto& InEntry) { return InEntry.Name == InName; }); + if (Index != INDEX_NONE) + { + GraphInfo.Entries.RemoveAt(Index); + } +} + + diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Scalability.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Scalability.cpp index 18beba8..6360b73 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Scalability.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Scalability.cpp @@ -1,7 +1,7 @@ #include "CogEngineWindow_Scalability.h" #include "imgui.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "Engine/Engine.h" #include "Scalability.h" @@ -22,7 +22,7 @@ void FCogEngineWindow_Scalability::RenderContent() Scalability::FQualityLevels Levels = Scalability::GetQualityLevels(); const FString CurrentQualityName = Scalability::GetQualityLevelText(Levels.GetMinQualityLevel(), SCALABILITY_NUM_LEVELS).ToString(); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); if (ImGui::BeginCombo("Scalability", TCHAR_TO_ANSI(*CurrentQualityName))) { for (int32 i = 0; i < SCALABILITY_NUM_LEVELS; ++i) @@ -45,37 +45,37 @@ void FCogEngineWindow_Scalability::RenderContent() bool Modified = false; - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); Modified |= ImGui::SliderFloat("Resolution", &Levels.ResolutionQuality, 10.0f, 100.0f, "%0.f"); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); Modified |= ImGui::SliderInt("View Distance", &Levels.ViewDistanceQuality, 0, SCALABILITY_NUM_LEVELS - 1); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); Modified |= ImGui::SliderInt("Anti Aliasing", &Levels.AntiAliasingQuality, 0, SCALABILITY_NUM_LEVELS - 1); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); Modified |= ImGui::SliderInt("Shadow", &Levels.ShadowQuality, 0, SCALABILITY_NUM_LEVELS - 1); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); Modified |= ImGui::SliderInt("Global Illumination", &Levels.GlobalIlluminationQuality, 0, SCALABILITY_NUM_LEVELS - 1); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); Modified |= ImGui::SliderInt("Reflection", &Levels.ReflectionQuality, 0, SCALABILITY_NUM_LEVELS - 1); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); Modified |= ImGui::SliderInt("Post Process", &Levels.PostProcessQuality, 0, SCALABILITY_NUM_LEVELS - 1); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); Modified |= ImGui::SliderInt("Texture", &Levels.TextureQuality, 0, SCALABILITY_NUM_LEVELS - 1); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); Modified |= ImGui::SliderInt("Effects", &Levels.EffectsQuality, 0, SCALABILITY_NUM_LEVELS - 1); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); Modified |= ImGui::SliderInt("Foliage", &Levels.FoliageQuality, 0, SCALABILITY_NUM_LEVELS - 1); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); Modified |= ImGui::SliderInt("Shading", &Levels.ShadingQuality, 0, SCALABILITY_NUM_LEVELS - 1); if (Modified) diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Selection.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Selection.cpp index 710ede4..1fdc994 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Selection.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Selection.cpp @@ -6,9 +6,10 @@ #include "CogEngineWindow_ImGui.h" #include "CogImguiHelper.h" #include "CogImguiInputHelper.h" -#include "CogWindowConsoleCommandManager.h" -#include "CogWindowManager.h" -#include "CogWindowWidgets.h" +#include "CogConsoleCommandManager.h" +#include "CogSubsystem.h" +#include "CogWidgets.h" +#include "CogWindow_Settings.h" #include "Components/PrimitiveComponent.h" #include "EngineUtils.h" #include "GameFramework/Character.h" @@ -25,17 +26,21 @@ void FCogEngineWindow_Selection::Initialize() bHasMenu = true; bHasWidget = true; - ActorClasses = { AActor::StaticClass(), ACharacter::StaticClass() }; + bIsWidgetVisible = true; Config = GetConfig(); - FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand( + GetOwner()->AddShortcut(Config.Get(), &UCogEngineConfig_Selection::Shortcut_ToggleSelection).BindLambda([this] (){ GetOwner()->SetActivateSelectionMode(!GetOwner()->GetActivateSelectionMode()); }); + + Asset = GetAsset(); + + FCogConsoleCommandManager::RegisterWorldConsoleCommand( *ToggleSelectionModeCommand, TEXT("Toggle the actor selection mode"), GetWorld(), FCogWindowConsoleCommandDelegate::CreateLambda([this](const TArray& InArgs, UWorld* InWorld) { - ToggleSelectionMode(); + GetOwner()->SetActivateSelectionMode(!GetOwner()->GetActivateSelectionMode()); })); TryReapplySelection(); @@ -57,19 +62,14 @@ void FCogEngineWindow_Selection::Shutdown() { } -//-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Selection::ResetConfig() -{ - Super::ResetConfig(); - - Config->Reset(); -} - //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Selection::PreSaveConfig() { Super::PreSaveConfig(); + if (Config == nullptr) + { return; } + Config->SelectionName = GetNameSafe(GetSelection()); } @@ -113,78 +113,44 @@ void FCogEngineWindow_Selection::TryReapplySelection() const TSubclassOf FCogEngineWindow_Selection::GetSelectedActorClass() const { TSubclassOf SelectedClass = AActor::StaticClass(); - if (ActorClasses.IsValidIndex(Config->SelectedClassIndex)) + const TArray>& SelectionFilters = GetSelectionFilters(); + if (SelectionFilters.IsValidIndex(Config->SelectedClassIndex)) { - SelectedClass = ActorClasses[Config->SelectedClassIndex]; + SelectedClass = SelectionFilters[Config->SelectedClassIndex]; } return SelectedClass; } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Selection::ToggleSelectionMode() -{ - if (bSelectionModeActive) - { - DeactivateSelectionMode(); - } - else - { - ActivateSelectionMode(); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Selection::ActivateSelectionMode() -{ - bSelectionModeActive = true; - bIsInputEnabledBeforeEnteringSelectionMode = GetOwner()->GetContext().GetEnableInput(); - GetOwner()->GetContext().SetEnableInput(true); - GetOwner()->SetActivateSelectionMode(true); -} - -//-------------------------------------------------------------------------------------------------------------------------- - void FCogEngineWindow_Selection::HackWaitInputRelease() { WaitInputReleased = 1; } -//-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Selection::DeactivateSelectionMode() -{ - bSelectionModeActive = false; - - //-------------------------------------------------------------------------------------------- - // We can enter selection mode by a command, and ImGui might not have the input focus - // When in selection mode we need ImGui to have the input focus - // When leaving selection mode we want to leave it as it was before - //-------------------------------------------------------------------------------------------- - GetOwner()->GetContext().SetEnableInput(bIsInputEnabledBeforeEnteringSelectionMode); - - GetOwner()->SetActivateSelectionMode(false); -} - //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Selection::RenderTick(float DeltaTime) { Super::RenderTick(DeltaTime); - if (FCogDebug::GetSelection() == nullptr) + if (GetSelection() == nullptr) { SetGlobalSelection(GetLocalPlayerPawn()); } - if (bSelectionModeActive) + if (GetOwner()->GetActivateSelectionMode()) { - TickSelectionMode(); + if (TickSelectionMode() == false) + { + GetOwner()->SetActivateSelectionMode(false); + } } if (const AActor* Actor = GetSelection()) { if (Actor != GetLocalPlayerPawn()) { - FCogWindowWidgets::ActorFrame(*Actor); + FCogWidgets::ActorFrame(*Actor); } } } @@ -198,7 +164,7 @@ void FCogEngineWindow_Selection::RenderContent() { if (ImGui::MenuItem("Pick")) { - ActivateSelectionMode(); + GetOwner()->SetActivateSelectionMode(true); //HackWaitInputRelease(); } @@ -228,7 +194,7 @@ void FCogEngineWindow_Selection::RenderContent() bool FCogEngineWindow_Selection::DrawSelectionCombo() { AActor* NewSelection = nullptr; - const bool result = FCogWindowWidgets::ActorsListWithFilters(NewSelection, *GetWorld(), ActorClasses, Config->SelectedClassIndex, &Filter, GetLocalPlayerPawn()); + const bool result = FCogWidgets::ActorsListWithFilters(NewSelection, *GetWorld(), GetSelectionFilters(), Config->SelectedClassIndex, &Filter, GetLocalPlayerPawn()); if (result) { SetGlobalSelection(NewSelection); @@ -238,32 +204,24 @@ bool FCogEngineWindow_Selection::DrawSelectionCombo() } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Selection::TickSelectionMode() +bool FCogEngineWindow_Selection::TickSelectionMode() { if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) - { - DeactivateSelectionMode(); - return; - } + { return false; } APlayerController* PlayerController = GetLocalPlayerController(); if (PlayerController == nullptr) - { - DeactivateSelectionMode(); - return; - } + { return false; } ImGuiViewport* Viewport = ImGui::GetMainViewport(); if (Viewport == nullptr) - { - return; - } + { return false; } const ImVec2 ViewportPos = Viewport->Pos; const ImVec2 ViewportSize = Viewport->Size; ImDrawList* DrawList = ImGui::GetBackgroundDrawList(Viewport); DrawList->AddRect(ViewportPos, ViewportPos + ViewportSize, IM_COL32(255, 0, 0, 128), 0.0f, 0, 20.0f); - FCogWindowWidgets::AddTextWithShadow(DrawList, ViewportPos + ImVec2(20, 20), IM_COL32(255, 255, 255, 255), "Picking Mode. \n[LMB] Pick \n[RMB] Cancel"); + FCogWidgets::AddTextWithShadow(DrawList, ViewportPos + ImVec2(20, 20), IM_COL32(255, 255, 255, 255), "Picking Mode. \n[LMB] Pick \n[RMB] Cancel"); TSubclassOf SelectedActorClass = GetSelectedActorClass(); @@ -283,12 +241,12 @@ void FCogEngineWindow_Selection::TickSelectionMode() // Prioritize another actor than the selected actor unless we only touch the selected actor. //-------------------------------------------------------------------------------------------------------- TArray IgnoreList; - IgnoreList.Add(FCogDebug::GetSelection()); + IgnoreList.Add(GetSelection()); FHitResult HitResult; for (int i = 0; i < 2; ++i) { - if (UKismetSystemLibrary::LineTraceSingle(GetWorld(), WorldOrigin, WorldOrigin + WorldDirection * 10000, TraceType, false, IgnoreList, EDrawDebugTrace::None, HitResult, true)) + if (UKismetSystemLibrary::LineTraceSingle(GetWorld(), WorldOrigin, WorldOrigin + WorldDirection * 10000, GetSelectionTraceChannel(), false, IgnoreList, EDrawDebugTrace::None, HitResult, true)) { if (SelectedActorClass == nullptr || HitResult.GetActor()->GetClass()->IsChildOf(SelectedActorClass)) { @@ -306,10 +264,10 @@ void FCogEngineWindow_Selection::TickSelectionMode() if (HoveredActor != nullptr) { - FCogWindowWidgets::ActorFrame(*HoveredActor); + FCogWidgets::ActorFrame(*HoveredActor); } - if (bSelectionModeActive) + if (GetOwner()->GetActivateSelectionMode()) { if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { @@ -320,7 +278,7 @@ void FCogEngineWindow_Selection::TickSelectionMode() SetGlobalSelection(HoveredActor); } - DeactivateSelectionMode(); + GetOwner()->SetActivateSelectionMode(false); } else { @@ -328,80 +286,41 @@ void FCogEngineWindow_Selection::TickSelectionMode() } } } + + return true; } //-------------------------------------------------------------------------------------------------------------------------- -float FCogEngineWindow_Selection::GetMainMenuWidgetWidth(int32 SubWidgetIndex, float MaxWidth) +void FCogEngineWindow_Selection::RenderMainMenuWidget() { - switch (SubWidgetIndex) + ImGui::PushStyleVarX(ImGuiStyleVar_ItemSpacing, 0); + ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 0, 0, 0)); + if (FCogWidgets::PickButton("##Pick", ImVec2(ImGui::GetFrameHeight(), ImGui::GetFrameHeight()))) { - case 0: return FCogWindowWidgets::GetFontWidth() * 6; - case 1: return FMath::Min(FMath::Max(MaxWidth, FCogWindowWidgets::GetFontWidth() * 10), FCogWindowWidgets::GetFontWidth() * 30); - case 2: return FCogWindowWidgets::GetFontWidth() * 3; + GetOwner()->SetActivateSelectionMode(true); + HackWaitInputRelease(); } - return -1.0f; -} + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + + RenderPickButtonTooltip(); + + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15); + AActor* NewSelection = nullptr; -//-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Selection::RenderMainMenuWidget(int32 SubWidgetIndex, float Width) -{ - //----------------------------------- - // Pick Button - //----------------------------------- - if (SubWidgetIndex == 0) + //TODO: Could be replaced by a BeginMenu + if (FCogWidgets::MenuActorsCombo( + "MenuActorSelection", + NewSelection, + *GetWorld(), + GetSelectionFilters(), + Config->SelectedClassIndex, + &Filter, + GetLocalPlayerPawn(), + [this](AActor& Actor) { RenderActorContextMenu(Actor); })) { - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 0, 0, 0)); - - if (ImGui::Button("Pick", ImVec2(Width, 0))) - { - ActivateSelectionMode(); - HackWaitInputRelease(); - } - RenderPickButtonTooltip(); - - ImGui::PopStyleColor(1); - ImGui::PopStyleVar(2); - } - else if (SubWidgetIndex == 1) - { - ImGui::SetNextItemWidth(Width); - AActor* NewSelection = nullptr; - if (FCogWindowWidgets::MenuActorsCombo( - "MenuActorSelection", - NewSelection, - *GetWorld(), - ActorClasses, - Config->SelectedClassIndex, - &Filter, - GetLocalPlayerPawn(), - [this](AActor& Actor) { RenderActorContextMenu(Actor); })) - { - SetGlobalSelection(NewSelection); - } - } - else if (SubWidgetIndex == 2) - { - //----------------------------------- - // Reset Button - //----------------------------------- - { - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); - ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 0, 0, 0)); - if (ImGui::Button("X", ImVec2(Width, 0))) - { - SetGlobalSelection(nullptr); - ImGui::CloseCurrentPopup(); - } - if (ImGui::IsItemHovered()) - { - ImGui::SetTooltip("Reset the selection to the controlled actor."); - } - ImGui::PopStyleColor(1); - ImGui::PopStyleVar(1); - } + SetGlobalSelection(NewSelection); } } @@ -414,15 +333,38 @@ void FCogEngineWindow_Selection::RenderActorContextMenu(AActor& Actor) //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Selection::SetGlobalSelection(AActor* Value) const { - FCogDebug::SetSelection(GetWorld(), Value); + FCogDebug::SetSelection(Value); } //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Selection::RenderPickButtonTooltip() { - if (ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary)) + if (FCogWidgets::BeginItemTooltipWrappedText()) { - const FString Shortcut = FCogImguiInputHelper::CommandToString(*GetWorld(), ToggleSelectionModeCommand); - ImGui::SetTooltip("Enter picking mode to pick an actor on screen. %s", TCHAR_TO_ANSI(*Shortcut)); + ImGui::Text("Enter selection mode to select an actor on screen. Change which actor type is selectable by clicking the selection combobox\n"); + ImGui::Spacing(); + ImGui::Separator(); + FCogWidgets::TextOfAllInputChordsOfConfig(*Config.Get()); + + FCogWidgets::EndItemTooltipWrappedText(); } -} \ No newline at end of file +} + +//-------------------------------------------------------------------------------------------------------------------------- +const TArray>& FCogEngineWindow_Selection::GetSelectionFilters() const +{ + if (Asset != nullptr) + { return Asset->SelectionFilters; } + + static TArray> SelectionFilters = { ACharacter::StaticClass(), AActor::StaticClass(), AGameModeBase::StaticClass(), AGameStateBase::StaticClass() }; + return SelectionFilters; +} + +//-------------------------------------------------------------------------------------------------------------------------- + ETraceTypeQuery FCogEngineWindow_Selection::GetSelectionTraceChannel() const +{ + if (Asset != nullptr) + { return Asset->SelectionTraceChannel; } + + return UEngineTypes::ConvertToTraceType(ECC_Pawn); +} diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Skeleton.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Skeleton.cpp index 9519c92..a81294f 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Skeleton.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Skeleton.cpp @@ -1,7 +1,7 @@ #include "CogEngineWindow_Skeleton.h" #include "CogDebug.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "Components/SkeletalMeshComponent.h" #include "DrawDebugHelpers.h" #include "Engine/SkeletalMesh.h" @@ -111,12 +111,12 @@ void FCogEngineWindow_Skeleton::RenderContent() ImGui::EndMenu(); } - FCogWindowWidgets::SearchBar(Filter); + FCogWidgets::SearchBar("##Filter", Filter); ImGui::EndMenuBar(); } - ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, FCogWindowWidgets::GetFontWidth()); + ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, FCogWidgets::GetFontWidth()); HoveredBoneIndex = INDEX_NONE; RenderBoneEntry(0, false); @@ -207,7 +207,7 @@ void FCogEngineWindow_Skeleton::RenderBoneEntry(int32 BoneIndex, bool OpenAllChi // Checkbox //------------------------ ImGui::SameLine(); - FCogWindowWidgets::PushStyleCompact(); + FCogWidgets::PushStyleCompact(); if (ImGui::Checkbox("##Visible", &BoneInfo.ShowBone)) { if (IsControlDown) @@ -223,10 +223,10 @@ void FCogEngineWindow_Skeleton::RenderBoneEntry(int32 BoneIndex, bool OpenAllChi BoneInfo.ShowTrajectory = false; } } - FCogWindowWidgets::PopStyleCompact(); + FCogWidgets::PopStyleCompact(); - const bool HasCustomVisiblity = BoneInfo.ShowName || BoneInfo.ShowAxes || BoneInfo.ShowLocalVelocity || BoneInfo.ShowTrajectory; - if (HasCustomVisiblity) + const bool HasCustomVisibility = BoneInfo.ShowName || BoneInfo.ShowAxes || BoneInfo.ShowLocalVelocity || BoneInfo.ShowTrajectory; + if (HasCustomVisibility) { BoneInfo.ShowBone = true; } @@ -235,7 +235,7 @@ void FCogEngineWindow_Skeleton::RenderBoneEntry(int32 BoneIndex, bool OpenAllChi // Name //------------------------ ImGui::SameLine(); - ImVec4 NameColor = HasCustomVisiblity ? ImVec4(1.0f, 1.0f, 0.0f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + ImVec4 NameColor = HasCustomVisibility ? ImVec4(1.0f, 1.0f, 0.0f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); ImGui::TextColored(NameColor, "%s", BoneName.Get()); } diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Slate.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Slate.cpp index ab63d64..63d45b9 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Slate.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Slate.cpp @@ -52,7 +52,7 @@ void FCogEngineWindow_Slate::RenderContent() void FCogEngineWindow_Slate::RenderUser(FSlateUser& User) { - if (ImGui::BeginTable("SlateUser", 2, ImGuiTableFlags_Borders)) + if (ImGui::BeginTable("SlateUser", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) { constexpr ImVec4 LabelColor(1.0f, 1.0f, 1.0f, 0.5f); diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Spawns.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Spawns.cpp index 258d04f..c409be8 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Spawns.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Spawns.cpp @@ -3,15 +3,7 @@ #include "CogEngineDataAsset.h" #include "CogEngineReplicator.h" #include "CogImguiHelper.h" -#include "CogWindowWidgets.h" - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Spawns::Initialize() -{ - Super::Initialize(); - - Asset = GetAsset(); -} +#include "CogWidgets.h" //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Spawns::RenderHelp() @@ -23,6 +15,14 @@ void FCogEngineWindow_Spawns::RenderHelp() ); } +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Spawns::Initialize() +{ + Super::Initialize(); + + Asset = GetAsset(); +} + //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Spawns::RenderContent() { @@ -47,24 +47,25 @@ void FCogEngineWindow_Spawns::RenderContent() return; } + int32 GroupIndex = 0; for (const FCogEngineSpawnGroup& SpawnGroup : Asset->SpawnGroups) { - RenderSpawnGroup(*Replicator, SpawnGroup); + RenderSpawnGroup(*Replicator, SpawnGroup, GroupIndex); + GroupIndex++; } } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Spawns::RenderSpawnGroup(ACogEngineReplicator& Replicator, const FCogEngineSpawnGroup& SpawnGroup) +void FCogEngineWindow_Spawns::RenderSpawnGroup(ACogEngineReplicator& Replicator, const FCogEngineSpawnGroup& SpawnGroup, int32 GroupIndex) { - if (FCogWindowWidgets::DarkCollapsingHeader(TCHAR_TO_ANSI(*SpawnGroup.Name), ImGuiTreeNodeFlags_DefaultOpen)) + if (FCogWidgets::DarkCollapsingHeader(TCHAR_TO_ANSI(*SpawnGroup.Name), ImGuiTreeNodeFlags_DefaultOpen)) { - int32 GroupIndex = 0; ImGui::PushID(GroupIndex); const bool PushColor = (SpawnGroup.Color != FColor::Transparent); if (PushColor) { - FCogWindowWidgets::PushBackColor(FCogImguiHelper::ToImVec4(SpawnGroup.Color)); + FCogWidgets::PushBackColor(FCogImguiHelper::ToImVec4(SpawnGroup.Color)); } static int32 SelectedAssetIndex = -1; @@ -82,11 +83,10 @@ void FCogEngineWindow_Spawns::RenderSpawnGroup(ACogEngineReplicator& Replicator, if (PushColor) { - FCogWindowWidgets::PopBackColor(); + FCogWidgets::PopBackColor(); } ImGui::PopID(); - GroupIndex++; } } @@ -95,7 +95,7 @@ bool FCogEngineWindow_Spawns::RenderSpawnAsset(ACogEngineReplicator& Replicator, { bool IsPressed = false; - ImGui::PushStyleColor(ImGuiCol_Button, IsLastSelected ? ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive) : ImGui::GetStyleColorVec4(ImGuiCol_Button)); + //ImGui::PushStyleColor(ImGuiCol_Button, IsLastSelected ? ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive) : ImGui::GetStyleColorVec4(ImGuiCol_Button)); ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f)); FString EntryName; @@ -115,7 +115,7 @@ bool FCogEngineWindow_Spawns::RenderSpawnAsset(ACogEngineReplicator& Replicator, } ImGui::PopStyleVar(1); - ImGui::PopStyleColor(1); + //ImGui::PopStyleColor(1); return IsPressed; } \ No newline at end of file diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Stats.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Stats.cpp index 0b62bad..246afdb 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Stats.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Stats.cpp @@ -1,6 +1,7 @@ #include "CogEngineWindow_Stats.h" -#include "CogWindowWidgets.h" +#include "CogImguiHelper.h" +#include "CogWidgets.h" #include "Engine/Engine.h" #include "Engine/NetConnection.h" #include "Engine/NetDriver.h" @@ -8,9 +9,7 @@ #include "GameFramework/PlayerController.h" #include "GameFramework/PlayerState.h" -ImVec4 StatRedColor(1.0f, 0.4f, 0.3f, 1.0f); -ImVec4 StatOrangeColor(1.0f, 0.7f, 0.4f, 1.0f); -ImVec4 StatGreenColor(0.5f, 1.0f, 0.6f, 1.0f); + //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Stats::Initialize() @@ -18,6 +17,9 @@ void FCogEngineWindow_Stats::Initialize() Super::Initialize(); bHasWidget = true; + bIsWidgetVisible = true; + + Config = GetConfig(); } //-------------------------------------------------------------------------------------------------------------------------- @@ -28,6 +30,15 @@ void FCogEngineWindow_Stats::RenderHelp() ); } +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Stats::RenderContextMenu() +{ + Config->RenderAllConfigs(); + + ImGui::Separator(); + FCogWindow::RenderContextMenu(); +} + //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_Stats::RenderContent() { @@ -36,7 +47,7 @@ void FCogEngineWindow_Stats::RenderContent() extern ENGINE_API float GAverageFPS; ImGui::Text("FPS "); ImGui::SameLine(); - ImGui::TextColored(GetFpsColor(GAverageFPS), "%0.0f", GAverageFPS); + ImGui::TextColored(Config->GetFpsColor(GAverageFPS), "%0.0f", GAverageFPS); if (const APlayerController* PlayerController = GetLocalPlayerController()) { @@ -45,7 +56,7 @@ void FCogEngineWindow_Stats::RenderContent() const float Ping = PlayerState->GetPingInMilliseconds(); ImGui::Text("Ping "); ImGui::SameLine(); - ImGui::TextColored(GetPingColor(Ping), "%0.0fms", Ping); + ImGui::TextColored(Config->GetPingColor(Ping), "%0.0fms", Ping); } if (const UNetConnection* Connection = PlayerController->GetNetConnection()) @@ -53,176 +64,183 @@ void FCogEngineWindow_Stats::RenderContent() const float OutPacketLost = Connection->GetOutLossPercentage().GetAvgLossPercentage() * 100.0f; ImGui::Text("Packet Loss Out "); ImGui::SameLine(); - ImGui::TextColored(GetPacketLossColor(OutPacketLost), "%0.0f%%", OutPacketLost); + ImGui::TextColored(Config->GetPacketLossColor(OutPacketLost), "%0.0f%%", OutPacketLost); const float InPacketLost = Connection->GetInLossPercentage().GetAvgLossPercentage() * 100.0f; ImGui::Text("Packet Loss In "); ImGui::SameLine(); - ImGui::TextColored(GetPacketLossColor(InPacketLost), "%0.0f%%", InPacketLost); + ImGui::TextColored(Config->GetPacketLossColor(InPacketLost), "%0.0f%%", InPacketLost); } } } //-------------------------------------------------------------------------------------------------------------------------- -float FCogEngineWindow_Stats::GetMainMenuWidgetWidth(const int32 SubWidgetIndex, float MaxWidth) +void FCogEngineWindow_Stats::RenderMainMenuWidget() { const APlayerController* PlayerController = GetLocalPlayerController(); + + RenderMainMenuWidgetFrameRate(); + const UNetConnection* Connection = PlayerController != nullptr ? PlayerController->GetNetConnection() : nullptr; - - switch (SubWidgetIndex) + if (Connection != nullptr) { - case 0: return FCogWindowWidgets::GetFontWidth() * 8; - case 1: return Connection != nullptr ? FCogWindowWidgets::GetFontWidth() * 7 : 0.0f; - case 2: return Connection != nullptr ? FCogWindowWidgets::GetFontWidth() * 7 : 0.0f; - } - - return -1; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Stats::RenderMainMenuWidget(const int32 SubWidgetIndex, const float Width) -{ - switch (SubWidgetIndex) - { - case 0: RenderMainMenuWidgetFramerate(Width); break; - case 1: RenderMainMenuWidgetPing(Width); break; - case 2: RenderMainMenuWidgetPacketLoss(Width); break; + RenderMainMenuWidgetPing(); + + RenderMainMenuWidgetPacketLoss(); } } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Stats::RenderMainMenuWidgetFramerate(const float Width) +void FCogEngineWindow_Stats::RenderMainMenuWidgetFrameRate() { extern ENGINE_API float GAverageFPS; - const int32 Fps = (int32)GAverageFPS; + const int32 Fps = static_cast(GAverageFPS); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 0, 0, 0)); - ImGui::PushStyleColor(ImGuiCol_Text, GetFpsColor(Fps)); + ImGui::PushStyleColor(ImGuiCol_Text, Config->GetFpsColor(Fps)); + const bool Open = ImGui::BeginMenu(TCHAR_TO_ANSI(*FString::Printf(TEXT("%3dfps###FrameRateButton"), Fps))); + const float Width = ImGui::GetItemRectSize().x; + ImGui::PopStyleColor(1); - if (ImGui::Button(TCHAR_TO_ANSI(*FString::Printf(TEXT("%3dfps###FramerateButton"), Fps)), ImVec2(Width, 0.0f))) + if (ImGui::BeginPopupContextItem()) { - ImGui::OpenPopup("FrameratePopup"); + Config->RenderColorConfig(); + Config->RenderFrameRateConfig(); + Super::RenderContextMenu(); + ImGui::EndPopup(); } - ImGui::PopStyleColor(2); - ImGui::PopStyleVar(2); - - ImGui::SetItemTooltip("Framerate"); - - if (ImGui::BeginPopup("FrameratePopup")) + if (Open == false) { - ImGui::Text("Fps"); - ImGui::SameLine(); - - int32 MaxFps = GEngine->GetMaxFPS(); - TArray Values{ 0, 10, 20, 30, 60, 120 }; - if (FCogWindowWidgets::MultiChoiceButtonsInt(Values, MaxFps, ImVec2(3.5f * FCogWindowWidgets::GetFontWidth(), 0))) + ImGui::SetItemTooltip("Frame Rate"); + } + + if (Open) + { + const int32 MaxFps = GEngine->GetMaxFPS(); + for (int32 i = 0; i < Config->FrameRates.Num(); ++i) { - GEngine->SetMaxFPS(MaxFps); + ImGui::PushID(i); + const float Value = Config->FrameRates[i]; + const auto ValueText = StringCast(*FCogWidgets::FormatSmallFloat(Value)); + if (ImGui::Selectable(ValueText.Get(), Value == MaxFps, ImGuiSelectableFlags_None, ImVec2(Width, 0))) + { + GEngine->SetMaxFPS(Value); + } + ImGui::PopID(); } - - ImGui::EndPopup(); + + ImGui::EndMenu(); } } + //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Stats::RenderMainMenuWidgetPing(const float Width) +void FCogEngineWindow_Stats::RenderMainMenuWidgetPing() { const APlayerController* PlayerController = GetLocalPlayerController(); const APlayerState* PlayerState = PlayerController != nullptr ? PlayerController->GetPlayerState() : nullptr; if (PlayerState == nullptr) + { return; } + + const int32 Ping = static_cast(PlayerState->GetPingInMilliseconds()); + ImGui::PushStyleColor(ImGuiCol_Text, Config->GetPingColor(Ping)); + const bool Open = ImGui::BeginMenu(TCHAR_TO_ANSI(*FString::Printf(TEXT("%3dms###PingButton"), Ping))); + const float Width = ImGui::GetItemRectSize().x; + ImGui::PopStyleColor(1); + + if (ImGui::BeginPopupContextItem()) { - return; + Config->RenderColorConfig(); + Config->RenderPingConfig(); + Super::RenderContextMenu(); + ImGui::EndPopup(); } - const float Ping = PlayerState->GetPingInMilliseconds(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 0, 0, 0)); - ImGui::PushStyleColor(ImGuiCol_Text, GetPingColor(Ping)); - - if (ImGui::Button(TCHAR_TO_ANSI(*FString::Printf(TEXT("%3dms###PingButton"), (int32)Ping)), ImVec2(Width, 0.0f))) + if (Open == false) { - ImGui::OpenPopup("PingPopup"); + ImGui::SetItemTooltip("Ping"); } - - ImGui::PopStyleColor(2); - ImGui::PopStyleVar(2); - - ImGui::SetItemTooltip("Ping"); - + #if DO_ENABLE_NET_TEST - if (ImGui::BeginPopup("PingPopup")) + if (Open) { - FWorldContext& WorldContext = GEngine->GetWorldContextFromWorldChecked(GetWorld()); if (WorldContext.ActiveNetDrivers.Num() > 0) { - ImGui::Text("Ping"); - ImGui::SameLine(); - const FNamedNetDriver* SelectedNetDriver = &WorldContext.ActiveNetDrivers[0]; FPacketSimulationSettings Settings = SelectedNetDriver->NetDriver->PacketSimulationSettings; - TArray Values{ 0, 50, 100, 200, 500, 1000 }; - if (FCogWindowWidgets::MultiChoiceButtonsInt(Values, Settings.PktIncomingLagMin, ImVec2(4.5f * FCogWindowWidgets::GetFontWidth(), 0))) + + for (int32 i = 0; i < Config->Pings.Num(); ++i) { - SelectedNetDriver->NetDriver->SetPacketSimulationSettings(Settings); + ImGui::PushID(i); + const float Value = Config->Pings[i]; + const auto ValueText = StringCast(*FCogWidgets::FormatSmallFloat(Value)); + if (ImGui::Selectable(ValueText.Get(), Value == Settings.PktIncomingLagMin, ImGuiSelectableFlags_None, ImVec2(Width, 0))) + { + Settings.PktIncomingLagMin = Value; + SelectedNetDriver->NetDriver->SetPacketSimulationSettings(Settings); + } + + ImGui::PopID(); } } - ImGui::EndPopup(); + ImGui::EndMenu(); } #endif //DO_ENABLE_NET_TEST } //-------------------------------------------------------------------------------------------------------------------------- -void FCogEngineWindow_Stats::RenderMainMenuWidgetPacketLoss(const float Width) +void FCogEngineWindow_Stats::RenderMainMenuWidgetPacketLoss() { const APlayerController* PlayerController = GetLocalPlayerController(); const UNetConnection* Connection = PlayerController != nullptr ? PlayerController->GetNetConnection() : nullptr; if (Connection == nullptr) - { - return; - } + { return; } const float OutPacketLost = Connection->GetOutLossPercentage().GetAvgLossPercentage() * 100.0f; const float InPacketLost = Connection->GetInLossPercentage().GetAvgLossPercentage() * 100.0f; const float TotalPacketLost = (OutPacketLost + InPacketLost) / 2; - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 0, 0, 0)); - ImGui::PushStyleColor(ImGuiCol_Text, GetPacketLossColor(TotalPacketLost)); + ImGui::PushStyleColor(ImGuiCol_Text, Config->GetPacketLossColor(TotalPacketLost)); + const bool Open = ImGui::BeginMenu(TCHAR_TO_ANSI(*FString::Printf(TEXT("%2d%% ###PacketLossButton"), static_cast(TotalPacketLost)))); + const float Width = ImGui::GetItemRectSize().x; + ImGui::PopStyleColor(1); - if (ImGui::Button(TCHAR_TO_ANSI(*FString::Printf(TEXT("%2d%%###PacketLossButton"), (int32)TotalPacketLost)), ImVec2(Width, 0.0f))) + if (ImGui::BeginPopupContextItem()) { - ImGui::OpenPopup("PacketLossPopup"); + Config->RenderColorConfig(); + Config->RenderPacketLossConfig(); + Super::RenderContextMenu(); + ImGui::EndPopup(); } - - ImGui::PopStyleColor(2); - ImGui::PopStyleVar(2); - - ImGui::SetItemTooltip("Packet Loss"); - -#if DO_ENABLE_NET_TEST - if (ImGui::BeginPopup("PacketLossPopup")) + + if (Open == false) + { + ImGui::SetItemTooltip("Packet Loss"); + } + +#if DO_ENABLE_NET_TEST + if (Open) { - FWorldContext& WorldContext = GEngine->GetWorldContextFromWorldChecked(GetWorld()); if (WorldContext.ActiveNetDrivers.Num() > 0) { - ImGui::Text("Packet Loss"); - ImGui::SameLine(); - const FNamedNetDriver* SelectedNetDriver = &WorldContext.ActiveNetDrivers[0]; FPacketSimulationSettings Settings = SelectedNetDriver->NetDriver->PacketSimulationSettings; - TArray Values{ 0, 5, 10, 20, 30, 40, 50 }; - if (FCogWindowWidgets::MultiChoiceButtonsInt(Values, Settings.PktIncomingLoss, ImVec2(3.5f * FCogWindowWidgets::GetFontWidth(), 0))) + for (int32 i = 0; i < Config->PacketLosses.Num(); ++i) { - Settings.PktLoss = Settings.PktIncomingLoss; - SelectedNetDriver->NetDriver->SetPacketSimulationSettings(Settings); + ImGui::PushID(i); + const float Value = Config->PacketLosses[i]; + const auto ValueText = StringCast(*FCogWidgets::FormatSmallFloat(Value)); + if (ImGui::Selectable(ValueText.Get(), Value == Settings.PktIncomingLagMin, ImGuiSelectableFlags_None, ImVec2(Width, 0))) + { + Settings.PktIncomingLoss = Value; + Settings.PktLoss = Settings.PktIncomingLoss; + SelectedNetDriver->NetDriver->SetPacketSimulationSettings(Settings); + } + + ImGui::PopID(); } } ImGui::EndPopup(); @@ -230,50 +248,98 @@ void FCogEngineWindow_Stats::RenderMainMenuWidgetPacketLoss(const float Width) #endif //DO_ENABLE_NET_TEST } + //-------------------------------------------------------------------------------------------------------------------------- -ImVec4 FCogEngineWindow_Stats::GetFpsColor(const float Value, const float Good /*= 50.0f*/, const float Medium /*= 30.0f*/) +ImVec4 UCogEngineWindowConfig_Stats::GetFpsColor(const float Value) const { - if (Value > Good) - { - return StatGreenColor; - } + if (Value > GoodFrameRate) + { return FCogImguiHelper::ToImVec4(GoodColor); } - if (Value > Medium) - { - return StatOrangeColor; - } + if (Value > MediumFrameRate) + { return FCogImguiHelper::ToImVec4(MediumColor); } - return StatRedColor; + return FCogImguiHelper::ToImVec4(BadColor); } //-------------------------------------------------------------------------------------------------------------------------- -ImVec4 FCogEngineWindow_Stats::GetPingColor(const float Value, const float Good /*= 100.0f*/, const float Medium /*= 200.0f*/) +ImVec4 UCogEngineWindowConfig_Stats::GetPingColor(const float Value) const { - if (Value > Medium) - { - return StatRedColor; - } + if (Value > MediumPing) + { return FCogImguiHelper::ToImVec4(BadColor); } - if (Value > Good) - { - return StatOrangeColor; - } + if (Value > GoodPing) + { return FCogImguiHelper::ToImVec4(MediumColor); } - return StatGreenColor; + return FCogImguiHelper::ToImVec4(GoodColor); } //-------------------------------------------------------------------------------------------------------------------------- -ImVec4 FCogEngineWindow_Stats::GetPacketLossColor(const float Value, const float Good /*= 10.0f*/, const float Medium /*= 20.0f*/) +ImVec4 UCogEngineWindowConfig_Stats::GetPacketLossColor(const float Value) const { - if (Value > Medium) - { - return StatRedColor; - } + if (Value > MediumPacketLoss) + { return FCogImguiHelper::ToImVec4(BadColor); } - if (Value > Good) - { - return StatOrangeColor; - } + if (Value > GoodPacketLoss) + { return FCogImguiHelper::ToImVec4(MediumColor); } - return StatGreenColor; -} \ No newline at end of file + return FCogImguiHelper::ToImVec4(GoodColor); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogEngineWindowConfig_Stats::RenderAllConfigs() +{ + RenderColorConfig(); + RenderFrameRateConfig(); + RenderPingConfig(); + RenderPacketLossConfig(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogEngineWindowConfig_Stats::RenderColorConfig() +{ + if (ImGui::CollapsingHeader("Display", ImGuiTreeNodeFlags_DefaultOpen)) + { + constexpr ImGuiColorEditFlags ColorEditFlags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf; + FCogImguiHelper::ColorEdit4("Good Color", GoodColor, ColorEditFlags); + ImGui::SetItemTooltip("Color of a stat with a good value."); + + FCogImguiHelper::ColorEdit4("Medium Color", MediumColor, ColorEditFlags); + ImGui::SetItemTooltip("Color of a stat with a medium value."); + + FCogImguiHelper::ColorEdit4("Bad Color", BadColor, ColorEditFlags); + ImGui::SetItemTooltip("Color of a stat with a bad value."); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogEngineWindowConfig_Stats::RenderFrameRateConfig() +{ + if (ImGui::CollapsingHeader("Frame Rate", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::InputInt("Good Frame Rate", &GoodFrameRate); + ImGui::InputInt("Medium Frame Rate", &MediumFrameRate); + FCogWidgets::IntArray("Max Frame Rate", FrameRates, 10, ImVec2(0, ImGui::GetFontSize() * 10)); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogEngineWindowConfig_Stats::RenderPingConfig() +{ + if (ImGui::CollapsingHeader("Ping", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::InputInt("Good Ping", &GoodPing); + ImGui::InputInt("Medium Ping", &MediumPing); + FCogWidgets::IntArray("Ping Emulation", Pings, 10, ImVec2(0, ImGui::GetFontSize() * 10)); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogEngineWindowConfig_Stats::RenderPacketLossConfig() +{ + if (ImGui::CollapsingHeader("Packet Loss", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::InputInt("Good Packet Loss", &GoodPacketLoss); + ImGui::InputInt("Medium Packet Loss", &MediumPacketLoss); + FCogWidgets::IntArray("Packet Loss Emulation", PacketLosses, 10, ImVec2(0, ImGui::GetFontSize() * 10)); + } +} diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_TimeScale.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_TimeScale.cpp index 6f36ffd..e4469b6 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_TimeScale.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_TimeScale.cpp @@ -1,23 +1,30 @@ #include "CogEngineWindow_TimeScale.h" #include "CogEngineReplicator.h" -#include "CogWindowWidgets.h" +#include "CogImguiHelper.h" +#include "CogImguiInputHelper.h" +#include "CogSubsystem.h" +#include "CogWidgets.h" #include "Engine/Engine.h" #include "Engine/World.h" + + //-------------------------------------------------------------------------------------------------------------------------- void FCogEngineWindow_TimeScale::Initialize() { Super::Initialize(); - TimingScales.Add(0.00f); - TimingScales.Add(0.01f); - TimingScales.Add(0.10f); - TimingScales.Add(0.50f); - TimingScales.Add(1.00f); - TimingScales.Add(2.00f); - TimingScales.Add(5.00f); - TimingScales.Add(10.0f); + bHasWidget = true; + bIsWidgetVisible = true; + + Config = GetConfig(); + + UCogEngineWindowConfig_TimeScale* ConfigPtr = Config.Get(); + GetOwner()->AddShortcut(ConfigPtr, &UCogEngineWindowConfig_TimeScale::Shortcut_FasterTimeScale).BindRaw(this, &FCogEngineWindow_TimeScale::FasterTimeScale); + GetOwner()->AddShortcut(ConfigPtr, &UCogEngineWindowConfig_TimeScale::Shortcut_SlowerTimeScale).BindRaw(this, &FCogEngineWindow_TimeScale::SlowerTimeScale); + GetOwner()->AddShortcut(ConfigPtr, &UCogEngineWindowConfig_TimeScale::Shortcut_ResetTimeScale).BindRaw(this, &FCogEngineWindow_TimeScale::ResetTimeScale); + GetOwner()->AddShortcut(ConfigPtr, &UCogEngineWindowConfig_TimeScale::Shortcut_ZeroTimeScale).BindRaw(this, &FCogEngineWindow_TimeScale::ZeroTimeScale); } //-------------------------------------------------------------------------------------------------------------------------- @@ -41,10 +48,206 @@ void FCogEngineWindow_TimeScale::RenderContent() return; } - float Value = Replicator->GetTimeDilation(); - if (FCogWindowWidgets::MultiChoiceButtonsFloat(TimingScales, Value, ImVec2(3.5f * FCogWindowWidgets::GetFontWidth(), 0))) + RenderTimeScaleChoices(Replicator); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_TimeScale::RenderContextMenu() +{ + UCogEngineWindowConfig_TimeScale* ConfigPtr = Config.Get(); + + if (IsWindowRenderedInMainMenu() == false) { - Replicator->SetTimeDilation(Value); + ImGui::Checkbox("Inline", &ConfigPtr->Inline); } + FCogImguiHelper::ColorEdit4("Time Scale Modified Color", ConfigPtr->TimeScaleModifiedColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::SetItemTooltip("Color of the current time scale, in widget mode, when the time scale in not 1."); + + FCogWidgets::FloatArray("Time Scales", ConfigPtr->TimeScales, 10, ImVec2(0, ImGui::GetFontSize() * 10)); + + if (ImGui::CollapsingHeader("Shortcuts", ImGuiTreeNodeFlags_DefaultOpen)) + { + RenderConfigShortcuts(*ConfigPtr); + } + + ImGui::Separator(); + FCogWindow::RenderContextMenu(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_TimeScale::RenderMainMenuWidget() +{ + Super::RenderMainMenuWidget(); + + ACogEngineReplicator* Replicator = ACogEngineReplicator::GetLocalReplicator(*GetWorld()); + if (Replicator == nullptr) + { + ImGui::TextDisabled("x?"); + ImGui::SetItemTooltip("Invalid Replicator"); + return; + } + + float TimeDilation = GetTimeDilation(); + if (TimeDilation != 1.0f) + { + ImGui::PushStyleColor(ImGuiCol_Text, FCogImguiHelper::ToImVec4(Config->TimeScaleModifiedColor)); + } + + if (FMath::IsNearlyZero(TimeDilation, 0.0001f)) + { + TimeDilation = 0.0f; + } + + const auto Text = StringCast(*FString::Printf(TEXT("x%g"), TimeDilation)); + const bool Open = ImGui::BeginMenu(Text.Get()); + + if (TimeDilation != 1) + { + ImGui::PopStyleColor(); + } + + if (ImGui::BeginPopupContextItem()) + { + RenderContextMenu(); + ImGui::EndPopup(); + } + + if (Open) + { + for (int32 i = 0; i < Config->TimeScales.Num(); ++i) + { + const float Value = Config->TimeScales[i]; + const auto ValueText = StringCast(*FString::Printf(TEXT("%g"), Value)); + if (ImGui::Selectable(ValueText.Get(), Value == TimeDilation)) + { + SetCurrentTimeScale(*Replicator, Value); + } + } + + ImGui::EndMenu(); + } + else + { + if (FCogWidgets::BeginItemTooltipWrappedText()) + { + ImGui::Text("Time Scale: x%g", TimeDilation); + ImGui::Spacing(); + ImGui::Separator(); + FCogWidgets::TextOfAllInputChordsOfConfig(*Config.Get()); + FCogWidgets::EndItemTooltipWrappedText(); + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +int32 FCogEngineWindow_TimeScale::GetCurrentTimeScaleIndex(const ACogEngineReplicator& Replicator) const +{ + return GetTimeScaleIndex(Replicator.GetTimeDilation()); +} + +//-------------------------------------------------------------------------------------------------------------------------- +int32 FCogEngineWindow_TimeScale::GetTimeScaleIndex(float InTimeScale) const +{ + for (int32 i = 0; i < Config->TimeScales.Num(); ++i) + { + const float Value = Config->TimeScales[i]; + if (FMath::IsNearlyEqual(Value, InTimeScale)) + { return i; } + } + + return INDEX_NONE; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_TimeScale::SetCurrentTimeScale(ACogEngineReplicator& Replicator, const float Value) const +{ + Replicator.SetTimeDilation(Value); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_TimeScale::SetCurrentTimeScaleIndex(ACogEngineReplicator& Replicator, int32 InTimeScaleIndex) const +{ + if (Config->TimeScales.IsValidIndex(InTimeScaleIndex) == false) + { return; } + + const float Value = Config->TimeScales[InTimeScaleIndex]; + + SetCurrentTimeScale(Replicator, Value); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_TimeScale::FasterTimeScale() +{ + ACogEngineReplicator* Replicator = ACogEngineReplicator::GetLocalReplicator(*GetWorld()); + if (Replicator == nullptr) + { return; } + + const int32 TimeScaleIndex = GetCurrentTimeScaleIndex(*Replicator); + if (TimeScaleIndex == INDEX_NONE) + { return; } + + SetCurrentTimeScaleIndex(*Replicator, TimeScaleIndex + 1); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_TimeScale::SlowerTimeScale() +{ + ACogEngineReplicator* Replicator = ACogEngineReplicator::GetLocalReplicator(*GetWorld()); + if (Replicator == nullptr) + { return; } + + const int32 TimeScaleIndex = GetCurrentTimeScaleIndex(*Replicator); + if (TimeScaleIndex == INDEX_NONE) + { return; } + + SetCurrentTimeScaleIndex(*Replicator, TimeScaleIndex - 1); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_TimeScale::ResetTimeScale() +{ + ACogEngineReplicator* Replicator = ACogEngineReplicator::GetLocalReplicator(*GetWorld()); + if (Replicator == nullptr) + { return; } + + SetCurrentTimeScale(*Replicator, 1.0f); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_TimeScale::ZeroTimeScale() +{ + ACogEngineReplicator* Replicator = ACogEngineReplicator::GetLocalReplicator(*GetWorld()); + if (Replicator == nullptr) + { return; } + + SetCurrentTimeScale(*Replicator, 0.0f); +} + +//-------------------------------------------------------------------------------------------------------------------------- +float FCogEngineWindow_TimeScale::GetTimeDilation() const +{ + const UWorld* World = GetWorld(); + if (World == nullptr) + { + return 1.0f; + } + + AWorldSettings* WorldSettings = World->GetWorldSettings(); + if (WorldSettings == nullptr) + { + return 1.0f; + } + + return WorldSettings->GetEffectiveTimeDilation(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_TimeScale::RenderTimeScaleChoices(ACogEngineReplicator* Replicator) +{ + float TimeDilation = GetTimeDilation(); + if (FCogWidgets::MultiChoiceButtonsFloat(Config->TimeScales, TimeDilation, ImVec2(3.5f * FCogWidgets::GetFontWidth(), 0), Config->Inline, 0.0001f)) + { + Replicator->SetTimeDilation(TimeDilation); + } } diff --git a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Transform.cpp b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Transform.cpp index d611ba0..5e673e3 100644 --- a/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Transform.cpp +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Transform.cpp @@ -2,7 +2,7 @@ #include "CogDebug.h" #include "CogImguiHelper.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "GameFramework/Actor.h" #include "imgui.h" #include "imgui_internal.h" @@ -39,13 +39,13 @@ void FCogEngineWindow_Transform::RenderContent() { if (ImGui::BeginMenu("Options")) { - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Drag Speed Location", &Config->LocationSpeed, 0.1f, 0.1f, 100.0f); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Drag Speed Rotation", &Config->RotationSpeed, 0.1f, 0.1f, 100.0f); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat("Drag Speed Scale", &Config->ScaleSpeed, 0.1f, 0.1f, 100.0f); ImGui::SeparatorText("Gizmo"); @@ -113,7 +113,7 @@ void FCogEngineWindow_Transform::RenderSnap(const char* CheckboxLabel, const cha ImGui::Checkbox(CheckboxLabel, SnapEnable); ImGui::SameLine(); - FCogWindowWidgets::SetNextItemToShortWidth(); + FCogWidgets::SetNextItemToShortWidth(); ImGui::DragFloat(InputLabel, Snap, 0.1f, 0.1f, 1000.0f, "%.1f"); } diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineCollisionTester.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineCollisionTester.h index 4568920..b80b351 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineCollisionTester.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineCollisionTester.h @@ -17,13 +17,22 @@ enum class ECogEngine_CollisionQueryType : uint8 //-------------------------------------------------------------------------------------------------------------------------- UENUM() -enum class ECogEngine_CollisionQueryMode : uint8 +enum class ECogEngine_CollisionQueryTraceMode : uint8 { Single, Multi, Test, }; +//-------------------------------------------------------------------------------------------------------------------------- +UENUM() +enum class ECogEngine_CollisionQueryOverlapMode : uint8 +{ + AnyTest, + BlockingTest, + Multi, +}; + //-------------------------------------------------------------------------------------------------------------------------- UENUM() enum class ECogEngine_CollisionQueryBy : uint8 @@ -49,7 +58,6 @@ class COGENGINE_API ACogEngineCollisionTester : public AActor GENERATED_BODY() public: - ACogEngineCollisionTester(const FObjectInitializer& ObjectInitializer); virtual void Tick(float DeltaSeconds) override; @@ -65,7 +73,10 @@ public: ECogEngine_CollisionQueryType Type = ECogEngine_CollisionQueryType::LineTrace; UPROPERTY(EditAnywhere, Category="Cog") - ECogEngine_CollisionQueryMode Mode = ECogEngine_CollisionQueryMode::Multi; + ECogEngine_CollisionQueryTraceMode TraceMode = ECogEngine_CollisionQueryTraceMode::Multi; + + UPROPERTY(EditAnywhere, Category="Cog") + ECogEngine_CollisionQueryOverlapMode OverlapMode = ECogEngine_CollisionQueryOverlapMode::Multi; UPROPERTY(EditAnywhere, Category="Cog") ECogEngine_CollisionQueryBy By = ECogEngine_CollisionQueryBy::Channel; @@ -80,7 +91,7 @@ public: int32 ObjectTypesToQuery = 0; UPROPERTY(EditAnywhere, Category="Cog") - TEnumAsByte Channel = ECC_WorldStatic; + TEnumAsByte TraceChannel = ECC_Visibility; UPROPERTY() int32 ProfileIndex = 0; @@ -125,8 +136,8 @@ public: FColor ImpactNormalColor = FColor::Cyan; UPROPERTY(EditAnywhere, Category="Cog") - USceneComponent* StartComponent = nullptr; + TObjectPtr StartComponent; UPROPERTY(EditAnywhere, Category="Cog") - USceneComponent* EndComponent = nullptr; + TObjectPtr EndComponent; }; diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineDataAsset.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineDataAsset.h index fc0842a..c65967f 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineDataAsset.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineDataAsset.h @@ -3,6 +3,9 @@ #include "CoreMinimal.h" #include "Engine/DataAsset.h" #include "Engine/EngineTypes.h" +#include "GameFramework/Character.h" +#include "GameFramework/GameModeBase.h" +#include "GameFramework/GameStateBase.h" #include "CogEngineDataAsset.generated.h" class FCogWindow; @@ -16,7 +19,7 @@ enum class ECogEngineCheat_ActiveState : uint8 }; //-------------------------------------------------------------------------------------------------------------------------- -UCLASS(BlueprintType, Abstract, Const, DefaultToInstanced, EditInlineNew, CollapseCategories) +UCLASS(BlueprintType, Blueprintable, Abstract, Const, DefaultToInstanced, EditInlineNew, CollapseCategories, Meta = (ShowWorldContextPin)) class COGENGINE_API UCogEngineCheat_Execution : public UObject { @@ -24,11 +27,11 @@ class COGENGINE_API UCogEngineCheat_Execution public: - UFUNCTION(BlueprintNativeEvent) - void Execute(const AActor* Instigator, const TArray& Targets) const; + UFUNCTION(BlueprintNativeEvent, meta = (DevelopmentOnly, WorldContext = "WorldContextObject")) + void Execute(const UObject* WorldContextObject, const AActor* Instigator, const TArray& Targets) const; - UFUNCTION(BlueprintNativeEvent) - ECogEngineCheat_ActiveState IsActiveOnTargets(const TArray& Targets) const; + UFUNCTION(BlueprintNativeEvent, meta = (DevelopmentOnly, WorldContext = "WorldContextObject")) + ECogEngineCheat_ActiveState IsActiveOnTargets(const UObject* WorldContextObject, const TArray& Targets) const; virtual bool GetColor(const FCogWindow& InCallingWindow, FLinearColor& OutColor) const; }; @@ -61,10 +64,10 @@ struct COGENGINE_API FCogEngineCheatCategory FString Name; UPROPERTY(Category = "Cheats", EditAnywhere, meta = (TitleProperty = "Name")) - TArray PersistentEffects; + TArray PersistentCheats; UPROPERTY(Category = "Cheats", EditAnywhere, meta = (TitleProperty = "Name")) - TArray InstantEffects; + TArray InstantCheats; }; //-------------------------------------------------------------------------------------------------------------------------- @@ -111,4 +114,10 @@ public: UPROPERTY(Category = "Spawns", EditAnywhere, meta = (TitleProperty = "Name")) TArray SpawnGroups; + + UPROPERTY(Category = "Selection", EditAnywhere) + TArray> SelectionFilters = { ACharacter::StaticClass(), AActor::StaticClass(), AGameModeBase::StaticClass(), AGameStateBase::StaticClass() }; + + UPROPERTY(Category = "Selection", EditAnywhere) + TEnumAsByte SelectionTraceChannel = UEngineTypes::ConvertToTraceType(ECC_Pawn); }; diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineHelper.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineHelper.h index 071b3c5..8044aa4 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineHelper.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineHelper.h @@ -1,6 +1,7 @@ #pragma once #include "CoreMinimal.h" +#include "CogEngineDataAsset.h" class AActor; @@ -10,4 +11,5 @@ public: static void ActorContextMenu(AActor& Actor); + static void RenderConfigureMessage(TWeakObjectPtr InAsset); }; diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineReplicator.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineReplicator.h index be13be5..6325fc0 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineReplicator.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineReplicator.h @@ -33,7 +33,7 @@ public: FCogEngineSpawnFunction GetSpawnFunction() const { return SpawnFunction; } - void SetSpawnFunction(FCogEngineSpawnFunction Value) { SpawnFunction = Value; } + void SetSpawnFunction(const FCogEngineSpawnFunction& Value) { SpawnFunction = Value; } UFUNCTION(Server, Reliable) void Server_Spawn(const FCogEngineSpawnEntry& SpawnEntry); @@ -54,7 +54,7 @@ public: UFUNCTION(Reliable, Server) void Server_ApplyCheat(const AActor* CheatInstigator, const TArray& TargetActors, const FCogEngineCheat& Cheat) const; - static ECogEngineCheat_ActiveState IsCheatActiveOnTargets(const TArray& Targets, const FCogEngineCheat& Cheat); + ECogEngineCheat_ActiveState IsCheatActiveOnTargets(const TArray& Targets, const FCogEngineCheat& Cheat) const; protected: @@ -64,7 +64,7 @@ protected: UFUNCTION() void OnRep_TimeDilation() const; - TObjectPtr OwnerPlayerController; + TWeakObjectPtr OwnerPlayerController; uint32 bHasAuthority : 1; uint32 bIsLocal : 1; diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineSubsystem.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineSubsystem.h new file mode 100644 index 0000000..6988a1a --- /dev/null +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineSubsystem.h @@ -0,0 +1,22 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CogEngineReplicator.h" +#include "CogDebugPluginSubsystem.h" +#include "CogEngineSubsystem.generated.h" + +UCLASS() +class COGENGINE_API UCogEngineSubsystem : public UCogDebugPluginSubsystem +{ + GENERATED_BODY() + +public: + + virtual void OnPlayerControllerReady(APlayerController* InController) override + { + if (InController != nullptr) + { + ACogEngineReplicator::Spawn(InController); + } + } +}; diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_BuildInfo.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_BuildInfo.h new file mode 100644 index 0000000..58cedcd --- /dev/null +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_BuildInfo.h @@ -0,0 +1,118 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CogWindow.h" +#include "CogWidgets.h" +#include "CogEngineWindow_BuildInfo.generated.h" + +class UCogEngineConfig_BuildInfo; + +class COGENGINE_API FCogEngineWindow_BuildInfo : public FCogWindow +{ + typedef FCogWindow Super; + +public: + + virtual void Initialize() override; + + virtual void RenderHelp() override; + + virtual void RenderTick(float DeltaTime) override; + + virtual void RenderContent() override; + +protected: + + void BuildText(); + + TWeakObjectPtr Config; + + FString Text; +}; + +//-------------------------------------------------------------------------------------------------------------------------- +UCLASS(Config = Cog) +class UCogEngineConfig_BuildInfo : public UCogCommonConfig +{ + GENERATED_BODY() + +public: + + UPROPERTY(Config) + bool ShowInEditor = false; + + UPROPERTY(Config) + bool ShowInPackage = true; + + UPROPERTY(Config) + bool ShowBranchName = false; + + UPROPERTY(Config) + bool ShowBuildDate = true; + + UPROPERTY(Config) + bool ShowCurrentChangelist = true; + + UPROPERTY(Config) + bool ShowCompatibleChangelist = false; + + UPROPERTY(Config) + bool ShowBuildConfiguration = true; + + UPROPERTY(Config) + bool ShowBuildUser = false; + + UPROPERTY(Config) + bool ShowBuildMachine = false; + + UPROPERTY(Config) + bool ShowBuildTargetType = true; + + UPROPERTY(Config) + bool ShowInForeground = true; + + UPROPERTY(Config) + FVector2f Alignment = { 0, 1 }; + + UPROPERTY(Config) + FIntVector2 Padding = { 10, 10 }; + + UPROPERTY(Config) + int32 Rounding = 6; + + UPROPERTY(Config) + FString Separator = "|"; + + UPROPERTY(Config) + FColor BackgroundColor = FColor(0, 0, 0, 80); + + UPROPERTY(Config) + FColor BorderColor = FColor(255, 255, 255, 50); + + UPROPERTY(Config) + FColor TextColor = FColor(255, 255, 255, 100); + + virtual void Reset() override + { + Super::Reset(); + + ShowInEditor = false; + ShowInPackage = true; + ShowInForeground = true; + ShowBranchName = false; + ShowBuildDate = true; + ShowCurrentChangelist = true; + ShowCompatibleChangelist = false; + ShowBuildConfiguration = true; + ShowBuildUser = false; + ShowBuildMachine = false; + ShowBuildTargetType = true; + Alignment = { 0, 1 }; + Padding = { 10, 10 }; + Rounding = 6; + Separator = " | "; + BackgroundColor = FColor(0, 0, 0, 80); + BorderColor = FColor(255, 255, 255, 50); + TextColor = FColor(255, 255, 255, 100); + } +}; \ No newline at end of file diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Cheats.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Cheats.h index 712c04d..7e640b3 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Cheats.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Cheats.h @@ -5,6 +5,7 @@ #include "CogWindow.h" #include "CogEngineWindow_Cheats.generated.h" +class ACogEngineReplicator; class AActor; class UCogEngineConfig_Cheats; class UCogEngineDataAsset; @@ -23,17 +24,15 @@ protected: virtual void GameTick(float DeltaTime) override; - virtual void ResetConfig() override; - virtual void RenderHelp() override; virtual void RenderContent() override; virtual void TryReapplyCheats(); - virtual bool AddCheat(const int32 Index, AActor* ControlledActor, AActor* TargetActor, const FCogEngineCheat& CheatEffect, bool IsPersistent); + virtual bool AddCheat(ACogEngineReplicator& Replicator, const int32 Index, AActor* ControlledActor, AActor* TargetActor, const FCogEngineCheat& CheatEffect, bool IsPersistent); - virtual void RequestCheat(AActor* ControlledActor, AActor* SelectedActor, const FCogEngineCheat& Cheat, bool ApplyToEnemies, bool ApplyToAllies, bool ApplyToControlled); + virtual void RequestCheat(ACogEngineReplicator& Replicator, AActor* ControlledActor, AActor* SelectedActor, const FCogEngineCheat& Cheat, bool ApplyToEnemies, bool ApplyToAllies, bool ApplyToControlled); virtual const FCogEngineCheat* FindCheatByName(const FString& CheatName, const bool OnlyPersistentCheats); diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_CollisionTester.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_CollisionTester.h index 9cca48b..24c4628 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_CollisionTester.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_CollisionTester.h @@ -25,8 +25,6 @@ public: protected: - virtual void ResetConfig() override; - virtual void RenderHelp() override; virtual void RenderContent() override; @@ -37,11 +35,12 @@ protected: FColor Color; }; - FChannel Channels[ECC_MAX]; - TObjectPtr Config = nullptr; + FChannel Channels[ECC_MAX] = {}; + FCogDebug_Gizmo StartGizmo; + FCogDebug_Gizmo EndGizmo; }; @@ -69,7 +68,7 @@ public: ECogEngine_CollisionQueryType Type; UPROPERTY(Config) - ECogEngine_CollisionQueryMode Mode; + ECogEngine_CollisionQueryTraceMode Mode; UPROPERTY(Config) ECogEngine_CollisionQueryBy By; @@ -92,18 +91,13 @@ public: UPROPERTY(Config) FVector ShapeExtent; - UCogEngineConfig_CollisionTester() - { - Reset(); - } - virtual void Reset() override { Super::Reset(); Type = ECogEngine_CollisionQueryType::LineTrace; By = ECogEngine_CollisionQueryBy::Channel; - Mode = ECogEngine_CollisionQueryMode::Multi; + Mode = ECogEngine_CollisionQueryTraceMode::Multi; Channel = ECC_WorldStatic; TraceComplex = false; Shape = ECogEngine_CollisionQueryShape::Sphere; diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_CollisionViewer.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_CollisionViewer.h index aea5efa..1c21937 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_CollisionViewer.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_CollisionViewer.h @@ -20,8 +20,6 @@ public: protected: - virtual void ResetConfig() override; - virtual void RenderHelp() override; virtual void RenderContent() override; diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Console.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Console.h new file mode 100644 index 0000000..28dd809 --- /dev/null +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Console.h @@ -0,0 +1,132 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CogWindow.h" +#include "CogEngineWindow_Console.generated.h" + +class UCogEngineConfig_Console; +class IConsoleObject; + +class COGENGINE_API FCogEngineWindow_Console : public FCogWindow +{ + typedef FCogWindow Super; + +protected: + + virtual void RenderHelp() override; + + virtual void Initialize() override; + + virtual void PreBegin(ImGuiWindowFlags& WindowFlags) override; + + virtual void PostBegin() override; + + virtual void RenderMainMenuWidget() override; + + virtual void RenderContent() override; + + virtual void RenderTick(float DeltaTime) override; + +private: + + static IConsoleObject* GetCommandObjectFromCommandLine(const FString& InCommandLine); + + static FString GetConsoleCommandHelp(const FString& InCommandLine); + + static int OnTextInputCallbackStub(ImGuiInputTextCallbackData* InData); + + void RenderMenu(); + + void RenderInput(); + + void SelectNextCommand(); + + void SelectPreviousCommand(); + + int OnTextInputCallback(ImGuiInputTextCallbackData* InData); + + void RenderCommandList(); + + void RenderCommand(const FString& CommandName, int32 Index, float RegionMinY, float RegionMaxY); + + void RefreshCommandList(); + + void ActivateInputText() const; + + void ExecuteCommand(const FString& InCommand); + + int32 SelectedCommandIndex = INDEX_NONE; + + TArray CommandList; + + int32 NumHistoryCommands = 0; + + FString CurrentUserInput; + + bool bScroll = false; + + bool bIsWindowFocused = false; + + bool bSetBufferToSelectedCommand = false; + + bool bIsWidgetMode = false; + + ImGuiID InputTextId = 0; + + bool WidgetMode_OpenCommandList = false; + + ImVec2 WidgetMode_CommandListPosition = ImVec2(0, 0); + + bool WidgetMode_IsTextInputActive = false; + + TWeakObjectPtr Config; +}; + +//-------------------------------------------------------------------------------------------------------------------------- +UCLASS(Config = Cog) +class UCogEngineConfig_Console : public UCogCommonConfig +{ + GENERATED_BODY() + +public: + + UPROPERTY(Config) + bool SortCommands = false; + + UPROPERTY(Config) + bool DockInputInMenuBar = false; + + UPROPERTY(Config) + bool FocusWidgetWhenAppearing = true; + + UPROPERTY(Config) + int32 WidgetWidth = 200; + + UPROPERTY(Config) + bool UseClipper = false; + + UPROPERTY(Config) + bool ShowHelp = true; + + UPROPERTY(Config) + int32 NumHistoryCommands = 10; + + UPROPERTY(Config) + int32 CompletionMinimumCharacters = 1; + + UPROPERTY(Config) + FVector4f HistoryColor = FVector4f(1.0f, 1.0f, 1.0f, 0.5f); + + virtual void Reset() override + { + Super::Reset(); + + SortCommands = false; + DockInputInMenuBar = false; + FocusWidgetWhenAppearing = false; + UseClipper = false; + NumHistoryCommands = 10; + CompletionMinimumCharacters = 1; + HistoryColor = FVector4f(1.0f, 1.0f, 1.0f, 0.5f); + } +}; \ No newline at end of file diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_DebugSettings.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_DebugSettings.h index f56fd9d..324fb29 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_DebugSettings.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_DebugSettings.h @@ -17,8 +17,6 @@ public: protected: - virtual void ResetConfig() override; - virtual void RenderHelp() override; virtual void PreSaveConfig() override; diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_ImGui.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_ImGui.h index 67cde25..0f17e2e 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_ImGui.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_ImGui.h @@ -7,8 +7,6 @@ class COGENGINE_API FCogEngineWindow_ImGui : public FCogWindow { typedef FCogWindow Super; -public: - protected: virtual void RenderTick(float DeltaTime) override; diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Inspector.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Inspector.h index 8218f42..98d0889 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Inspector.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Inspector.h @@ -44,7 +44,7 @@ protected: virtual bool RenderPropertyList(TArray& Properties, uint8* PointerToValue); - virtual bool RenderProperty(const FProperty* Property, uint8* PointerToValue, int IndexInArray); + virtual bool RenderProperty(const FProperty* Property, uint8* PointerToValue, int IndexInArray, const char* NameSuffix = nullptr); virtual bool RenderBool(const FBoolProperty* BoolProperty, uint8* PointerToValue); @@ -79,12 +79,16 @@ protected: virtual bool RenderObject(UObject* Object, bool ShowChildren); virtual bool RenderArray(const FArrayProperty* ArrayProperty, uint8* PointerToValue, bool ShowChildren); + + virtual bool RenderSet(const FSetProperty* SetProperty, uint8* PointerToValue, bool ShowChildren); + + virtual bool RenderMap(const FMapProperty* MapProperty, uint8* PointerToValue, bool ShowChildren); virtual FString GetPropertyName(const FProperty& Property); FCogEngineInspectorApplyFunction FindObjectApplyFunction(const UObject* Object) const; - struct Favorite + struct FFavorite { TWeakObjectPtr Object = nullptr; @@ -99,7 +103,7 @@ protected: bool bCollapseAllCategories = false; - TArray Favorites; + TArray Favorites; TArray> History; diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Levels.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Levels.h new file mode 100644 index 0000000..1ad36be --- /dev/null +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Levels.h @@ -0,0 +1,71 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CogWindow.h" +#include "CogEngineWindow_Levels.generated.h" + +class UCogEngineWindowConfig_LevelLoader; + +class COGENGINE_API FCogEngineWindow_Levels : public FCogWindow +{ + typedef FCogWindow Super; + +public: + + virtual void Initialize() override; + +protected: + + virtual void RenderHelp() override; + + virtual void RenderContent() override; + + virtual void RenderMenu(); + + virtual void RenderLevel(int32 InIndex, const FAssetData& InAsset); + + virtual void RenderLevelContextMenu(int Index, const FAssetData& Asset); + + virtual void GetAllLevels(TArray& OutLevels); + void RefreshSorting(); + + virtual void LoadLevel(const FAssetData& InAsset); + + +private: + + TArray Levels; + + TArray UnsortedLevels; + + bool HasGatheredLevels = false; + + int32 SelectedIndex = -1; + + TObjectPtr Config = nullptr; + + ImGuiTextFilter Filter; +}; + +//-------------------------------------------------------------------------------------------------------------------------- +UCLASS(Config = Cog) +class UCogEngineWindowConfig_LevelLoader : public UCogCommonConfig +{ + GENERATED_BODY() + +public: + + virtual void Reset() override + { + UCogCommonConfig::Reset(); + + SortByName = true; + ShowPath = false; + } + + UPROPERTY(Config) + bool SortByName = true; + + UPROPERTY(Config) + bool ShowPath = false; +}; diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Metrics.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Metrics.h index 6d97def..1646b26 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Metrics.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Metrics.h @@ -19,8 +19,6 @@ public: protected: - virtual void ResetConfig() override; - virtual void PreSaveConfig() override; virtual void RenderHelp() override; diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_NetEmulation.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_NetEmulation.h index 8651c59..7e94d1e 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_NetEmulation.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_NetEmulation.h @@ -3,6 +3,8 @@ #include "CoreMinimal.h" #include "CogWindow.h" +class UCogEngineWindowConfig_Stats; + class COGENGINE_API FCogEngineWindow_NetEmulation : public FCogWindow { typedef FCogWindow Super; @@ -11,12 +13,15 @@ protected: virtual void RenderHelp() override; + virtual void Initialize() override; + + virtual void RenderContextMenu() override; + virtual void RenderContent() override; virtual void DrawStats(); virtual void DrawControls(); -private: - + TWeakObjectPtr Config; }; diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_NetImGui.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_NetImGui.h index 07e7db7..2802f0b 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_NetImGui.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_NetImGui.h @@ -27,21 +27,17 @@ public: protected: - virtual void ResetConfig() override; - virtual void RenderHelp() override; virtual void RenderContent() override; - virtual void RenderTick(float DeltaTime); + virtual void RenderTick(float DeltaTime) override; - void ConnectTo(); + void ConnectTo() const; - void ConnectFrom(); + void ConnectFrom() const; - void Disconnect(); - - void TryStartup(); + void Disconnect() const; void RunServer(); diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Notifications.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Notifications.h new file mode 100644 index 0000000..2e4c8c4 --- /dev/null +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Notifications.h @@ -0,0 +1,170 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CogCommonConfig.h" +#include "CogWindow.h" +#include "CogWidgets.h" +#include "imgui.h" +#include "Misc/OutputDevice.h" +#include "CogEngineWindow_Notifications.generated.h" + +class UCogEngineConfig_Notifications; + +//-------------------------------------------------------------------------------------------------------------------------- +class FCogNotificationOutputDevice : public FOutputDevice +{ +public: + friend class FCogEngineWindow_Notifications; + + FCogNotificationOutputDevice(); + virtual ~FCogNotificationOutputDevice() override; + + virtual void Serialize(const TCHAR* Message, ELogVerbosity::Type Verbosity, const FName& Category) override; + + FCogEngineWindow_Notifications* Notifications = nullptr; +}; + +//-------------------------------------------------------------------------------------------------------------------------- +class COGENGINE_API FCogEngineWindow_Notifications : public FCogWindow +{ + typedef FCogWindow Super; + +public: + + virtual void Initialize() override; + + void OnLogReceived(const TCHAR* InMessage, ELogVerbosity::Type InVerbosity, const FName& InCategory); + + void Clear(); + void AddNotification(const TCHAR* InMessage, ELogVerbosity::Type InVerbosity); + +protected: + + struct FNotification + { + FString Id; + FDateTime Time; + ELogVerbosity::Type Verbosity; + FString Message; + }; + + virtual void RenderHelp() override; + + virtual void RenderContent() override; + + virtual void RenderTick(float DeltaTime) override; + + virtual void RenderNotifications(); + + virtual void RenderSettings(); + + ImGuiTextFilter Filter; + + FCogNotificationOutputDevice OutputDevice; + + TArray Notifications; + + static int32 NotificationsId; + + TWeakObjectPtr Config; +}; + +//-------------------------------------------------------------------------------------------------------------------------- +UCLASS(Config = Cog) +class UCogEngineConfig_Notifications : public UCogCommonConfig +{ + GENERATED_BODY() + +public: + + UPROPERTY(Config) + bool DisableNotifications = false; + + UPROPERTY(Config) + bool NotifyAllWarnings = false; + + UPROPERTY(Config) + bool NotifyAllErrors = false; + + UPROPERTY(Config) + bool NotifyConsoleCommands = true; + + UPROPERTY(Config) + FColor BackgroundDefaultColor = FColor::White; + + UPROPERTY(Config) + FColor BackgroundWarningColor = FColor::White; + + UPROPERTY(Config) + FColor BackgroundErrorColor = FColor::White; + + UPROPERTY(Config) + FColor BorderDefaultColor = FColor::White; + + UPROPERTY(Config) + FColor BorderWarningColor = FColor::White; + + UPROPERTY(Config) + FColor BorderErrorColor = FColor::White; + + UPROPERTY(Config) + FColor TextDefaultColor = FColor::White; + + UPROPERTY(Config) + FColor TextWarningColor = FColor::White; + + UPROPERTY(Config) + FColor TextErrorColor = FColor::White; + + UPROPERTY(Config) + FVector2f Alignment = FVector2f(1.0f, 1.0f); + + UPROPERTY(Config) + FIntVector2 Padding = FIntVector2(10, 10); + + UPROPERTY(Config) + bool UseFixedWidth = true; + + UPROPERTY(Config) + int32 MaxHeight = 100; + + UPROPERTY(Config) + int32 TextWrapping = 200; + + UPROPERTY(Config) + int32 Rounding = 6; + + UPROPERTY(Config) + bool ShowBorder = true; + + UPROPERTY(Config) + float Duration = 5.0f; + + UPROPERTY(Config) + float FadeOut = 0.5f; + + virtual void Reset() override + { + Super::Reset(); + + TextDefaultColor = FColor(200, 200, 200, 255); + TextWarningColor = FColor(255, 200, 0, 255); + TextErrorColor = FColor(240, 77, 77, 255); + BackgroundDefaultColor = FColor( 15, 15, 15, 150); + BackgroundWarningColor = FColor( 23, 9, 0, 150); + BackgroundErrorColor = FColor( 21, 0, 0, 150); + BorderDefaultColor = FColor(200, 200, 200, 100); + BorderWarningColor = FColor(255, 200, 0, 100); + BorderErrorColor = FColor(240, 77, 77, 100); + + Alignment = { 1, 1 }; + Padding = { 10, 10 }; + UseFixedWidth = true; + TextWrapping = 200; + MaxHeight = 100; + Rounding = 6; + ShowBorder = true; + Duration = 5.0f; + FadeOut = 0.5f; + } +}; \ No newline at end of file diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_OutputLog.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_OutputLog.h index 3cb9f9a..cb13085 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_OutputLog.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_OutputLog.h @@ -7,7 +7,6 @@ #include "Misc/OutputDevice.h" #include "CogEngineWindow_OutputLog.generated.h" -class FCogEngineWindow_OutputLog; class UCogEngineConfig_OutputLog; //-------------------------------------------------------------------------------------------------------------------------- @@ -17,7 +16,7 @@ public: friend class FCogEngineWindow_OutputLog; FCogLogOutputDevice(); - ~FCogLogOutputDevice(); + virtual ~FCogLogOutputDevice() override; virtual void Serialize(const TCHAR* Message, ELogVerbosity::Type Verbosity, const FName& Category) override; @@ -33,40 +32,41 @@ public: virtual void Initialize() override; - void AddLog(const TCHAR* Message, ELogVerbosity::Type Verbosity, const FName& Category); + void AddLog(const TCHAR* InMessage, ELogVerbosity::Type InVerbosity, const FName& InCategory); void Clear(); + void Copy() const; + protected: - virtual void RenderHelp() override; - - virtual void ResetConfig() override; - - virtual void RenderContent() override; - -private: - - struct FLineInfo + struct FLogInfo { - int32 Start = 0; - int32 End = 0; - int32 Frame = 0; + int32 LineStart = 0; + int32 LineEnd = 0; + uint64 Frame = 0; + FDateTime Time; ELogVerbosity::Type Verbosity; FName Category; }; + + virtual void RenderHelp() override; - void DrawRow(const char* BufferStart, const FLineInfo& Info, bool IsTableShown) const; + virtual void RenderContent() override; + + virtual void DrawRow(const char* InBufferStart, const FLogInfo& Info, bool InShowAsTableRow) const; + + virtual int32 GetDisplayedFrame(const FLogInfo& InLogInfo) const; ImGuiTextBuffer TextBuffer; ImGuiTextFilter Filter; - TArray LineInfos; + TArray LogInfos; FCogLogOutputDevice OutputDevice; - TObjectPtr Config = nullptr; + TWeakObjectPtr Config; }; //-------------------------------------------------------------------------------------------------------------------------- @@ -80,9 +80,15 @@ public: UPROPERTY(Config) bool AutoScroll = true; + UPROPERTY(Config) + bool ShowTime = true; + UPROPERTY(Config) bool ShowFrame = true; + UPROPERTY(Config) + int32 FrameCycle = 1000; + UPROPERTY(Config) bool ShowCategory = true; @@ -95,15 +101,31 @@ public: UPROPERTY(Config) int32 VerbosityFilter = ELogVerbosity::VeryVerbose; + UPROPERTY(Config) + FColor DefaultColor = FColor::White; + + UPROPERTY(Config) + FColor WarningColor = FColor::White; + + UPROPERTY(Config) + FColor ErrorColor = FColor::White; + + UPROPERTY(Config) + bool UseUTCTime = false; + virtual void Reset() override { Super::Reset(); AutoScroll = true; + ShowTime = false; ShowFrame = true; ShowCategory = true; ShowVerbosity = false; ShowAsTable = false; VerbosityFilter = ELogVerbosity::VeryVerbose; + DefaultColor = FColor(200, 200, 200, 255); + WarningColor = FColor(255, 200, 0, 255); + ErrorColor = FColor(255, 0, 0, 255); } }; \ No newline at end of file diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Plots.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Plots.h index f337506..6959362 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Plots.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Plots.h @@ -3,13 +3,18 @@ #include "CoreMinimal.h" #include "CogCommonConfig.h" #include "CogWindow.h" +#include "implot.h" #include "CogEngineWindow_Plots.generated.h" +struct FCogDebugTrack; +struct FCogDebugTracker; +struct FCogDebugPlotTrack; +struct FCogDebugEventTrack; +struct FCogDebugEvent; struct ImVec2; -struct FCogDebugPlotEvent; -struct FCogDebugPlotEntry; class UCogEngineConfig_Plots; +//-------------------------------------------------------------------------------------------------------------------------- class COGENGINE_API FCogEngineWindow_Plots : public FCogWindow { typedef FCogWindow Super; @@ -24,27 +29,61 @@ protected: virtual void RenderTick(float DeltaTime) override; + virtual void PreBegin(ImGuiWindowFlags& WindowFlags) override; + + virtual void PostBegin() override; + virtual void RenderContent() override; - virtual void RenderAllEntriesNames(const ImVec2& InSize); + virtual void RenderAllEntriesNames(FCogDebugTracker& InTracker, const ImVec2& InSize); - virtual void RenderEntryName(const int Index, FCogDebugPlotEntry& Entry); + virtual void RenderEntryName(FCogDebugTracker& InTracker, int Index, FCogDebugTrack& Entry); - virtual void RenderPlots(const TArray& VisiblePlots) const; + virtual void RenderPlots(FCogDebugTracker& InTracker); - virtual void RenderMenu(); + virtual void RenderMenu(FCogDebugTracker& InTracker); - virtual void RenderValues(FCogDebugPlotEntry& Entry, const char* Label) const; + virtual void RenderValues(FCogDebugPlotTrack& Timeline, const char* Label) const; - virtual void RenderEvents(FCogDebugPlotEntry& Entry, const char* Label, const ImVec2& PlotMin, const ImVec2& PlotMax) const; + virtual void RenderEvents(FCogDebugEventTrack& InTrack, const char* InLabel, const ImVec2& InPlotMin, const ImVec2& InPlotMax) const; - static void RenderEventTooltip(const FCogDebugPlotEvent* HoveredEvent, const FCogDebugPlotEntry& Entry); + virtual void RenderEventTooltip(const FCogDebugEvent* HoveredEvent, const FCogDebugTrack& Entry) const; + virtual void AssignToGraphAndAxis(FCogDebugTracker& InTracker, FName InName, int32 InGraphIndex, ImAxis InYAxis); + + virtual void UnassignToGraphAndAxis(FCogDebugTracker& InTracker, FName InName); + + virtual void RefreshPlotSettings(); + + static FName GetDroppedEntryName(const ImGuiPayload* Payload); + TObjectPtr Config = nullptr; bool bApplyTimeScale = false; +}; -private: +//-------------------------------------------------------------------------------------------------------------------------- +USTRUCT() +struct FCogEngineConfig_Plots_GraphEntryInfo +{ + GENERATED_BODY() + + UPROPERTY(Config) + FName Name; + + UPROPERTY(Config) + int32 YAxis = 0; +}; + + +//-------------------------------------------------------------------------------------------------------------------------- +USTRUCT() +struct FCogEngineConfig_Plots_GraphInfo +{ + GENERATED_BODY() + + UPROPERTY(Config) + TArray Entries; }; @@ -56,15 +95,23 @@ class UCogEngineConfig_Plots : public UCogCommonConfig public: + static constexpr int32 MaxNumGraphs = 5; + UPROPERTY(Config) int NumGraphs = 1; UPROPERTY(Config) int NumYAxis = 1; + UPROPERTY(Config) + bool RecordValuesWhenPaused = true; + UPROPERTY(Config) float TimeRange = 20.0f; + UPROPERTY(Config) + int32 NumRecordedValues = 2000; + UPROPERTY(Config) bool ShowTimeBarAtGameTime = true; @@ -83,15 +130,29 @@ public: UPROPERTY(Config) bool DockEntries = false; + UPROPERTY(Config) + float AutoFitPadding = 0.1f; + + UPROPERTY(Config) + FCogEngineConfig_Plots_GraphInfo Graphs[MaxNumGraphs]; + virtual void Reset() override { NumGraphs = 1; TimeRange = 20.0f; + RecordValuesWhenPaused = true; ShowTimeBarAtGameTime = true; ShowTimeBarAtCursor = true; ShowValueAtCursor = true; DragPauseSensitivity = 10.0f; PauseBackgroundColor = FColor(10, 0, 0, 255); DockEntries = false; + NumRecordedValues = 2000; + AutoFitPadding = 0.1f; + + for (FCogEngineConfig_Plots_GraphInfo& Graph : Graphs) + { + Graph.Entries.Empty(); + } } }; \ No newline at end of file diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Selection.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Selection.h index d40fc94..8502017 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Selection.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Selection.h @@ -2,6 +2,7 @@ #include "CoreMinimal.h" #include "CogCommonConfig.h" +#include "CogEngineDataAsset.h" #include "GameFramework/Actor.h" #include "CogWindow.h" #include "CogEngineWindow_Selection.generated.h" @@ -23,28 +24,10 @@ public: virtual void Shutdown() override; - bool GetIsSelecting() const { return bSelectionModeActive; } - - const TArray>& GetActorClasses() const { return ActorClasses; } - - void SetActorClasses(const TArray>& Value) { ActorClasses = Value; } - - ETraceTypeQuery GetTraceType() const { return TraceType; } - - void SetTraceType(ETraceTypeQuery Value) { TraceType = Value; } - - virtual void ActivateSelectionMode(); - - virtual void DeactivateSelectionMode(); - - virtual void ToggleSelectionMode(); - protected: virtual void TryReapplySelection() const; - virtual void ResetConfig() override; - virtual void PreSaveConfig() override; virtual void RenderHelp() override; @@ -53,9 +36,7 @@ protected: virtual void RenderContent() override; - virtual float GetMainMenuWidgetWidth(int32 SubWidgetIndex, float MaxWidth) override; - - virtual void RenderMainMenuWidget(int32 SubWidgetIndex, float Width) override; + virtual void RenderMainMenuWidget() override; virtual bool DrawSelectionCombo(); @@ -67,25 +48,25 @@ protected: virtual void RenderActorContextMenu(AActor& Actor); + virtual const TArray>& GetSelectionFilters() const; + + virtual ETraceTypeQuery GetSelectionTraceChannel() const; + TSubclassOf GetSelectedActorClass() const; - void TickSelectionMode(); + bool TickSelectionMode(); FVector LastSelectedActorLocation = FVector::ZeroVector; - bool bSelectionModeActive = false; - bool bIsInputEnabledBeforeEnteringSelectionMode = false; int32 WaitInputReleased = 0; - TArray> ActorClasses; + TWeakObjectPtr Config; - ETraceTypeQuery TraceType = TraceTypeQuery1; + TWeakObjectPtr Asset; - TObjectPtr Config; - - ImGuiTextFilter Filter; + ImGuiTextFilter Filter; }; //-------------------------------------------------------------------------------------------------------------------------- @@ -105,6 +86,9 @@ public: UPROPERTY(Config) int32 SelectedClassIndex = 0; + UPROPERTY(Config) + FInputChord Shortcut_ToggleSelection = FInputChord(EKeys::F5); + virtual void Reset() override { Super::Reset(); @@ -112,5 +96,6 @@ public: bReapplySelection = true; SelectionName.Reset(); SelectedClassIndex = 0; + Shortcut_ToggleSelection = FInputChord(EKeys::F5); } }; \ No newline at end of file diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Spawns.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Spawns.h index 221e9c9..759c581 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Spawns.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Spawns.h @@ -18,11 +18,11 @@ public: protected: - virtual void RenderHelp(); + virtual void RenderHelp() override; virtual void RenderContent() override; - virtual void RenderSpawnGroup(ACogEngineReplicator& Replicator, const FCogEngineSpawnGroup& SpawnGroup); + virtual void RenderSpawnGroup(ACogEngineReplicator& Replicator, const FCogEngineSpawnGroup& SpawnGroup, int32 GroupIndex); virtual bool RenderSpawnAsset(ACogEngineReplicator& Replicator, const FCogEngineSpawnEntry& SpawnEntry, bool IsLastSelected); diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Stats.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Stats.h index 6f5a341..e8909f1 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Stats.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Stats.h @@ -2,34 +2,113 @@ #include "CoreMinimal.h" #include "CogWindow.h" +#include "CogEngineWindow_Stats.generated.h" +class UCogEngineWindowConfig_Stats; + +//-------------------------------------------------------------------------------------------------------------------------- class COGENGINE_API FCogEngineWindow_Stats : public FCogWindow { typedef FCogWindow Super; -public: - - static ImVec4 GetFpsColor(float Value, float Good = 50.0f, float Medium = 30.0f); - - static ImVec4 GetPingColor(float Value, float Good = 100.0f, float Medium = 200.0f); - - static ImVec4 GetPacketLossColor(float Value, float Good = 10.0f, float Medium = 20.0f); - protected: virtual void Initialize() override; virtual void RenderHelp() override; + virtual void RenderContextMenu() override; + virtual void RenderContent() override; - virtual float GetMainMenuWidgetWidth(int32 SubWidgetIndex, float MaxWidth) override; + virtual void RenderMainMenuWidget() override; - virtual void RenderMainMenuWidget(int32 SubWidgetIndex, float Width) override; + virtual void RenderMainMenuWidgetPacketLoss(); - virtual void RenderMainMenuWidgetPacketLoss(float Width); + virtual void RenderMainMenuWidgetPing(); - virtual void RenderMainMenuWidgetPing(float Width); - - virtual void RenderMainMenuWidgetFramerate(float Width); + virtual void RenderMainMenuWidgetFrameRate(); + + TWeakObjectPtr Config; +}; + +//-------------------------------------------------------------------------------------------------------------------------- +UCLASS(Config = Cog) +class UCogEngineWindowConfig_Stats : public UCogCommonConfig +{ + GENERATED_BODY() + +public: + + virtual void Reset() override + { + UCogCommonConfig::Reset(); + + GoodColor = FColor(30, 255, 60, 255); + MediumColor = FColor(255, 200,0, 255); + BadColor = FColor(255, 80, 80, 255); + + FrameRates = { 0, 10, 20, 30, 60, 120 }; + Pings = { 0, 50, 100, 200, 500, 1000 }; + PacketLosses = { 0, 5, 10, 20, 30, 40, 50 }; + + GoodFrameRate = 60; + MediumFrameRate = 30; + GoodPing = 100; + MediumPing = 200; + GoodPacketLoss = 5; + MediumPacketLoss = 10; + } + + ImVec4 GetFpsColor(float Value) const; + + ImVec4 GetPingColor(float Value) const; + + ImVec4 GetPacketLossColor(float Value) const; + + void RenderAllConfigs(); + + void RenderColorConfig(); + + void RenderFrameRateConfig(); + + void RenderPingConfig(); + + void RenderPacketLossConfig(); + + UPROPERTY(Config) + TArray FrameRates; + + UPROPERTY(Config) + int GoodFrameRate = 0; + + UPROPERTY(Config) + int MediumFrameRate = 0; + + UPROPERTY(Config) + TArray Pings; + + UPROPERTY(Config) + int GoodPing = 0; + + UPROPERTY(Config) + int MediumPing = 0; + + UPROPERTY(Config) + TArray PacketLosses; + + UPROPERTY(Config) + int GoodPacketLoss = 0; + + UPROPERTY(Config) + int MediumPacketLoss = 0; + + UPROPERTY(Config) + FColor GoodColor = FColor(); + + UPROPERTY(Config) + FColor MediumColor = FColor(); + + UPROPERTY(Config) + FColor BadColor = FColor(); }; diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_TimeScale.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_TimeScale.h index 9c0b152..0e6298c 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_TimeScale.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_TimeScale.h @@ -1,24 +1,97 @@ #pragma once #include "CoreMinimal.h" +#include "CogEngineReplicator.h" #include "CogWindow.h" +#include "CogEngineWindow_TimeScale.generated.h" +class UCogEngineWindowConfig_TimeScale; + +//-------------------------------------------------------------------------------------------------------------------------- class COGENGINE_API FCogEngineWindow_TimeScale : public FCogWindow { typedef FCogWindow Super; public: - - void Initialize(); + virtual void Initialize() override; protected: virtual void RenderHelp() override; virtual void RenderContent() override; + + virtual void RenderContextMenu() override; - TArray TimingScales; + virtual void RenderMainMenuWidget() override; -private: + virtual void RenderTimeScaleChoices(ACogEngineReplicator* Replicator); + virtual int32 GetCurrentTimeScaleIndex(const ACogEngineReplicator& Replicator) const; + + virtual void SetCurrentTimeScaleIndex(ACogEngineReplicator& Replicator, int32 InTimeScaleIndex) const; + + virtual void FasterTimeScale(); + + virtual void SlowerTimeScale(); + + virtual void ResetTimeScale(); + + virtual void ZeroTimeScale(); + + virtual float GetTimeDilation() const; + + virtual int32 GetTimeScaleIndex(float InTimeScale) const; + + virtual void SetCurrentTimeScale(ACogEngineReplicator& Replicator, float Value) const; + + TWeakObjectPtr Config; }; + +//-------------------------------------------------------------------------------------------------------------------------- +UCLASS(Config = Cog) +class UCogEngineWindowConfig_TimeScale : public UCogCommonConfig +{ + GENERATED_BODY() + +public: + + virtual void Reset() override + { + UCogCommonConfig::Reset(); + + TimeScale = 1.0f; + Inline = true; + TimeScales = { 0.00f, 0.01f, 0.10f, 0.50f, 1.00f, 2.00f, 5.00f, 10.0f }; + TimeScaleModifiedColor = FColor(255, 30, 210, 255); + + Shortcut_ZeroTimeScale = FInputChord(EKeys::NumPadZero); + Shortcut_ResetTimeScale = FInputChord(EKeys::NumPadOne); + Shortcut_FasterTimeScale = FInputChord(EKeys::Add); + Shortcut_SlowerTimeScale = FInputChord(EKeys::Subtract); + } + + UPROPERTY(Config) + float TimeScale = 1.0f; + + UPROPERTY(Config) + TArray TimeScales = { 0.00f, 0.01f, 0.10f, 0.50f, 1.00f, 2.00f, 5.00f, 10.0f }; + + UPROPERTY(Config) + bool Inline = true; + + UPROPERTY(Config) + FColor TimeScaleModifiedColor = FColor(255, 30, 210, 255); + + UPROPERTY(Config) + FInputChord Shortcut_ZeroTimeScale = FInputChord(EKeys::NumPadZero); + + UPROPERTY(Config) + FInputChord Shortcut_ResetTimeScale = FInputChord(EKeys::NumPadOne); + + UPROPERTY(Config) + FInputChord Shortcut_FasterTimeScale = FInputChord(EKeys::Add); + + UPROPERTY(Config) + FInputChord Shortcut_SlowerTimeScale = FInputChord(EKeys::Subtract); +}; \ No newline at end of file diff --git a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Transform.h b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Transform.h index 2bf370b..ffdb094 100644 --- a/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Transform.h +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Transform.h @@ -22,17 +22,17 @@ protected: virtual void RenderContent() override; - bool RenderComponent(const char* Label, double* Value, float Speed, double Min, double Max, double Reset); + virtual bool RenderComponent(const char* Label, double* Value, float Speed, double Min, double Max, double Reset); - void RenderSnap(const char* CheckboxLabel, const char* InputLabel, bool* SnapEnable, float* Snap); + virtual void RenderSnap(const char* CheckboxLabel, const char* InputLabel, bool* SnapEnable, float* Snap); - bool RenderLocation(FTransform& InOutTransform); + virtual bool RenderLocation(FTransform& InOutTransform); - bool RenderRotation(FTransform& InOutTransform); + virtual bool RenderRotation(FTransform& InOutTransform); - bool RenderScale(FTransform& InOutTransform); + virtual bool RenderScale(FTransform& InOutTransform); - bool RenderTransform(FTransform& InOutTransform); + virtual bool RenderTransform(FTransform& InOutTransform); private: diff --git a/Plugins/Cog/Source/CogImgui/Private/CogImguiConfig.cpp b/Plugins/Cog/Source/CogImgui/Private/CogImguiConfig.cpp index 2dd03d5..ef8de74 100644 --- a/Plugins/Cog/Source/CogImgui/Private/CogImguiConfig.cpp +++ b/Plugins/Cog/Source/CogImgui/Private/CogImguiConfig.cpp @@ -1,3 +1,4 @@ +// ReSharper disable CppUnusedIncludeDirective #include "CogImguiConfig.h" THIRD_PARTY_INCLUDES_START diff --git a/Plugins/Cog/Source/CogImgui/Private/CogImguiContext.cpp b/Plugins/Cog/Source/CogImgui/Private/CogImguiContext.cpp index 80ad99b..78558d6 100644 --- a/Plugins/Cog/Source/CogImgui/Private/CogImguiContext.cpp +++ b/Plugins/Cog/Source/CogImgui/Private/CogImguiContext.cpp @@ -13,7 +13,7 @@ #include "Framework/Application/SlateApplication.h" #include "Framework/Application/SlateUser.h" #include "GameFramework/PlayerController.h" -#include "GameFramework/PlayerInput.h" +#include "HAL/PlatformApplicationMisc.h" #include "imgui.h" #include "imgui_internal.h" #include "implot.h" @@ -23,20 +23,18 @@ #include "Widgets/SViewport.h" #include "Widgets/SWindow.h" -static UPlayerInput* GetPlayerInput(const UWorld* World); - -FCogImGuiContextScope:: -FCogImGuiContextScope(FCogImguiContext& CogImguiContext) +//-------------------------------------------------------------------------------------------------------------------------- +FCogImGuiContextScope::FCogImGuiContextScope(const FCogImguiContext& CogImguiContext) { PrevContext = ImGui::GetCurrentContext(); PrevPlotContext = ImPlot::GetCurrentContext(); - ImGui::SetCurrentContext(CogImguiContext.ImGuiContext); + ImGui::SetCurrentContext(CogImguiContext.Context); ImPlot::SetCurrentContext(CogImguiContext.PlotContext); } -FCogImGuiContextScope:: -FCogImGuiContextScope(ImGuiContext* GuiCtx, ImPlotContext* PlotCtx) +//-------------------------------------------------------------------------------------------------------------------------- +FCogImGuiContextScope::FCogImGuiContextScope(ImGuiContext* GuiCtx, ImPlotContext* PlotCtx) { PrevContext = ImGui::GetCurrentContext(); PrevPlotContext = ImPlot::GetCurrentContext(); @@ -45,22 +43,29 @@ FCogImGuiContextScope(ImGuiContext* GuiCtx, ImPlotContext* PlotCtx) ImPlot::SetCurrentContext(PlotCtx); } -FCogImGuiContextScope:: -~FCogImGuiContextScope() +//-------------------------------------------------------------------------------------------------------------------------- +FCogImGuiContextScope::~FCogImGuiContextScope() { ImGui::SetCurrentContext(PrevContext); ImPlot::SetCurrentContext(PrevPlotContext); } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogImguiContext::bIsNetImguiInitialized = false; +bool FCogImguiContext::bIsNetImGuiInitialized = false; //-------------------------------------------------------------------------------------------------------------------------- -void FCogImguiContext::Initialize() +void FCogImguiContext::Initialize(UGameViewportClient* InGameViewport) { IMGUI_CHECKVERSION(); - GameViewport = GEngine->GameViewport; + GameViewport = InGameViewport; + + // ImGui Context must be created before creating widgets as widgets can receive events that uses the ImGui context right away. + Context = ImGui::CreateContext(); + PlotContext = ImPlot::CreateContext(); + ImGui::SetCurrentContext(Context); + ImPlot::SetImGuiContext(Context); + ImPlot::SetCurrentContext(PlotContext); if (GameViewport != nullptr) { @@ -71,12 +76,6 @@ void FCogImguiContext::Initialize() GameViewport->AddViewportWidgetContent(InputCatcherWidget.ToSharedRef(), -TNumericLimits::Max()); } - ImGuiContext = ImGui::CreateContext(); - PlotContext = ImPlot::CreateContext(); - ImGui::SetCurrentContext(ImGuiContext); - ImPlot::SetImGuiContext(ImGuiContext); - ImPlot::SetCurrentContext(PlotContext); - ImGuiIO& IO = ImGui::GetIO(); IO.UserData = this; @@ -121,6 +120,11 @@ void FCogImguiContext::Initialize() PlatformIO.Platform_SetWindowTitle = ImGui_SetWindowTitle; PlatformIO.Platform_SetWindowAlpha = ImGui_SetWindowAlpha; PlatformIO.Platform_RenderWindow = ImGui_RenderWindow; + + PlatformIO.Platform_ClipboardUserData = &ClipboardBuffer; + PlatformIO.Platform_GetClipboardTextFn = ImGui_GetClipboardTextFn; + PlatformIO.Platform_SetClipboardTextFn = ImGui_SetClipboardTextFn; + PlatformIO.Platform_OpenInShellFn = ImGui_OpenInShell; if (FSlateApplication::IsInitialized()) { @@ -143,10 +147,10 @@ void FCogImguiContext::Initialize() } #if NETIMGUI_ENABLED - if (bIsNetImguiInitialized == false) + if (bIsNetImGuiInitialized == false) { NetImgui::Startup(); - bIsNetImguiInitialized = true; + bIsNetImGuiInitialized = true; } #endif } @@ -154,16 +158,16 @@ void FCogImguiContext::Initialize() //-------------------------------------------------------------------------------------------------------------------------- void FCogImguiContext::Shutdown() { - FCogImGuiContextScope ImGuiContextScope(ImGuiContext, PlotContext); + FCogImGuiContextScope ImGuiContextScope(Context, PlotContext); //------------------------------------------------------------------ // NetImgui must be shutdown before imgui as it uses context hooks //------------------------------------------------------------------ #if NETIMGUI_ENABLED - if (bIsNetImguiInitialized) + if (bIsNetImGuiInitialized) { NetImgui::Shutdown(); - bIsNetImguiInitialized = false; + bIsNetImGuiInitialized = false; } #endif @@ -196,17 +200,46 @@ void FCogImguiContext::Shutdown() PlotContext = nullptr; } - if (ImGuiContext) + if (Context) { - ImGui::DestroyContext(ImGuiContext); - ImGuiContext = nullptr; + Context->IO.IniFilename = nullptr; + ImGui::DestroyContext(Context); + Context = nullptr; } } +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::SaveSettings() const +{ + if (Context && Context->SettingsLoaded && Context->IO.IniFilename != nullptr) + { + ImGui::SaveIniSettingsToDisk(Context->IO.IniFilename); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::OnImGuiWidgetFocusLost() +{ + if (bEnableInput == false) + { return; } + + if (GameViewport == nullptr) + { return; } + + const SViewport* ViewportWidget = GameViewport->GetGameViewportWidget().Get(); + if (ViewportWidget == nullptr) + { return; } + + if (!ViewportWidget->HasUserFocus(0)) + { return; } + + bRetakeFocus = true; +} + //-------------------------------------------------------------------------------------------------------------------------- void FCogImguiContext::OnDisplayMetricsChanged(const FDisplayMetrics& DisplayMetrics) const { - FCogImGuiContextScope ImGuiContextScope(ImGuiContext, PlotContext); + FCogImGuiContextScope ImGuiContextScope(Context, PlotContext); ImGuiPlatformIO& PlatformIO = ImGui::GetPlatformIO(); PlatformIO.Monitors.resize(0); @@ -234,11 +267,11 @@ void FCogImguiContext::OnDisplayMetricsChanged(const FDisplayMetrics& DisplayMet //-------------------------------------------------------------------------------------------------------------------------- bool FCogImguiContext::BeginFrame(float InDeltaTime) { - FCogImGuiContextScope ImGuiContextScope(ImGuiContext, PlotContext); - + FCogImGuiContextScope ImGuiContextScope(Context, PlotContext); + //------------------------------------------------------------------------------------------------------- // Skip the first frame, to let the main widget update its TickSpaceGeometry which is returned by the - // plateform callback ImGui_GetWindowPos. When using viewports Imgui needs to know the main viewport + // platform callback ImGui_GetWindowPos. When using viewports Imgui needs to know the main viewport // absolute position to correctly place the initial imgui windows. //------------------------------------------------------------------------------------------------------- if (bIsFirstFrame) @@ -247,6 +280,16 @@ bool FCogImguiContext::BeginFrame(float InDeltaTime) return false; } + //------------------------------------------------------------------------------------------------------- + // Sometime the game can retake unaware that ImGui want to keep the focus and mouse unlock. + // This typically happens when switching level. + //------------------------------------------------------------------------------------------------------- + if (bRetakeFocus && IsConsoleOpened() == false) + { + SetEnableInput(true); + bRetakeFocus = false; + } + ImGuiIO& IO = ImGui::GetIO(); IO.DeltaTime = InDeltaTime; @@ -298,11 +341,14 @@ bool FCogImguiContext::BeginFrame(float InDeltaTime) //------------------------------------------------------------------------------------------------------- // Refresh modifiers otherwise, when pressing ALT-TAB, the Alt modifier is always true //------------------------------------------------------------------------------------------------------- - FModifierKeysState ModifierKeys = FSlateApplication::Get().GetModifierKeys(); - if (ModifierKeys.IsControlDown() != IO.KeyCtrl) { IO.AddKeyEvent(ImGuiMod_Ctrl, ModifierKeys.IsControlDown()); } - if (ModifierKeys.IsShiftDown() != IO.KeyShift) { IO.AddKeyEvent(ImGuiMod_Shift, ModifierKeys.IsShiftDown()); } - if (ModifierKeys.IsAltDown() != IO.KeyAlt) { IO.AddKeyEvent(ImGuiMod_Alt, ModifierKeys.IsAltDown()); } - if (ModifierKeys.IsCommandDown() != IO.KeySuper) { IO.AddKeyEvent(ImGuiMod_Super, ModifierKeys.IsCommandDown()); } + if (bEnableInput) + { + FModifierKeysState ModifierKeys = FSlateApplication::Get().GetModifierKeys(); + if (ModifierKeys.IsControlDown() != IO.KeyCtrl) { IO.AddKeyEvent(ImGuiMod_Ctrl, ModifierKeys.IsControlDown()); } + if (ModifierKeys.IsShiftDown() != IO.KeyShift) { IO.AddKeyEvent(ImGuiMod_Shift, ModifierKeys.IsShiftDown()); } + if (ModifierKeys.IsAltDown() != IO.KeyAlt) { IO.AddKeyEvent(ImGuiMod_Alt, ModifierKeys.IsAltDown()); } + if (ModifierKeys.IsCommandDown() != IO.KeySuper) { IO.AddKeyEvent(ImGuiMod_Super, ModifierKeys.IsCommandDown()); } + } } //------------------------------------------------------------------------------------------------------- @@ -358,7 +404,7 @@ bool FCogImguiContext::BeginFrame(float InDeltaTime) } //-------------------------------------------------------------------------------------------------------------------------- -ImVec2 FCogImguiContext::GetImguiMousePos() +ImVec2 FCogImguiContext::GetImguiMousePos() const { const FVector2D& MousePosition = FSlateApplication::Get().GetCursorPos(); if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) @@ -373,7 +419,7 @@ ImVec2 FCogImguiContext::GetImguiMousePos() //-------------------------------------------------------------------------------------------------------------------------- void FCogImguiContext::EndFrame() { - FCogImGuiContextScope ImGuiContextScope(ImGuiContext, PlotContext); + FCogImGuiContextScope ImGuiContextScope(Context, PlotContext); ImGui::Render(); //NetImgui::EndFrame(); @@ -644,53 +690,60 @@ void FCogImguiContext::ImGui_RenderWindow(ImGuiViewport* Viewport, void* Data) } //-------------------------------------------------------------------------------------------------------------------------- -static APlayerController* GetLocalPlayerController(const UWorld* World) +const char* FCogImguiContext::ImGui_GetClipboardTextFn(ImGuiContext* InImGuiContext) { - if (World == nullptr) + TArray* ClipboardBuffer = static_cast*>(InImGuiContext->PlatformIO.Platform_ClipboardUserData); + if (ClipboardBuffer) { - return nullptr; - } + FString ClipboardText; + FPlatformApplicationMisc::ClipboardPaste(ClipboardText); - APlayerController* PlayerController = nullptr; - for (FConstPlayerControllerIterator Iterator = World->GetPlayerControllerIterator(); Iterator; ++Iterator) - { - APlayerController* ItPlayerController = Iterator->Get(); - if (ItPlayerController->IsLocalController()) - { - return ItPlayerController; - } + ClipboardBuffer->SetNumUninitialized(FPlatformString::ConvertedLength(*ClipboardText)); + FPlatformString::Convert(reinterpret_cast(ClipboardBuffer->GetData()), ClipboardBuffer->Num(), *ClipboardText, ClipboardText.Len() + 1); + + return ClipboardBuffer->GetData(); } return nullptr; } //-------------------------------------------------------------------------------------------------------------------------- -static UPlayerInput* GetPlayerInput(const UWorld* World) +void FCogImguiContext::ImGui_SetClipboardTextFn(ImGuiContext* InImGuiContext, const char* ClipboardText) { - if (World == nullptr) - { - return nullptr; - } - - APlayerController* PlayerController = GetLocalPlayerController(World); - if (PlayerController == nullptr) - { - return nullptr; - } - - UPlayerInput* PlayerInput = PlayerController->PlayerInput; - return PlayerInput; + FPlatformApplicationMisc::ClipboardCopy(UTF8_TO_TCHAR(ClipboardText)); } //-------------------------------------------------------------------------------------------------------------------------- -void FCogImguiContext::SetEnableInput(bool Value) + bool FCogImguiContext::ImGui_OpenInShell(ImGuiContext* Context, const char* Path) { - bEnableInput = Value; + return FPlatformProcess::LaunchFileInDefaultExternalApplication(UTF8_TO_TCHAR(Path)); +} + +//-------------------------------------------------------------------------------------------------------------------------- +static APlayerController* GetLocalPlayerController(const UWorld* World) +{ + if (World == nullptr) + { return nullptr; } + + for (FConstPlayerControllerIterator Iterator = World->GetPlayerControllerIterator(); Iterator; ++Iterator) + { + APlayerController* ItPlayerController = Iterator->Get(); + if (ItPlayerController->IsLocalController()) + { return ItPlayerController; } + } + + return nullptr; +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogImguiContext::SetEnableInput(const bool InValue) +{ + FCogImGuiContextScope ImGuiContextScope(Context, PlotContext); + + bEnableInput = InValue; if (FSlateApplication::IsInitialized() == false) - { - return; - } + { return; } if (bEnableInput) { @@ -715,6 +768,14 @@ void FCogImguiContext::SetEnableInput(bool Value) { LocalPlayer->GetSlateOperations().CaptureMouse(GameViewport->GetGameViewportWidget().ToSharedRef()); } + + //--------------------------------------------------------------------------------- + // Make sure no keys stay down after disabling inputs + //--------------------------------------------------------------------------------- + ImGuiIO& IO = ImGui::GetIO(); + IO.ClearInputMouse(); + IO.ClearEventsQueue(); + IO.ClearInputKeys(); } RefreshMouseCursor(); @@ -792,7 +853,7 @@ void FCogImguiContext::SetDPIScale(float Value) //-------------------------------------------------------------------------------------------------------------------------- void FCogImguiContext::BuildFont() { - FCogImGuiContextScope ImGuiContextScope(ImGuiContext, PlotContext); + FCogImGuiContextScope ImGuiContextScope(Context, PlotContext); if (FontAtlasTexture != nullptr) { @@ -842,7 +903,7 @@ bool FCogImguiContext::IsConsoleOpened() const } //-------------------------------------------------------------------------------------------------------------------------- -void FCogImguiContext::DrawDebug() +void FCogImguiContext::DrawDebug() const { if (ImGui::Begin("ImGui Integration Debug")) { diff --git a/Plugins/Cog/Source/CogImgui/Private/CogImguiHelper.cpp b/Plugins/Cog/Source/CogImgui/Private/CogImguiHelper.cpp index 956bef6..730b82b 100644 --- a/Plugins/Cog/Source/CogImgui/Private/CogImguiHelper.cpp +++ b/Plugins/Cog/Source/CogImgui/Private/CogImguiHelper.cpp @@ -56,10 +56,10 @@ FColor FCogImguiHelper::ToFColor(ImU32 Color) { return FColor { - (uint8)((Color >> IM_COL32_R_SHIFT) & 0xFF), - (uint8)((Color >> IM_COL32_G_SHIFT) & 0xFF), - (uint8)((Color >> IM_COL32_B_SHIFT) & 0xFF), - (uint8)((Color >> IM_COL32_A_SHIFT) & 0xFF) + static_cast((Color >> IM_COL32_R_SHIFT) & 0xFF), + static_cast((Color >> IM_COL32_G_SHIFT) & 0xFF), + static_cast((Color >> IM_COL32_B_SHIFT) & 0xFF), + static_cast((Color >> IM_COL32_A_SHIFT) & 0xFF) }; } @@ -87,6 +87,17 @@ ImVec2 FCogImguiHelper::ToImVec2(const FVector2D& Value) return ImVec2(Value.X, Value.Y); } +//-------------------------------------------------------------------------------------------------------------------------- +ImVec2 FCogImguiHelper::ToImVec2(const FIntVector2& Value) +{ + return ImVec2(Value.X, Value.Y); +} +//-------------------------------------------------------------------------------------------------------------------------- +ImVec2 FCogImguiHelper::ToImVec2(const FVector2f& Value) +{ + return ImVec2(Value.X, Value.Y); +} + //-------------------------------------------------------------------------------------------------------------------------- ImColor FCogImguiHelper::ToImColor(const FColor& Value) { @@ -118,11 +129,16 @@ ImVec4 FCogImguiHelper::ToImVec4(const FVector4f& Value) } //-------------------------------------------------------------------------------------------------------------------------- -ImU32 FCogImguiHelper::ToImU32(const FColor& Value) +ImU32 FCogImguiHelper::ToImU32(const FLinearColor& Value) { - return (ImU32)ToImColor(Value); + return ToImColor(Value); } +//-------------------------------------------------------------------------------------------------------------------------- +ImU32 FCogImguiHelper::ToImU32(const FColor& Value) +{ + return ToImColor(Value); +} //-------------------------------------------------------------------------------------------------------------------------- ImU32 FCogImguiHelper::ToImU32(const FVector4f& Value) { @@ -243,8 +259,14 @@ bool FCogImguiHelper::DragFVector2D(const char* Label, FVector2D& Vector, float //-------------------------------------------------------------------------------------------------------------------------- bool FCogImguiHelper::ColorEdit4(const char* Label, FColor& Color, ImGuiColorEditFlags Flags) { - FLinearColor Linear(Color); - const bool Result = ImGui::ColorEdit4(Label, &Linear.R, Flags); - Color = Linear.ToFColor(true); + ImColor c = ToImColor(Color); + const bool Result = ImGui::ColorEdit4(Label, &c.Value.x, Flags); + Color = ToFColor(c); return Result; } + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogImguiHelper::ColorEdit4(const char* Label, FLinearColor& Color, ImGuiColorEditFlags Flags) +{ + return ImGui::ColorEdit4(Label, &Color.R, Flags); +} diff --git a/Plugins/Cog/Source/CogImgui/Private/CogImguiInputCatcherWidget.cpp b/Plugins/Cog/Source/CogImgui/Private/CogImguiInputCatcherWidget.cpp index ec99bca..55f087e 100644 --- a/Plugins/Cog/Source/CogImgui/Private/CogImguiInputCatcherWidget.cpp +++ b/Plugins/Cog/Source/CogImgui/Private/CogImguiInputCatcherWidget.cpp @@ -1,6 +1,7 @@ #include "CogImguiInputCatcherWidget.h" #include "CogImguiContext.h" +#include "CogImguiHelper.h" #include "CogImguiInputHelper.h" #include "Engine/GameViewportClient.h" #include "imgui.h" @@ -17,11 +18,6 @@ void SCogImguiInputCatcherWidget::Construct(const FArguments& InArgs) } END_SLATE_FUNCTION_BUILD_OPTIMIZATION -//-------------------------------------------------------------------------------------------------------------------------- -SCogImguiInputCatcherWidget::~SCogImguiInputCatcherWidget() -{ -} - //-------------------------------------------------------------------------------------------------------------------------- void SCogImguiInputCatcherWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { @@ -91,7 +87,7 @@ FReply SCogImguiInputCatcherWidget::OnMouseButtonUp(const FGeometry& MyGeometry, } //-------------------------------------------------------------------------------------------------------------------------- -FReply SCogImguiInputCatcherWidget::HandleMouseButtonEvent(const FPointerEvent& MouseEvent, bool Down) +FReply SCogImguiInputCatcherWidget::HandleMouseButtonEvent(const FPointerEvent& MouseEvent, bool Down) const { FCogImGuiContextScope ImGuiContextScope(*Context); @@ -132,19 +128,19 @@ FReply SCogImguiInputCatcherWidget::OnMouseMove(const FGeometry& MyGeometry, con //-------------------------------------------------------------------------------------------------------------------------- void SCogImguiInputCatcherWidget::RefreshVisibility() { - EVisibility DesiredVisiblity = EVisibility::SelfHitTestInvisible; + EVisibility DesiredVisibility; if (Context->GetEnableInput() && Context->GetShareMouseWithGameplay() == false) { - DesiredVisiblity = EVisibility::Visible; + DesiredVisibility = EVisibility::Visible; } else { - DesiredVisiblity = EVisibility::SelfHitTestInvisible; + DesiredVisibility = EVisibility::SelfHitTestInvisible; } - if (DesiredVisiblity != GetVisibility()) + if (DesiredVisibility != GetVisibility()) { - SetVisibility(DesiredVisiblity); + SetVisibility(DesiredVisibility); } } diff --git a/Plugins/Cog/Source/CogImgui/Private/CogImguiInputHelper.cpp b/Plugins/Cog/Source/CogImgui/Private/CogImguiInputHelper.cpp index fd7e593..4611172 100644 --- a/Plugins/Cog/Source/CogImgui/Private/CogImguiInputHelper.cpp +++ b/Plugins/Cog/Source/CogImgui/Private/CogImguiInputHelper.cpp @@ -1,6 +1,5 @@ #include "CogImguiInputHelper.h" -#include "CogImguiKeyInfo.h" #include "Engine/World.h" #include "Framework/Application/SlateApplication.h" #include "Framework/Commands/UICommandInfo.h" @@ -16,6 +15,9 @@ #include "Kismet2/DebuggerCommands.h" #endif //WITH_EDITOR +//-------------------------------------------------------------------------------------------------------------------------- + TArray FCogImguiInputHelper::CogPrioritizedShortcuts; + //-------------------------------------------------------------------------------------------------------------------------- APlayerController* FCogImguiInputHelper::GetFirstLocalPlayerController(const UWorld& World) { @@ -45,23 +47,35 @@ UPlayerInput* FCogImguiInputHelper::GetPlayerInput(const UWorld& World) } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogImguiInputHelper::IsKeyEventHandled(UWorld* World, const FKeyEvent& KeyEvent) +bool FCogImguiInputHelper::IsTopPriorityKey(const UPlayerInput& PlayerInput, const FKey& InKey) { + FKeyEvent KeyEvent(InKey, FModifierKeysState(), 0, false, 0, 0); + return IsTopPriorityKeyEvent(PlayerInput, KeyEvent); +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogImguiInputHelper::IsTopPriorityKeyEvent(const UPlayerInput& PlayerInput, const FKeyEvent& InKeyEvent) +{ + //------------------------------------------------------------------------------------------------ + // We want the user to be able to use Cog shortcuts when imgui has the input. + //------------------------------------------------------------------------------------------------ + for (const FInputChord& InputChord : CogPrioritizedShortcuts) + { + if (IsInputChordMatchingKeyInfo(InKeyEvent, InputChord)) + { return true; } + } + //------------------------------------------------------------------------------------------------ // We want the user to be able to open the console command when imgui has the input. //------------------------------------------------------------------------------------------------ - if (IsConsoleEvent(KeyEvent)) - { - return false; - } + if (IsConsoleEvent(InKeyEvent)) + { return true; } //------------------------------------------------------------------------------------------------ // We want the user to be able to stop its session by pressing Esc, even when imgui has the input //------------------------------------------------------------------------------------------------ - if (IsStopPlaySessionEvent(KeyEvent)) - { - return false; - } + if (IsStopPlaySessionEvent(InKeyEvent)) + { return true; } //------------------------------------------------------------------------------------------------ // If we receive a key modifier, we want to let others systems know about it. @@ -71,22 +85,16 @@ bool FCogImguiInputHelper::IsKeyEventHandled(UWorld* World, const FKeyEvent& Key // and not the Key+Modifier event. // We update ImGui modifier keys in SCogImguiWidget::TickKeyModifiers(). //------------------------------------------------------------------------------------------------ - if (KeyEvent.GetKey().IsModifierKey()) - { - return false; - } + if (InKeyEvent.GetKey().IsModifierKey()) + { return true; } //------------------------------------------------------------------------------------------------ // We want the user to be able to use command bindings, even when imgui has the input. - // We actually use a console command to toggle the input from the game to imgui, and other - // windows command such as LoadLayout. //------------------------------------------------------------------------------------------------ - if (IsKeyBoundToCommand(World, KeyEvent)) - { - return false; - } + if (IsKeyBoundToCommand(PlayerInput, InKeyEvent)) + { return true; } - return true; + return false; } //-------------------------------------------------------------------------------------------------------------------------- @@ -95,40 +103,19 @@ bool FCogImguiInputHelper::IsCheckBoxStateMatchingValue(ECheckBoxState CheckBoxS const bool Result = (CheckBoxState == ECheckBoxState::Undetermined) || ((CheckBoxState == ECheckBoxState::Checked) == bValue); return Result; } - + //-------------------------------------------------------------------------------------------------------------------------- -bool FCogImguiInputHelper::IsKeyEventMatchingKeyInfo(const FKeyEvent& KeyEvent, const FCogImGuiKeyInfo& KeyInfo) +bool FCogImguiInputHelper::IsCheckBoxStateMatchingKeyBindModifier(ECheckBoxState InCheckBoxState, bool InRequireModifier, bool InIgnoreModifier) { - const bool Result = (KeyInfo.Key == KeyEvent.GetKey()) - && IsCheckBoxStateMatchingValue(KeyInfo.Shift, KeyEvent.IsShiftDown()) - && IsCheckBoxStateMatchingValue(KeyInfo.Ctrl, KeyEvent.IsControlDown()) - && IsCheckBoxStateMatchingValue(KeyInfo.Alt, KeyEvent.IsAltDown()) - && IsCheckBoxStateMatchingValue(KeyInfo.Cmd, KeyEvent.IsCommandDown()); - - return Result; + switch (InCheckBoxState) + { + case ECheckBoxState::Undetermined: return true; + case ECheckBoxState::Checked: return InRequireModifier && InIgnoreModifier == false; + case ECheckBoxState::Unchecked: return InRequireModifier == false && InIgnoreModifier; + } + return false; } -//-------------------------------------------------------------------------------------------------------------------------- -#define BREAK_CHECKBOX_STATE(CheckBoxState, RequireValue, IgnoreValue) \ -{ \ - if (CheckBoxState == ECheckBoxState::Checked) \ - { \ - RequireValue = true; \ - IgnoreValue = false; \ - } \ - else if (CheckBoxState == ECheckBoxState::Unchecked) \ - { \ - RequireValue = false; \ - IgnoreValue = true; \ - } \ - else if (CheckBoxState == ECheckBoxState::Undetermined) \ - { \ - RequireValue = false; \ - IgnoreValue = false; \ - } \ -} \ - - //-------------------------------------------------------------------------------------------------------------------------- ECheckBoxState FCogImguiInputHelper::MakeCheckBoxState(uint8 RequireValue, uint8 IgnoreValue) { @@ -146,60 +133,35 @@ ECheckBoxState FCogImguiInputHelper::MakeCheckBoxState(uint8 RequireValue, uint8 } //-------------------------------------------------------------------------------------------------------------------------- -void FCogImguiInputHelper::KeyBindToKeyInfo(const FKeyBind& KeyBind, FCogImGuiKeyInfo& KeyInfo) +bool FCogImguiInputHelper::IsInputChordMatchingKeyInfo(const FKeyEvent& InKeyEvent, const FInputChord& InInputChord) { - KeyInfo.Key = KeyBind.Key; - KeyInfo.Shift = MakeCheckBoxState(KeyBind.Shift, KeyBind.bIgnoreShift); - KeyInfo.Ctrl = MakeCheckBoxState(KeyBind.Control, KeyBind.bIgnoreCtrl); - KeyInfo.Alt = MakeCheckBoxState(KeyBind.Alt, KeyBind.bIgnoreAlt); - KeyInfo.Alt = MakeCheckBoxState(KeyBind.Cmd, KeyBind.bIgnoreCmd); -} + const bool Result = (InInputChord.Key == InKeyEvent.GetKey()) + && (InInputChord.bShift == InKeyEvent.IsShiftDown()) + && (InInputChord.bCtrl == InKeyEvent.IsControlDown()) + && (InInputChord.bAlt == InKeyEvent.IsAltDown()) + && (InInputChord.bCmd == InKeyEvent.IsCommandDown()); - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogImguiInputHelper::KeyInfoToKeyBind(const FCogImGuiKeyInfo& KeyInfo, FKeyBind& KeyBind) -{ - KeyBind.Key = KeyInfo.Key; - BREAK_CHECKBOX_STATE(KeyInfo.Shift, KeyBind.Shift, KeyBind.bIgnoreShift); - BREAK_CHECKBOX_STATE(KeyInfo.Ctrl, KeyBind.Control, KeyBind.bIgnoreCtrl); - BREAK_CHECKBOX_STATE(KeyInfo.Alt, KeyBind.Alt, KeyBind.bIgnoreAlt); - BREAK_CHECKBOX_STATE(KeyInfo.Cmd, KeyBind.Cmd, KeyBind.bIgnoreCmd); + return Result; } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogImguiInputHelper::WasKeyInfoJustPressed(APlayerController& PlayerController, const FCogImGuiKeyInfo& KeyInfo) +bool FCogImguiInputHelper::IsKeyBindMatchingInputChord(const FKeyBind& InKeyBind, const FInputChord& InInputChord) { - if (PlayerController.WasInputKeyJustPressed(KeyInfo.Key)) - { - const FModifierKeysState& ModifierKeys = FSlateApplication::Get().GetModifierKeys(); + const bool Result = + InKeyBind.bDisabled == false + && (InInputChord.Key == InKeyBind.Key) + && (InInputChord.bShift == InKeyBind.Shift) + && (InInputChord.bCtrl == InKeyBind.Control) + && (InInputChord.bAlt == InKeyBind.Alt) + && (InInputChord.bCmd == InKeyBind.Cmd); - const bool MatchCtrl = IsCheckBoxStateMatchingValue(KeyInfo.Ctrl, ModifierKeys.IsControlDown()); - const bool MatchAlt = IsCheckBoxStateMatchingValue(KeyInfo.Alt, ModifierKeys.IsAltDown()); - const bool MatchShift = IsCheckBoxStateMatchingValue(KeyInfo.Shift, ModifierKeys.IsShiftDown()); - const bool MatchCmd = IsCheckBoxStateMatchingValue(KeyInfo.Cmd, ModifierKeys.IsCommandDown()); - - const bool Result = MatchCtrl && MatchAlt && MatchShift && MatchCmd; - return Result; - } - - return false; + return Result; } //-------------------------------------------------------------------------------------------------------------------------- -bool FCogImguiInputHelper::IsKeyBoundToCommand(UWorld* World, const FKeyEvent& KeyEvent) +bool FCogImguiInputHelper::IsKeyBoundToCommand(const UPlayerInput& PlayerInput, const FKeyEvent& KeyEvent) { - if (World == nullptr) - { - return false; - } - - const UPlayerInput* PlayerInput = GetPlayerInput(*World); - if (PlayerInput == nullptr) - { - return false; - } - - for (const FKeyBind& KeyBind : PlayerInput->DebugExecBindings) + for (const FKeyBind& KeyBind : PlayerInput.DebugExecBindings) { if (IsKeyEventMatchingKeyBind(KeyEvent, KeyBind)) { @@ -252,85 +214,57 @@ EMouseCursor::Type FCogImguiInputHelper::ToSlateMouseCursor(ImGuiMouseCursor Mou { switch (MouseCursor) { - case ImGuiMouseCursor_Arrow: return EMouseCursor::Default; - case ImGuiMouseCursor_TextInput: return EMouseCursor::TextEditBeam; - case ImGuiMouseCursor_ResizeAll: return EMouseCursor::CardinalCross; - case ImGuiMouseCursor_ResizeNS: return EMouseCursor::ResizeUpDown; - case ImGuiMouseCursor_ResizeEW: return EMouseCursor::ResizeLeftRight; - case ImGuiMouseCursor_ResizeNESW: return EMouseCursor::ResizeSouthWest; - case ImGuiMouseCursor_ResizeNWSE: return EMouseCursor::ResizeSouthEast; + case ImGuiMouseCursor_Arrow: return EMouseCursor::Default; + case ImGuiMouseCursor_TextInput: return EMouseCursor::TextEditBeam; + case ImGuiMouseCursor_ResizeAll: return EMouseCursor::CardinalCross; + case ImGuiMouseCursor_ResizeNS: return EMouseCursor::ResizeUpDown; + case ImGuiMouseCursor_ResizeEW: return EMouseCursor::ResizeLeftRight; + case ImGuiMouseCursor_ResizeNESW: return EMouseCursor::ResizeSouthWest; + case ImGuiMouseCursor_ResizeNWSE: return EMouseCursor::ResizeSouthEast; + case ImGuiMouseCursor_Hand: return EMouseCursor::Hand; + case ImGuiMouseCursor_NotAllowed: return EMouseCursor::SlashedCircle; - case ImGuiMouseCursor_None: - default: - return EMouseCursor::None; + case ImGuiMouseCursor_None: + default: + return EMouseCursor::None; } } //-------------------------------------------------------------------------------------------------------------------------- -FString FCogImguiInputHelper::CommandToString(const UWorld& World, const FString& Command) +FString FCogImguiInputHelper::InputChordToString(const FInputChord& InInputChord) { - const UPlayerInput* PlayerInput = GetPlayerInput(World); - if (PlayerInput == nullptr) + if (InInputChord.Key == FKey()) { - return FString(); + return FString(""); + } + + FString Result = "["; + if (InInputChord.bAlt) + { + Result += FString("Alt "); } - const FKeyBind* Result = PlayerInput->DebugExecBindings.FindByPredicate([&](const FKeyBind& KeyBind) { return KeyBind.Command == Command; }); - if (Result == nullptr) + if (InInputChord.bShift) { - return FString(); + Result += FString("Shift "); } - return KeyBindToString(*Result); -} - -//-------------------------------------------------------------------------------------------------------------------------- -FString FCogImguiInputHelper::CommandToString(const UPlayerInput* PlayerInput, const FString& Command) -{ - if (PlayerInput == nullptr) + if (InInputChord.bCtrl) { - return FString(); + Result += FString("Ctrl "); } - const FKeyBind* Result = PlayerInput->DebugExecBindings.FindByPredicate([&](const FKeyBind& KeyBind) { return KeyBind.Command == Command; }); - if (Result == nullptr) + if (InInputChord.bCmd) { - return FString(); + Result += FString("Cmd "); } - return KeyBindToString(*Result); -} - -//-------------------------------------------------------------------------------------------------------------------------- -FString FCogImguiInputHelper::KeyBindToString(const FKeyBind& KeyBind) -{ - FString Result; - if (KeyBind.Alt) - { - Result = Result.Append("Alt "); - } - - if (KeyBind.Shift) - { - Result = Result.Append("Shift "); - } - - if (KeyBind.Control) - { - Result = Result.Append("Ctrl "); - } - - if (KeyBind.Cmd) - { - Result = Result.Append("Cmd "); - } - - Result = Result.Printf(TEXT("[%s]"), *KeyBind.Key.ToString()); - + Result += InInputChord.Key.ToString(); + Result += FString("]"); + return Result; } - //-------------------------------------------------------------------------------------------------------------------------- bool FCogImguiInputHelper::IsKeyEventMatchingKeyBind(const FKeyEvent& KeyEvent, const FKeyBind& KeyBind) { @@ -386,7 +320,7 @@ bool FCogImguiInputHelper::IsKeyBoundToCommand(const UPlayerInput* InPlayerInput //-------------------------------------------------------------------------------------------------------------------------- bool FCogImguiInputHelper::IsMouseInsideMainViewport() { - if (ImGuiViewportP* Viewport = (ImGuiViewportP*)ImGui::GetMainViewport()) + if (ImGuiViewportP* Viewport = static_cast(ImGui::GetMainViewport())) { ImGuiIO& IO = ImGui::GetIO(); const bool Result = Viewport->GetMainRect().Contains(IO.MousePos); @@ -396,6 +330,31 @@ bool FCogImguiInputHelper::IsMouseInsideMainViewport() return false; } +//-------------------------------------------------------------------------------------------------------------------------- +bool FCogImguiInputHelper::DisableCommandsConflictingWithShortcuts(UPlayerInput& PlayerInput) +{ + bool HasDisabled = false; + + for (const FInputChord& Shortcut : CogPrioritizedShortcuts) + { + for (FKeyBind& KeyBind : PlayerInput.DebugExecBindings) + { + if (IsKeyBindMatchingInputChord(KeyBind, Shortcut)) + { + KeyBind.bDisabled = true; + HasDisabled = false; + } + } + } + + if (HasDisabled) + { + PlayerInput.SaveConfig(); + } + + return false; +} + //-------------------------------------------------------------------------------------------------------------------------- ImGuiKey FCogImguiInputHelper::ToImKey(const FKey& Key) { @@ -540,3 +499,4 @@ ImGuiKey FCogImguiInputHelper::ToImKey(const FKey& Key) const ImGuiKey* Result = LookupMap.Find(Key); return (Result != nullptr) ? *Result : ImGuiKey_None; } + diff --git a/Plugins/Cog/Source/CogImgui/Private/CogImguiWidget.cpp b/Plugins/Cog/Source/CogImgui/Private/CogImguiWidget.cpp index dd32365..8ad7315 100644 --- a/Plugins/Cog/Source/CogImgui/Private/CogImguiWidget.cpp +++ b/Plugins/Cog/Source/CogImgui/Private/CogImguiWidget.cpp @@ -1,6 +1,7 @@ #include "CogImguiWidget.h" #include "CogImguiContext.h" +#include "CogImguiHelper.h" #include "CogImguiInputHelper.h" #include "Engine/GameViewportClient.h" #include "imgui.h" @@ -17,11 +18,6 @@ void SCogImguiWidget::Construct(const FArguments& InArgs) } END_SLATE_FUNCTION_BUILD_OPTIMIZATION -//-------------------------------------------------------------------------------------------------------------------------- -SCogImguiWidget::~SCogImguiWidget() -{ -} - //-------------------------------------------------------------------------------------------------------------------------- void SCogImguiWidget::SetDrawData(const ImDrawData* InDrawData) { @@ -118,6 +114,11 @@ FReply SCogImguiWidget::OnKeyChar(const FGeometry& MyGeometry, const FCharacterE { FCogImGuiContextScope ImGuiContextScope(*Context); + if (Context->GetEnableInput() == false) + { + return FReply::Unhandled(); + } + ImGuiIO& IO = ImGui::GetIO(); IO.AddInputCharacter(FCogImguiInputHelper::CastInputChar(CharacterEvent.GetCharacter())); @@ -138,7 +139,7 @@ FReply SCogImguiWidget::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& Ke } //-------------------------------------------------------------------------------------------------------------------------- -FReply SCogImguiWidget::HandleKeyEvent(const FKeyEvent& KeyEvent, bool Down) +FReply SCogImguiWidget::HandleKeyEvent(const FKeyEvent& KeyEvent, bool Down) const { FCogImGuiContextScope ImGuiContextScope(*Context); @@ -152,9 +153,15 @@ FReply SCogImguiWidget::HandleKeyEvent(const FKeyEvent& KeyEvent, bool Down) return FReply::Unhandled(); } - if (FCogImguiInputHelper::IsKeyEventHandled(Context->GetGameViewport()->GetWorld(), KeyEvent) == false) + if (const UWorld* World = Context->GetGameViewport()->GetWorld()) { - return FReply::Unhandled(); + if (const UPlayerInput* PlayerInput = FCogImguiInputHelper::GetPlayerInput(*World)) + { + if (FCogImguiInputHelper::IsTopPriorityKeyEvent(*PlayerInput, KeyEvent)) + { + return FReply::Unhandled(); + } + } } ImGuiIO& IO = ImGui::GetIO(); @@ -169,6 +176,11 @@ FReply SCogImguiWidget::HandleKeyEvent(const FKeyEvent& KeyEvent, bool Down) return FReply::Unhandled(); } + // if (IO.WantTextInput == false) + // { + // return FReply::Unhandled(); + // } + return FReply::Handled(); } @@ -202,7 +214,7 @@ FReply SCogImguiWidget::OnMouseButtonUp(const FGeometry& MyGeometry, const FPoin } //-------------------------------------------------------------------------------------------------------------------------- -FReply SCogImguiWidget::HandleMouseButtonEvent(const FPointerEvent& MouseEvent, bool Down) +FReply SCogImguiWidget::HandleMouseButtonEvent(const FPointerEvent& MouseEvent, bool Down) const { FCogImGuiContextScope ImGuiContextScope(*Context); @@ -265,29 +277,40 @@ FReply SCogImguiWidget::OnFocusReceived(const FGeometry& MyGeometry, const FFocu return Super::OnFocusReceived(MyGeometry, FocusEvent); } +//-------------------------------------------------------------------------------------------------------------------------- +void SCogImguiWidget::OnFocusLost(const FFocusEvent& InFocusEvent) +{ + SLeafWidget::OnFocusLost(InFocusEvent); + + if (Context != nullptr) + { + Context->OnImGuiWidgetFocusLost(); + } +} + //-------------------------------------------------------------------------------------------------------------------------- void SCogImguiWidget::RefreshVisibility() { - EVisibility DesiredVisiblity = EVisibility::SelfHitTestInvisible; + EVisibility DesiredVisibility; if (Context->GetEnableInput()) { if (Context->GetShareMouse() && Context->GetWantCaptureMouse() == false) { - DesiredVisiblity = EVisibility::SelfHitTestInvisible; + DesiredVisibility = EVisibility::SelfHitTestInvisible; } else { - DesiredVisiblity = EVisibility::Visible; + DesiredVisibility = EVisibility::Visible; } } else { - DesiredVisiblity = EVisibility::SelfHitTestInvisible; + DesiredVisibility = EVisibility::SelfHitTestInvisible; } - if (DesiredVisiblity != GetVisibility()) + if (DesiredVisibility != GetVisibility()) { - SetVisibility(DesiredVisiblity); + SetVisibility(DesiredVisibility); } } diff --git a/Plugins/Cog/Source/CogImgui/Public/CogImguiContext.h b/Plugins/Cog/Source/CogImgui/Public/CogImguiContext.h index 66a9d1b..30b896e 100644 --- a/Plugins/Cog/Source/CogImgui/Public/CogImguiContext.h +++ b/Plugins/Cog/Source/CogImgui/Public/CogImguiContext.h @@ -28,7 +28,7 @@ struct COGIMGUI_API FCogImGuiViewportData struct COGIMGUI_API FCogImGuiContextScope { - UE_NODISCARD_CTOR explicit FCogImGuiContextScope(FCogImguiContext& CogImguiContext); + UE_NODISCARD_CTOR explicit FCogImGuiContextScope(const FCogImguiContext& CogImguiContext); UE_NODISCARD_CTOR explicit FCogImGuiContextScope(ImGuiContext* GuiCtx, ImPlotContext* PlotCtx); ~FCogImGuiContextScope(); @@ -41,13 +41,15 @@ class COGIMGUI_API FCogImguiContext : public TSharedFromThis { public: - void Initialize(); + void Initialize(UGameViewportClient* InGameViewport); void Shutdown(); + void SaveSettings() const; + bool GetEnableInput() const { return bEnableInput; } - void SetEnableInput(bool Value); + void SetEnableInput(bool InValue); bool GetWantCaptureMouse() const { return bWantCaptureMouse; } @@ -65,8 +67,6 @@ public: bool BeginFrame(float InDeltaTime); - void GetCursorPos(ImGuiIO& IO); - void EndFrame(); float GetDpiScale() const { return DpiScale; } @@ -77,13 +77,15 @@ public: void SetSkipRendering(bool Value); - ImVec2 GetImguiMousePos(); + ImVec2 GetImguiMousePos() const; TObjectPtr GetGameViewport() const { return GameViewport; } TSharedPtr GetMainWidget() const { return MainWidget; } - static bool GetIsNetImguiInitialized() { return bIsNetImguiInitialized; } + void OnImGuiWidgetFocusLost(); + + static bool GetIsNetImguiInitialized() { return bIsNetImGuiInitialized; } private: @@ -93,7 +95,7 @@ private: bool IsConsoleOpened() const; - void DrawDebug(); + void DrawDebug() const; void BuildFont(); @@ -127,7 +129,12 @@ private: static void ImGui_RenderWindow(ImGuiViewport* Viewport, void* Data); - UPROPERTY() + static const char* ImGui_GetClipboardTextFn(ImGuiContext* InImGuiContext); + + static void ImGui_SetClipboardTextFn(ImGuiContext* InImGuiContext, const char* Arg); + + static bool ImGui_OpenInShell(ImGuiContext* Context, const char* Path); + UTexture2D* FontAtlasTexture = nullptr; TMap, ImGuiID> WindowToViewportMap; @@ -144,12 +151,14 @@ private: TObjectPtr GameViewport = nullptr; - ImGuiContext* ImGuiContext = nullptr; + ImGuiContext* Context = nullptr; ImPlotContext* PlotContext = nullptr; char IniFilename[512] = {}; + TArray ClipboardBuffer; + bool bEnableInput = false; bool bShareMouse = false; @@ -174,6 +183,8 @@ private: bool bSkipRendering = false; - static bool bIsNetImguiInitialized; + bool bRetakeFocus = false; + + static bool bIsNetImGuiInitialized; }; diff --git a/Plugins/Cog/Source/CogImgui/Public/CogImguiDrawList.h b/Plugins/Cog/Source/CogImgui/Public/CogImguiDrawList.h index 415f155..4dc2d6e 100644 --- a/Plugins/Cog/Source/CogImgui/Public/CogImguiDrawList.h +++ b/Plugins/Cog/Source/CogImgui/Public/CogImguiDrawList.h @@ -1,7 +1,6 @@ #pragma once #include "CoreMinimal.h" -#include "CogImguiHelper.h" #include "imgui.h" #include "Rendering/RenderingCommon.h" diff --git a/Plugins/Cog/Source/CogImgui/Public/CogImguiHelper.h b/Plugins/Cog/Source/CogImgui/Public/CogImguiHelper.h index e1d7cf6..cd8cdad 100644 --- a/Plugins/Cog/Source/CogImgui/Public/CogImguiHelper.h +++ b/Plugins/Cog/Source/CogImgui/Public/CogImguiHelper.h @@ -36,6 +36,10 @@ public: static ImVec2 ToImVec2(const FVector2D& Value); + static ImVec2 ToImVec2(const FIntVector2& Value); + + static ImVec2 ToImVec2(const FVector2f& Value); + static ImColor ToImColor(const FColor& Value); static ImColor ToImColor(const FLinearColor& Value); @@ -46,6 +50,8 @@ public: static ImVec4 ToImVec4(const FVector4f& Value); + static ImU32 ToImU32(const FLinearColor& Value); + static ImU32 ToImU32(const FColor& Value); static ImU32 ToImU32(const FVector4f& Value); @@ -73,4 +79,6 @@ public: static bool DragFVector2D(const char* Label, FVector2D& Vector, float Speed = 1.0f, double Min = 0.0f, double Max = 0.0f, const char* Format = "%.3f", ImGuiSliderFlags Flags = 0); static bool ColorEdit4(const char* Label, FColor& Color, ImGuiColorEditFlags Flags = 0); + + static bool ColorEdit4(const char* Label, FLinearColor& Color, ImGuiColorEditFlags Flags = 0); }; diff --git a/Plugins/Cog/Source/CogImgui/Public/CogImguiInputCatcherWidget.h b/Plugins/Cog/Source/CogImgui/Public/CogImguiInputCatcherWidget.h index 7e54002..21d7d5e 100644 --- a/Plugins/Cog/Source/CogImgui/Public/CogImguiInputCatcherWidget.h +++ b/Plugins/Cog/Source/CogImgui/Public/CogImguiInputCatcherWidget.h @@ -1,7 +1,6 @@ #pragma once #include "CoreMinimal.h" -#include "CogImguiDrawList.h" #include "Rendering/RenderingCommon.h" #include "UObject/WeakObjectPtr.h" #include "Widgets/DeclarativeSyntaxSupport.h" @@ -24,8 +23,6 @@ public: void Construct(const FArguments& InArgs); - ~SCogImguiInputCatcherWidget(); - virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& WidgetStyle, bool bParentEnabled) const override; @@ -58,9 +55,7 @@ public: protected: - FReply HandleKeyEvent(const FKeyEvent& KeyEvent, bool Down); - - FReply HandleMouseButtonEvent(const FPointerEvent& MouseEvent, bool Down); + FReply HandleMouseButtonEvent(const FPointerEvent& MouseEvent, bool Down) const; void RefreshVisibility(); diff --git a/Plugins/Cog/Source/CogImgui/Public/CogImguiInputHelper.h b/Plugins/Cog/Source/CogImgui/Public/CogImguiInputHelper.h index 6fcb468..9fcfa5f 100644 --- a/Plugins/Cog/Source/CogImgui/Public/CogImguiInputHelper.h +++ b/Plugins/Cog/Source/CogImgui/Public/CogImguiInputHelper.h @@ -8,12 +8,30 @@ class APlayerController; class UPlayerInput; class UWorld; enum class ECheckBoxState : uint8; -struct FCogImGuiKeyInfo; -struct FCogImGuiKeyInfo; struct FKey; struct FKeyBind; struct FKeyEvent; +//-------------------------------------------------------------------------------------------------------------------------- +#define BREAK_CHECKBOX_STATE(CheckBoxState, RequireValue, IgnoreValue) \ +{ \ + if (CheckBoxState == ECheckBoxState::Checked) \ + { \ + RequireValue = true; \ + IgnoreValue = false; \ + } \ + else if (CheckBoxState == ECheckBoxState::Unchecked) \ + { \ + RequireValue = false; \ + IgnoreValue = true; \ + } \ + else if (CheckBoxState == ECheckBoxState::Undetermined) \ + { \ + RequireValue = false; \ + IgnoreValue = false; \ + } \ +} \ + class COGIMGUI_API FCogImguiInputHelper { public: @@ -22,25 +40,25 @@ public: static UPlayerInput* GetPlayerInput(const UWorld& World); - static bool IsKeyEventHandled(UWorld* World, const FKeyEvent& KeyEvent); + static bool IsTopPriorityKey(const UPlayerInput& PlayerInput, const FKey& InKey); - static bool WasKeyInfoJustPressed(APlayerController& PlayerController, const FCogImGuiKeyInfo& KeyInfo); + static bool IsTopPriorityKeyEvent(const UPlayerInput& PlayerInput, const FKeyEvent& InKeyEvent); static bool IsCheckBoxStateMatchingValue(ECheckBoxState CheckBoxState, bool bValue); - static bool IsKeyEventMatchingKeyInfo(const FKeyEvent& KeyEvent, const FCogImGuiKeyInfo& InputChord); + static bool IsCheckBoxStateMatchingKeyBindModifier(ECheckBoxState InCheckBoxState, bool InRequireModifier, bool InIgnoreModifier); static bool IsKeyEventMatchingKeyBind(const FKeyEvent& KeyEvent, const FKeyBind& KeyBind); static ECheckBoxState MakeCheckBoxState(uint8 RequireValue, uint8 IgnoreValue); - static void KeyBindToKeyInfo(const FKeyBind& KeyBind, FCogImGuiKeyInfo& KeyInfo); + static bool IsInputChordMatchingKeyInfo(const FKeyEvent& InKeyEvent, const FInputChord& InInputChord); - static void KeyInfoToKeyBind(const FCogImGuiKeyInfo& KeyInfo, FKeyBind& KeyBind); + static bool IsKeyBindMatchingInputChord(const FKeyBind& InKeyBind, const FInputChord& InInputChord); static bool IsConsoleEvent(const FKeyEvent& KeyEvent); - static bool IsKeyBoundToCommand(UWorld* World, const FKeyEvent& KeyEvent); + static bool IsKeyBoundToCommand(const UPlayerInput& PlayerInput, const FKeyEvent& KeyEvent); static bool IsStopPlaySessionEvent(const FKeyEvent& KeyEvent); @@ -50,19 +68,22 @@ public: static EMouseCursor::Type ToSlateMouseCursor(ImGuiMouseCursor MouseCursor); - static FString CommandToString(const UWorld& World, const FString& Command); - - static FString CommandToString(const UPlayerInput* PlayerInput, const FString& Command); - - static FString KeyBindToString(const FKeyBind& KeyBind); + static FString InputChordToString(const FInputChord& InInputChord); static bool IsMouseInsideMainViewport(); static bool IsKeyBoundToCommand(const UPlayerInput* InPlayerInput, const FKeyEvent& KeyEvent); + static TArray& GetPrioritizedShortcuts() { return CogPrioritizedShortcuts; } + + static bool DisableCommandsConflictingWithShortcuts(UPlayerInput& PlayerInput); + template* = nullptr> static ImWchar CastInputChar(T Char) { return static_cast(Char); } + +private: + static TArray CogPrioritizedShortcuts; }; diff --git a/Plugins/Cog/Source/CogImgui/Public/CogImguiKeyInfo.h b/Plugins/Cog/Source/CogImgui/Public/CogImguiKeyInfo.h deleted file mode 100644 index 0f41035..0000000 --- a/Plugins/Cog/Source/CogImgui/Public/CogImguiKeyInfo.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "InputCoreTypes.h" -#include "Styling/SlateTypes.h" - -#include "CogImguiKeyInfo.generated.h" - -USTRUCT() -struct COGIMGUI_API FCogImGuiKeyInfo -{ - GENERATED_BODY() - - UPROPERTY(EditAnywhere, Category = "Input") - FKey Key = EKeys::Invalid; - - UPROPERTY(EditAnywhere, Category = "Input") - ECheckBoxState Shift = ECheckBoxState::Undetermined; - - UPROPERTY(EditAnywhere, Category = "Input") - ECheckBoxState Ctrl = ECheckBoxState::Undetermined; - - UPROPERTY(EditAnywhere, Category = "Input") - ECheckBoxState Alt = ECheckBoxState::Undetermined; - - UPROPERTY(EditAnywhere, Category = "Input") - ECheckBoxState Cmd = ECheckBoxState::Undetermined; - - FCogImGuiKeyInfo() - { - } - - FCogImGuiKeyInfo(const FKey InKey, - const ECheckBoxState InShift = ECheckBoxState::Undetermined, - const ECheckBoxState InCtrl = ECheckBoxState::Undetermined, - const ECheckBoxState InAlt = ECheckBoxState::Undetermined, - const ECheckBoxState InCmd = ECheckBoxState::Undetermined) - : Key(InKey) - , Shift(InShift) - , Ctrl(InCtrl) - , Alt(InAlt) - , Cmd(InCmd) - { - } - - friend bool operator==(const FCogImGuiKeyInfo& Lhs, const FCogImGuiKeyInfo& Rhs) - { - return Lhs.Key == Rhs.Key - && Lhs.Shift == Rhs.Shift - && Lhs.Ctrl == Rhs.Ctrl - && Lhs.Alt == Rhs.Alt - && Lhs.Cmd == Rhs.Cmd; - } - - friend bool operator!=(const FCogImGuiKeyInfo& Lhs, const FCogImGuiKeyInfo& Rhs) - { - return !(Lhs == Rhs); - } -}; \ No newline at end of file diff --git a/Plugins/Cog/Source/CogImgui/Public/CogImguiModule.h b/Plugins/Cog/Source/CogImgui/Public/CogImguiModule.h index 73a474c..9eeef79 100644 --- a/Plugins/Cog/Source/CogImgui/Public/CogImguiModule.h +++ b/Plugins/Cog/Source/CogImgui/Public/CogImguiModule.h @@ -7,7 +7,7 @@ class COGIMGUI_API FCogImguiModule : public IModuleInterface { public: - static inline FCogImguiModule& Get() + static FCogImguiModule& Get() { return FModuleManager::LoadModuleChecked("CogImgui"); } @@ -18,6 +18,4 @@ public: virtual void StartupModule() override; virtual void ShutdownModule() override; -private: - }; diff --git a/Plugins/Cog/Source/CogImgui/Public/CogImguiWidget.h b/Plugins/Cog/Source/CogImgui/Public/CogImguiWidget.h index 8ae187e..a67a9ef 100644 --- a/Plugins/Cog/Source/CogImgui/Public/CogImguiWidget.h +++ b/Plugins/Cog/Source/CogImgui/Public/CogImguiWidget.h @@ -24,8 +24,6 @@ public: void Construct(const FArguments& InArgs); - ~SCogImguiWidget(); - virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& WidgetStyle, bool bParentEnabled) const override; @@ -53,18 +51,20 @@ public: virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; virtual FReply OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& FocusEvent) override; + + virtual void OnFocusLost(const FFocusEvent& InFocusEvent) override; void SetDrawData(const ImDrawData* InDrawData); TSharedPtr GetWindow() const { return Window; } - void SetWindow(TSharedPtr Value) { Window = Value; } - + void SetWindow(const TSharedPtr& Value) { Window = Value; } + protected: - FReply HandleKeyEvent(const FKeyEvent& KeyEvent, bool Down); + FReply HandleKeyEvent(const FKeyEvent& KeyEvent, bool Down) const; - FReply HandleMouseButtonEvent(const FPointerEvent& MouseEvent, bool Down); + FReply HandleMouseButtonEvent(const FPointerEvent& MouseEvent, bool Down) const; void RefreshVisibility(); diff --git a/Plugins/Cog/Source/CogWindow/Private/CogWindowManager.cpp b/Plugins/Cog/Source/CogWindow/Private/CogWindowManager.cpp deleted file mode 100644 index c7b7e54..0000000 --- a/Plugins/Cog/Source/CogWindow/Private/CogWindowManager.cpp +++ /dev/null @@ -1,892 +0,0 @@ -#include "CogWindowManager.h" - -#include "CogDebugDrawImGui.h" -#include "CogImguiHelper.h" -#include "CogImguiInputHelper.h" -#include "CogWindow_Layouts.h" -#include "CogWindow_Settings.h" -#include "CogWindow_Spacing.h" -#include "CogWindowConsoleCommandManager.h" -#include "CogWindowHelper.h" -#include "CogWindowWidgets.h" -#include "Engine/Engine.h" -#include "GameFramework/PlayerInput.h" -#include "HAL/IConsoleManager.h" -#include "imgui_internal.h" -#include "Misc/CoreMisc.h" -#include "NetImgui_Api.h" - -FString UCogWindowManager::ToggleInputCommand = TEXT("Cog.ToggleInput"); -FString UCogWindowManager::DisableInputCommand = TEXT("Cog.DisableInput"); -FString UCogWindowManager::LoadLayoutCommand = TEXT("Cog.LoadLayout"); -FString UCogWindowManager::SaveLayoutCommand = TEXT("Cog.SaveLayout"); -FString UCogWindowManager::ResetLayoutCommand = TEXT("Cog.ResetLayout"); - -//-------------------------------------------------------------------------------------------------------------------------- -UCogWindowManager::UCogWindowManager() -{ -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::PostInitProperties() -{ - Super::PostInitProperties(); - - //if (bRegisterDefaultCommands) - //{ - // if (RegisterDefaultCommandBindings()) - // { - // bRegisterDefaultCommands = false; - // } - //} - - //------------------------------------------------------------------------------- - // Currently always register default commands. - // Since UE5.4, the ini files must have this to be saved: - // [SectionsToSave] - // bCanSaveAllSections = True - //------------------------------------------------------------------------------- - RegisterDefaultCommandBindings(); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::InitializeInternal() -{ - Context.Initialize(); - - FCogImGuiContextScope ImGuiContextScope(Context); - - ImGuiSettingsHandler IniHandler; - IniHandler.TypeName = "Cog"; - IniHandler.TypeHash = ImHashStr("Cog"); - IniHandler.ClearAllFn = SettingsHandler_ClearAll; - IniHandler.ReadOpenFn = SettingsHandler_ReadOpen; - IniHandler.ReadLineFn = SettingsHandler_ReadLine; - IniHandler.ApplyAllFn = SettingsHandler_ApplyAll; - IniHandler.WriteAllFn = SettingsHandler_WriteAll; - IniHandler.UserData = this; - ImGui::AddSettingsHandler(&IniHandler); - - SpaceWindows.Add(AddWindow("Spacing 1", false)); - SpaceWindows.Add(AddWindow("Spacing 2", false)); - SpaceWindows.Add(AddWindow("Spacing 3", false)); - SpaceWindows.Add(AddWindow("Spacing 4", false)); - - LayoutsWindow = AddWindow("Window.Layouts", false); - SettingsWindow = AddWindow("Window.Settings", false); - - FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand( - *ToggleInputCommand, - TEXT("Toggle the input focus between the Game and ImGui"), - GetWorld(), - FCogWindowConsoleCommandDelegate::CreateLambda([this](const TArray& InArgs, UWorld* InWorld) - { - ToggleInputMode(); - })); - - FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand( - *DisableInputCommand, - TEXT("Disable ImGui input"), - GetWorld(), - FCogWindowConsoleCommandDelegate::CreateLambda([this](const TArray& InArgs, UWorld* InWorld) - { - DisableInputMode(); - })); - - FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand( - *ResetLayoutCommand, - TEXT("Reset the layout."), - GetWorld(), - FCogWindowConsoleCommandDelegate::CreateLambda([this](const TArray& InArgs, UWorld* InWorld) - { - if (InArgs.Num() > 0) - { - ResetLayout(); - } - })); - - FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand( - *LoadLayoutCommand, - TEXT("Load the layout. Cog.LoadLayout "), - GetWorld(), - FCogWindowConsoleCommandDelegate::CreateLambda([this](const TArray& InArgs, UWorld* InWorld) - { - if (InArgs.Num() > 0) - { - LoadLayout(FCString::Atoi(*InArgs[0])); - } - })); - - FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand( - *SaveLayoutCommand, - TEXT("Save the layout. Cog.SaveLayout "), - GetWorld(), - FCogWindowConsoleCommandDelegate::CreateLambda([this](const TArray& InArgs, UWorld* InWorld) - { - if (InArgs.Num() > 0) - { - SaveLayout(FCString::Atoi(*InArgs[0])); - } - })); - - IsInitialized = true; - -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::Shutdown() -{ - FCogImGuiContextScope ImGuiContextScope(Context); - - //------------------------------------------------------------------ - // Call PreSaveConfig before destroying imgui context - // if PreSaveConfig needs to read ImGui IO for example - //------------------------------------------------------------------ - for (FCogWindow* Window : Windows) - { - Window->PreSaveConfig(); - } - - //------------------------------------------------------------------ - // Destroy ImGui before destroying the windows to make sure - // imgui serialize their visibility state in imgui.ini - //------------------------------------------------------------------ - if (IsInitialized == true) - { - Context.Shutdown(); - } - - SaveConfig(); - - for (FCogWindow* Window : Windows) - { - Window->Shutdown(); - delete Window; - } - Windows.Empty(); - - for (UCogCommonConfig* Config : Configs) - { - Config->SaveConfig(); - } - - FCogWindowConsoleCommandManager::UnregisterAllWorldConsoleCommands(GetWorld()); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::Tick(float DeltaTime) -{ - FCogImGuiContextScope ImGuiContextScope(Context); - - if (GEngine->GameViewport == nullptr && IsRunningDedicatedServer() == false) - { - return; - } - - if (IsInitialized == false) - { - InitializeInternal(); - } - - if (LayoutToLoad != -1) - { - const FString Filename = FCogImguiHelper::GetIniFilePath(FString::Printf(TEXT("ImGui_Layout_%d"), LayoutToLoad)); - ImGui::LoadIniSettingsFromDisk(TCHAR_TO_ANSI(*Filename)); - LayoutToLoad = -1; - } - - for (FCogWindow* Window : Windows) - { - Window->GameTick(DeltaTime); - } - - const bool shouldSkipRendering = NetImgui::IsConnected() && bIsSelectionModeActive == false; - Context.SetSkipRendering(shouldSkipRendering); - - if (Context.BeginFrame(DeltaTime)) - { - Render(DeltaTime); - Context.EndFrame(); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::Render(float DeltaTime) -{ - FCogImGuiContextScope ImGuiContextScope(Context); - - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0)); - ImGui::DockSpaceOverViewport(0, nullptr, ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoDockingOverCentralNode | ImGuiDockNodeFlags_AutoHideTabBar); - ImGui::PopStyleColor(1); - - - const bool bCompactSaved = SettingsWindow->GetSettingsConfig()->bCompactMode; - if (bCompactSaved) - { - FCogWindowWidgets::PushStyleCompact(); - } - - //---------------------------------------------------------------------- - // There is no need to have Imgui input enabled if the imgui rendering - // is only done on the NetImgui server. So we disable imgui input. - //---------------------------------------------------------------------- - if (Context.GetEnableInput() && NetImgui::IsConnected() && bIsSelectionModeActive == false) - { - Context.SetEnableInput(false); - } - - if ((Context.GetEnableInput() || NetImgui::IsConnected()) && bIsSelectionModeActive == false) - { - RenderMainMenu(); - } - - for (FCogWindow* Window : Windows) - { - Window->RenderTick(DeltaTime); - - if (Window->GetIsVisible() && bIsSelectionModeActive == false) - { - if (SettingsWindow->GetSettingsConfig()->bTransparentMode) - { - ImGui::SetNextWindowBgAlpha(0.35f); - } - - Window->Render(DeltaTime); - } - } - - if (bCompactSaved) - { - FCogWindowWidgets::PopStyleCompact(); - } - - FCogDebugDrawImGui::Draw(); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::AddWindow(FCogWindow* Window, const FString& Name, const bool AddToMainMenu /*= true*/) -{ - Window->SetFullName(Name); - Window->SetOwner(this); - Window->Initialize(); - Windows.Add(Window); - - if (Window->HasWidget()) - { - Widgets.Add(Window); - //Widgets.Sort() - } - - if (AddToMainMenu) - { - if (FMenu* Menu = AddMenu(Window->GetFullName())) - { - Menu->Window = Window; - } - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -FCogWindow* UCogWindowManager::FindWindowByID(const ImGuiID ID) -{ - for (FCogWindow* Window : Windows) - { - if (Window->GetID() == ID) - { - return Window; - } - } - return nullptr; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::SetActivateSelectionMode(const bool Value) -{ - SelectionModeActiveCounter = FMath::Max(SelectionModeActiveCounter + (Value ? 1 : -1), 0); - bIsSelectionModeActive = SelectionModeActiveCounter > 0; - - if (bIsSelectionModeActive) - { - Context.SetEnableInput(true); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::ResetLayout() -{ - FCogImGuiContextScope ImGuiContextScope(Context); - - for (const FCogWindow* Window : Windows) - { - ImGui::SetWindowPos(TCHAR_TO_ANSI(*Window->GetName()), ImVec2(10, 10), ImGuiCond_Always); - } - - ImGui::ClearIniSettings(); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::CloseAllWindows() -{ - for (FCogWindow* Window : Windows) - { - Window->SetIsVisible(false); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::LoadLayout(const int32 LayoutIndex) -{ - for (FCogWindow* Window : Windows) - { - Window->SetIsVisible(false); - } - - LayoutToLoad = LayoutIndex; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::SaveLayout(const int32 LayoutIndex) -{ - FCogImGuiContextScope ImGuiContextScope(Context); - - const FString Filename = *FCogImguiHelper::GetIniFilePath(FString::Printf(TEXT("imgui_layout_%d"), LayoutIndex)); - ImGui::SaveIniSettingsToDisk(TCHAR_TO_ANSI(*Filename)); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::SortMainMenu() -{ - MainMenu.SubMenus.Empty(); - - TArray SortedWindows = Windows; - SortedWindows.Sort([](const FCogWindow& Lhs, const FCogWindow& Rhs) { return Lhs.GetFullName() < Rhs.GetFullName(); }); - - for (FCogWindow* Window : SortedWindows) - { - if (FMenu* Menu = AddMenu(Window->GetFullName())) - { - Menu->Window = Window; - } - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -UCogWindowManager::FMenu* UCogWindowManager::AddMenu(const FString& Name) -{ - TArray Path; - Name.ParseIntoArray(Path, TEXT(".")); - - FMenu* CurrentMenu = &MainMenu; - for (int i = 0; i < Path.Num(); ++i) - { - FString MenuName = Path[i]; - - int SubMenuIndex = CurrentMenu->SubMenus.IndexOfByPredicate([&](const FMenu& Menu) { return Menu.Name == MenuName; }); - if (SubMenuIndex != -1) - { - CurrentMenu = &CurrentMenu->SubMenus[SubMenuIndex]; - } - else - { - CurrentMenu = &CurrentMenu->SubMenus.AddDefaulted_GetRef(); - CurrentMenu->Name = MenuName; - } - } - - return CurrentMenu; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::RenderMainMenu() -{ - const UPlayerInput* PlayerInput = FCogImguiInputHelper::GetPlayerInput(*GetWorld()); - - if (ImGui::BeginMainMenuBar()) - { - for (FMenu& Menu : MainMenu.SubMenus) - { - RenderOptionMenu(Menu); - } - - if (ImGui::BeginMenu("Window")) - { - if (ImGui::MenuItem("Close All Windows")) - { - CloseAllWindows(); - } - - - - ImGui::Separator(); - - RenderMenuItem(*LayoutsWindow, "Layouts"); - RenderMenuItem(*SettingsWindow, "Settings"); - - if (ImGui::BeginMenu("Spacing")) - { - for (FCogWindow* SpaceWindow : SpaceWindows) - { - bool bSpaceVisible = SpaceWindow->GetIsVisible(); - if (ImGui::MenuItem(TCHAR_TO_ANSI(*SpaceWindow->GetName()), nullptr, &bSpaceVisible)) - { - SpaceWindow->SetIsVisible(bSpaceVisible); - } - } - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Widgets")) - { - for (int32 i = 0; i < Widgets.Num(); ++i) - { - FCogWindow* Window = Widgets[i]; - - ImGui::PushID(i); - - bool Visible = Window->GetIsWidgetVisible(); - if (ImGui::Checkbox(TCHAR_TO_ANSI(*Window->GetName()), &Visible)) - { - Window->SetIsWidgetVisible(Visible); - } - - if (ImGui::IsItemActive() && ImGui::IsItemHovered() == false) - { - const int iNext = i + (ImGui::GetMouseDragDelta(0).y < 0.f ? -1 : 1); - if (iNext >= 0 && iNext < Widgets.Num()) - { - Widgets[i] = Widgets[iNext]; - Widgets[iNext] = Window; - ImGui::ResetMouseDragDelta(); - } - } - - if (i == 0) - { - ImGui::SameLine(); - FCogWindowWidgets::HelpMarker("Drag and drop the widget names to reorder them."); - } - - ImGui::PopID(); - } - - ImGui::EndMenu(); - } - - ImGui::EndMenu(); - } - - const float MinCursorX = ImGui::GetCursorPosX(); - float CursorX = ImGui::GetWindowWidth(); - - //------------------------------------------------------------ - // Render in reverse order because it makes more sense - // when looking at the widget ordered list in the UI. - //------------------------------------------------------------ - for (int32 WindowIndex = Widgets.Num() - 1; WindowIndex >= 0; WindowIndex--) - { - FCogWindow* Window = Widgets[WindowIndex]; - - if (Window->GetIsWidgetVisible() == false) - { - continue; - } - - TArray SubWidgetsWidths; - float SimCursorX = CursorX; - for (int32 SubWidgetIndex = 0; ; ++SubWidgetIndex) - { - const float MaxWidth = SimCursorX - MinCursorX; - float SubWidgetWidth = Window->GetMainMenuWidgetWidth(SubWidgetIndex, MaxWidth); - if (SubWidgetWidth == -1) - { - break; - } - - SimCursorX -= SubWidgetWidth; - SubWidgetsWidths.Add(SubWidgetWidth); - } - - bool Stop = false; - for (int32 SubWidgetIndex = SubWidgetsWidths.Num() - 1; SubWidgetIndex >= 0; SubWidgetIndex--) - { - const float SubWidgetWidth = SubWidgetsWidths[SubWidgetIndex]; - const float MaxWidth = CursorX - MinCursorX; - - //------------------------------------------- - // Bypass this subwidget if its width is 0 - //------------------------------------------- - if (SubWidgetWidth == 0) - { - continue; - } - - //------------------------------------------- - // Stop drawing if there is not enough room - //------------------------------------------- - if (SubWidgetWidth > MaxWidth) - { - Stop = true; - break; - } - - CursorX -= SubWidgetWidth; - ImGui::SetCursorPosX(CursorX); - - Window->RenderMainMenuWidget(SubWidgetIndex, SubWidgetWidth); - } - - if (Stop) - { - break; - } - - CursorX -= ImGui::GetStyle().ItemSpacing.x; - } - - ImGui::EndMainMenuBar(); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::RenderOptionMenu(FMenu& Menu) -{ - if (Menu.Window != nullptr) - { - RenderMenuItem(*Menu.Window, TCHAR_TO_ANSI(*Menu.Name)); - } - else - { - if (ImGui::BeginMenu(TCHAR_TO_ANSI(*Menu.Name))) - { - for (FMenu& SubMenu : Menu.SubMenus) - { - RenderOptionMenu(SubMenu); - } - ImGui::EndMenu(); - } - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::RenderMenuItem(FCogWindow& Window, const char* MenuItemName) -{ - if (SettingsWindow->GetSettingsConfig()->bShowWindowsInMainMenu) - { - ImGui::SetNextWindowSizeConstraints( - ImVec2(FCogWindowWidgets::GetFontWidth() * 40, ImGui::GetTextLineHeightWithSpacing() * 5), - ImVec2(FCogWindowWidgets::GetFontWidth() * 50, ImGui::GetTextLineHeightWithSpacing() * 60)); - - if (ImGui::BeginMenu(MenuItemName)) - { - Window.RenderContent(); - ImGui::EndMenu(); - } - - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) - { - Window.SetIsVisible(!Window.GetIsVisible()); - } - - RenderMenuItemHelp(Window); - } - else - { - bool bIsVisible = Window.GetIsVisible(); - if (ImGui::MenuItem(MenuItemName, nullptr, &bIsVisible)) - { - Window.SetIsVisible(bIsVisible); - } - - RenderMenuItemHelp(Window); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::RenderMenuItemHelp(FCogWindow& Window) -{ - if (SettingsWindow->GetSettingsConfig()->bShowHelp) - { - ImGui::SameLine(); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() - FCogWindowWidgets::GetFontWidth() * 3.0f); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) - { - ImGui::PushStyleColor(ImGuiCol_PopupBg, IM_COL32(29, 42, 62, 240)); - const float HelpWidth = FCogWindowWidgets::GetFontWidth() * 80; - ImGui::SetNextWindowSizeConstraints(ImVec2(HelpWidth / 2.0f, 0.0f), ImVec2(HelpWidth, FLT_MAX)); - if (ImGui::BeginTooltip()) - { - ImGui::PushTextWrapPos(HelpWidth - 1 * FCogWindowWidgets::GetFontWidth()); - Window.RenderHelp(); - ImGui::Separator(); - ImGui::TextDisabled("Help can be hidden in Window/Settings."); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - ImGui::PopStyleColor(); - } - ImGui::SameLine(); - ImGui::Dummy(ImVec2(FCogWindowWidgets::GetFontWidth() * 1, 0)); - } -} - - - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::SettingsHandler_ClearAll(ImGuiContext* Context, ImGuiSettingsHandler* Handler) -{ -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::SettingsHandler_ApplyAll(ImGuiContext* Context, ImGuiSettingsHandler* Handler) -{ - UCogWindowManager* Manager = (UCogWindowManager*)Handler->UserData; - - Manager->Widgets.Sort([](const FCogWindow& Window1, const FCogWindow& Window2) - { - return Window1.GetWidgetOrderIndex() < Window2.GetWidgetOrderIndex(); - }); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void* UCogWindowManager::SettingsHandler_ReadOpen(ImGuiContext* Context, ImGuiSettingsHandler* Handler, const char* Name) -{ - if (strcmp(Name, "Windows") == 0) - { - return (void*)1; - } - - if (strcmp(Name, "Widgets") == 0) - { - UCogWindowManager* Manager = (UCogWindowManager*)Handler->UserData; - Manager->WidgetsOrderIndex = 0; - - return (void*)2; - } - - return nullptr; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::SettingsHandler_ReadLine(ImGuiContext* Context, ImGuiSettingsHandler* Handler, void* Entry, const char* Line) -{ - //----------------------------------------------------------------------------------- - // Load the visibility of windows. - //----------------------------------------------------------------------------------- - if (Entry == (void*)1) - { - ImGuiID Id; - int32 ShowMenu; -#if PLATFORM_WINDOWS || PLATFORM_MICROSOFT - if (sscanf_s(Line, "0x%08X %d", &Id, &ShowMenu) == 2) -#else - if (sscanf(Line, "0x%08X", &Id) == 1) -#endif - { - UCogWindowManager* Manager = (UCogWindowManager*)Handler->UserData; - if (FCogWindow* Window = Manager->FindWindowByID(Id)) - { - Window->SetIsVisible(true); - Window->bShowMenu = (ShowMenu > 0); - } - } - } - //----------------------------------------------------------------------------------- - // Load which widgets are present in the main menu bar and with what order. - //----------------------------------------------------------------------------------- - else if (Entry == (void*)2) - { - ImGuiID Id; - int32 Visible = false; -#if PLATFORM_WINDOWS || PLATFORM_MICROSOFT - if (sscanf_s(Line, "0x%08X %d", &Id, &Visible) == 2) -#else - if (sscanf(Line, "0x%08X %d", &Id, &Visible) == 2) -#endif - { - UCogWindowManager* Manager = (UCogWindowManager*)Handler->UserData; - if (FCogWindow* Window = Manager->FindWindowByID(Id)) - { - Window->SetWidgetOrderIndex(Manager->WidgetsOrderIndex); - Window->SetIsWidgetVisible(Visible > 0); - } - - Manager->WidgetsOrderIndex++; - } - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::SettingsHandler_WriteAll(ImGuiContext* Context, ImGuiSettingsHandler* Handler, ImGuiTextBuffer* Buffer) -{ - const UCogWindowManager* Manager = (UCogWindowManager*)Handler->UserData; - - //----------------------------------------------------------------------------------- - // Save the visibility of windows. Example: - // [Cog][Windows] - // 0xB5D96693 - // 0xBF3390B5 - //----------------------------------------------------------------------------------- - Buffer->appendf("[%s][Windows]\n", Handler->TypeName); - for (const FCogWindow* Window : Manager->Windows) - { - if (Window->GetIsVisible()) - { - Buffer->appendf("0x%08X %d\n", Window->GetID(), (int32)Window->bShowMenu); - } - } - Buffer->append("\n"); - - //----------------------------------------------------------------------------------- - // Save which widgets are present in the main menu bar and with what order. Example: - // [Cog][Widgets] - // 0x639F1181 1 - // 0x52BDE3E0 1 - //----------------------------------------------------------------------------------- - Buffer->appendf("[%s][Widgets]\n", Handler->TypeName); - for (const FCogWindow* Window : Manager->Widgets) - { - Buffer->appendf("0x%08X %d\n", Window->GetID(), Window->GetIsWidgetVisible()); - } - Buffer->append("\n"); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::ResetAllWindowsConfig() -{ - for (FCogWindow* Window : Windows) - { - Window->ResetConfig(); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -bool UCogWindowManager::RegisterDefaultCommandBindings() -{ - if (GetWorld() == nullptr) - { - return false; - } - - UPlayerInput* PlayerInput = FCogImguiInputHelper::GetPlayerInput(*GetWorld()); - if (PlayerInput == nullptr) - { - return false; - } - - AddCommand(PlayerInput, "Cog.ToggleInput", EKeys::F1); - AddCommand(PlayerInput, "Cog.LoadLayout 1", EKeys::F2); - AddCommand(PlayerInput, "Cog.LoadLayout 2", EKeys::F3); - AddCommand(PlayerInput, "Cog.LoadLayout 3", EKeys::F4); - AddCommand(PlayerInput, "Cog.ToggleSelectionMode", EKeys::F5); - - SortCommands(PlayerInput); - PlayerInput->SaveConfig(); - return true; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::AddCommand(UPlayerInput* PlayerInput, const FString& Command, const FKey& Key) -{ - if (PlayerInput == nullptr) - { - return; - } - - //--------------------------------------------------- - // Reassign conflicting commands - //--------------------------------------------------- - for (FKeyBind& KeyBind : PlayerInput->DebugExecBindings) - { - if (KeyBind.Key == Key && KeyBind.Command != Command) - { - KeyBind.Control = true; - KeyBind.bIgnoreCtrl = false; - } - } - - //--------------------------------------------------- - // Find or add desired command - //--------------------------------------------------- - FKeyBind* ExistingKeyBind = PlayerInput->DebugExecBindings.FindByPredicate([Command](const FKeyBind& KeyBind) { return KeyBind.Command == Command; }); - if (ExistingKeyBind == nullptr) - { - ExistingKeyBind = &PlayerInput->DebugExecBindings.AddDefaulted_GetRef(); - } - - //--------------------------------------------------- - // Assign the key to the command - //--------------------------------------------------- - FKeyBind CogKeyBind; - CogKeyBind.Command = Command; - CogKeyBind.Control = false; - CogKeyBind.bIgnoreCtrl = true; - CogKeyBind.Key = Key; - - *ExistingKeyBind = CogKeyBind; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::SortCommands(UPlayerInput* PlayerInput) -{ - PlayerInput->DebugExecBindings.Sort([](const FKeyBind& Key1, const FKeyBind& Key2) - { - return Key1.Command.Compare(Key2.Command) < 0; - }); -} - -//-------------------------------------------------------------------------------------------------------------------------- -UCogCommonConfig* UCogWindowManager::GetConfig(const TSubclassOf ConfigClass) -{ - const UClass* Class = ConfigClass.Get(); - - for (UCogCommonConfig* Config : Configs) - { - if (Config && Config->IsA(Class)) - { - return Cast(Config); - } - } - - UCogCommonConfig* Config = NewObject(this, Class); - Configs.Add(Config); - return Config; -} - -//-------------------------------------------------------------------------------------------------------------------------- -const UObject* UCogWindowManager::GetAsset(const TSubclassOf AssetClass) const -{ - const UClass* Class = AssetClass.Get(); - - for (const UObject* Asset : Assets) - { - if (Asset && Asset->IsA(Class)) - { - return Asset; - } - } - - const UObject* Asset = FCogWindowHelper::GetFirstAssetByClass(AssetClass); - if (Asset == nullptr) - { - return nullptr; - } - - Assets.Add(Asset); - - return Asset; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::ToggleInputMode() -{ - UE_LOG(LogCogImGui, Verbose, TEXT("UCogWindowManager::ToggleInputMode")); - Context.SetEnableInput(!Context.GetEnableInput()); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void UCogWindowManager::DisableInputMode() -{ - UE_LOG(LogCogImGui, Verbose, TEXT("UCogWindowManager::DisableInputMode")); - Context.SetEnableInput(false); -} diff --git a/Plugins/Cog/Source/CogWindow/Private/CogWindow_Inputs.cpp b/Plugins/Cog/Source/CogWindow/Private/CogWindow_Inputs.cpp deleted file mode 100644 index 5a44d55..0000000 --- a/Plugins/Cog/Source/CogWindow/Private/CogWindow_Inputs.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include "CogWindow_Inputs.h" - diff --git a/Plugins/Cog/Source/CogWindow/Private/CogWindow_Layouts.cpp b/Plugins/Cog/Source/CogWindow/Private/CogWindow_Layouts.cpp deleted file mode 100644 index e1bbdc6..0000000 --- a/Plugins/Cog/Source/CogWindow/Private/CogWindow_Layouts.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "CogWindow_Layouts.h" - -#include "CogImguiInputHelper.h" -#include "CogWindowManager.h" -#include "InputCoreTypes.h" - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogWindow_Layouts::Initialize() -{ - Super::Initialize(); - - bHasMenu = false; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogWindow_Layouts::RenderContent() -{ - const UPlayerInput* PlayerInput = FCogImguiInputHelper::GetPlayerInput(*GetWorld()); - if (PlayerInput == nullptr) - { - return; - } - - if (ImGui::MenuItem("Reset Window Layout")) - { - GetOwner()->ResetLayout(); - } - - ImGui::Separator(); - for (int32 i = 1; i <= 4; ++i) - { - RenderLoadLayoutMenuItem(PlayerInput, i); - } - - ImGui::Separator(); - for (int32 i = 1; i <= 4; ++i) - { - RenderSaveLayoutMenuItem(PlayerInput, i); - } - -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogWindow_Layouts::RenderLoadLayoutMenuItem(const UPlayerInput* PlayerInput, int LayoutIndex) -{ - const FString Command = FString::Printf(TEXT("%s %d"), *UCogWindowManager::LoadLayoutCommand, LayoutIndex); - const FString Shortcut = FCogImguiInputHelper::CommandToString(PlayerInput, Command); - if (ImGui::MenuItem(TCHAR_TO_ANSI(*FString::Printf(TEXT("Load Layout %d"), LayoutIndex)), TCHAR_TO_ANSI(*Shortcut))) - { - GetOwner()->LoadLayout(LayoutIndex); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogWindow_Layouts::RenderSaveLayoutMenuItem(const UPlayerInput* PlayerInput, int LayoutIndex) -{ - const FString Command = FString::Printf(TEXT("%s %d"), *UCogWindowManager::SaveLayoutCommand, LayoutIndex); - const FString Shortcut = FCogImguiInputHelper::CommandToString(PlayerInput, Command); - if (ImGui::MenuItem(TCHAR_TO_ANSI(*FString::Printf(TEXT("Save Layout %d"), LayoutIndex)), TCHAR_TO_ANSI(*Shortcut))) - { - GetOwner()->SaveLayout(LayoutIndex); - } -} \ No newline at end of file diff --git a/Plugins/Cog/Source/CogWindow/Public/CogWindowHelper.h b/Plugins/Cog/Source/CogWindow/Public/CogWindowHelper.h deleted file mode 100644 index ff7fa51..0000000 --- a/Plugins/Cog/Source/CogWindow/Public/CogWindowHelper.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include "AssetRegistry/AssetData.h" -#include "CoreMinimal.h" -#include "Templates/SubclassOf.h" - - -class COGWINDOW_API FCogWindowHelper -{ -public: - - static FString GetActorName(const AActor* Actor); - - static FString GetActorName(const AActor& Actor); - - static bool ComputeBoundingBoxScreenPosition(const APlayerController* PlayerController, const FVector& Origin, const FVector& Extent, FVector2D& Min, FVector2D& Max); - - template - static const T* GetFirstAssetByClass(); - - static const UObject* GetFirstAssetByClass(const TSubclassOf AssetClass); -}; - -//---------------------------------------------------------------------------------------------------------------------- -template -const T* FCogWindowHelper::GetFirstAssetByClass() -{ - return Cast(GetFirstAssetByClass(T::StaticClass())); -} diff --git a/Plugins/Cog/Source/CogWindow/Public/CogWindowModule.h b/Plugins/Cog/Source/CogWindow/Public/CogWindowModule.h deleted file mode 100644 index 6ae7c71..0000000 --- a/Plugins/Cog/Source/CogWindow/Public/CogWindowModule.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "Modules/ModuleManager.h" - -class COGWINDOW_API FCogWindowModule : public IModuleInterface -{ -public: - - static inline FCogWindowModule& Get() { return FModuleManager::LoadModuleChecked("CogWindow"); } - - virtual void StartupModule() override; - - virtual void ShutdownModule() override; - -private: -}; diff --git a/Plugins/Cog/Source/CogWindow/Public/CogWindowWidgets.h b/Plugins/Cog/Source/CogWindow/Public/CogWindowWidgets.h deleted file mode 100644 index 408984b..0000000 --- a/Plugins/Cog/Source/CogWindow/Public/CogWindowWidgets.h +++ /dev/null @@ -1,153 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "imgui.h" -#include "UObject/ReflectedTypeAccessors.h" - -#include - -class AActor; -class APawn; -class FEnumProperty; -class UCollisionProfile; -class UEnum; -class UObject; -enum class ECheckBoxState : uint8; -enum ECollisionChannel : int; -struct FCogImGuiKeyInfo; -struct FKeyBind; - -using FCogWindowActorContextMenuFunction = TFunction; - -class COGWINDOW_API FCogWindowWidgets -{ -public: - - static bool BeginTableTooltip(); - - static void EndTableTooltip(); - - static bool BeginItemTableTooltip(); - - static void EndItemTableTooltip(); - - static void ThinSeparatorText(const char* Label); - - static bool DarkCollapsingHeader(const char* InLabel, ImGuiTreeNodeFlags InFlags); - - static void ProgressBarCentered(float Fraction, const ImVec2& Size, const char* Overlay); - - static bool ToggleMenuButton(bool* Value, const char* Text, const ImVec4& TrueColor); - - static bool ToggleButton(bool* Value, const char* Text, const ImVec4& TrueColor, const ImVec4& FalseColor, const ImVec2& Size = ImVec2(0, 0)); - - static bool ToggleButton(bool* Value, const char* TextTrue, const char* TextFalse, const ImVec4& TrueColor, const ImVec4& FalseColor, const ImVec2& Size = ImVec2(0, 0)); - - static bool MultiChoiceButton(const char* Label, bool IsSelected, const ImVec2& Size = ImVec2(0, 0)); - - static bool MultiChoiceButtonsInt(TArray& Values, int32& Value, const ImVec2& Size = ImVec2(0, 0)); - - static bool MultiChoiceButtonsFloat(TArray& Values, float& Value, const ImVec2& Size = ImVec2(0, 0)); - - static void SliderWithReset(const char* Name, float* Value, float Min, float Max, const float& ResetValue, const char* Format); - - static void HelpMarker(const char* Text); - - static void PushStyleCompact(); - - static void PopStyleCompact(); - - static void AddTextWithShadow(ImDrawList* DrawList, const ImVec2& Position, ImU32 Color, const char* TextBegin, const char* TextEnd = NULL); - - static void SearchBar(ImGuiTextFilter& Filter, float Width = -1.0f); - - static void PushBackColor(const ImVec4& Color); - - static void PopBackColor(); - - static float GetShortWidth(); - - static void SetNextItemToShortWidth(); - - static float GetFontWidth(); - - template - static bool ComboboxEnum(const char* Label, const EnumType CurrentValue, EnumType& NewValue); - - template - static bool ComboboxEnum(const char* Label, EnumType& Value); - - static bool ComboboxEnum(const char* Label, UEnum* Enum, int64 CurrentValue, int64& NewValue); - - static bool ComboboxEnum(const char* Label, UObject* Object, const char* FieldName, uint8* PointerToEnumValue); - - static bool ComboboxEnum(const char* Label, const FEnumProperty* EnumProperty, uint8* PointerToEnumValue); - - static bool CheckBoxState(const char* Label, ECheckBoxState& State, bool ShowTooltip = true); - - static bool InputKey(const char* Label, FCogImGuiKeyInfo& KeyInfo); - - static bool InputKey(FCogImGuiKeyInfo& KeyInfo); - - static bool KeyBind(FKeyBind& KeyBind); - - static bool ButtonWithTooltip(const char* Text, const char* Tooltip); - - static bool DeleteArrayItemButton(); - - static bool ComboCollisionChannel(const char* Label, ECollisionChannel& Channel); - - static bool CollisionProfileChannel(const UCollisionProfile& CollisionProfile, int32 ChannelIndex, FColor& ChannelColor, int32& Channels); - - static bool CollisionProfileChannels(int32& Channels); - - static bool MenuActorsCombo(const char* StrID, AActor*& NewSelection, const UWorld& World, TSubclassOf ActorClass, const FCogWindowActorContextMenuFunction& ContextMenuFunction = nullptr); - - static bool MenuActorsCombo(const char* StrID, AActor*& NewSelection, const UWorld& World, const TArray>& ActorClasses, int32& SelectedActorClassIndex, ImGuiTextFilter* Filter, const APawn* LocalPlayerPawn, const FCogWindowActorContextMenuFunction& ContextMenuFunction = nullptr); - - static bool ActorsListWithFilters(AActor*& NewSelection, const UWorld& World, const TArray>& ActorClasses, int32& SelectedActorClassIndex, ImGuiTextFilter* Filter, const APawn* LocalPlayerPawn, const FCogWindowActorContextMenuFunction& ContextMenuFunction = nullptr); - - static bool ActorsList(AActor*& NewSelection, const UWorld& World, const TSubclassOf ActorClass, const ImGuiTextFilter* Filter = nullptr, const APawn* LocalPlayerPawn = nullptr, const FCogWindowActorContextMenuFunction& ContextMenuFunction = nullptr); - - static void ActorContextMenu(AActor& Selection, const FCogWindowActorContextMenuFunction& ContextMenuFunction); - - static void ActorFrame(const AActor& Actor); - - static void SmallButton(const char* Text, const ImVec4& Color); - - static bool InputText(const char* Text, FString& Value); - - static bool BeginRightAlign(const char* Id); - - static void EndRightAlign(); - - static void MenuItemShortcut(const char* Id, const FString& Text); - - static bool BrowseToAssetButton(const UObject* InAsset, const ImVec2& InSize = ImVec2(0, 0)); - - static bool BrowseToObjectAssetButton(const UObject* InObject, const ImVec2& InSize = ImVec2(0, 0)); - - static bool OpenAssetButton(const UObject* InAsset, const ImVec2& InSize = ImVec2(0, 0)); - - static bool OpenObjectAssetButton(const UObject* InObject, const ImVec2& InSize = ImVec2(0, 0)); - -}; - -template -bool FCogWindowWidgets::ComboboxEnum(const char* Label, const EnumType CurrentValue, EnumType& NewValue) -{ - int64 NewValueInt; - if (ComboboxEnum(Label, StaticEnum(), (int64)CurrentValue, NewValueInt)) - { - NewValue = (EnumType)NewValueInt; - return true; - } - - return false; -} - -template -bool FCogWindowWidgets::ComboboxEnum(const char* Label, EnumType& Value) -{ - return ComboboxEnum(Label, Value, Value); -} \ No newline at end of file diff --git a/Plugins/Cog/Source/CogWindow/Public/CogWindow_Inputs.h b/Plugins/Cog/Source/CogWindow/Public/CogWindow_Inputs.h deleted file mode 100644 index 6f70f09..0000000 --- a/Plugins/Cog/Source/CogWindow/Public/CogWindow_Inputs.h +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/Plugins/Cog/Source/CogWindow/Public/CogWindow_Layouts.h b/Plugins/Cog/Source/CogWindow/Public/CogWindow_Layouts.h deleted file mode 100644 index 81a41c9..0000000 --- a/Plugins/Cog/Source/CogWindow/Public/CogWindow_Layouts.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "CogWindow.h" - -class UPlayerInput; - -class COGWINDOW_API FCogWindow_Layouts : public FCogWindow -{ - typedef FCogWindow Super; - -public: - - virtual void Initialize() override; - -protected: - - virtual void RenderContent() override; - - virtual void RenderLoadLayoutMenuItem(const UPlayerInput* PlayerInput, int LayoutIndex); - - virtual void RenderSaveLayoutMenuItem(const UPlayerInput* PlayerInput, int LayoutIndex); -}; diff --git a/Plugins/Cog/Source/CogWindow/Public/CogWindow_Settings.h b/Plugins/Cog/Source/CogWindow/Public/CogWindow_Settings.h deleted file mode 100644 index 8775614..0000000 --- a/Plugins/Cog/Source/CogWindow/Public/CogWindow_Settings.h +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "CogCommonConfig.h" -#include "CogWindow.h" -#include "CogWindow_Settings.generated.h" - -class UCogEngineConfig_Settings; - -//-------------------------------------------------------------------------------------------------------------------------- -class COGWINDOW_API FCogWindow_Settings : public FCogWindow -{ - typedef FCogWindow Super; - -public: - - virtual void Initialize() override; - - virtual void RenderTick(float DeltaTime) override; - - const UCogWindowConfig_Settings* GetSettingsConfig() const { return Config; } - - void SetDPIScale(float Value) const; - -protected: - - virtual void RenderContent() override; - - virtual void PreSaveConfig() override; - - virtual void ResetConfig() override; - - TObjectPtr Config = nullptr; -}; - - -//-------------------------------------------------------------------------------------------------------------------------- -UCLASS(Config = Cog) -class UCogWindowConfig_Settings : public UCogCommonConfig -{ - GENERATED_BODY() - -public: - - UPROPERTY(Config) - float DPIScale = 1.0f; - - UPROPERTY(Config) - bool bEnableViewports = false; - - UPROPERTY(Config) - bool bCompactMode = false; - - UPROPERTY(Config) - bool bTransparentMode = false; - - UPROPERTY(Config) - bool bShowHelp = true; - - UPROPERTY(Config) - bool bShowWindowsInMainMenu = true; - - UPROPERTY(Config) - bool bEnableInput = false; - - UPROPERTY(Config) - bool bShareMouse = false; - - UPROPERTY(Config) - bool bShareMouseWithGameplay = false; - - UPROPERTY(Config) - bool bShareKeyboard = false; - - UPROPERTY(Config) - bool bNavEnableKeyboard = false; - - //UPROPERTY(Config) - //bool bNavEnableGamepad = false; - - //UPROPERTY(Config) - //bool bNavNoCaptureInput = true; - - virtual void Reset() override - { - Super::Reset(); - - DPIScale = 1.0f; - bEnableViewports = false; - bCompactMode = false; - bTransparentMode = false; - bShowHelp = true; - bShowWindowsInMainMenu = true; - bEnableInput = false; - bShareMouse = false; - bShareKeyboard = false; - bNavEnableKeyboard = false; - //bNavEnableGamepad = false; - //bNavNoCaptureInput = true; - } -}; \ No newline at end of file diff --git a/Plugins/Cog/Source/CogWindow/Public/CogWindow_Spacing.h b/Plugins/Cog/Source/CogWindow/Public/CogWindow_Spacing.h deleted file mode 100644 index 15997a2..0000000 --- a/Plugins/Cog/Source/CogWindow/Public/CogWindow_Spacing.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "CogWindow.h" - -class COGWINDOW_API FCogWindow_Spacing : public FCogWindow -{ - typedef FCogWindow Super; - -public: - -protected: - - virtual void PreRender(ImGuiWindowFlags& WindowFlags) override; - - virtual void PostRender() override; - -private: - -}; diff --git a/Plugins/Cog/Source/ThirdParty/ImGui/imgui_widgets.cpp b/Plugins/Cog/Source/ThirdParty/ImGui/imgui_widgets.cpp new file mode 100644 index 0000000..9550073 --- /dev/null +++ b/Plugins/Cog/Source/ThirdParty/ImGui/imgui_widgets.cpp @@ -0,0 +1,10485 @@ +// dear imgui, v1.91.7 +// (widgets code) + +/* + +Index of this file: + +// [SECTION] Forward Declarations +// [SECTION] Widgets: Text, etc. +// [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.) +// [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.) +// [SECTION] Widgets: ComboBox +// [SECTION] Data Type and Data Formatting Helpers +// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc. +// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc. +// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc. +// [SECTION] Widgets: InputText, InputTextMultiline +// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. +// [SECTION] Widgets: TreeNode, CollapsingHeader, etc. +// [SECTION] Widgets: Selectable +// [SECTION] Widgets: Typing-Select support +// [SECTION] Widgets: Box-Select support +// [SECTION] Widgets: Multi-Select support +// [SECTION] Widgets: Multi-Select helpers +// [SECTION] Widgets: ListBox +// [SECTION] Widgets: PlotLines, PlotHistogram +// [SECTION] Widgets: Value helpers +// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc. +// [SECTION] Widgets: BeginTabBar, EndTabBar, etc. +// [SECTION] Widgets: BeginTabItem, EndTabItem, etc. +// [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc. + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef IMGUI_DEFINE_MATH_OPERATORS +#define IMGUI_DEFINE_MATH_OPERATORS +#endif + +#include "imgui.h" +#ifndef IMGUI_DISABLE +#include "imgui_internal.h" + +// System includes +#include // intptr_t + +//------------------------------------------------------------------------- +// Warnings +//------------------------------------------------------------------------- + +// Visual Studio warnings +#ifdef _MSC_VER +#pragma warning (disable: 4127) // condition expression is constant +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later +#pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types +#endif +#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). +#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). +#endif + +// Clang/GCC warnings with -Weverything +#if defined(__clang__) +#if __has_warning("-Wunknown-warning-option") +#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great! +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#pragma clang diagnostic ignored "-Wunused-macros" // warning: macro is not used // we define snprintf/vsnprintf on Windows so they are available, but not always used. +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. +#pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') +#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe +#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*' +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1 +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers +#endif + +//------------------------------------------------------------------------- +// Data +//------------------------------------------------------------------------- + +// Widgets +static const float DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f; // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior. +static const float DRAG_MOUSE_THRESHOLD_FACTOR = 0.50f; // Multiplier for the default value of io.MouseDragThreshold to make DragFloat/DragInt react faster to mouse drags. + +// Those MIN/MAX values are not define because we need to point to them +static const signed char IM_S8_MIN = -128; +static const signed char IM_S8_MAX = 127; +static const unsigned char IM_U8_MIN = 0; +static const unsigned char IM_U8_MAX = 0xFF; +static const signed short IM_S16_MIN = -32768; +static const signed short IM_S16_MAX = 32767; +static const unsigned short IM_U16_MIN = 0; +static const unsigned short IM_U16_MAX = 0xFFFF; +static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000); +static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF) +static const ImU32 IM_U32_MIN = 0; +static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF) +#ifdef LLONG_MIN +static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll); +static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll); +#else +static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1; +static const ImS64 IM_S64_MAX = 9223372036854775807LL; +#endif +static const ImU64 IM_U64_MIN = 0; +#ifdef ULLONG_MAX +static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull); +#else +static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); +#endif + +//------------------------------------------------------------------------- +// [SECTION] Forward Declarations +//------------------------------------------------------------------------- + +// For InputTextEx() +static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false); +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); +static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Text, etc. +//------------------------------------------------------------------------- +// - TextEx() [Internal] +// - TextUnformatted() +// - Text() +// - TextV() +// - TextColored() +// - TextColoredV() +// - TextDisabled() +// - TextDisabledV() +// - TextWrapped() +// - TextWrappedV() +// - LabelText() +// - LabelTextV() +// - BulletText() +// - BulletTextV() +//------------------------------------------------------------------------- + +void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + // Accept null ranges + if (text == text_end) + text = text_end = ""; + + // Calculate length + const char* text_begin = text; + if (text_end == NULL) + text_end = text + strlen(text); // FIXME-OPT + + const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + const float wrap_pos_x = window->DC.TextWrapPos; + const bool wrap_enabled = (wrap_pos_x >= 0.0f); + if (text_end - text <= 2000 || wrap_enabled) + { + // Common case + const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f; + const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width); + + ImRect bb(text_pos, text_pos + text_size); + ItemSize(text_size, 0.0f); + if (!ItemAdd(bb, 0)) + return; + + // Render (we don't hide text after ## in this end-user function) + RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width); + } + else + { + // Long text! + // Perform manual coarse clipping to optimize for long multi-line text + // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled. + // - We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line. + // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop. + const char* line = text; + const float line_height = GetTextLineHeight(); + ImVec2 text_size(0, 0); + + // Lines to skip (can't skip when logging text) + ImVec2 pos = text_pos; + if (!g.LogEnabled) + { + int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height); + if (lines_skippable > 0) + { + int lines_skipped = 0; + while (line < text_end && lines_skipped < lines_skippable) + { + const char* line_end = (const char*)memchr(line, '\n', text_end - line); + if (!line_end) + line_end = text_end; + if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0) + text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x); + line = line_end + 1; + lines_skipped++; + } + pos.y += lines_skipped * line_height; + } + } + + // Lines to render + if (line < text_end) + { + ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height)); + while (line < text_end) + { + if (IsClippedEx(line_rect, 0)) + break; + + const char* line_end = (const char*)memchr(line, '\n', text_end - line); + if (!line_end) + line_end = text_end; + text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x); + RenderText(pos, line, line_end, false); + line = line_end + 1; + line_rect.Min.y += line_height; + line_rect.Max.y += line_height; + pos.y += line_height; + } + + // Count remaining lines + int lines_skipped = 0; + while (line < text_end) + { + const char* line_end = (const char*)memchr(line, '\n', text_end - line); + if (!line_end) + line_end = text_end; + if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0) + text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x); + line = line_end + 1; + lines_skipped++; + } + pos.y += lines_skipped * line_height; + } + text_size.y = (pos - text_pos).y; + + ImRect bb(text_pos, text_pos + text_size); + ItemSize(text_size, 0.0f); + ItemAdd(bb, 0); + } +} + +void ImGui::TextUnformatted(const char* text, const char* text_end) +{ + TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); +} + +void ImGui::Text(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextV(fmt, args); + va_end(args); +} + +void ImGui::TextV(const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + const char* text, *text_end; + ImFormatStringToTempBufferV(&text, &text_end, fmt, args); + TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); +} + +void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextColoredV(col, fmt, args); + va_end(args); +} + +void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args) +{ + PushStyleColor(ImGuiCol_Text, col); + TextV(fmt, args); + PopStyleColor(); +} + +void ImGui::TextDisabled(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextDisabledV(fmt, args); + va_end(args); +} + +void ImGui::TextDisabledV(const char* fmt, va_list args) +{ + ImGuiContext& g = *GImGui; + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); + TextV(fmt, args); + PopStyleColor(); +} + +void ImGui::TextWrapped(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextWrappedV(fmt, args); + va_end(args); +} + +void ImGui::TextWrappedV(const char* fmt, va_list args) +{ + ImGuiContext& g = *GImGui; + const bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position if one is already set + if (need_backup) + PushTextWrapPos(0.0f); + TextV(fmt, args); + if (need_backup) + PopTextWrapPos(); +} + +void ImGui::LabelText(const char* label, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + LabelTextV(label, fmt, args); + va_end(args); +} + +// Add a label+text combo aligned to other label+value widgets +void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float w = CalcItemWidth(); + + const char* value_text_begin, *value_text_end; + ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args); + const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const ImVec2 pos = window->DC.CursorPos; + const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2)); + const ImRect total_bb(pos, pos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), ImMax(value_size.y, label_size.y) + style.FramePadding.y * 2)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, 0)) + return; + + // Render + RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f)); + if (label_size.x > 0.0f) + RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); +} + +void ImGui::BulletText(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + BulletTextV(fmt, args); + va_end(args); +} + +// Text with a little bullet aligned to the typical tree node. +void ImGui::BulletTextV(const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const char* text_begin, *text_end; + ImFormatStringToTempBufferV(&text_begin, &text_end, fmt, args); + const ImVec2 label_size = CalcTextSize(text_begin, text_end, false); + const ImVec2 total_size = ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x * 2) : 0.0f), label_size.y); // Empty text doesn't add padding + ImVec2 pos = window->DC.CursorPos; + pos.y += window->DC.CurrLineTextBaseOffset; + ItemSize(total_size, 0.0f); + const ImRect bb(pos, pos + total_size); + if (!ItemAdd(bb, 0)) + return; + + // Render + ImU32 text_col = GetColorU32(ImGuiCol_Text); + RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), text_col); + RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text_begin, text_end, false); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Main +//------------------------------------------------------------------------- +// - ButtonBehavior() [Internal] +// - Button() +// - SmallButton() +// - InvisibleButton() +// - ArrowButton() +// - CloseButton() [Internal] +// - CollapseButton() [Internal] +// - GetWindowScrollbarID() [Internal] +// - GetWindowScrollbarRect() [Internal] +// - Scrollbar() [Internal] +// - ScrollbarEx() [Internal] +// - Image() +// - ImageButton() +// - Checkbox() +// - CheckboxFlagsT() [Internal] +// - CheckboxFlags() +// - RadioButton() +// - ProgressBar() +// - Bullet() +// - Hyperlink() +//------------------------------------------------------------------------- + +// The ButtonBehavior() function is key to many interactions and used by many/most widgets. +// Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_), +// this code is a little complex. +// By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior. +// See the series of events below and the corresponding state reported by dear imgui: +//------------------------------------------------------------------------------------------------------------------------------------------------ +// with PressedOnClickRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() +// Frame N+0 (mouse is outside bb) - - - - - - +// Frame N+1 (mouse moves inside bb) - true - - - - +// Frame N+2 (mouse button is down) - true true true - true +// Frame N+3 (mouse button is down) - true true - - - +// Frame N+4 (mouse moves outside bb) - - true - - - +// Frame N+5 (mouse moves inside bb) - true true - - - +// Frame N+6 (mouse button is released) true true - - true - +// Frame N+7 (mouse button is released) - true - - - - +// Frame N+8 (mouse moves outside bb) - - - - - - +//------------------------------------------------------------------------------------------------------------------------------------------------ +// with PressedOnClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() +// Frame N+2 (mouse button is down) true true true true - true +// Frame N+3 (mouse button is down) - true true - - - +// Frame N+6 (mouse button is released) - true - - true - +// Frame N+7 (mouse button is released) - true - - - - +//------------------------------------------------------------------------------------------------------------------------------------------------ +// with PressedOnRelease: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() +// Frame N+2 (mouse button is down) - true - - - true +// Frame N+3 (mouse button is down) - true - - - - +// Frame N+6 (mouse button is released) true true - - - - +// Frame N+7 (mouse button is released) - true - - - - +//------------------------------------------------------------------------------------------------------------------------------------------------ +// with PressedOnDoubleClick: return-value IsItemHovered() IsItemActive() IsItemActivated() IsItemDeactivated() IsItemClicked() +// Frame N+0 (mouse button is down) - true - - - true +// Frame N+1 (mouse button is down) - true - - - - +// Frame N+2 (mouse button is released) - true - - - - +// Frame N+3 (mouse button is released) - true - - - - +// Frame N+4 (mouse button is down) true true true true - true +// Frame N+5 (mouse button is down) - true true - - - +// Frame N+6 (mouse button is released) - true - - true - +// Frame N+7 (mouse button is released) - true - - - - +//------------------------------------------------------------------------------------------------------------------------------------------------ +// Note that some combinations are supported, +// - PressedOnDragDropHold can generally be associated with any flag. +// - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported. +//------------------------------------------------------------------------------------------------------------------------------------------------ +// The behavior of the return-value changes when ImGuiItemFlags_ButtonRepeat is set: +// Repeat+ Repeat+ Repeat+ Repeat+ +// PressedOnClickRelease PressedOnClick PressedOnRelease PressedOnDoubleClick +//------------------------------------------------------------------------------------------------------------------------------------------------- +// Frame N+0 (mouse button is down) - true - true +// ... - - - - +// Frame N + RepeatDelay true true - true +// ... - - - - +// Frame N + RepeatDelay + RepeatRate*N true true - true +//------------------------------------------------------------------------------------------------------------------------------------------------- + +// - FIXME: For refactor we could output flags, incl mouse hovered vs nav keyboard vs nav triggered etc. +// And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);' +// For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading. +// - Since v1.91.2 (Sept 2024) we included io.ConfigDebugHighlightIdConflicts feature. +// One idiom which was previously valid which will now emit a warning is when using multiple overlayed ButtonBehavior() +// with same ID and different MouseButton (see #8030). You can fix it by: +// (1) switching to use a single ButtonBehavior() with multiple _MouseButton flags. +// or (2) surrounding those calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag() +bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + // Default behavior inherited from item flags + // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that. + ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags); + if (flags & ImGuiButtonFlags_AllowOverlap) + item_flags |= ImGuiItemFlags_AllowOverlap; + + // Default only reacts to left mouse button + if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0) + flags |= ImGuiButtonFlags_MouseButtonLeft; + + // Default behavior requires click + release inside bounding box + if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0) + flags |= (item_flags & ImGuiItemFlags_ButtonRepeat) ? ImGuiButtonFlags_PressedOnClick : ImGuiButtonFlags_PressedOnDefault_; + + ImGuiWindow* backup_hovered_window = g.HoveredWindow; + const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindowDockTree == window->RootWindowDockTree; + if (flatten_hovered_children) + g.HoveredWindow = window; + +#ifdef IMGUI_ENABLE_TEST_ENGINE + // Alternate registration spot, for when caller didn't use ItemAdd() + if (g.LastItemData.ID != id) + IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, NULL); +#endif + + bool pressed = false; + bool hovered = ItemHoverable(bb, id, item_flags); + + // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button + if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) + if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + { + hovered = true; + SetHoveredID(id); + if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER) + { + pressed = true; + g.DragDropHoldJustPressedId = id; + FocusWindow(window); + } + } + + if (flatten_hovered_children) + g.HoveredWindow = backup_hovered_window; + + // Mouse handling + const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id; + if (hovered) + { + IM_ASSERT(id != 0); // Lazily check inside rare path. + + // Poll mouse buttons + // - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId. + // - Technically we only need some values in one code path, but since this is gated by hovered test this is fine. + int mouse_button_clicked = -1; + int mouse_button_released = -1; + for (int button = 0; button < 3; button++) + if (flags & (ImGuiButtonFlags_MouseButtonLeft << button)) // Handle ImGuiButtonFlags_MouseButtonRight and ImGuiButtonFlags_MouseButtonMiddle here. + { + if (IsMouseClicked(button, ImGuiInputFlags_None, test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; } + if (IsMouseReleased(button, test_owner_id) && mouse_button_released == -1) { mouse_button_released = button; } + } + + // Process initial action + const bool mods_ok = !(flags & ImGuiButtonFlags_NoKeyModsAllowed) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt); + if (mods_ok) + { + if (mouse_button_clicked != -1 && g.ActiveId != id) + { + if (!(flags & ImGuiButtonFlags_NoSetKeyOwner)) + SetKeyOwner(MouseButtonToKey(mouse_button_clicked), id); + if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere)) + { + SetActiveID(id, window); + g.ActiveIdMouseButton = mouse_button_clicked; + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + { + SetFocusID(id, window); + FocusWindow(window); + } + else + { + FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child + } + } + if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2)) + { + pressed = true; + if (flags & ImGuiButtonFlags_NoHoldingActiveId) + ClearActiveID(); + else + SetActiveID(id, window); // Hold on ID + g.ActiveIdMouseButton = mouse_button_clicked; + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + { + SetFocusID(id, window); + FocusWindow(window); + } + else + { + FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child + } + } + } + if (flags & ImGuiButtonFlags_PressedOnRelease) + { + if (mouse_button_released != -1) + { + const bool has_repeated_at_least_once = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay; // Repeat mode trumps on release behavior + if (!has_repeated_at_least_once) + pressed = true; + if (!(flags & ImGuiButtonFlags_NoNavFocus)) + SetFocusID(id, window); // FIXME: Lack of FocusWindow() call here is inconsistent with other paths. Research why. + ClearActiveID(); + } + } + + // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). + // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. + if (g.ActiveId == id && (item_flags & ImGuiItemFlags_ButtonRepeat)) + if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, ImGuiInputFlags_Repeat, test_owner_id)) + pressed = true; + } + + if (pressed && g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = false; + } + + // Keyboard/Gamepad navigation handling + // We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse. + if (g.NavId == id && g.NavCursorVisible && g.NavHighlightItemUnderNav) + if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus)) + hovered = true; + if (g.NavActivateDownId == id) + { + bool nav_activated_by_code = (g.NavActivateId == id); + bool nav_activated_by_inputs = (g.NavActivatePressedId == id); + if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat)) + { + // Avoid pressing multiple keys from triggering excessive amount of repeat events + const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space); + const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_Enter); + const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate); + const float t1 = ImMax(ImMax(key1->DownDuration, key2->DownDuration), key3->DownDuration); + nav_activated_by_inputs = CalcTypematicRepeatAmount(t1 - g.IO.DeltaTime, t1, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0; + } + if (nav_activated_by_code || nav_activated_by_inputs) + { + // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button. + pressed = true; + SetActiveID(id, window); + g.ActiveIdSource = g.NavInputSource; + if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)) + SetFocusID(id, window); + if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut) + g.ActiveIdFromShortcut = true; + } + } + + // Process while held + bool held = false; + if (g.ActiveId == id) + { + if (g.ActiveIdSource == ImGuiInputSource_Mouse) + { + if (g.ActiveIdIsJustActivated) + g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; + + const int mouse_button = g.ActiveIdMouseButton; + if (mouse_button == -1) + { + // Fallback for the rare situation were g.ActiveId was set programmatically or from another widget (e.g. #6304). + ClearActiveID(); + } + else if (IsMouseDown(mouse_button, test_owner_id)) + { + held = true; + } + else + { + bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0; + bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0; + if ((release_in || release_anywhere) && !g.DragDropActive) + { + // Report as pressed when releasing the mouse (this is the most common path) + bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseReleased[mouse_button] && g.IO.MouseClickedLastCount[mouse_button] == 2; + bool is_repeating_already = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps + bool is_button_avail_or_owned = TestKeyOwner(MouseButtonToKey(mouse_button), test_owner_id); + if (!is_double_click_release && !is_repeating_already && is_button_avail_or_owned) + pressed = true; + } + ClearActiveID(); + } + if (!(flags & ImGuiButtonFlags_NoNavFocus) && g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = false; + } + else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) + { + // When activated using Nav, we hold on the ActiveID until activation button is released + if (g.NavActivateDownId == id) + held = true; // hovered == true not true as we are already likely hovered on direct activation. + else + ClearActiveID(); + } + if (pressed) + g.ActiveIdHasBeenPressedBefore = true; + } + + // Activation highlight (this may be a remote activation) + if (g.NavHighlightActivatedId == id) + hovered = true; + + if (out_hovered) *out_hovered = hovered; + if (out_held) *out_held = held; + + return pressed; +} + +bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + ImVec2 pos = window->DC.CursorPos; + if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) + pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y; + ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); + + const ImRect bb(pos, pos + size); + ItemSize(size, style.FramePadding.y); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavCursor(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); + + if (g.LogEnabled) + LogSetNextTextDecoration("[", "]"); + RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); + + // Automatically close popups + //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) + // CloseCurrentPopup(); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + return pressed; +} + +bool ImGui::Button(const char* label, const ImVec2& size_arg) +{ + return ButtonEx(label, size_arg, ImGuiButtonFlags_None); +} + +// Small buttons fits within text without additional vertical spacing. +bool ImGui::SmallButton(const char* label) +{ + ImGuiContext& g = *GImGui; + float backup_padding_y = g.Style.FramePadding.y; + g.Style.FramePadding.y = 0.0f; + bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine); + g.Style.FramePadding.y = backup_padding_y; + return pressed; +} + +// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack. +// Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id) +bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size. + IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f); + + const ImGuiID id = window->GetID(str_id); + ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(size); + if (!ItemAdd(bb, id, NULL, (flags & ImGuiButtonFlags_EnableNav) ? ImGuiItemFlags_None : ImGuiItemFlags_NoNav)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + RenderNavCursor(bb, id); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags); + return pressed; +} + +bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiID id = window->GetID(str_id); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + const float default_size = GetFrameHeight(); + ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + const ImU32 text_col = GetColorU32(ImGuiCol_Text); + RenderNavCursor(bb, id); + RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding); + RenderArrow(window->DrawList, bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), text_col, dir); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags); + return pressed; +} + +bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir) +{ + float sz = GetFrameHeight(); + return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None); +} + +// Button to close a window +bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Tweak 1: Shrink hit-testing area if button covers an abnormally large proportion of the visible region. That's in order to facilitate moving the window away. (#3825) + // This may better be applied as a general hit-rect reduction mechanism for all widgets to ensure the area to move window is always accessible? + const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize)); + ImRect bb_interact = bb; + const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea(); + if (area_to_visible_ratio < 1.5f) + bb_interact.Expand(ImTrunc(bb_interact.GetSize() * -0.25f)); + + // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window. + // (this isn't the common behavior of buttons, but it doesn't affect the user because navigation tends to keep items visible in scrolling layer). + bool is_clipped = !ItemAdd(bb_interact, id); + + bool hovered, held; + bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held); + if (is_clipped) + return pressed; + + // Render + ImU32 bg_col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); + if (hovered) + window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); + RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); + ImU32 cross_col = GetColorU32(ImGuiCol_Text); + ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); + float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; + window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f); + window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f); + + return pressed; +} + +// The Collapse button also functions as a Dock Menu button. +bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize)); + bool is_clipped = !ItemAdd(bb, id); + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None); + if (is_clipped) + return pressed; + + // Render + //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed); + ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + ImU32 text_col = GetColorU32(ImGuiCol_Text); + if (hovered || held) + window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); + RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); + + if (dock_node) + RenderArrowDockMenu(window->DrawList, bb.Min, g.FontSize, text_col); + else + RenderArrow(window->DrawList, bb.Min, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); + + // Switch to moving the window after mouse is moved beyond the initial drag threshold + if (IsItemActive() && IsMouseDragging(0)) + StartMouseMovingWindowOrNode(window, dock_node, true); // Undock from window/collapse menu button + + return pressed; +} + +ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis) +{ + return window->GetID(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY"); +} + +// Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set. +ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis) +{ + const ImRect outer_rect = window->Rect(); + const ImRect inner_rect = window->InnerRect; + const float border_size = window->WindowBorderSize; + const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar) + IM_ASSERT(scrollbar_size > 0.0f); + if (axis == ImGuiAxis_X) + return ImRect(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size); + else + return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size); +} + +void ImGui::Scrollbar(ImGuiAxis axis) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const ImGuiID id = GetWindowScrollbarID(window, axis); + + // Calculate scrollbar bounding box + ImRect bb = GetWindowScrollbarRect(window, axis); + ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone; + if (axis == ImGuiAxis_X) + { + rounding_corners |= ImDrawFlags_RoundCornersBottomLeft; + if (!window->ScrollbarY) + rounding_corners |= ImDrawFlags_RoundCornersBottomRight; + } + else + { + if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) + rounding_corners |= ImDrawFlags_RoundCornersTopRight; + if (!window->ScrollbarX) + rounding_corners |= ImDrawFlags_RoundCornersBottomRight; + } + float size_visible = window->InnerRect.Max[axis] - window->InnerRect.Min[axis]; + float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f; + ImS64 scroll = (ImS64)window->Scroll[axis]; + ScrollbarEx(bb, id, axis, &scroll, (ImS64)size_visible, (ImS64)size_contents, rounding_corners); + window->Scroll[axis] = (float)scroll; +} + +// Vertical/Horizontal scrollbar +// The entire piece of code below is rather confusing because: +// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab) +// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar +// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. +// Still, the code should probably be made simpler.. +bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags draw_rounding_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + const float bb_frame_width = bb_frame.GetWidth(); + const float bb_frame_height = bb_frame.GetHeight(); + if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f) + return false; + + // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the window resize grab) + float alpha = 1.0f; + if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f) + alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f)); + if (alpha <= 0.0f) + return false; + + const ImGuiStyle& style = g.Style; + const bool allow_interaction = (alpha >= 1.0f); + + ImRect bb = bb_frame; + bb.Expand(ImVec2(-ImClamp(IM_TRUNC((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_TRUNC((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f))); + + // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) + const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight(); + + // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount) + // But we maintain a minimum size in pixel to allow for the user to still aim inside. + IM_ASSERT(ImMax(size_contents_v, size_visible_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers. + const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_visible_v), (ImS64)1); + const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), style.GrabMinSize, scrollbar_size_v); + const float grab_h_norm = grab_h_pixels / scrollbar_size_v; + + // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar(). + bool held = false; + bool hovered = false; + ItemAdd(bb_frame, id, NULL, ImGuiItemFlags_NoNav); + ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus); + + const ImS64 scroll_max = ImMax((ImS64)1, size_contents_v - size_visible_v); + float scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max); + float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space + if (held && allow_interaction && grab_h_norm < 1.0f) + { + const float scrollbar_pos_v = bb.Min[axis]; + const float mouse_pos_v = g.IO.MousePos[axis]; + + // Click position in scrollbar normalized space (0.0f->1.0f) + const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); + + const int held_dir = (clicked_v_norm < grab_v_norm) ? -1 : (clicked_v_norm > grab_v_norm + grab_h_norm) ? +1 : 0; + if (g.ActiveIdIsJustActivated) + { + // On initial click when held_dir == 0 (clicked over grab): calculate the distance between mouse and the center of the grab + const bool scroll_to_clicked_location = (g.IO.ConfigScrollbarScrollByPage == false || g.IO.KeyShift || held_dir == 0); + g.ScrollbarSeekMode = scroll_to_clicked_location ? 0 : (short)held_dir; + g.ScrollbarClickDeltaToGrabCenter = (held_dir == 0 && !g.IO.KeyShift) ? clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f : 0.0f; + } + + // Apply scroll (p_scroll_v will generally point on one member of window->Scroll) + // It is ok to modify Scroll here because we are being called in Begin() after the calculation of ContentSize and before setting up our starting position + if (g.ScrollbarSeekMode == 0) + { + // Absolute seeking + const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm)); + *p_scroll_v = (ImS64)(scroll_v_norm * scroll_max); + } + else + { + // Page by page + if (IsMouseClicked(ImGuiMouseButton_Left, ImGuiInputFlags_Repeat) && held_dir == g.ScrollbarSeekMode) + { + float page_dir = (g.ScrollbarSeekMode > 0.0f) ? +1.0f : -1.0f; + *p_scroll_v = ImClamp(*p_scroll_v + (ImS64)(page_dir * size_visible_v), (ImS64)0, scroll_max); + } + } + + // Update values for rendering + scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max); + grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; + + // Update distance to grab now that we have seek'ed and saturated + //if (seek_absolute) + // g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f; + } + + // Render + const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg); + const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha); + window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, draw_rounding_flags); + ImRect grab_rect; + if (axis == ImGuiAxis_X) + grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y); + else + grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels); + window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding); + + return held; +} + +// - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples +// - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above. +void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + const float border_size = (border_col.w > 0.0f) ? 1.0f : 0.0f; + const ImVec2 padding(border_size, border_size); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + return; + + // Render + if (border_size > 0.0f) + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f, ImDrawFlags_None, border_size); + window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); +} + +bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImVec2 padding = g.Style.FramePadding; + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f); + ItemSize(bb); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + RenderNavCursor(bb, id); + RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); + if (bg_col.w > 0.0f) + window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); + window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + + return pressed; +} + +// - ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button. +// - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. (#8165) // FIXME: Maybe that's not the best design? +bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + return ImageButtonEx(window->GetID(str_id), user_texture_id, image_size, uv0, uv1, bg_col, tint_col); +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +// Legacy API obsoleted in 1.89. Two differences with new ImageButton() +// - old ImageButton() used ImTextureID as item id (created issue with multiple buttons with same image, transient texture id values, opaque computation of ID) +// - new ImageButton() requires an explicit 'const char* str_id' +// - old ImageButton() had frame_padding' override argument. +// - new ImageButton() always use style.FramePadding. +/* +bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) +{ + // Default to using texture ID as ID. User can still push string/integer prefixes. + PushID((ImTextureID)(intptr_t)user_texture_id); + if (frame_padding >= 0) + PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2((float)frame_padding, (float)frame_padding)); + bool ret = ImageButton("", user_texture_id, size, uv0, uv1, bg_col, tint_col); + if (frame_padding >= 0) + PopStyleVar(); + PopID(); + return ret; +} +*/ +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +bool ImGui::Checkbox(const char* label, bool* v) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const float square_sz = GetFrameHeight(); + const ImVec2 pos = window->DC.CursorPos; + const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); + ItemSize(total_bb, style.FramePadding.y); + const bool is_visible = ItemAdd(total_bb, id); + const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0; + if (!is_visible) + if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(total_bb)) // Extra layer of "no logic clip" for box-select support + { + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); + return false; + } + + // Range-Selection/Multi-selection support (header) + bool checked = *v; + if (is_multi_select) + MultiSelectItemHeader(id, &checked, NULL); + + bool hovered, held; + bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + + // Range-Selection/Multi-selection support (footer) + if (is_multi_select) + MultiSelectItemFooter(id, &checked, &pressed); + else if (pressed) + checked = !checked; + + if (*v != checked) + { + *v = checked; + pressed = true; // return value + MarkItemEdited(id); + } + + const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); + const bool mixed_value = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; + if (is_visible) + { + RenderNavCursor(total_bb, id); + RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); + ImU32 check_col = GetColorU32(ImGuiCol_CheckMark); + if (mixed_value) + { + // Undocumented tristate/mixed/indeterminate checkbox (#2644) + // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox) + ImVec2 pad(ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(square_sz / 3.6f))); + window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding); + } + else if (*v) + { + const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f)); + RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f); + } + } + const ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); + if (g.LogEnabled) + LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]"); + if (is_visible && label_size.x > 0.0f) + RenderText(label_pos, label); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); + return pressed; +} + +template +bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value) +{ + bool all_on = (*flags & flags_value) == flags_value; + bool any_on = (*flags & flags_value) != 0; + bool pressed; + if (!all_on && any_on) + { + ImGuiContext& g = *GImGui; + g.NextItemData.ItemFlags |= ImGuiItemFlags_MixedValue; + pressed = Checkbox(label, &all_on); + } + else + { + pressed = Checkbox(label, &all_on); + + } + if (pressed) + { + if (all_on) + *flags |= flags_value; + else + *flags &= ~flags_value; + } + return pressed; +} + +bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value) +{ + return CheckboxFlagsT(label, flags, flags_value); +} + +bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) +{ + return CheckboxFlagsT(label, flags, flags_value); +} + +bool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value) +{ + return CheckboxFlagsT(label, flags, flags_value); +} + +bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value) +{ + return CheckboxFlagsT(label, flags, flags_value); +} + +bool ImGui::RadioButton(const char* label, bool active) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + const float square_sz = GetFrameHeight(); + const ImVec2 pos = window->DC.CursorPos; + const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); + const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id)) + return false; + + ImVec2 center = check_bb.GetCenter(); + center.x = IM_ROUND(center.x); + center.y = IM_ROUND(center.y); + const float radius = (square_sz - 1.0f) * 0.5f; + + bool hovered, held; + bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); + if (pressed) + MarkItemEdited(id); + + RenderNavCursor(total_bb, id); + const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius); + window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment); + if (active) + { + const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f)); + window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark)); + } + + if (style.FrameBorderSize > 0.0f) + { + window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), num_segment, style.FrameBorderSize); + window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), num_segment, style.FrameBorderSize); + } + + ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y); + if (g.LogEnabled) + LogRenderedText(&label_pos, active ? "(x)" : "( )"); + if (label_size.x > 0.0f) + RenderText(label_pos, label); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + return pressed; +} + +// FIXME: This would work nicely if it was a public template, e.g. 'template RadioButton(const char* label, T* v, T v_button)', but I'm not sure how we would expose it.. +bool ImGui::RadioButton(const char* label, int* v, int v_button) +{ + const bool pressed = RadioButton(label, *v == v_button); + if (pressed) + *v = v_button; + return pressed; +} + +// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size +void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + ImVec2 pos = window->DC.CursorPos; + ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f); + ImRect bb(pos, pos + size); + ItemSize(size, style.FramePadding.y); + if (!ItemAdd(bb, 0)) + return; + + // Fraction < 0.0f will display an indeterminate progress bar animation + // The value must be animated along with time, so e.g. passing '-1.0f * ImGui::GetTime()' as fraction works. + const bool is_indeterminate = (fraction < 0.0f); + if (!is_indeterminate) + fraction = ImSaturate(fraction); + + // Out of courtesy we accept a NaN fraction without crashing + float fill_n0 = 0.0f; + float fill_n1 = (fraction == fraction) ? fraction : 0.0f; + + if (is_indeterminate) + { + const float fill_width_n = 0.2f; + fill_n0 = ImFmod(-fraction, 1.0f) * (1.0f + fill_width_n) - fill_width_n; + fill_n1 = ImSaturate(fill_n0 + fill_width_n); + fill_n0 = ImSaturate(fill_n0); + } + + // Render + RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); + RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_n0, fill_n1, style.FrameRounding); + + // Default displaying the fraction as percentage string, but user can override it + // Don't display text for indeterminate bars by default + char overlay_buf[32]; + if (!is_indeterminate || overlay != NULL) + { + if (!overlay) + { + ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f); + overlay = overlay_buf; + } + + ImVec2 overlay_size = CalcTextSize(overlay, NULL); + if (overlay_size.x > 0.0f) + { + float text_x = is_indeterminate ? (bb.Min.x + bb.Max.x - overlay_size.x) * 0.5f : ImLerp(bb.Min.x, bb.Max.x, fill_n1) + style.ItemSpacing.x; + RenderTextClipped(ImVec2(ImClamp(text_x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f, 0.5f), &bb); + } + } +} + +void ImGui::Bullet() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), g.FontSize); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height)); + ItemSize(bb); + if (!ItemAdd(bb, 0)) + { + SameLine(0, style.FramePadding.x * 2); + return; + } + + // Render and stay on same line + ImU32 text_col = GetColorU32(ImGuiCol_Text); + RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), text_col); + SameLine(0, style.FramePadding.x * 2.0f); +} + +// This is provided as a convenience for being an often requested feature. +// FIXME-STYLE: we delayed adding as there is a larger plan to revamp the styling system. +// Because of this we currently don't provide many styling options for this widget +// (e.g. hovered/active colors are automatically inferred from a single color). +bool ImGui::TextLink(const char* label) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiID id = window->GetID(label); + const char* label_end = FindRenderedTextEnd(label); + + ImVec2 pos = window->DC.CursorPos; + ImVec2 size = CalcTextSize(label, label_end, true); + ImRect bb(pos, pos + size); + ItemSize(size, 0.0f); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + RenderNavCursor(bb, id); + + if (hovered) + SetMouseCursor(ImGuiMouseCursor_Hand); + + ImVec4 text_colf = g.Style.Colors[ImGuiCol_TextLink]; + ImVec4 line_colf = text_colf; + { + // FIXME-STYLE: Read comments above. This widget is NOT written in the same style as some earlier widgets, + // as we are currently experimenting/planning a different styling system. + float h, s, v; + ColorConvertRGBtoHSV(text_colf.x, text_colf.y, text_colf.z, h, s, v); + if (held || hovered) + { + v = ImSaturate(v + (held ? 0.4f : 0.3f)); + h = ImFmod(h + 0.02f, 1.0f); + } + ColorConvertHSVtoRGB(h, s, v, text_colf.x, text_colf.y, text_colf.z); + v = ImSaturate(v - 0.20f); + ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z); + } + + float line_y = bb.Max.y + ImFloor(g.Font->Descent * g.FontScale * 0.20f); + window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode. + + PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf)); + RenderText(bb.Min, label, label_end); + PopStyleColor(); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + return pressed; +} + +void ImGui::TextLinkOpenURL(const char* label, const char* url) +{ + ImGuiContext& g = *GImGui; + if (url == NULL) + url = label; + if (TextLink(label)) + if (g.PlatformIO.Platform_OpenInShellFn != NULL) + g.PlatformIO.Platform_OpenInShellFn(&g, url); + SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when we same as label + if (BeginPopupContextItem()) + { + if (MenuItem(LocalizeGetMsg(ImGuiLocKey_CopyLink))) + SetClipboardText(url); + EndPopup(); + } +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Low-level Layout helpers +//------------------------------------------------------------------------- +// - Spacing() +// - Dummy() +// - NewLine() +// - AlignTextToFramePadding() +// - SeparatorEx() [Internal] +// - Separator() +// - SplitterBehavior() [Internal] +// - ShrinkWidths() [Internal] +//------------------------------------------------------------------------- + +void ImGui::Spacing() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ItemSize(ImVec2(0, 0)); +} + +void ImGui::Dummy(const ImVec2& size) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(size); + ItemAdd(bb, 0); +} + +void ImGui::NewLine() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + const ImGuiLayoutType backup_layout_type = window->DC.LayoutType; + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.IsSameLine = false; + if (window->DC.CurrLineSize.y > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height. + ItemSize(ImVec2(0, 0)); + else + ItemSize(ImVec2(0.0f, g.FontSize)); + window->DC.LayoutType = backup_layout_type; +} + +void ImGui::AlignTextToFramePadding() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + window->DC.CurrLineSize.y = ImMax(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2); + window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y); +} + +// Horizontal/vertical separating line +// FIXME: Surprisingly, this seemingly trivial widget is a victim of many different legacy/tricky layout issues. +// Note how thickness == 1.0f is handled specifically as not moving CursorPos by 'thickness', but other values are. +void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + ImGuiContext& g = *GImGui; + IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical))); // Check that only 1 option is selected + IM_ASSERT(thickness > 0.0f); + + if (flags & ImGuiSeparatorFlags_Vertical) + { + // Vertical separator, for menu bars (use current line height). + float y1 = window->DC.CursorPos.y; + float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y; + const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness, y2)); + ItemSize(ImVec2(thickness, 0.0f)); + if (!ItemAdd(bb, 0)) + return; + + // Draw + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator)); + if (g.LogEnabled) + LogText(" |"); + } + else if (flags & ImGuiSeparatorFlags_Horizontal) + { + // Horizontal Separator + float x1 = window->DC.CursorPos.x; + float x2 = window->WorkRect.Max.x; + + // Preserve legacy behavior inside Columns() + // Before Tables API happened, we relied on Separator() to span all columns of a Columns() set. + // We currently don't need to provide the same feature for tables because tables naturally have border features. + ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL; + if (columns) + { + x1 = window->Pos.x + window->DC.Indent.x; // Used to be Pos.x before 2023/10/03 + x2 = window->Pos.x + window->Size.x; + PushColumnsBackground(); + } + + // We don't provide our width to the layout so that it doesn't get feed back into AutoFit + // FIXME: This prevents ->CursorMaxPos based bounding box evaluation from working (e.g. TableEndCell) + const float thickness_for_layout = (thickness == 1.0f) ? 0.0f : thickness; // FIXME: See 1.70/1.71 Separator() change: makes legacy 1-px separator not affect layout yet. Should change. + const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness)); + ItemSize(ImVec2(0.0f, thickness_for_layout)); + + if (ItemAdd(bb, 0)) + { + // Draw + window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator)); + if (g.LogEnabled) + LogRenderedText(&bb.Min, "--------------------------------\n"); + + } + if (columns) + { + PopColumnsBackground(); + columns->LineMinY = window->DC.CursorPos.y; + } + } +} + +void ImGui::Separator() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + // Those flags should eventually be configurable by the user + // FIXME: We cannot g.Style.SeparatorTextBorderSize for thickness as it relates to SeparatorText() which is a decorated separator, not defaulting to 1.0f. + ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal; + + // Only applies to legacy Columns() api as they relied on Separator() a lot. + if (window->DC.CurrentColumns) + flags |= ImGuiSeparatorFlags_SpanAllColumns; + + SeparatorEx(flags, 1.0f); +} + +void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStyle& style = g.Style; + + const ImVec2 label_size = CalcTextSize(label, label_end, false); + const ImVec2 pos = window->DC.CursorPos; + const ImVec2 padding = style.SeparatorTextPadding; + + const float separator_thickness = style.SeparatorTextBorderSize; + const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(label_size.y + padding.y * 2.0f, separator_thickness)); + const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y)); + const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImTrunc((style.SeparatorTextSize - label_size.y) * 0.5f)); + ItemSize(min_size, text_baseline_y); + if (!ItemAdd(bb, id)) + return; + + const float sep1_x1 = pos.x; + const float sep2_x2 = bb.Max.x; + const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.99999f); + + const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f); + const ImVec2 label_pos(pos.x + padding.x + ImMax(0.0f, (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN + + // This allows using SameLine() to position something in the 'extra_w' + window->DC.CursorPosPrevLine.x = label_pos.x + label_size.x; + + const ImU32 separator_col = GetColorU32(ImGuiCol_Separator); + if (label_size.x > 0.0f) + { + const float sep1_x2 = label_pos.x - style.ItemSpacing.x; + const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x; + if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f) + window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep1_x2, seps_y), separator_col, separator_thickness); + if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f) + window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness); + if (g.LogEnabled) + LogSetNextTextDecoration("---", NULL); + RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, bb.Max.x, label, label_end, &label_size); + } + else + { + if (g.LogEnabled) + LogText("---"); + if (separator_thickness > 0.0f) + window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness); + } +} + +void ImGui::SeparatorText(const char* label) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + // The SeparatorText() vs SeparatorTextEx() distinction is designed to be considerate that we may want: + // - allow separator-text to be draggable items (would require a stable ID + a noticeable highlight) + // - this high-level entry point to allow formatting? (which in turns may require ID separate from formatted string) + // - because of this we probably can't turn 'const char* label' into 'const char* fmt, ...' + // Otherwise, we can decide that users wanting to drag this would layout a dedicated drag-item, + // and then we can turn this into a format function. + SeparatorTextEx(0, label, FindRenderedTextEnd(label), 0.0f); +} + +// Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise. +bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay, ImU32 bg_col) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (!ItemAdd(bb, id, NULL, ImGuiItemFlags_NoNav)) + return false; + + // FIXME: AFAIK the only leftover reason for passing ImGuiButtonFlags_AllowOverlap here is + // to allow caller of SplitterBehavior() to call SetItemAllowOverlap() after the item. + // Nowadays we would instead want to use SetNextItemAllowOverlap() before the item. + ImGuiButtonFlags button_flags = ImGuiButtonFlags_FlattenChildren; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + button_flags |= ImGuiButtonFlags_AllowOverlap; +#endif + + bool hovered, held; + ImRect bb_interact = bb; + bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f)); + ButtonBehavior(bb_interact, id, &hovered, &held, button_flags); + if (hovered) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb + + if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay)) + SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); + + ImRect bb_render = bb; + if (held) + { + float mouse_delta = (g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min)[axis]; + + // Minimum pane size + float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1); + float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2); + if (mouse_delta < -size_1_maximum_delta) + mouse_delta = -size_1_maximum_delta; + if (mouse_delta > size_2_maximum_delta) + mouse_delta = size_2_maximum_delta; + + // Apply resize + if (mouse_delta != 0.0f) + { + *size1 = ImMax(*size1 + mouse_delta, min_size1); + *size2 = ImMax(*size2 - mouse_delta, min_size2); + bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta)); + MarkItemEdited(id); + } + } + + // Render at new position + if (bg_col & IM_COL32_A_MASK) + window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, bg_col, 0.0f); + const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); + window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f); + + return held; +} + +static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs) +{ + const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs; + const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs; + if (int d = (int)(b->Width - a->Width)) + return d; + return (b->Index - a->Index); +} + +// Shrink excess width from a set of item, by removing width from the larger items first. +// Set items Width to -1.0f to disable shrinking this item. +void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess) +{ + if (count == 1) + { + if (items[0].Width >= 0.0f) + items[0].Width = ImMax(items[0].Width - width_excess, 1.0f); + return; + } + ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer); + int count_same_width = 1; + while (width_excess > 0.0f && count_same_width < count) + { + while (count_same_width < count && items[0].Width <= items[count_same_width].Width) + count_same_width++; + float max_width_to_remove_per_item = (count_same_width < count && items[count_same_width].Width >= 0.0f) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f); + if (max_width_to_remove_per_item <= 0.0f) + break; + float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item); + for (int item_n = 0; item_n < count_same_width; item_n++) + items[item_n].Width -= width_to_remove_per_item; + width_excess -= width_to_remove_per_item * count_same_width; + } + + // Round width and redistribute remainder + // Ensure that e.g. the right-most tab of a shrunk tab-bar always reaches exactly at the same distance from the right-most edge of the tab bar separator. + width_excess = 0.0f; + for (int n = 0; n < count; n++) + { + float width_rounded = ImTrunc(items[n].Width); + width_excess += items[n].Width - width_rounded; + items[n].Width = width_rounded; + } + while (width_excess > 0.0f) + for (int n = 0; n < count && width_excess > 0.0f; n++) + { + float width_to_add = ImMin(items[n].InitialWidth - items[n].Width, 1.0f); + items[n].Width += width_to_add; + width_excess -= width_to_add; + } +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: ComboBox +//------------------------------------------------------------------------- +// - CalcMaxPopupHeightFromItemCount() [Internal] +// - BeginCombo() +// - BeginComboPopup() [Internal] +// - EndCombo() +// - BeginComboPreview() [Internal] +// - EndComboPreview() [Internal] +// - Combo() +//------------------------------------------------------------------------- + +static float CalcMaxPopupHeightFromItemCount(int items_count) +{ + ImGuiContext& g = *GImGui; + if (items_count <= 0) + return FLT_MAX; + return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2); +} + +bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.Flags; + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together + if (flags & ImGuiComboFlags_WidthFitPreview) + IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0); + + const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, true).x : 0.0f; + const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth()); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); + const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &bb)) + return false; + + // Open on click + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + const ImGuiID popup_id = ImHashStr("##ComboPopup", 0, id); + bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None); + if (pressed && !popup_open) + { + OpenPopupEx(popup_id, ImGuiPopupFlags_None); + popup_open = true; + } + + // Render shape + const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size); + RenderNavCursor(bb, id); + if (!(flags & ImGuiComboFlags_NoPreview)) + window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft); + if (!(flags & ImGuiComboFlags_NoArrowButton)) + { + ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + ImU32 text_col = GetColorU32(ImGuiCol_Text); + window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight); + if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x) + RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f); + } + RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding); + + // Custom preview + if (flags & ImGuiComboFlags_CustomPreview) + { + g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y); + IM_ASSERT(preview_value == NULL || preview_value[0] == 0); + preview_value = NULL; + } + + // Render preview and label + if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview)) + { + if (g.LogEnabled) + LogSetNextTextDecoration("{", "}"); + RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL); + } + if (label_size.x > 0) + RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label); + + if (!popup_open) + return false; + + g.NextWindowData.Flags = backup_next_window_data_flags; + return BeginComboPopup(popup_id, bb, flags); +} + +bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags) +{ + ImGuiContext& g = *GImGui; + if (!IsPopupOpen(popup_id, ImGuiPopupFlags_None)) + { + g.NextWindowData.ClearFlags(); + return false; + } + + // Set popup size + float w = bb.GetWidth(); + if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint) + { + g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w); + } + else + { + if ((flags & ImGuiComboFlags_HeightMask_) == 0) + flags |= ImGuiComboFlags_HeightRegular; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one + int popup_max_height_in_items = -1; + if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8; + else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4; + else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20; + ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX); + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size + constraint_min.x = w; + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f) + constraint_max.y = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items); + SetNextWindowSizeConstraints(constraint_min, constraint_max); + } + + // This is essentially a specialized version of BeginPopupEx() + char name[16]; + ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth + + // Set position given a custom constraint (peak into expected window size so we can position it) + // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function? + // FIXME: This might be moved to Begin() or at least around the same spot where Tooltips and other Popups are calling FindBestWindowPosForPopupEx()? + if (ImGuiWindow* popup_window = FindWindowByName(name)) + if (popup_window->WasActive) + { + // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us. + ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window); + popup_window->AutoPosLastDirection = (flags & ImGuiComboFlags_PopupAlignLeft) ? ImGuiDir_Left : ImGuiDir_Down; // Left = "Below, Toward Left", Down = "Below, Toward Right (default)" + ImRect r_outer = GetPopupAllowedExtentRect(popup_window); + ImVec2 pos = FindBestWindowPosForPopupEx(bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, bb, ImGuiPopupPositionPolicy_ComboBox); + SetNextWindowPos(pos); + } + + // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx() + ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove; + PushStyleVarX(ImGuiStyleVar_WindowPadding, g.Style.FramePadding.x); // Horizontally align ourselves with the framed text + bool ret = Begin(name, NULL, window_flags); + PopStyleVar(); + if (!ret) + { + EndPopup(); + IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above + return false; + } + g.BeginComboDepth++; + return true; +} + +void ImGui::EndCombo() +{ + ImGuiContext& g = *GImGui; + EndPopup(); + g.BeginComboDepth--; +} + +// Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements +// (Experimental, see GitHub issues: #1658, #4168) +bool ImGui::BeginComboPreview() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; + + if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)) + return false; + IM_ASSERT(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y); // Didn't call after BeginCombo/EndCombo block or forgot to pass ImGuiComboFlags_CustomPreview flag? + if (!window->ClipRect.Overlaps(preview_data->PreviewRect)) // Narrower test (optional) + return false; + + // FIXME: This could be contained in a PushWorkRect() api + preview_data->BackupCursorPos = window->DC.CursorPos; + preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos; + preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine; + preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; + preview_data->BackupLayout = window->DC.LayoutType; + window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding; + window->DC.CursorMaxPos = window->DC.CursorPos; + window->DC.LayoutType = ImGuiLayoutType_Horizontal; + window->DC.IsSameLine = false; + PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true); + + return true; +} + +void ImGui::EndComboPreview() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; + + // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future + ImDrawList* draw_list = window->DrawList; + if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y) + if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command + { + draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect; + draw_list->_TryMergeDrawCmds(); + } + PopClipRect(); + window->DC.CursorPos = preview_data->BackupCursorPos; + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos); + window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine; + window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset; + window->DC.LayoutType = preview_data->BackupLayout; + window->DC.IsSameLine = false; + preview_data->PreviewRect = ImRect(); +} + +// Getter for the old Combo() API: const char*[] +static const char* Items_ArrayGetter(void* data, int idx) +{ + const char* const* items = (const char* const*)data; + return items[idx]; +} + +// Getter for the old Combo() API: "item1\0item2\0item3\0" +static const char* Items_SingleStringGetter(void* data, int idx) +{ + const char* items_separated_by_zeros = (const char*)data; + int items_count = 0; + const char* p = items_separated_by_zeros; + while (*p) + { + if (idx == items_count) + break; + p += strlen(p) + 1; + items_count++; + } + return *p ? p : NULL; +} + +// Old API, prefer using BeginCombo() nowadays if you can. +bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int popup_max_height_in_items) +{ + ImGuiContext& g = *GImGui; + + // Call the getter to obtain the preview string which is a parameter to BeginCombo() + const char* preview_value = NULL; + if (*current_item >= 0 && *current_item < items_count) + preview_value = getter(user_data, *current_item); + + // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here. + if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)) + SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); + + if (!BeginCombo(label, preview_value, ImGuiComboFlags_None)) + return false; + + // Display items + bool value_changed = false; + ImGuiListClipper clipper; + clipper.Begin(items_count); + clipper.IncludeItemByIndex(*current_item); + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + const char* item_text = getter(user_data, i); + if (item_text == NULL) + item_text = "*Unknown item*"; + + PushID(i); + const bool item_selected = (i == *current_item); + if (Selectable(item_text, item_selected) && *current_item != i) + { + value_changed = true; + *current_item = i; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); + } + + EndCombo(); + if (value_changed) + MarkItemEdited(g.LastItemData.ID); + + return value_changed; +} + +// Combo box helper allowing to pass an array of strings. +bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items) +{ + const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items); + return value_changed; +} + +// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0" +bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items) +{ + int items_count = 0; + const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open + while (*p) + { + p += strlen(p) + 1; + items_count++; + } + bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items); + return value_changed; +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +struct ImGuiGetNameFromIndexOldToNewCallbackData { void* UserData; bool (*OldCallback)(void*, int, const char**); }; +static const char* ImGuiGetNameFromIndexOldToNewCallback(void* user_data, int idx) +{ + ImGuiGetNameFromIndexOldToNewCallbackData* data = (ImGuiGetNameFromIndexOldToNewCallbackData*)user_data; + const char* s = NULL; + data->OldCallback(data->UserData, idx, &s); + return s; +} + +bool ImGui::ListBox(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int height_in_items) +{ + ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter }; + return ListBox(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, height_in_items); +} +bool ImGui::Combo(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int popup_max_height_in_items) +{ + ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter }; + return Combo(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, popup_max_height_in_items); +} + +#endif + +//------------------------------------------------------------------------- +// [SECTION] Data Type and Data Formatting Helpers [Internal] +//------------------------------------------------------------------------- +// - DataTypeGetInfo() +// - DataTypeFormatString() +// - DataTypeApplyOp() +// - DataTypeApplyFromText() +// - DataTypeCompare() +// - DataTypeClamp() +// - GetMinimumStepAtDecimalPrecision +// - RoundScalarWithFormat<>() +//------------------------------------------------------------------------- + +static const ImGuiDataTypeInfo GDataTypeInfo[] = +{ + { sizeof(char), "S8", "%d", "%d" }, // ImGuiDataType_S8 + { sizeof(unsigned char), "U8", "%u", "%u" }, + { sizeof(short), "S16", "%d", "%d" }, // ImGuiDataType_S16 + { sizeof(unsigned short), "U16", "%u", "%u" }, + { sizeof(int), "S32", "%d", "%d" }, // ImGuiDataType_S32 + { sizeof(unsigned int), "U32", "%u", "%u" }, +#ifdef _MSC_VER + { sizeof(ImS64), "S64", "%I64d","%I64d" }, // ImGuiDataType_S64 + { sizeof(ImU64), "U64", "%I64u","%I64u" }, +#else + { sizeof(ImS64), "S64", "%lld", "%lld" }, // ImGuiDataType_S64 + { sizeof(ImU64), "U64", "%llu", "%llu" }, +#endif + { sizeof(float), "float", "%.3f","%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg) + { sizeof(double), "double","%f", "%lf" }, // ImGuiDataType_Double + { sizeof(bool), "bool", "%d", "%d" }, // ImGuiDataType_Bool + { 0, "char*","%s", "%s" }, // ImGuiDataType_String +}; +IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); + +const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type) +{ + IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); + return &GDataTypeInfo[data_type]; +} + +int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format) +{ + // Signedness doesn't matter when pushing integer arguments + if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32) + return ImFormatString(buf, buf_size, format, *(const ImU32*)p_data); + if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) + return ImFormatString(buf, buf_size, format, *(const ImU64*)p_data); + if (data_type == ImGuiDataType_Float) + return ImFormatString(buf, buf_size, format, *(const float*)p_data); + if (data_type == ImGuiDataType_Double) + return ImFormatString(buf, buf_size, format, *(const double*)p_data); + if (data_type == ImGuiDataType_S8) + return ImFormatString(buf, buf_size, format, *(const ImS8*)p_data); + if (data_type == ImGuiDataType_U8) + return ImFormatString(buf, buf_size, format, *(const ImU8*)p_data); + if (data_type == ImGuiDataType_S16) + return ImFormatString(buf, buf_size, format, *(const ImS16*)p_data); + if (data_type == ImGuiDataType_U16) + return ImFormatString(buf, buf_size, format, *(const ImU16*)p_data); + IM_ASSERT(0); + return 0; +} + +void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2) +{ + IM_ASSERT(op == '+' || op == '-'); + switch (data_type) + { + case ImGuiDataType_S8: + if (op == '+') { *(ImS8*)output = ImAddClampOverflow(*(const ImS8*)arg1, *(const ImS8*)arg2, IM_S8_MIN, IM_S8_MAX); } + if (op == '-') { *(ImS8*)output = ImSubClampOverflow(*(const ImS8*)arg1, *(const ImS8*)arg2, IM_S8_MIN, IM_S8_MAX); } + return; + case ImGuiDataType_U8: + if (op == '+') { *(ImU8*)output = ImAddClampOverflow(*(const ImU8*)arg1, *(const ImU8*)arg2, IM_U8_MIN, IM_U8_MAX); } + if (op == '-') { *(ImU8*)output = ImSubClampOverflow(*(const ImU8*)arg1, *(const ImU8*)arg2, IM_U8_MIN, IM_U8_MAX); } + return; + case ImGuiDataType_S16: + if (op == '+') { *(ImS16*)output = ImAddClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); } + if (op == '-') { *(ImS16*)output = ImSubClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); } + return; + case ImGuiDataType_U16: + if (op == '+') { *(ImU16*)output = ImAddClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); } + if (op == '-') { *(ImU16*)output = ImSubClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); } + return; + case ImGuiDataType_S32: + if (op == '+') { *(ImS32*)output = ImAddClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); } + if (op == '-') { *(ImS32*)output = ImSubClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); } + return; + case ImGuiDataType_U32: + if (op == '+') { *(ImU32*)output = ImAddClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); } + if (op == '-') { *(ImU32*)output = ImSubClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); } + return; + case ImGuiDataType_S64: + if (op == '+') { *(ImS64*)output = ImAddClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); } + if (op == '-') { *(ImS64*)output = ImSubClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); } + return; + case ImGuiDataType_U64: + if (op == '+') { *(ImU64*)output = ImAddClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); } + if (op == '-') { *(ImU64*)output = ImSubClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); } + return; + case ImGuiDataType_Float: + if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; } + if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; } + return; + case ImGuiDataType_Double: + if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; } + if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; } + return; + case ImGuiDataType_COUNT: break; + } + IM_ASSERT(0); +} + +// User can input math operators (e.g. +100) to edit a numerical values. +// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess.. +bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format, void* p_data_when_empty) +{ + // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all. + const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type); + ImGuiDataTypeStorage data_backup; + memcpy(&data_backup, p_data, type_info->Size); + + while (ImCharIsBlankA(*buf)) + buf++; + if (!buf[0]) + { + if (p_data_when_empty != NULL) + { + memcpy(p_data, p_data_when_empty, type_info->Size); + return memcmp(&data_backup, p_data, type_info->Size) != 0; + } + return false; + } + + // Sanitize format + // - For float/double we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in, so force them into %f and %lf + // - In theory could treat empty format as using default, but this would only cover rare/bizarre case of using InputScalar() + integer + format string without %. + char format_sanitized[32]; + if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) + format = type_info->ScanFmt; + else + format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_ARRAYSIZE(format_sanitized)); + + // Small types need a 32-bit buffer to receive the result from scanf() + int v32 = 0; + if (sscanf(buf, format, type_info->Size >= 4 ? p_data : &v32) < 1) + return false; + if (type_info->Size < 4) + { + if (data_type == ImGuiDataType_S8) + *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX); + else if (data_type == ImGuiDataType_U8) + *(ImU8*)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX); + else if (data_type == ImGuiDataType_S16) + *(ImS16*)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX); + else if (data_type == ImGuiDataType_U16) + *(ImU16*)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX); + else + IM_ASSERT(0); + } + + return memcmp(&data_backup, p_data, type_info->Size) != 0; +} + +template +static int DataTypeCompareT(const T* lhs, const T* rhs) +{ + if (*lhs < *rhs) return -1; + if (*lhs > *rhs) return +1; + return 0; +} + +int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2) +{ + switch (data_type) + { + case ImGuiDataType_S8: return DataTypeCompareT((const ImS8* )arg_1, (const ImS8* )arg_2); + case ImGuiDataType_U8: return DataTypeCompareT((const ImU8* )arg_1, (const ImU8* )arg_2); + case ImGuiDataType_S16: return DataTypeCompareT((const ImS16* )arg_1, (const ImS16* )arg_2); + case ImGuiDataType_U16: return DataTypeCompareT((const ImU16* )arg_1, (const ImU16* )arg_2); + case ImGuiDataType_S32: return DataTypeCompareT((const ImS32* )arg_1, (const ImS32* )arg_2); + case ImGuiDataType_U32: return DataTypeCompareT((const ImU32* )arg_1, (const ImU32* )arg_2); + case ImGuiDataType_S64: return DataTypeCompareT((const ImS64* )arg_1, (const ImS64* )arg_2); + case ImGuiDataType_U64: return DataTypeCompareT((const ImU64* )arg_1, (const ImU64* )arg_2); + case ImGuiDataType_Float: return DataTypeCompareT((const float* )arg_1, (const float* )arg_2); + case ImGuiDataType_Double: return DataTypeCompareT((const double*)arg_1, (const double*)arg_2); + case ImGuiDataType_COUNT: break; + } + IM_ASSERT(0); + return 0; +} + +template +static bool DataTypeClampT(T* v, const T* v_min, const T* v_max) +{ + // Clamp, both sides are optional, return true if modified + if (v_min && *v < *v_min) { *v = *v_min; return true; } + if (v_max && *v > *v_max) { *v = *v_max; return true; } + return false; +} + +bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max) +{ + switch (data_type) + { + case ImGuiDataType_S8: return DataTypeClampT((ImS8* )p_data, (const ImS8* )p_min, (const ImS8* )p_max); + case ImGuiDataType_U8: return DataTypeClampT((ImU8* )p_data, (const ImU8* )p_min, (const ImU8* )p_max); + case ImGuiDataType_S16: return DataTypeClampT((ImS16* )p_data, (const ImS16* )p_min, (const ImS16* )p_max); + case ImGuiDataType_U16: return DataTypeClampT((ImU16* )p_data, (const ImU16* )p_min, (const ImU16* )p_max); + case ImGuiDataType_S32: return DataTypeClampT((ImS32* )p_data, (const ImS32* )p_min, (const ImS32* )p_max); + case ImGuiDataType_U32: return DataTypeClampT((ImU32* )p_data, (const ImU32* )p_min, (const ImU32* )p_max); + case ImGuiDataType_S64: return DataTypeClampT((ImS64* )p_data, (const ImS64* )p_min, (const ImS64* )p_max); + case ImGuiDataType_U64: return DataTypeClampT((ImU64* )p_data, (const ImU64* )p_min, (const ImU64* )p_max); + case ImGuiDataType_Float: return DataTypeClampT((float* )p_data, (const float* )p_min, (const float* )p_max); + case ImGuiDataType_Double: return DataTypeClampT((double*)p_data, (const double*)p_min, (const double*)p_max); + case ImGuiDataType_COUNT: break; + } + IM_ASSERT(0); + return false; +} + +bool ImGui::DataTypeIsZero(ImGuiDataType data_type, const void* p_data) +{ + ImGuiContext& g = *GImGui; + return DataTypeCompare(data_type, p_data, &g.DataTypeZeroValue) == 0; +} + +static float GetMinimumStepAtDecimalPrecision(int decimal_precision) +{ + static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f }; + if (decimal_precision < 0) + return FLT_MIN; + return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision); +} + +template +TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v) +{ + IM_UNUSED(data_type); + IM_ASSERT(data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double); + const char* fmt_start = ImParseFormatFindStart(format); + if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string + return v; + + // Sanitize format + char fmt_sanitized[32]; + ImParseFormatSanitizeForPrinting(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized)); + fmt_start = fmt_sanitized; + + // Format value with our rounding, and read back + char v_str[64]; + ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v); + const char* p = v_str; + while (*p == ' ') + p++; + v = (TYPE)ImAtof(p); + + return v; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc. +//------------------------------------------------------------------------- +// - DragBehaviorT<>() [Internal] +// - DragBehavior() [Internal] +// - DragScalar() +// - DragScalarN() +// - DragFloat() +// - DragFloat2() +// - DragFloat3() +// - DragFloat4() +// - DragFloatRange2() +// - DragInt() +// - DragInt2() +// - DragInt3() +// - DragInt4() +// - DragIntRange2() +//------------------------------------------------------------------------- + +// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls) +template +bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags) +{ + ImGuiContext& g = *GImGui; + const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; + const bool is_bounded = (v_min < v_max) || ((v_min == v_max) && (v_min != 0.0f || (flags & ImGuiSliderFlags_ClampZeroRange))); + const bool is_wrapped = is_bounded && (flags & ImGuiSliderFlags_WrapAround); + const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0; + const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); + + // Default tweak speed + if (v_speed == 0.0f && is_bounded && (v_max - v_min < FLT_MAX)) + v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio); + + // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings + float adjust_delta = 0.0f; + if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) + { + adjust_delta = g.IO.MouseDelta[axis]; + if (g.IO.KeyAlt && !(flags & ImGuiSliderFlags_NoSpeedTweaks)) + adjust_delta *= 1.0f / 100.0f; + if (g.IO.KeyShift && !(flags & ImGuiSliderFlags_NoSpeedTweaks)) + adjust_delta *= 10.0f; + } + else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) + { + const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0; + const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow); + const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast); + const float tweak_factor = (flags & ImGuiSliderFlags_NoSpeedTweaks) ? 1.0f : tweak_slow ? 1.0f / 10.0f : tweak_fast ? 10.0f : 1.0f; + adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor; + v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); + } + adjust_delta *= v_speed; + + // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter. + if (axis == ImGuiAxis_Y) + adjust_delta = -adjust_delta; + + // For logarithmic use our range is effectively 0..1 so scale the delta into that range + if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) // Epsilon to avoid /0 + adjust_delta /= (float)(v_max - v_min); + + // Clear current value on activation + // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300. + const bool is_just_activated = g.ActiveIdIsJustActivated; + const bool is_already_past_limits_and_pushing_outward = is_bounded && !is_wrapped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f)); + if (is_just_activated || is_already_past_limits_and_pushing_outward) + { + g.DragCurrentAccum = 0.0f; + g.DragCurrentAccumDirty = false; + } + else if (adjust_delta != 0.0f) + { + g.DragCurrentAccum += adjust_delta; + g.DragCurrentAccumDirty = true; + } + + if (!g.DragCurrentAccumDirty) + return false; + + TYPE v_cur = *v; + FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f; + + float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true + const float zero_deadzone_halfsize = 0.0f; // Drag widgets have no deadzone (as it doesn't make sense) + if (is_logarithmic) + { + // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. + const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1; + logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision); + + // Convert to parametric space, apply delta, convert back + float v_old_parametric = ScaleRatioFromValueT(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + float v_new_parametric = v_old_parametric + g.DragCurrentAccum; + v_cur = ScaleValueFromRatioT(data_type, v_new_parametric, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + v_old_ref_for_accum_remainder = v_old_parametric; + } + else + { + v_cur += (SIGNEDTYPE)g.DragCurrentAccum; + } + + // Round to user desired precision based on format string + if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat)) + v_cur = RoundScalarWithFormatT(format, data_type, v_cur); + + // Preserve remainder after rounding has been applied. This also allow slow tweaking of values. + g.DragCurrentAccumDirty = false; + if (is_logarithmic) + { + // Convert to parametric space, apply delta, convert back + float v_new_parametric = ScaleRatioFromValueT(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder); + } + else + { + g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v); + } + + // Lose zero sign for float/double + if (v_cur == (TYPE)-0) + v_cur = (TYPE)0; + + if (*v != v_cur && is_bounded) + { + if (is_wrapped) + { + // Wrap values + if (v_cur < v_min) + v_cur += v_max - v_min + (is_floating_point ? 0 : 1); + if (v_cur > v_max) + v_cur -= v_max - v_min + (is_floating_point ? 0 : 1); + } + else + { + // Clamp values + handle overflow/wrap-around for integer types. + if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point)) + v_cur = v_min; + if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point)) + v_cur = v_max; + } + } + + // Apply result + if (*v == v_cur) + return false; + *v = v_cur; + return true; +} + +bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) +{ + // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert. + IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); + + ImGuiContext& g = *GImGui; + if (g.ActiveId == id) + { + // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation. + if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) + ClearActiveID(); + else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) + ClearActiveID(); + } + if (g.ActiveId != id) + return false; + if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) + return false; + + switch (data_type) + { + case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = DragBehaviorT(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS8*) p_min : IM_S8_MIN, p_max ? *(const ImS8*)p_max : IM_S8_MAX, format, flags); if (r) *(ImS8*)p_v = (ImS8)v32; return r; } + case ImGuiDataType_U8: { ImU32 v32 = (ImU32)*(ImU8*)p_v; bool r = DragBehaviorT(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU8*) p_min : IM_U8_MIN, p_max ? *(const ImU8*)p_max : IM_U8_MAX, format, flags); if (r) *(ImU8*)p_v = (ImU8)v32; return r; } + case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = DragBehaviorT(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS16*)p_min : IM_S16_MIN, p_max ? *(const ImS16*)p_max : IM_S16_MAX, format, flags); if (r) *(ImS16*)p_v = (ImS16)v32; return r; } + case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = DragBehaviorT(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU16*)p_min : IM_U16_MIN, p_max ? *(const ImU16*)p_max : IM_U16_MAX, format, flags); if (r) *(ImU16*)p_v = (ImU16)v32; return r; } + case ImGuiDataType_S32: return DragBehaviorT(data_type, (ImS32*)p_v, v_speed, p_min ? *(const ImS32* )p_min : IM_S32_MIN, p_max ? *(const ImS32* )p_max : IM_S32_MAX, format, flags); + case ImGuiDataType_U32: return DragBehaviorT(data_type, (ImU32*)p_v, v_speed, p_min ? *(const ImU32* )p_min : IM_U32_MIN, p_max ? *(const ImU32* )p_max : IM_U32_MAX, format, flags); + case ImGuiDataType_S64: return DragBehaviorT(data_type, (ImS64*)p_v, v_speed, p_min ? *(const ImS64* )p_min : IM_S64_MIN, p_max ? *(const ImS64* )p_max : IM_S64_MAX, format, flags); + case ImGuiDataType_U64: return DragBehaviorT(data_type, (ImU64*)p_v, v_speed, p_min ? *(const ImU64* )p_min : IM_U64_MIN, p_max ? *(const ImU64* )p_max : IM_U64_MAX, format, flags); + case ImGuiDataType_Float: return DragBehaviorT(data_type, (float*)p_v, v_speed, p_min ? *(const float* )p_min : -FLT_MAX, p_max ? *(const float* )p_max : FLT_MAX, format, flags); + case ImGuiDataType_Double: return DragBehaviorT(data_type, (double*)p_v, v_speed, p_min ? *(const double*)p_min : -DBL_MAX, p_max ? *(const double*)p_max : DBL_MAX, format, flags); + case ImGuiDataType_COUNT: break; + } + IM_ASSERT(0); + return false; +} + +// Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a Drag widget, p_min and p_max are optional. +// Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly. +bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0)) + return false; + + // Default format string when passing NULL + if (format == NULL) + format = DataTypeGetInfo(data_type)->PrintFmt; + + const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags); + bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); + if (!temp_input_is_active) + { + // Tabbing or CTRL-clicking on Drag turns it into an InputText + const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id); + const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id)); + const bool make_active = (clicked || double_clicked || g.NavActivateId == id); + if (make_active && (clicked || double_clicked)) + SetKeyOwner(ImGuiKey_MouseLeft, id); + if (make_active && temp_input_allowed) + if ((clicked && g.IO.KeyCtrl) || double_clicked || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput))) + temp_input_is_active = true; + + // (Optional) simple click (without moving) turns Drag into an InputText + if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active) + if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) + { + g.NavActivateId = id; + g.NavActivateFlags = ImGuiActivateFlags_PreferInput; + temp_input_is_active = true; + } + + // Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert) + if (make_active) + memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size); + + if (make_active && !temp_input_is_active) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + } + } + + if (temp_input_is_active) + { + // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp) + bool clamp_enabled = false; + if ((flags & ImGuiSliderFlags_ClampOnInput) && (p_min != NULL || p_max != NULL)) + { + const int clamp_range_dir = (p_min != NULL && p_max != NULL) ? DataTypeCompare(data_type, p_min, p_max) : 0; // -1 when *p_min < *p_max, == 0 when *p_min == *p_max + if (p_min == NULL || p_max == NULL || clamp_range_dir < 0) + clamp_enabled = true; + else if (clamp_range_dir == 0) + clamp_enabled = DataTypeIsZero(data_type, p_min) ? ((flags & ImGuiSliderFlags_ClampZeroRange) != 0) : true; + } + return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL); + } + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavCursor(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); + + // Drag behavior + const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags); + if (value_changed) + MarkItemEdited(id); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + char value_buf[64]; + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); + if (g.LogEnabled) + LogSetNextTextDecoration("{", "}"); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0)); + return value_changed; +} + +bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components, CalcItemWidth()); + size_t type_size = GDataTypeInfo[data_type].Size; + for (int i = 0; i < components; i++) + { + PushID(i); + if (i > 0) + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags); + PopID(); + PopItemWidth(); + p_data = (void*)((char*)p_data + type_size); + } + PopID(); + + const char* label_end = FindRenderedTextEnd(label); + if (label != label_end) + { + SameLine(0, g.Style.ItemInnerSpacing.x); + TextEx(label, label_end); + } + + EndGroup(); + return value_changed; +} + +bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, flags); +} + +bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, flags); +} + +bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, flags); +} + +bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, flags); +} + +// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this. +bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, ImGuiSliderFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + PushID(label); + BeginGroup(); + PushMultiItemsWidths(2, CalcItemWidth()); + + float min_min = (v_min >= v_max) ? -FLT_MAX : v_min; + float min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max); + ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0); + bool value_changed = DragScalar("##min", ImGuiDataType_Float, v_current_min, v_speed, &min_min, &min_max, format, min_flags); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + float max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min); + float max_max = (v_min >= v_max) ? FLT_MAX : v_max; + ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0); + value_changed |= DragScalar("##max", ImGuiDataType_Float, v_current_max, v_speed, &max_min, &max_max, format_max ? format_max : format, max_flags); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + TextEx(label, FindRenderedTextEnd(label)); + EndGroup(); + PopID(); + + return value_changed; +} + +// NB: v_speed is float to allow adjusting the drag speed with more precision +bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format, flags); +} + +bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format, flags); +} + +bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format, flags); +} + +bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format, flags); +} + +// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this. +bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max, ImGuiSliderFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + PushID(label); + BeginGroup(); + PushMultiItemsWidths(2, CalcItemWidth()); + + int min_min = (v_min >= v_max) ? INT_MIN : v_min; + int min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max); + ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0); + bool value_changed = DragInt("##min", v_current_min, v_speed, min_min, min_max, format, min_flags); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + int max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min); + int max_max = (v_min >= v_max) ? INT_MAX : v_max; + ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0); + value_changed |= DragInt("##max", v_current_max, v_speed, max_min, max_max, format_max ? format_max : format, max_flags); + PopItemWidth(); + SameLine(0, g.Style.ItemInnerSpacing.x); + + TextEx(label, FindRenderedTextEnd(label)); + EndGroup(); + PopID(); + + return value_changed; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc. +//------------------------------------------------------------------------- +// - ScaleRatioFromValueT<> [Internal] +// - ScaleValueFromRatioT<> [Internal] +// - SliderBehaviorT<>() [Internal] +// - SliderBehavior() [Internal] +// - SliderScalar() +// - SliderScalarN() +// - SliderFloat() +// - SliderFloat2() +// - SliderFloat3() +// - SliderFloat4() +// - SliderAngle() +// - SliderInt() +// - SliderInt2() +// - SliderInt3() +// - SliderInt4() +// - VSliderScalar() +// - VSliderFloat() +// - VSliderInt() +//------------------------------------------------------------------------- + +// Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT) +template +float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize) +{ + if (v_min == v_max) + return 0.0f; + IM_UNUSED(data_type); + + const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min); + if (is_logarithmic) + { + bool flipped = v_max < v_min; + + if (flipped) // Handle the case where the range is backwards + ImSwap(v_min, v_max); + + // Fudge min/max to avoid getting close to log(0) + FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min; + FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max; + + // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) + if ((v_min == 0.0f) && (v_max < 0.0f)) + v_min_fudged = -logarithmic_zero_epsilon; + else if ((v_max == 0.0f) && (v_min < 0.0f)) + v_max_fudged = -logarithmic_zero_epsilon; + + float result; + if (v_clamped <= v_min_fudged) + result = 0.0f; // Workaround for values that are in-range but below our fudge + else if (v_clamped >= v_max_fudged) + result = 1.0f; // Workaround for values that are in-range but above our fudge + else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions + { + float zero_point_center = (-(float)v_min) / ((float)v_max - (float)v_min); // The zero point in parametric space. There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine) + float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize; + float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize; + if (v == 0.0f) + result = zero_point_center; // Special case for exactly zero + else if (v < 0.0f) + result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) * zero_point_snap_L; + else + result = zero_point_snap_R + ((float)(ImLog((FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(v_max_fudged / logarithmic_zero_epsilon)) * (1.0f - zero_point_snap_R)); + } + else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider + result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged)); + else + result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged)); + + return flipped ? (1.0f - result) : result; + } + else + { + // Linear slider + return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min)); + } +} + +// Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT) +template +TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize) +{ + // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct" + // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler. + if (t <= 0.0f || v_min == v_max) + return v_min; + if (t >= 1.0f) + return v_max; + + TYPE result = (TYPE)0; + if (is_logarithmic) + { + // Fudge min/max to avoid getting silly results close to zero + FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min; + FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max; + + const bool flipped = v_max < v_min; // Check if range is "backwards" + if (flipped) + ImSwap(v_min_fudged, v_max_fudged); + + // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) + if ((v_max == 0.0f) && (v_min < 0.0f)) + v_max_fudged = -logarithmic_zero_epsilon; + + float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range + + if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts + { + float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space + float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize; + float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize; + if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R) + result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise) + else if (t_with_flip < zero_point_center) + result = (TYPE)-(logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L)))); + else + result = (TYPE)(logarithmic_zero_epsilon * ImPow(v_max_fudged / logarithmic_zero_epsilon, (FLOATTYPE)((t_with_flip - zero_point_snap_R) / (1.0f - zero_point_snap_R)))); + } + else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider + result = (TYPE)-(-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip))); + else + result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip)); + } + else + { + // Linear slider + const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); + if (is_floating_point) + { + result = ImLerp(v_min, v_max, t); + } + else if (t < 1.0) + { + // - For integer values we want the clicking position to match the grab box so we round above + // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. + // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64 + // range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits. + FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t; + result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5))); + } + } + + return result; +} + +// FIXME: Try to move more of the code into shared SliderBehavior() +template +bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb) +{ + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X; + const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0; + const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); + const float v_range_f = (float)(v_min < v_max ? v_max - v_min : v_min - v_max); // We don't need high precision for what we do with it. + + // Calculate bounds + const float grab_padding = 2.0f; // FIXME: Should be part of style. + const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f; + float grab_sz = style.GrabMinSize; + if (!is_floating_point && v_range_f >= 0.0f) // v_range_f < 0 may happen on integer overflows + grab_sz = ImMax(slider_sz / (v_range_f + 1), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit + grab_sz = ImMin(grab_sz, slider_sz); + const float slider_usable_sz = slider_sz - grab_sz; + const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f; + const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f; + + float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true + float zero_deadzone_halfsize = 0.0f; // Only valid when is_logarithmic is true + if (is_logarithmic) + { + // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. + const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1; + logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision); + zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5f) / ImMax(slider_usable_sz, 1.0f); + } + + // Process interacting with the slider + bool value_changed = false; + if (g.ActiveId == id) + { + bool set_new_value = false; + float clicked_t = 0.0f; + if (g.ActiveIdSource == ImGuiInputSource_Mouse) + { + if (!g.IO.MouseDown[0]) + { + ClearActiveID(); + } + else + { + const float mouse_abs_pos = g.IO.MousePos[axis]; + if (g.ActiveIdIsJustActivated) + { + float grab_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + if (axis == ImGuiAxis_Y) + grab_t = 1.0f - grab_t; + const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); + const bool clicked_around_grab = (mouse_abs_pos >= grab_pos - grab_sz * 0.5f - 1.0f) && (mouse_abs_pos <= grab_pos + grab_sz * 0.5f + 1.0f); // No harm being extra generous here. + g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f; + } + if (slider_usable_sz > 0.0f) + clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz); + if (axis == ImGuiAxis_Y) + clicked_t = 1.0f - clicked_t; + set_new_value = true; + } + } + else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) + { + if (g.ActiveIdIsJustActivated) + { + g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation + g.SliderCurrentAccumDirty = false; + } + + float input_delta = (axis == ImGuiAxis_X) ? GetNavTweakPressedAmount(axis) : -GetNavTweakPressedAmount(axis); + if (input_delta != 0.0f) + { + const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow); + const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast); + const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0; + if (decimal_precision > 0) + { + input_delta /= 100.0f; // Keyboard/Gamepad tweak speeds in % of slider bounds + if (tweak_slow) + input_delta /= 10.0f; + } + else + { + if ((v_range_f >= -100.0f && v_range_f <= 100.0f && v_range_f != 0.0f) || tweak_slow) + input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / v_range_f; // Keyboard/Gamepad tweak speeds in integer steps + else + input_delta /= 100.0f; + } + if (tweak_fast) + input_delta *= 10.0f; + + g.SliderCurrentAccum += input_delta; + g.SliderCurrentAccumDirty = true; + } + + float delta = g.SliderCurrentAccum; + if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) + { + ClearActiveID(); + } + else if (g.SliderCurrentAccumDirty) + { + clicked_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + + if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits + { + set_new_value = false; + g.SliderCurrentAccum = 0.0f; // If pushing up against the limits, don't continue to accumulate + } + else + { + set_new_value = true; + float old_clicked_t = clicked_t; + clicked_t = ImSaturate(clicked_t + delta); + + // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator + TYPE v_new = ScaleValueFromRatioT(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat)) + v_new = RoundScalarWithFormatT(format, data_type, v_new); + float new_clicked_t = ScaleRatioFromValueT(data_type, v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + + if (delta > 0) + g.SliderCurrentAccum -= ImMin(new_clicked_t - old_clicked_t, delta); + else + g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta); + } + + g.SliderCurrentAccumDirty = false; + } + } + + if (set_new_value) + if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) + set_new_value = false; + + if (set_new_value) + { + TYPE v_new = ScaleValueFromRatioT(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + + // Round to user desired precision based on format string + if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat)) + v_new = RoundScalarWithFormatT(format, data_type, v_new); + + // Apply result + if (*v != v_new) + { + *v = v_new; + value_changed = true; + } + } + } + + if (slider_sz < 1.0f) + { + *out_grab_bb = ImRect(bb.Min, bb.Min); + } + else + { + // Output grab position so it can be displayed by the caller + float grab_t = ScaleRatioFromValueT(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize); + if (axis == ImGuiAxis_Y) + grab_t = 1.0f - grab_t; + const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); + if (axis == ImGuiAxis_X) + *out_grab_bb = ImRect(grab_pos - grab_sz * 0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz * 0.5f, bb.Max.y - grab_padding); + else + *out_grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz * 0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz * 0.5f); + } + + return value_changed; +} + +// For 32-bit and larger types, slider bounds are limited to half the natural type range. +// So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok. +// It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders. +bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb) +{ + // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert. + IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead."); + IM_ASSERT((flags & ImGuiSliderFlags_WrapAround) == 0); // Not supported by SliderXXX(), only by DragXXX() + + switch (data_type) + { + case ImGuiDataType_S8: { ImS32 v32 = (ImS32)*(ImS8*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_S32, &v32, *(const ImS8*)p_min, *(const ImS8*)p_max, format, flags, out_grab_bb); if (r) *(ImS8*)p_v = (ImS8)v32; return r; } + case ImGuiDataType_U8: { ImU32 v32 = (ImU32)*(ImU8*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_U32, &v32, *(const ImU8*)p_min, *(const ImU8*)p_max, format, flags, out_grab_bb); if (r) *(ImU8*)p_v = (ImU8)v32; return r; } + case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_S32, &v32, *(const ImS16*)p_min, *(const ImS16*)p_max, format, flags, out_grab_bb); if (r) *(ImS16*)p_v = (ImS16)v32; return r; } + case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = SliderBehaviorT(bb, id, ImGuiDataType_U32, &v32, *(const ImU16*)p_min, *(const ImU16*)p_max, format, flags, out_grab_bb); if (r) *(ImU16*)p_v = (ImU16)v32; return r; } + case ImGuiDataType_S32: + IM_ASSERT(*(const ImS32*)p_min >= IM_S32_MIN / 2 && *(const ImS32*)p_max <= IM_S32_MAX / 2); + return SliderBehaviorT(bb, id, data_type, (ImS32*)p_v, *(const ImS32*)p_min, *(const ImS32*)p_max, format, flags, out_grab_bb); + case ImGuiDataType_U32: + IM_ASSERT(*(const ImU32*)p_max <= IM_U32_MAX / 2); + return SliderBehaviorT(bb, id, data_type, (ImU32*)p_v, *(const ImU32*)p_min, *(const ImU32*)p_max, format, flags, out_grab_bb); + case ImGuiDataType_S64: + IM_ASSERT(*(const ImS64*)p_min >= IM_S64_MIN / 2 && *(const ImS64*)p_max <= IM_S64_MAX / 2); + return SliderBehaviorT(bb, id, data_type, (ImS64*)p_v, *(const ImS64*)p_min, *(const ImS64*)p_max, format, flags, out_grab_bb); + case ImGuiDataType_U64: + IM_ASSERT(*(const ImU64*)p_max <= IM_U64_MAX / 2); + return SliderBehaviorT(bb, id, data_type, (ImU64*)p_v, *(const ImU64*)p_min, *(const ImU64*)p_max, format, flags, out_grab_bb); + case ImGuiDataType_Float: + IM_ASSERT(*(const float*)p_min >= -FLT_MAX / 2.0f && *(const float*)p_max <= FLT_MAX / 2.0f); + return SliderBehaviorT(bb, id, data_type, (float*)p_v, *(const float*)p_min, *(const float*)p_max, format, flags, out_grab_bb); + case ImGuiDataType_Double: + IM_ASSERT(*(const double*)p_min >= -DBL_MAX / 2.0f && *(const double*)p_max <= DBL_MAX / 2.0f); + return SliderBehaviorT(bb, id, data_type, (double*)p_v, *(const double*)p_min, *(const double*)p_max, format, flags, out_grab_bb); + case ImGuiDataType_COUNT: break; + } + IM_ASSERT(0); + return false; +} + +// Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a slider, they are all required. +// Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly. +bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + const float w = CalcItemWidth(); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0)) + return false; + + // Default format string when passing NULL + if (format == NULL) + format = DataTypeGetInfo(data_type)->PrintFmt; + + const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags); + bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); + if (!temp_input_is_active) + { + // Tabbing or CTRL-clicking on Slider turns it into an input box + const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id); + const bool make_active = (clicked || g.NavActivateId == id); + if (make_active && clicked) + SetKeyOwner(ImGuiKey_MouseLeft, id); + if (make_active && temp_input_allowed) + if ((clicked && g.IO.KeyCtrl) || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput))) + temp_input_is_active = true; + + // Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert) + if (make_active) + memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size); + + if (make_active && !temp_input_is_active) + { + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + } + } + + if (temp_input_is_active) + { + // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp) + const bool clamp_enabled = (flags & ImGuiSliderFlags_ClampOnInput) != 0; + return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL); + } + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavCursor(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); + + // Slider behavior + ImRect grab_bb; + const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags, &grab_bb); + if (value_changed) + MarkItemEdited(id); + + // Render grab + if (grab_bb.Max.x > grab_bb.Min.x) + window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + char value_buf[64]; + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); + if (g.LogEnabled) + LogSetNextTextDecoration("{", "}"); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0)); + return value_changed; +} + +// Add multiple sliders on 1 line for compact edition of multiple components +bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, ImGuiSliderFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components, CalcItemWidth()); + size_t type_size = GDataTypeInfo[data_type].Size; + for (int i = 0; i < components; i++) + { + PushID(i); + if (i > 0) + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags); + PopID(); + PopItemWidth(); + v = (void*)((char*)v + type_size); + } + PopID(); + + const char* label_end = FindRenderedTextEnd(label); + if (label != label_end) + { + SameLine(0, g.Style.ItemInnerSpacing.x); + TextEx(label, label_end); + } + + EndGroup(); + return value_changed; +} + +bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, flags); +} + +bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, flags); +} + +bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, flags); +} + +bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, flags); +} + +bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags) +{ + if (format == NULL) + format = "%.0f deg"; + float v_deg = (*v_rad) * 360.0f / (2 * IM_PI); + bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags); + if (value_changed) + *v_rad = v_deg * (2 * IM_PI) / 360.0f; + return value_changed; +} + +bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format, flags); +} + +bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format, flags); +} + +bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format, flags); +} + +bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format, flags); +} + +bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); + const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(frame_bb, id)) + return false; + + // Default format string when passing NULL + if (format == NULL) + format = DataTypeGetInfo(data_type)->PrintFmt; + + const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags); + const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id); + if (clicked || g.NavActivateId == id) + { + if (clicked) + SetKeyOwner(ImGuiKey_MouseLeft, id); + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + } + + // Draw frame + const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + RenderNavCursor(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); + + // Slider behavior + ImRect grab_bb; + const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags | ImGuiSliderFlags_Vertical, &grab_bb); + if (value_changed) + MarkItemEdited(id); + + // Render grab + if (grab_bb.Max.y > grab_bb.Min.y) + window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + + // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. + // For the vertical slider we allow centered text to overlap the frame padding + char value_buf[64]; + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format); + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.0f)); + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + return value_changed; +} + +bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) +{ + return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, flags); +} + +bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) +{ + return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc. +//------------------------------------------------------------------------- +// - ImParseFormatFindStart() [Internal] +// - ImParseFormatFindEnd() [Internal] +// - ImParseFormatTrimDecorations() [Internal] +// - ImParseFormatSanitizeForPrinting() [Internal] +// - ImParseFormatSanitizeForScanning() [Internal] +// - ImParseFormatPrecision() [Internal] +// - TempInputTextScalar() [Internal] +// - InputScalar() +// - InputScalarN() +// - InputFloat() +// - InputFloat2() +// - InputFloat3() +// - InputFloat4() +// - InputInt() +// - InputInt2() +// - InputInt3() +// - InputInt4() +// - InputDouble() +//------------------------------------------------------------------------- + +// We don't use strchr() because our strings are usually very short and often start with '%' +const char* ImParseFormatFindStart(const char* fmt) +{ + while (char c = fmt[0]) + { + if (c == '%' && fmt[1] != '%') + return fmt; + else if (c == '%') + fmt++; + fmt++; + } + return fmt; +} + +const char* ImParseFormatFindEnd(const char* fmt) +{ + // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format. + if (fmt[0] != '%') + return fmt; + const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A')); + const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a')); + for (char c; (c = *fmt) != 0; fmt++) + { + if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0) + return fmt + 1; + if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0) + return fmt + 1; + } + return fmt; +} + +// Extract the format out of a format string with leading or trailing decorations +// fmt = "blah blah" -> return "" +// fmt = "%.3f" -> return fmt +// fmt = "hello %.3f" -> return fmt + 6 +// fmt = "%.3f hello" -> return buf written with "%.3f" +const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size) +{ + const char* fmt_start = ImParseFormatFindStart(fmt); + if (fmt_start[0] != '%') + return ""; + const char* fmt_end = ImParseFormatFindEnd(fmt_start); + if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data. + return fmt_start; + ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size)); + return buf; +} + +// Sanitize format +// - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi +// - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi. +void ImParseFormatSanitizeForPrinting(const char* fmt_in, char* fmt_out, size_t fmt_out_size) +{ + const char* fmt_end = ImParseFormatFindEnd(fmt_in); + IM_UNUSED(fmt_out_size); + IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you! + while (fmt_in < fmt_end) + { + char c = *fmt_in++; + if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '. + *(fmt_out++) = c; + } + *fmt_out = 0; // Zero-terminate +} + +// - For scanning we need to remove all width and precision fields and flags "%+3.7f" -> "%f". BUT don't strip types like "%I64d" which includes digits. ! "%07I64d" -> "%I64d" +const char* ImParseFormatSanitizeForScanning(const char* fmt_in, char* fmt_out, size_t fmt_out_size) +{ + const char* fmt_end = ImParseFormatFindEnd(fmt_in); + const char* fmt_out_begin = fmt_out; + IM_UNUSED(fmt_out_size); + IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you! + bool has_type = false; + while (fmt_in < fmt_end) + { + char c = *fmt_in++; + if (!has_type && ((c >= '0' && c <= '9') || c == '.' || c == '+' || c == '#')) + continue; + has_type |= ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); // Stop skipping digits + if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '. + *(fmt_out++) = c; + } + *fmt_out = 0; // Zero-terminate + return fmt_out_begin; +} + +template +static const char* ImAtoi(const char* src, TYPE* output) +{ + int negative = 0; + if (*src == '-') { negative = 1; src++; } + if (*src == '+') { src++; } + TYPE v = 0; + while (*src >= '0' && *src <= '9') + v = (v * 10) + (*src++ - '0'); + *output = negative ? -v : v; + return src; +} + +// Parse display precision back from the display format string +// FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed. +int ImParseFormatPrecision(const char* fmt, int default_precision) +{ + fmt = ImParseFormatFindStart(fmt); + if (fmt[0] != '%') + return default_precision; + fmt++; + while (*fmt >= '0' && *fmt <= '9') + fmt++; + int precision = INT_MAX; + if (*fmt == '.') + { + fmt = ImAtoi(fmt + 1, &precision); + if (precision < 0 || precision > 99) + precision = default_precision; + } + if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation + precision = -1; + if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX) + precision = -1; + return (precision == INT_MAX) ? default_precision : precision; +} + +// Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets) +// FIXME: Facilitate using this in variety of other situations. +// FIXME: Among other things, setting ImGuiItemFlags_AllowDuplicateId in LastItemData is currently correct but +// the expected relationship between TempInputXXX functions and LastItemData is a little fishy. +bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags) +{ + // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id. + // We clear ActiveID on the first frame to allow the InputText() taking it back. + ImGuiContext& g = *GImGui; + const bool init = (g.TempInputId != id); + if (init) + ClearActiveID(); + + g.CurrentWindow->DC.CursorPos = bb.Min; + g.LastItemData.ItemFlags |= ImGuiItemFlags_AllowDuplicateId; + bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem); + if (init) + { + // First frame we started displaying the InputText widget, we expect it to take the active id. + IM_ASSERT(g.ActiveId == id); + g.TempInputId = g.ActiveId; + } + return value_changed; +} + +// Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set! +// This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility. +// However this may not be ideal for all uses, as some user code may break on out of bound values. +bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min, const void* p_clamp_max) +{ + // FIXME: May need to clarify display behavior if format doesn't contain %. + // "%d" -> "%d" / "There are %d items" -> "%d" / "items" -> "%d" (fallback). Also see #6405 + ImGuiContext& g = *GImGui; + const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type); + char fmt_buf[32]; + char data_buf[32]; + format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf)); + if (format[0] == 0) + format = type_info->PrintFmt; + DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format); + ImStrTrimBlanks(data_buf); + + ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; + g.LastItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; // Because TempInputText() uses ImGuiInputTextFlags_MergedItem it doesn't submit a new item, so we poke LastItemData. + bool value_changed = false; + if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags)) + { + // Backup old value + size_t data_type_size = type_info->Size; + ImGuiDataTypeStorage data_backup; + memcpy(&data_backup, p_data, data_type_size); + + // Apply new value (or operations) then clamp + DataTypeApplyFromText(data_buf, data_type, p_data, format, NULL); + if (p_clamp_min || p_clamp_max) + { + if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0) + ImSwap(p_clamp_min, p_clamp_max); + DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max); + } + + // Only mark as edited if new value is different + g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; + value_changed = memcmp(&data_backup, p_data, data_type_size) != 0; + if (value_changed) + MarkItemEdited(id); + } + return value_changed; +} + +void ImGui::SetNextItemRefVal(ImGuiDataType data_type, void* p_data) +{ + ImGuiContext& g = *GImGui; + g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasRefVal; + memcpy(&g.NextItemData.RefVal, p_data, DataTypeGetInfo(data_type)->Size); +} + +// Note: p_data, p_step, p_step_fast are _pointers_ to a memory address holding the data. For an Input widget, p_step and p_step_fast are optional. +// Read code of e.g. InputFloat(), InputInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly. +bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + IM_ASSERT((flags & ImGuiInputTextFlags_EnterReturnsTrue) == 0); // Not supported by InputScalar(). Please open an issue if you this would be useful to you. Otherwise use IsItemDeactivatedAfterEdit()! + + if (format == NULL) + format = DataTypeGetInfo(data_type)->PrintFmt; + + void* p_data_default = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasRefVal) ? &g.NextItemData.RefVal : &g.DataTypeZeroValue; + + char buf[64]; + if ((flags & ImGuiInputTextFlags_DisplayEmptyRefVal) && DataTypeCompare(data_type, p_data, p_data_default) == 0) + buf[0] = 0; + else + DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format); + + // Disable the MarkItemEdited() call in InputText but keep ImGuiItemStatusFlags_Edited. + // We call MarkItemEdited() ourselves by comparing the actual data rather than the string. + g.NextItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; + flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; + + bool value_changed = false; + if (p_step == NULL) + { + if (InputText(label, buf, IM_ARRAYSIZE(buf), flags)) + value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); + } + else + { + const float button_size = GetFrameHeight(); + + BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive() + PushID(label); + SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2)); + if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view + value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); + IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable); + + // Step buttons + const ImVec2 backup_frame_padding = style.FramePadding; + style.FramePadding.x = style.FramePadding.y; + if (flags & ImGuiInputTextFlags_ReadOnly) + BeginDisabled(); + PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("-", ImVec2(button_size, button_size))) + { + DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); + value_changed = true; + } + SameLine(0, style.ItemInnerSpacing.x); + if (ButtonEx("+", ImVec2(button_size, button_size))) + { + DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); + value_changed = true; + } + PopItemFlag(); + if (flags & ImGuiInputTextFlags_ReadOnly) + EndDisabled(); + + const char* label_end = FindRenderedTextEnd(label); + if (label != label_end) + { + SameLine(0, style.ItemInnerSpacing.x); + TextEx(label, label_end); + } + style.FramePadding = backup_frame_padding; + + PopID(); + EndGroup(); + } + + g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; + if (value_changed) + MarkItemEdited(g.LastItemData.ID); + + return value_changed; +} + +bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + bool value_changed = false; + BeginGroup(); + PushID(label); + PushMultiItemsWidths(components, CalcItemWidth()); + size_t type_size = GDataTypeInfo[data_type].Size; + for (int i = 0; i < components; i++) + { + PushID(i); + if (i > 0) + SameLine(0, g.Style.ItemInnerSpacing.x); + value_changed |= InputScalar("", data_type, p_data, p_step, p_step_fast, format, flags); + PopID(); + PopItemWidth(); + p_data = (void*)((char*)p_data + type_size); + } + PopID(); + + const char* label_end = FindRenderedTextEnd(label); + if (label != label_end) + { + SameLine(0.0f, g.Style.ItemInnerSpacing.x); + TextEx(label, label_end); + } + + EndGroup(); + return value_changed; +} + +bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags) +{ + return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), format, flags); +} + +bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags) +{ + return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags); +} + +bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags) +{ + return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags); +} + +bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags) +{ + return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags); +} + +bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags) +{ + // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes. + const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d"; + return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step > 0 ? &step : NULL), (void*)(step_fast > 0 ? &step_fast : NULL), format, flags); +} + +bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags) +{ + return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags); +} + +bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags) +{ + return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags); +} + +bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags) +{ + return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags); +} + +bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags) +{ + return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step > 0.0 ? &step : NULL), (void*)(step_fast > 0.0 ? &step_fast : NULL), format, flags); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: InputText, InputTextMultiline, InputTextWithHint +//------------------------------------------------------------------------- +// - imstb_textedit.h include +// - InputText() +// - InputTextWithHint() +// - InputTextMultiline() +// - InputTextGetCharInfo() [Internal] +// - InputTextReindexLines() [Internal] +// - InputTextReindexLinesRange() [Internal] +// - InputTextEx() [Internal] +// - DebugNodeInputTextState() [Internal] +//------------------------------------------------------------------------- + +namespace ImStb +{ +#include "imstb_textedit.h" +} + +bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) +{ + IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() + return InputTextEx(label, NULL, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data); +} + +bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) +{ + return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data); +} + +bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) +{ + IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() or InputTextEx() manually if you need multi-line + hint. + return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data); +} + +// This is only used in the path where the multiline widget is inactivate. +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) +{ + int line_count = 0; + const char* s = text_begin; + while (true) + { + const char* s_eol = strchr(s, '\n'); + line_count++; + if (s_eol == NULL) + { + s = s + strlen(s); + break; + } + s = s_eol + 1; + } + *out_text_end = s; + return line_count; +} + +// FIXME: Ideally we'd share code with ImFont::CalcTextSizeA() +static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining, ImVec2* out_offset, bool stop_on_new_line) +{ + ImGuiContext& g = *ctx; + ImFont* font = g.Font; + const float line_height = g.FontSize; + const float scale = line_height / font->FontSize; + + ImVec2 text_size = ImVec2(0, 0); + float line_width = 0.0f; + + const char* s = text_begin; + while (s < text_end) + { + unsigned int c = (unsigned int)*s; + if (c < 0x80) + s += 1; + else + s += ImTextCharFromUtf8(&c, s, text_end); + + if (c == '\n') + { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + if (stop_on_new_line) + break; + continue; + } + if (c == '\r') + continue; + + const float char_width = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX) * scale; + line_width += char_width; + } + + if (text_size.x < line_width) + text_size.x = line_width; + + if (out_offset) + *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n + + if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n + text_size.y += line_height; + + if (remaining) + *remaining = s; + + return text_size; +} + +// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) +// With our UTF-8 use of stb_textedit: +// - STB_TEXTEDIT_GETCHAR is nothing more than a a "GETBYTE". It's only used to compare to ascii or to copy blocks of text so we are fine. +// - One exception is the STB_TEXTEDIT_IS_SPACE feature which would expect a full char in order to handle full-width space such as 0x3000 (see ImCharIsBlankW). +// - ...but we don't use that feature. +namespace ImStb +{ +static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->TextLen; } +static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->TextLen); return obj->TextSrc[idx]; } +static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; } +static char STB_TEXTEDIT_NEWLINE = '\n'; +static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx) +{ + const char* text = obj->TextSrc; + const char* text_remaining = NULL; + const ImVec2 size = InputTextCalcTextSize(obj->Ctx, text + line_start_idx, text + obj->TextLen, &text_remaining, NULL, true); + r->x0 = 0.0f; + r->x1 = size.x; + r->baseline_y_delta = size.y; + r->ymin = 0.0f; + r->ymax = size.y; + r->num_chars = (int)(text_remaining - (text + line_start_idx)); +} + +#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL +#define IMSTB_TEXTEDIT_GETPREVCHARINDEX IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL + +static int IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx) +{ + if (idx >= obj->TextLen) + return obj->TextLen + 1; + unsigned int c; + return idx + ImTextCharFromUtf8(&c, obj->TextSrc + idx, obj->TextSrc + obj->TextLen); +} + +static int IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx) +{ + if (idx <= 0) + return -1; + const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, obj->TextSrc + idx); + return (int)(p - obj->TextSrc); +} + +static bool ImCharIsSeparatorW(unsigned int c) +{ + static const unsigned int separator_list[] = + { + ',', 0x3001, '.', 0x3002, ';', 0xFF1B, '(', 0xFF08, ')', 0xFF09, '{', 0xFF5B, '}', 0xFF5D, + '[', 0x300C, ']', 0x300D, '|', 0xFF5C, '!', 0xFF01, '\\', 0xFFE5, '/', 0x30FB, 0xFF0F, + '\n', '\r', + }; + for (unsigned int separator : separator_list) + if (c == separator) + return true; + return false; +} + +static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx) +{ + // When ImGuiInputTextFlags_Password is set, we don't want actions such as CTRL+Arrow to leak the fact that underlying data are blanks or separators. + if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0) + return 0; + + const char* curr_p = obj->TextSrc + idx; + const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p); + unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextSrc + obj->TextLen); + unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextSrc + obj->TextLen); + + bool prev_white = ImCharIsBlankW(prev_c); + bool prev_separ = ImCharIsSeparatorW(prev_c); + bool curr_white = ImCharIsBlankW(curr_c); + bool curr_separ = ImCharIsSeparatorW(curr_c); + return ((prev_white || prev_separ) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ); +} +static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) +{ + if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0) + return 0; + + const char* curr_p = obj->TextSrc + idx; + const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p); + unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextSrc + obj->TextLen); + unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextSrc + obj->TextLen); + + bool prev_white = ImCharIsBlankW(prev_c); + bool prev_separ = ImCharIsSeparatorW(prev_c); + bool curr_white = ImCharIsBlankW(curr_c); + bool curr_separ = ImCharIsSeparatorW(curr_c); + return ((prev_white) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ); +} +static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx) +{ + idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx); + while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) + idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx); + return idx < 0 ? 0 : idx; +} +static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx) +{ + int len = obj->TextLen; + idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); + while (idx < len && !is_word_boundary_from_left(obj, idx)) + idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); + return idx > len ? len : idx; +} +static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx) +{ + idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); + int len = obj->TextLen; + while (idx < len && !is_word_boundary_from_right(obj, idx)) + idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); + return idx > len ? len : idx; +} +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { ImGuiContext& g = *obj->Ctx; if (g.IO.ConfigMacOSXBehaviors) return STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx); else return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx); } +#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h +#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL + +static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n) +{ + // Offset remaining text (+ copy zero terminator) + IM_ASSERT(obj->TextSrc == obj->TextA.Data); + char* dst = obj->TextA.Data + pos; + char* src = obj->TextA.Data + pos + n; + memmove(dst, src, obj->TextLen - n - pos + 1); + obj->Edited = true; + obj->TextLen -= n; +} + +static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const char* new_text, int new_text_len) +{ + const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0; + const int text_len = obj->TextLen; + IM_ASSERT(pos <= text_len); + + if (!is_resizable && (new_text_len + obj->TextLen + 1 > obj->BufCapacity)) + return false; + + // Grow internal buffer if needed + IM_ASSERT(obj->TextSrc == obj->TextA.Data); + if (new_text_len + text_len + 1 > obj->TextA.Size) + { + if (!is_resizable) + return false; + obj->TextA.resize(text_len + ImClamp(new_text_len, 32, ImMax(256, new_text_len)) + 1); + obj->TextSrc = obj->TextA.Data; + } + + char* text = obj->TextA.Data; + if (pos != text_len) + memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos)); + memcpy(text + pos, new_text, (size_t)new_text_len); + + obj->Edited = true; + obj->TextLen += new_text_len; + obj->TextA[obj->TextLen] = '\0'; + + return true; +} + +// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols) +#define STB_TEXTEDIT_K_LEFT 0x200000 // keyboard input to move cursor left +#define STB_TEXTEDIT_K_RIGHT 0x200001 // keyboard input to move cursor right +#define STB_TEXTEDIT_K_UP 0x200002 // keyboard input to move cursor up +#define STB_TEXTEDIT_K_DOWN 0x200003 // keyboard input to move cursor down +#define STB_TEXTEDIT_K_LINESTART 0x200004 // keyboard input to move cursor to start of line +#define STB_TEXTEDIT_K_LINEEND 0x200005 // keyboard input to move cursor to end of line +#define STB_TEXTEDIT_K_TEXTSTART 0x200006 // keyboard input to move cursor to start of text +#define STB_TEXTEDIT_K_TEXTEND 0x200007 // keyboard input to move cursor to end of text +#define STB_TEXTEDIT_K_DELETE 0x200008 // keyboard input to delete selection or character under cursor +#define STB_TEXTEDIT_K_BACKSPACE 0x200009 // keyboard input to delete selection or character left of cursor +#define STB_TEXTEDIT_K_UNDO 0x20000A // keyboard input to perform undo +#define STB_TEXTEDIT_K_REDO 0x20000B // keyboard input to perform redo +#define STB_TEXTEDIT_K_WORDLEFT 0x20000C // keyboard input to move cursor left one word +#define STB_TEXTEDIT_K_WORDRIGHT 0x20000D // keyboard input to move cursor right one word +#define STB_TEXTEDIT_K_PGUP 0x20000E // keyboard input to move cursor up a page +#define STB_TEXTEDIT_K_PGDOWN 0x20000F // keyboard input to move cursor down a page +#define STB_TEXTEDIT_K_SHIFT 0x400000 + +#define IMSTB_TEXTEDIT_IMPLEMENTATION +#define IMSTB_TEXTEDIT_memmove memmove +#include "imstb_textedit.h" + +// stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling +// the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?) +static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len) +{ + stb_text_makeundo_replace(str, state, 0, str->TextLen, text_len); + ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->TextLen); + state->cursor = state->select_start = state->select_end = 0; + if (text_len <= 0) + return; + if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len)) + { + state->cursor = state->select_start = state->select_end = text_len; + state->has_preferred_x = 0; + return; + } + IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace() +} + +} // namespace ImStb + +// We added an extra indirection where 'Stb' is heap-allocated, in order facilitate the work of bindings generators. +ImGuiInputTextState::ImGuiInputTextState() +{ + memset(this, 0, sizeof(*this)); + Stb = IM_NEW(ImStbTexteditState); + memset(Stb, 0, sizeof(*Stb)); +} + +ImGuiInputTextState::~ImGuiInputTextState() +{ + IM_DELETE(Stb); +} + +void ImGuiInputTextState::OnKeyPressed(int key) +{ + stb_textedit_key(this, Stb, key); + CursorFollow = true; + CursorAnimReset(); +} + +void ImGuiInputTextState::OnCharPressed(unsigned int c) +{ + // Convert the key to a UTF8 byte sequence. + // The changes we had to make to stb_textedit_key made it very much UTF-8 specific which is not too great. + char utf8[5]; + ImTextCharToUtf8(utf8, c); + stb_textedit_text(this, Stb, utf8, (int)strlen(utf8)); + CursorFollow = true; + CursorAnimReset(); +} + +// Those functions are not inlined in imgui_internal.h, allowing us to hide ImStbTexteditState from that header. +void ImGuiInputTextState::CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking +void ImGuiInputTextState::CursorClamp() { Stb->cursor = ImMin(Stb->cursor, TextLen); Stb->select_start = ImMin(Stb->select_start, TextLen); Stb->select_end = ImMin(Stb->select_end, TextLen); } +bool ImGuiInputTextState::HasSelection() const { return Stb->select_start != Stb->select_end; } +void ImGuiInputTextState::ClearSelection() { Stb->select_start = Stb->select_end = Stb->cursor; } +int ImGuiInputTextState::GetCursorPos() const { return Stb->cursor; } +int ImGuiInputTextState::GetSelectionStart() const { return Stb->select_start; } +int ImGuiInputTextState::GetSelectionEnd() const { return Stb->select_end; } +void ImGuiInputTextState::SelectAll() { Stb->select_start = 0; Stb->cursor = Stb->select_end = TextLen; Stb->has_preferred_x = 0; } +void ImGuiInputTextState::ReloadUserBufAndSelectAll() { WantReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; } +void ImGuiInputTextState::ReloadUserBufAndKeepSelection() { WantReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; } +void ImGuiInputTextState::ReloadUserBufAndMoveToEnd() { WantReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; } + +ImGuiInputTextCallbackData::ImGuiInputTextCallbackData() +{ + memset(this, 0, sizeof(*this)); +} + +// Public API to manipulate UTF-8 text from within a callback. +// FIXME: The existence of this rarely exercised code path is a bit of a nuisance. +// Historically they existed because STB_TEXTEDIT_INSERTCHARS() etc. worked on our ImWchar +// buffer, but nowadays they both work on UTF-8 data. Should aim to merge both. +void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count) +{ + IM_ASSERT(pos + bytes_count <= BufTextLen); + char* dst = Buf + pos; + const char* src = Buf + pos + bytes_count; + memmove(dst, src, BufTextLen - bytes_count - pos + 1); + + if (CursorPos >= pos + bytes_count) + CursorPos -= bytes_count; + else if (CursorPos >= pos) + CursorPos = pos; + SelectionStart = SelectionEnd = CursorPos; + BufDirty = true; + BufTextLen -= bytes_count; +} + +void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end) +{ + // Accept null ranges + if (new_text == new_text_end) + return; + + // Grow internal buffer if needed + const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0; + const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text); + if (new_text_len + BufTextLen >= BufSize) + { + if (!is_resizable) + return; + + ImGuiContext& g = *Ctx; + ImGuiInputTextState* edit_state = &g.InputTextState; + IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID); + IM_ASSERT(Buf == edit_state->TextA.Data); + int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; + edit_state->TextA.resize(new_buf_size + 1); + edit_state->TextSrc = edit_state->TextA.Data; + Buf = edit_state->TextA.Data; + BufSize = edit_state->BufCapacity = new_buf_size; + } + + if (BufTextLen != pos) + memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos)); + memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char)); + Buf[BufTextLen + new_text_len] = '\0'; + + if (CursorPos >= pos) + CursorPos += new_text_len; + SelectionStart = SelectionEnd = CursorPos; + BufDirty = true; + BufTextLen += new_text_len; +} + +// Return false to discard a character. +static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard) +{ + unsigned int c = *p_char; + + // Filter non-printable (NB: isprint is unreliable! see #2467) + bool apply_named_filters = true; + if (c < 0x20) + { + bool pass = false; + pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0; // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code) + pass |= (c == '\t') && (flags & ImGuiInputTextFlags_AllowTabInput) != 0; + if (!pass) + return false; + apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted. + } + + if (input_source_is_clipboard == false) + { + // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817) + if (c == 127) + return false; + + // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME) + if (c >= 0xE000 && c <= 0xF8FF) + return false; + } + + // Filter Unicode ranges we are not handling in this build + if (c > IM_UNICODE_CODEPOINT_MAX) + return false; + + // Generic named filters + if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint))) + { + // The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf to use e.g. ',' instead of '.'. + // The standard mandate that programs starts in the "C" locale where the decimal point is '.'. + // We don't really intend to provide widespread support for it, but out of empathy for people stuck with using odd API, we support the bare minimum aka overriding the decimal point. + // Change the default decimal_point with: + // ImGui::GetPlatformIO()->Platform_LocaleDecimalPoint = *localeconv()->decimal_point; + // Users of non-default decimal point (in particular ',') may be affected by word-selection logic (is_word_boundary_from_right/is_word_boundary_from_left) functions. + ImGuiContext& g = *ctx; + const unsigned c_decimal_point = (unsigned int)g.PlatformIO.Platform_LocaleDecimalPoint; + if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint)) + if (c == '.' || c == ',') + c = c_decimal_point; + + // Full-width -> half-width conversion for numeric fields (https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block) + // While this is mostly convenient, this has the side-effect for uninformed users accidentally inputting full-width characters that they may + // scratch their head as to why it works in numerical fields vs in generic text fields it would require support in the font. + if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | ImGuiInputTextFlags_CharsHexadecimal)) + if (c >= 0xFF01 && c <= 0xFF5E) + c = c - 0xFF01 + 0x21; + + // Allow 0-9 . - + * / + if (flags & ImGuiInputTextFlags_CharsDecimal) + if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/')) + return false; + + // Allow 0-9 . - + * / e E + if (flags & ImGuiInputTextFlags_CharsScientific) + if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E')) + return false; + + // Allow 0-9 a-F A-F + if (flags & ImGuiInputTextFlags_CharsHexadecimal) + if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F')) + return false; + + // Turn a-z into A-Z + if (flags & ImGuiInputTextFlags_CharsUppercase) + if (c >= 'a' && c <= 'z') + c += (unsigned int)('A' - 'a'); + + if (flags & ImGuiInputTextFlags_CharsNoBlank) + if (ImCharIsBlankW(c)) + return false; + + *p_char = c; + } + + // Custom callback filter + if (flags & ImGuiInputTextFlags_CallbackCharFilter) + { + ImGuiContext& g = *GImGui; + ImGuiInputTextCallbackData callback_data; + callback_data.Ctx = &g; + callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; + callback_data.EventChar = (ImWchar)c; + callback_data.Flags = flags; + callback_data.UserData = user_data; + if (callback(&callback_data) != 0) + return false; + *p_char = callback_data.EventChar; + if (!callback_data.EventChar) + return false; + } + + return true; +} + +// Find the shortest single replacement we can make to get from old_buf to new_buf +// Note that this doesn't directly alter state->TextA, state->TextLen. They are expected to be made valid separately. +// FIXME: Ideally we should transition toward (1) making InsertChars()/DeleteChars() update undo-stack (2) discourage (and keep reconcile) or obsolete (and remove reconcile) accessing buffer directly. +static void InputTextReconcileUndoState(ImGuiInputTextState* state, const char* old_buf, int old_length, const char* new_buf, int new_length) +{ + const int shorter_length = ImMin(old_length, new_length); + int first_diff; + for (first_diff = 0; first_diff < shorter_length; first_diff++) + if (old_buf[first_diff] != new_buf[first_diff]) + break; + if (first_diff == old_length && first_diff == new_length) + return; + + int old_last_diff = old_length - 1; + int new_last_diff = new_length - 1; + for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--) + if (old_buf[old_last_diff] != new_buf[new_last_diff]) + break; + + const int insert_len = new_last_diff - first_diff + 1; + const int delete_len = old_last_diff - first_diff + 1; + if (insert_len > 0 || delete_len > 0) + if (IMSTB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->Stb->undostate, first_diff, delete_len, insert_len)) + for (int i = 0; i < delete_len; i++) + p[i] = old_buf[first_diff + i]; +} + +// As InputText() retain textual data and we currently provide a path for user to not retain it (via local variables) +// we need some form of hook to reapply data back to user buffer on deactivation frame. (#4714) +// It would be more desirable that we discourage users from taking advantage of the "user not retaining data" trick, +// but that more likely be attractive when we do have _NoLiveEdit flag available. +void ImGui::InputTextDeactivateHook(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + ImGuiInputTextState* state = &g.InputTextState; + if (id == 0 || state->ID != id) + return; + g.InputTextDeactivatedState.ID = state->ID; + if (state->Flags & ImGuiInputTextFlags_ReadOnly) + { + g.InputTextDeactivatedState.TextA.resize(0); // In theory this data won't be used, but clear to be neat. + } + else + { + IM_ASSERT(state->TextA.Data != 0); + IM_ASSERT(state->TextA[state->TextLen] == 0); + g.InputTextDeactivatedState.TextA.resize(state->TextLen + 1); + memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->TextLen + 1); + } +} + +// Edit a string of text +// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!". +// This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match +// Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator. +// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect. +// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h +// (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are +// doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188) +bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + IM_ASSERT(buf != NULL && buf_size >= 0); + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) + IM_ASSERT(!((flags & ImGuiInputTextFlags_ElideLeft) && (flags & ImGuiInputTextFlags_Multiline))); // Multiline will not work with left-trimming + + ImGuiContext& g = *GImGui; + ImGuiIO& io = g.IO; + const ImGuiStyle& style = g.Style; + + const bool RENDER_SELECTION_WHEN_INACTIVE = false; + const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; + + if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope (including the scrollbar) + BeginGroup(); + const ImGuiID id = window->GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? g.FontSize * 8.0f : label_size.y) + style.FramePadding.y * 2.0f); // Arbitrary default of 8 lines high for multi-line + const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y); + + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); + const ImRect total_bb(frame_bb.Min, frame_bb.Min + total_size); + + ImGuiWindow* draw_window = window; + ImVec2 inner_size = frame_size; + ImGuiLastItemData item_data_backup; + if (is_multiline) + { + ImVec2 backup_pos = window->DC.CursorPos; + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable)) + { + EndGroup(); + return false; + } + item_data_backup = g.LastItemData; + window->DC.CursorPos = backup_pos; + + // Prevent NavActivation from Tabbing when our widget accepts Tab inputs: this allows cycling through widgets without stopping. + if (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_FromTabbing) && (flags & ImGuiInputTextFlags_AllowTabInput)) + g.NavActivateId = 0; + + // Prevent NavActivate reactivating in BeginChild() when we are already active. + const ImGuiID backup_activate_id = g.NavActivateId; + if (g.ActiveId == id) // Prevent reactivation + g.NavActivateId = 0; + + // We reproduce the contents of BeginChildFrame() in order to provide 'label' so our window internal data are easier to read/debug. + PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]); + PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding); + PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize); + PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Ensure no clip rect so mouse hover can reach FramePadding edges + bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove); + g.NavActivateId = backup_activate_id; + PopStyleVar(3); + PopStyleColor(); + if (!child_visible) + { + EndChild(); + EndGroup(); + return false; + } + draw_window = g.CurrentWindow; // Child window + draw_window->DC.NavLayersActiveMaskNext |= (1 << draw_window->DC.NavLayerCurrent); // This is to ensure that EndChild() will display a navigation highlight so we can "enter" into it. + draw_window->DC.CursorPos += style.FramePadding; + inner_size.x -= draw_window->ScrollbarSizes.x; + } + else + { + // Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd) + ItemSize(total_bb, style.FramePadding.y); + if (!(flags & ImGuiInputTextFlags_MergedItem)) + if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable)) + return false; + } + + // Ensure mouse cursor is set even after switching to keyboard/gamepad mode. May generalize further? (#6417) + bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags | ImGuiItemFlags_NoNavDisableMouseHover); + if (hovered) + SetMouseCursor(ImGuiMouseCursor_TextInput); + if (hovered && g.NavHighlightItemUnderNav) + hovered = false; + + // We are only allowed to access the state if we are already the active widget. + ImGuiInputTextState* state = GetInputTextState(id); + + if (g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) + flags |= ImGuiInputTextFlags_ReadOnly; + const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0; + const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; + const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; + const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0; + if (is_resizable) + IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag! + + const bool input_requested_by_nav = (g.ActiveId != id) && ((g.NavActivateId == id) && ((g.NavActivateFlags & ImGuiActivateFlags_PreferInput) || (g.NavInputSource == ImGuiInputSource_Keyboard))); + + const bool user_clicked = hovered && io.MouseClicked[0]; + const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y); + const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y); + bool clear_active_id = false; + bool select_all = false; + + float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX; + + const bool init_reload_from_user_buf = (state != NULL && state->WantReloadUserBuf); + const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state. + const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav); + const bool init_state = (init_make_active || user_scroll_active); + if (init_reload_from_user_buf) + { + int new_len = (int)strlen(buf); + IM_ASSERT(new_len + 1 <= buf_size && "Is your input buffer properly zero-terminated?"); + state->WantReloadUserBuf = false; + InputTextReconcileUndoState(state, state->TextA.Data, state->TextLen, buf, new_len); + state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string. + state->TextLen = new_len; + memcpy(state->TextA.Data, buf, state->TextLen + 1); + state->Stb->select_start = state->ReloadSelectionStart; + state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd; + state->CursorClamp(); + } + else if ((init_state && g.ActiveId != id) || init_changed_specs) + { + // Access state even if we don't own it yet. + state = &g.InputTextState; + state->CursorAnimReset(); + + // Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714) + InputTextDeactivateHook(state->ID); + + // Take a copy of the initial buffer value. + // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode) + const int buf_len = (int)strlen(buf); + IM_ASSERT(buf_len + 1 <= buf_size && "Is your input buffer properly zero-terminated?"); + state->TextToRevertTo.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. + memcpy(state->TextToRevertTo.Data, buf, buf_len + 1); + + // Preserve cursor position and undo/redo stack if we come back to same widget + // FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate? + bool recycle_state = (state->ID == id && !init_changed_specs); + if (recycle_state && (state->TextLen != buf_len || (state->TextA.Data == NULL || strncmp(state->TextA.Data, buf, buf_len) != 0))) + recycle_state = false; + + // Start edition + state->ID = id; + state->TextLen = buf_len; + if (!is_readonly) + { + state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string. + memcpy(state->TextA.Data, buf, state->TextLen + 1); + } + + // Find initial scroll position for right alignment + state->Scroll = ImVec2(0.0f, 0.0f); + if (flags & ImGuiInputTextFlags_ElideLeft) + state->Scroll.x += ImMax(0.0f, CalcTextSize(buf).x - frame_size.x + style.FramePadding.x * 2.0f); + + // Recycle existing cursor/selection/undo stack but clamp position + // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. + if (recycle_state) + state->CursorClamp(); + else + stb_textedit_initialize_state(state->Stb, !is_multiline); + + if (!is_multiline) + { + if (flags & ImGuiInputTextFlags_AutoSelectAll) + select_all = true; + if (input_requested_by_nav && (!recycle_state || !(g.NavActivateFlags & ImGuiActivateFlags_TryToPreserveState))) + select_all = true; + if (user_clicked && io.KeyCtrl) + select_all = true; + } + + if (flags & ImGuiInputTextFlags_AlwaysOverwrite) + state->Stb->insert_mode = 1; // stb field name is indeed incorrect (see #2863) + } + + const bool is_osx = io.ConfigMacOSXBehaviors; + if (g.ActiveId != id && init_make_active) + { + IM_ASSERT(state && state->ID == id); + SetActiveID(id, window); + SetFocusID(id, window); + FocusWindow(window); + } + if (g.ActiveId == id) + { + // Declare some inputs, the other are registered and polled via Shortcut() routing system. + // FIXME: The reason we don't use Shortcut() is we would need a routing flag to specify multiple mods, or to all mods combinaison into individual shortcuts. + const ImGuiKey always_owned_keys[] = { ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_Enter, ImGuiKey_KeypadEnter, ImGuiKey_Delete, ImGuiKey_Backspace, ImGuiKey_Home, ImGuiKey_End }; + for (ImGuiKey key : always_owned_keys) + SetKeyOwner(key, id); + if (user_clicked) + SetKeyOwner(ImGuiKey_MouseLeft, id); + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); + if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory)) + { + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + SetKeyOwner(ImGuiKey_UpArrow, id); + SetKeyOwner(ImGuiKey_DownArrow, id); + } + if (is_multiline) + { + SetKeyOwner(ImGuiKey_PageUp, id); + SetKeyOwner(ImGuiKey_PageDown, id); + } + // FIXME: May be a problem to always steal Alt on OSX, would ideally still allow an uninterrupted Alt down-up to toggle menu + if (is_osx) + SetKeyOwner(ImGuiMod_Alt, id); + + // Expose scroll in a manner that is agnostic to us using a child window + if (is_multiline && state != NULL) + state->Scroll.y = draw_window->Scroll.y; + + // Read-only mode always ever read from source buffer. Refresh TextLen when active. + if (is_readonly && state != NULL) + state->TextLen = (int)strlen(buf); + //if (is_readonly && state != NULL) + // state->TextA.clear(); // Uncomment to facilitate debugging, but we otherwise prefer to keep/amortize th allocation. + } + if (state != NULL) + state->TextSrc = is_readonly ? buf : state->TextA.Data; + + // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function) + if (g.ActiveId == id && state == NULL) + ClearActiveID(); + + // Release focus when we click outside + if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560 + clear_active_id = true; + + // Lock the decision of whether we are going to take the path displaying the cursor or selection + bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active); + bool render_selection = state && (state->HasSelection() || select_all) && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor); + bool value_changed = false; + bool validated = false; + + // Select the buffer to render. + const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state; + const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); + + // Password pushes a temporary font with only a fallback glyph + if (is_password && !is_displaying_hint) + { + const ImFontGlyph* glyph = g.Font->FindGlyph('*'); + ImFont* password_font = &g.InputTextPasswordFont; + password_font->FontSize = g.Font->FontSize; + password_font->Scale = g.Font->Scale; + password_font->Ascent = g.Font->Ascent; + password_font->Descent = g.Font->Descent; + password_font->ContainerAtlas = g.Font->ContainerAtlas; + password_font->FallbackGlyph = glyph; + password_font->FallbackAdvanceX = glyph->AdvanceX; + IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty()); + PushFont(password_font); + } + + // Process mouse inputs and character inputs + if (g.ActiveId == id) + { + IM_ASSERT(state != NULL); + state->Edited = false; + state->BufCapacity = buf_size; + state->Flags = flags; + + // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. + // Down the line we should have a cleaner library-wide concept of Selected vs Active. + g.ActiveIdAllowOverlap = !io.MouseDown[0]; + + // Edit in progress + const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->Scroll.x; + const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f)); + + if (select_all) + { + state->SelectAll(); + state->SelectedAllMouseLock = true; + } + else if (hovered && io.MouseClickedCount[0] >= 2 && !io.KeyShift) + { + stb_textedit_click(state, state->Stb, mouse_x, mouse_y); + const int multiclick_count = (io.MouseClickedCount[0] - 2); + if ((multiclick_count % 2) == 0) + { + // Double-click: Select word + // We always use the "Mac" word advance for double-click select vs CTRL+Right which use the platform dependent variant: + // FIXME: There are likely many ways to improve this behavior, but there's no "right" behavior (depends on use-case, software, OS) + const bool is_bol = (state->Stb->cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor - 1) == '\n'; + if (STB_TEXT_HAS_SELECTION(state->Stb) || !is_bol) + state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); + //state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); + if (!STB_TEXT_HAS_SELECTION(state->Stb)) + ImStb::stb_textedit_prep_selection_at_cursor(state->Stb); + state->Stb->cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb->cursor); + state->Stb->select_end = state->Stb->cursor; + ImStb::stb_textedit_clamp(state, state->Stb); + } + else + { + // Triple-click: Select line + const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor) == '\n'; + state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART); + state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT); + state->OnKeyPressed(STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT); + if (!is_eol && is_multiline) + { + ImSwap(state->Stb->select_start, state->Stb->select_end); + state->Stb->cursor = state->Stb->select_end; + } + state->CursorFollow = false; + } + state->CursorAnimReset(); + } + else if (io.MouseClicked[0] && !state->SelectedAllMouseLock) + { + if (hovered) + { + if (io.KeyShift) + stb_textedit_drag(state, state->Stb, mouse_x, mouse_y); + else + stb_textedit_click(state, state->Stb, mouse_x, mouse_y); + state->CursorAnimReset(); + } + } + else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) + { + stb_textedit_drag(state, state->Stb, mouse_x, mouse_y); + state->CursorAnimReset(); + state->CursorFollow = true; + } + if (state->SelectedAllMouseLock && !io.MouseDown[0]) + state->SelectedAllMouseLock = false; + + // We expect backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336) + // (For Tab and Enter: Win32/SFML/Allegro are sending both keys and chars, GLFW and SDL are only sending keys. For Space they all send all threes) + if ((flags & ImGuiInputTextFlags_AllowTabInput) && !is_readonly) + { + if (Shortcut(ImGuiKey_Tab, ImGuiInputFlags_Repeat, id)) + { + unsigned int c = '\t'; // Insert TAB + if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) + state->OnCharPressed(c); + } + // FIXME: Implement Shift+Tab + /* + if (Shortcut(ImGuiKey_Tab | ImGuiMod_Shift, ImGuiInputFlags_Repeat, id)) + { + } + */ + } + + // Process regular text input (before we check for Return because using some IME will effectively send a Return?) + // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters. + const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeyCtrl); + if (io.InputQueueCharacters.Size > 0) + { + if (!ignore_char_inputs && !is_readonly && !input_requested_by_nav) + for (int n = 0; n < io.InputQueueCharacters.Size; n++) + { + // Insert character if they pass filtering + unsigned int c = (unsigned int)io.InputQueueCharacters[n]; + if (c == '\t') // Skip Tab, see above. + continue; + if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) + state->OnCharPressed(c); + } + + // Consume characters + io.InputQueueCharacters.resize(0); + } + } + + // Process other shortcuts/key-presses + bool revert_edit = false; + if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) + { + IM_ASSERT(state != NULL); + + const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1); + state->Stb->row_count_per_page = row_count_per_page; + + const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); + const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl + const bool is_startend_key_down = is_osx && io.KeyCtrl && !io.KeySuper && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End + + // Using Shortcut() with ImGuiInputFlags_RouteFocused (default policy) to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: formet would be handled by InputText) + // Otherwise we could simply assume that we own the keys as we are active. + const ImGuiInputFlags f_repeat = ImGuiInputFlags_Repeat; + const bool is_cut = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_X, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Delete, f_repeat, id)) && !is_readonly && !is_password && (!is_multiline || state->HasSelection()); + const bool is_copy = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, 0, id) || Shortcut(ImGuiMod_Ctrl | ImGuiKey_Insert, 0, id)) && !is_password && (!is_multiline || state->HasSelection()); + const bool is_paste = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_V, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Insert, f_repeat, id)) && !is_readonly; + const bool is_undo = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable; + const bool is_redo = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Y, f_repeat, id) || (is_osx && Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Z, f_repeat, id))) && !is_readonly && is_undoable; + const bool is_select_all = Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, 0, id); + + // We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful. + const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; + const bool is_enter_pressed = IsKeyPressed(ImGuiKey_Enter, true) || IsKeyPressed(ImGuiKey_KeypadEnter, true); + const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false)); + const bool is_cancel = Shortcut(ImGuiKey_Escape, f_repeat, id) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, f_repeat, id)); + + // FIXME: Should use more Shortcut() and reduce IsKeyPressed()+SetKeyOwner(), but requires modifiers combination to be taken account of. + // FIXME-OSX: Missing support for Alt(option)+Right/Left = go to end of line, or next line if already in end of line. + if (IsKeyPressed(ImGuiKey_LeftArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } + else if (IsKeyPressed(ImGuiKey_RightArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } + else if (IsKeyPressed(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } + else if (IsKeyPressed(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); } + else if (IsKeyPressed(ImGuiKey_PageUp) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); scroll_y -= row_count_per_page * g.FontSize; } + else if (IsKeyPressed(ImGuiKey_PageDown) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; } + else if (IsKeyPressed(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } + else if (IsKeyPressed(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } + else if (IsKeyPressed(ImGuiKey_Delete) && !is_readonly && !is_cut) + { + if (!state->HasSelection()) + { + // OSX doesn't seem to have Super+Delete to delete until end-of-line, so we don't emulate that (as opposed to Super+Backspace) + if (is_wordmove_key_down) + state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); + } + state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); + } + else if (IsKeyPressed(ImGuiKey_Backspace) && !is_readonly) + { + if (!state->HasSelection()) + { + if (is_wordmove_key_down) + state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT); + else if (is_osx && io.KeyCtrl && !io.KeyAlt && !io.KeySuper) + state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT); + } + state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); + } + else if (is_enter_pressed || is_gamepad_validate) + { + // Determine if we turn Enter into a \n character + bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; + if (!is_multiline || is_gamepad_validate || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl)) + { + validated = true; + if (io.ConfigInputTextEnterKeepActive && !is_multiline) + state->SelectAll(); // No need to scroll + else + clear_active_id = true; + } + else if (!is_readonly) + { + unsigned int c = '\n'; // Insert new line + if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) + state->OnCharPressed(c); + } + } + else if (is_cancel) + { + if (flags & ImGuiInputTextFlags_EscapeClearsAll) + { + if (buf[0] != 0) + { + revert_edit = true; + } + else + { + render_cursor = render_selection = false; + clear_active_id = true; + } + } + else + { + clear_active_id = revert_edit = true; + render_cursor = render_selection = false; + } + } + else if (is_undo || is_redo) + { + state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO); + state->ClearSelection(); + } + else if (is_select_all) + { + state->SelectAll(); + state->CursorFollow = true; + } + else if (is_cut || is_copy) + { + // Cut, Copy + if (g.PlatformIO.Platform_SetClipboardTextFn != NULL) + { + // SetClipboardText() only takes null terminated strings + state->TextSrc may point to read-only user buffer, so we need to make a copy. + const int ib = state->HasSelection() ? ImMin(state->Stb->select_start, state->Stb->select_end) : 0; + const int ie = state->HasSelection() ? ImMax(state->Stb->select_start, state->Stb->select_end) : state->TextLen; + g.TempBuffer.reserve(ie - ib + 1); + memcpy(g.TempBuffer.Data, state->TextSrc + ib, ie - ib); + g.TempBuffer.Data[ie - ib] = 0; + SetClipboardText(g.TempBuffer.Data); + } + if (is_cut) + { + if (!state->HasSelection()) + state->SelectAll(); + state->CursorFollow = true; + stb_textedit_cut(state, state->Stb); + } + } + else if (is_paste) + { + if (const char* clipboard = GetClipboardText()) + { + // Filter pasted buffer + const int clipboard_len = (int)strlen(clipboard); + ImVector clipboard_filtered; + clipboard_filtered.reserve(clipboard_len + 1); + for (const char* s = clipboard; *s != 0; ) + { + unsigned int c; + int in_len = ImTextCharFromUtf8(&c, s, NULL); + s += in_len; + if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, true)) + continue; + char c_utf8[5]; + ImTextCharToUtf8(c_utf8, c); + int out_len = (int)strlen(c_utf8); + clipboard_filtered.resize(clipboard_filtered.Size + out_len); + memcpy(clipboard_filtered.Data + clipboard_filtered.Size - out_len, c_utf8, out_len); + } + if (clipboard_filtered.Size > 0) // If everything was filtered, ignore the pasting operation + { + clipboard_filtered.push_back(0); + stb_textedit_paste(state, state->Stb, clipboard_filtered.Data, clipboard_filtered.Size - 1); + state->CursorFollow = true; + } + } + } + + // Update render selection flag after events have been handled, so selection highlight can be displayed during the same frame. + render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor); + } + + // Process callbacks and apply result back to user's buffer. + const char* apply_new_text = NULL; + int apply_new_text_length = 0; + if (g.ActiveId == id) + { + IM_ASSERT(state != NULL); + if (revert_edit && !is_readonly) + { + if (flags & ImGuiInputTextFlags_EscapeClearsAll) + { + // Clear input + IM_ASSERT(buf[0] != 0); + apply_new_text = ""; + apply_new_text_length = 0; + value_changed = true; + IMSTB_TEXTEDIT_CHARTYPE empty_string; + stb_textedit_replace(state, state->Stb, &empty_string, 0); + } + else if (strcmp(buf, state->TextToRevertTo.Data) != 0) + { + apply_new_text = state->TextToRevertTo.Data; + apply_new_text_length = state->TextToRevertTo.Size - 1; + + // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents. + // Push records into the undo stack so we can CTRL+Z the revert operation itself + value_changed = true; + stb_textedit_replace(state, state->Stb, state->TextToRevertTo.Data, state->TextToRevertTo.Size - 1); + } + } + + // FIXME-OPT: We always reapply the live buffer back to the input buffer before clearing ActiveId, + // even though strictly speaking it wasn't modified on this frame. Should mark dirty state from the stb_textedit callbacks. + // If we do that, need to ensure that as special case, 'validated == true' also writes back. + // This also allows the user to use InputText() without maintaining any user-side storage. + // (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object + // unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize). + const bool apply_edit_back_to_user_buffer = true;// !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); + if (apply_edit_back_to_user_buffer) + { + // Apply current edited text immediately. + // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer + + // User callback + if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0) + { + IM_ASSERT(callback != NULL); + + // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment. + ImGuiInputTextFlags event_flag = 0; + ImGuiKey event_key = ImGuiKey_None; + if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, 0, id)) + { + event_flag = ImGuiInputTextFlags_CallbackCompletion; + event_key = ImGuiKey_Tab; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_UpArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_UpArrow; + } + else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_DownArrow)) + { + event_flag = ImGuiInputTextFlags_CallbackHistory; + event_key = ImGuiKey_DownArrow; + } + else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited) + { + event_flag = ImGuiInputTextFlags_CallbackEdit; + } + else if (flags & ImGuiInputTextFlags_CallbackAlways) + { + event_flag = ImGuiInputTextFlags_CallbackAlways; + } + + if (event_flag) + { + ImGuiInputTextCallbackData callback_data; + callback_data.Ctx = &g; + callback_data.EventFlag = event_flag; + callback_data.Flags = flags; + callback_data.UserData = callback_user_data; + + // FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925 + char* callback_buf = is_readonly ? buf : state->TextA.Data; + IM_ASSERT(callback_buf == state->TextSrc); + state->CallbackTextBackup.resize(state->TextLen + 1); + memcpy(state->CallbackTextBackup.Data, callback_buf, state->TextLen + 1); + + callback_data.EventKey = event_key; + callback_data.Buf = callback_buf; + callback_data.BufTextLen = state->TextLen; + callback_data.BufSize = state->BufCapacity; + callback_data.BufDirty = false; + + const int utf8_cursor_pos = callback_data.CursorPos = state->Stb->cursor; + const int utf8_selection_start = callback_data.SelectionStart = state->Stb->select_start; + const int utf8_selection_end = callback_data.SelectionEnd = state->Stb->select_end; + + // Call user code + callback(&callback_data); + + // Read back what user may have modified + callback_buf = is_readonly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback + IM_ASSERT(callback_data.Buf == callback_buf); // Invalid to modify those fields + IM_ASSERT(callback_data.BufSize == state->BufCapacity); + IM_ASSERT(callback_data.Flags == flags); + const bool buf_dirty = callback_data.BufDirty; + if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty) { state->Stb->cursor = callback_data.CursorPos; state->CursorFollow = true; } + if (callback_data.SelectionStart != utf8_selection_start || buf_dirty) { state->Stb->select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb->cursor : callback_data.SelectionStart; } + if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty) { state->Stb->select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb->select_start : callback_data.SelectionEnd; } + if (buf_dirty) + { + // Callback may update buffer and thus set buf_dirty even in read-only mode. + IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! + InputTextReconcileUndoState(state, state->CallbackTextBackup.Data, state->CallbackTextBackup.Size - 1, callback_data.Buf, callback_data.BufTextLen); + state->TextLen = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() + state->CursorAnimReset(); + } + } + } + + // Will copy result string if modified + if (!is_readonly && strcmp(state->TextSrc, buf) != 0) + { + apply_new_text = state->TextSrc; + apply_new_text_length = state->TextLen; + value_changed = true; + } + } + } + + // Handle reapplying final data on deactivation (see InputTextDeactivateHook() for details) + if (g.InputTextDeactivatedState.ID == id) + { + if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0) + { + apply_new_text = g.InputTextDeactivatedState.TextA.Data; + apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1; + value_changed = true; + //IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text); + } + g.InputTextDeactivatedState.ID = 0; + } + + // Copy result to user buffer. This can currently only happen when (g.ActiveId == id) + if (apply_new_text != NULL) + { + //// We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size + //// of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used + //// without any storage on user's side. + IM_ASSERT(apply_new_text_length >= 0); + if (is_resizable) + { + ImGuiInputTextCallbackData callback_data; + callback_data.Ctx = &g; + callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; + callback_data.Flags = flags; + callback_data.Buf = buf; + callback_data.BufTextLen = apply_new_text_length; + callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1); + callback_data.UserData = callback_user_data; + callback(&callback_data); + buf = callback_data.Buf; + buf_size = callback_data.BufSize; + apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1); + IM_ASSERT(apply_new_text_length <= buf_size); + } + //IMGUI_DEBUG_PRINT("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length); + + // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size. + ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size)); + } + + // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) + // Otherwise request text input ahead for next frame. + if (g.ActiveId == id && clear_active_id) + ClearActiveID(); + else if (g.ActiveId == id) + g.WantTextInputNextFrame = 1; + + // Render frame + if (!is_multiline) + { + RenderNavCursor(frame_bb, id); + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + } + + const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + inner_size.x, frame_bb.Min.y + inner_size.y); // Not using frame_bb.Max because we have adjusted size + ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; + ImVec2 text_size(0.0f, 0.0f); + + // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line + // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether. + // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash. + const int buf_display_max_length = 2 * 1024 * 1024; + const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595 + const char* buf_display_end = NULL; // We have specialized paths below for setting the length + if (is_displaying_hint) + { + buf_display = hint; + buf_display_end = hint + strlen(hint); + } + + // Render text. We currently only render selection when the widget is active or while scrolling. + // FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive. + if (render_cursor || render_selection) + { + IM_ASSERT(state != NULL); + if (!is_displaying_hint) + buf_display_end = buf_display + state->TextLen; + + // Render text (with cursor and selection) + // This is going to be messy. We need to: + // - Display the text (this alone can be more easily clipped) + // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation) + // - Measure text height (for scrollbar) + // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) + // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. + const char* text_begin = buf_display; + const char* text_end = text_begin + state->TextLen; + ImVec2 cursor_offset, select_start_offset; + + { + // Find lines numbers straddling cursor and selection min position + int cursor_line_no = render_cursor ? -1 : -1000; + int selmin_line_no = render_selection ? -1 : -1000; + const char* cursor_ptr = render_cursor ? text_begin + state->Stb->cursor : NULL; + const char* selmin_ptr = render_selection ? text_begin + ImMin(state->Stb->select_start, state->Stb->select_end) : NULL; + + // Count lines and find line number for cursor and selection ends + int line_count = 1; + if (is_multiline) + { + for (const char* s = text_begin; (s = (const char*)memchr(s, '\n', (size_t)(text_end - s))) != NULL; s++) + { + if (cursor_line_no == -1 && s >= cursor_ptr) { cursor_line_no = line_count; } + if (selmin_line_no == -1 && s >= selmin_ptr) { selmin_line_no = line_count; } + line_count++; + } + } + if (cursor_line_no == -1) + cursor_line_no = line_count; + if (selmin_line_no == -1) + selmin_line_no = line_count; + + // Calculate 2d position by finding the beginning of the line and measuring distance + cursor_offset.x = InputTextCalcTextSize(&g, ImStrbol(cursor_ptr, text_begin), cursor_ptr).x; + cursor_offset.y = cursor_line_no * g.FontSize; + if (selmin_line_no >= 0) + { + select_start_offset.x = InputTextCalcTextSize(&g, ImStrbol(selmin_ptr, text_begin), selmin_ptr).x; + select_start_offset.y = selmin_line_no * g.FontSize; + } + + // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224) + if (is_multiline) + text_size = ImVec2(inner_size.x, line_count * g.FontSize); + } + + // Scroll + if (render_cursor && state->CursorFollow) + { + // Horizontal scroll in chunks of quarter width + if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll)) + { + const float scroll_increment_x = inner_size.x * 0.25f; + const float visible_width = inner_size.x - style.FramePadding.x; + if (cursor_offset.x < state->Scroll.x) + state->Scroll.x = IM_TRUNC(ImMax(0.0f, cursor_offset.x - scroll_increment_x)); + else if (cursor_offset.x - visible_width >= state->Scroll.x) + state->Scroll.x = IM_TRUNC(cursor_offset.x - visible_width + scroll_increment_x); + } + else + { + state->Scroll.y = 0.0f; + } + + // Vertical scroll + if (is_multiline) + { + // Test if cursor is vertically visible + if (cursor_offset.y - g.FontSize < scroll_y) + scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); + else if (cursor_offset.y - (inner_size.y - style.FramePadding.y * 2.0f) >= scroll_y) + scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f; + const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f); + scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y); + draw_pos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag + draw_window->Scroll.y = scroll_y; + } + + state->CursorFollow = false; + } + + // Draw selection + const ImVec2 draw_scroll = ImVec2(state->Scroll.x, 0.0f); + if (render_selection) + { + const char* text_selected_begin = text_begin + ImMin(state->Stb->select_start, state->Stb->select_end); + const char* text_selected_end = text_begin + ImMax(state->Stb->select_start, state->Stb->select_end); + + ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests. + float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. + float bg_offy_dn = is_multiline ? 0.0f : 2.0f; + ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll; + for (const char* p = text_selected_begin; p < text_selected_end; ) + { + if (rect_pos.y > clip_rect.w + g.FontSize) + break; + if (rect_pos.y < clip_rect.y) + { + p = (const char*)memchr((void*)p, '\n', text_selected_end - p); + p = p ? p + 1 : text_selected_end; + } + else + { + ImVec2 rect_size = InputTextCalcTextSize(&g, p, text_selected_end, &p, NULL, true); + if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines + ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn)); + rect.ClipWith(clip_rect); + if (rect.Overlaps(clip_rect)) + draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); + rect_pos.x = draw_pos.x - draw_scroll.x; + } + rect_pos.y += g.FontSize; + } + } + + // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash. + // FIXME-OPT: Multiline could submit a smaller amount of contents to AddText() since we already iterated through it. + if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) + { + ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); + draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); + } + + // Draw blinking cursor + if (render_cursor) + { + state->CursorAnim += io.DeltaTime; + bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; + ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll); + ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); + if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) + draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); + + // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) + if (!is_readonly) + { + g.PlatformImeData.WantVisible = true; + g.PlatformImeData.InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize); + g.PlatformImeData.InputLineHeight = g.FontSize; + g.PlatformImeViewport = window->Viewport->ID; + } + } + } + else + { + // Render text only (no selection, no cursor) + if (is_multiline) + text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width + else if (!is_displaying_hint && g.ActiveId == id) + buf_display_end = buf_display + state->TextLen; + else if (!is_displaying_hint) + buf_display_end = buf_display + strlen(buf_display); + + if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) + { + // Find render position for right alignment + if (flags & ImGuiInputTextFlags_ElideLeft) + draw_pos.x = ImMin(draw_pos.x, frame_bb.Max.x - CalcTextSize(buf_display, NULL).x - style.FramePadding.x); + + const ImVec2 draw_scroll = /*state ? ImVec2(state->Scroll.x, 0.0f) :*/ ImVec2(0.0f, 0.0f); // Preserve scroll when inactive? + ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); + draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); + } + } + + if (is_password && !is_displaying_hint) + PopFont(); + + if (is_multiline) + { + // For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (see #4761, #7870)... + Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y)); + g.NextItemData.ItemFlags |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop; + EndChild(); + item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow); + + // ...and then we need to undo the group overriding last item data, which gets a bit messy as EndGroup() tries to forward scrollbar being active... + // FIXME: This quite messy/tricky, should attempt to get rid of the child window. + EndGroup(); + if (g.LastItemData.ID == 0 || g.LastItemData.ID != GetWindowScrollbarID(draw_window, ImGuiAxis_Y)) + { + g.LastItemData.ID = id; + g.LastItemData.ItemFlags = item_data_backup.ItemFlags; + g.LastItemData.StatusFlags = item_data_backup.StatusFlags; + } + } + if (state) + state->TextSrc = NULL; + + // Log as text + if (g.LogEnabled && (!is_password || is_displaying_hint)) + { + LogSetNextTextDecoration("{", "}"); + LogRenderedText(&draw_pos, buf_display, buf_display_end); + } + + if (label_size.x > 0) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + + if (value_changed) + MarkItemEdited(id); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable); + if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0) + return validated; + else + return value_changed; +} + +void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) +{ +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + ImGuiContext& g = *GImGui; + ImStb::STB_TexteditState* stb_state = state->Stb; + ImStb::StbUndoState* undo_state = &stb_state->undostate; + Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId); + DebugLocateItemOnHover(state->ID); + Text("CurLenA: %d, Cursor: %d, Selection: %d..%d", state->TextLen, stb_state->cursor, stb_state->select_start, stb_state->select_end); + Text("BufCapacityA: %d", state->BufCapacity); + Text("(Internal Buffer: TextA Size: %d, Capacity: %d)", state->TextA.Size, state->TextA.Capacity); + Text("has_preferred_x: %d (%.2f)", stb_state->has_preferred_x, stb_state->preferred_x); + Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point); + if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 10), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY)) // Visualize undo state + { + PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + for (int n = 0; n < IMSTB_TEXTEDIT_UNDOSTATECOUNT; n++) + { + ImStb::StbUndoRecord* undo_rec = &undo_state->undo_rec[n]; + const char undo_rec_type = (n < undo_state->undo_point) ? 'u' : (n >= undo_state->redo_point) ? 'r' : ' '; + if (undo_rec_type == ' ') + BeginDisabled(); + const int buf_preview_len = (undo_rec_type != ' ' && undo_rec->char_storage != -1) ? undo_rec->insert_length : 0; + const char* buf_preview_str = undo_state->undo_char + undo_rec->char_storage; + Text("%c [%02d] where %03d, insert %03d, delete %03d, char_storage %03d \"%.*s\"", + undo_rec_type, n, undo_rec->where, undo_rec->insert_length, undo_rec->delete_length, undo_rec->char_storage, buf_preview_len, buf_preview_str); + if (undo_rec_type == ' ') + EndDisabled(); + } + PopStyleVar(); + } + EndChild(); +#else + IM_UNUSED(state); +#endif +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. +//------------------------------------------------------------------------- +// - ColorEdit3() +// - ColorEdit4() +// - ColorPicker3() +// - RenderColorRectWithAlphaCheckerboard() [Internal] +// - ColorPicker4() +// - ColorButton() +// - SetColorEditOptions() +// - ColorTooltip() [Internal] +// - ColorEditOptionsPopup() [Internal] +// - ColorPickerOptionsPopup() [Internal] +//------------------------------------------------------------------------- + +bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags) +{ + return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha); +} + +static void ColorEditRestoreH(const float* col, float* H) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.ColorEditCurrentID != 0); + if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0))) + return; + *H = g.ColorEditSavedHue; +} + +// ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation. +// Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting. +static void ColorEditRestoreHS(const float* col, float* H, float* S, float* V) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.ColorEditCurrentID != 0); + if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0))) + return; + + // When S == 0, H is undefined. + // When H == 1 it wraps around to 0. + if (*S == 0.0f || (*H == 0.0f && g.ColorEditSavedHue == 1)) + *H = g.ColorEditSavedHue; + + // When V == 0, S is undefined. + if (*V == 0.0f) + *S = g.ColorEditSavedSat; +} + +// Edit colors components (each component in 0.0f..1.0f range). +// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +// With typical options: Left-click on color square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item. +bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float square_sz = GetFrameHeight(); + const char* label_display_end = FindRenderedTextEnd(label); + float w_full = CalcItemWidth(); + g.NextItemData.ClearFlags(); + + BeginGroup(); + PushID(label); + const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0); + if (set_current_color_edit_id) + g.ColorEditCurrentID = window->IDStack.back(); + + // If we're not showing any slider there's no point in doing any HSV conversions + const ImGuiColorEditFlags flags_untouched = flags; + if (flags & ImGuiColorEditFlags_NoInputs) + flags = (flags & (~ImGuiColorEditFlags_DisplayMask_)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions; + + // Context menu: display and modify options (before defaults are applied) + if (!(flags & ImGuiColorEditFlags_NoOptions)) + ColorEditOptionsPopup(col, flags); + + // Read stored options + if (!(flags & ImGuiColorEditFlags_DisplayMask_)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DisplayMask_); + if (!(flags & ImGuiColorEditFlags_DataTypeMask_)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DataTypeMask_); + if (!(flags & ImGuiColorEditFlags_PickerMask_)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_); + if (!(flags & ImGuiColorEditFlags_InputMask_)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_InputMask_); + flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_)); + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check that only 1 is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected + + const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0; + const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0; + const int components = alpha ? 4 : 3; + const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x); + const float w_inputs = ImMax(w_full - w_button, 1.0f); + w_full = w_inputs + w_button; + + // Convert to the formats we need + float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f }; + if ((flags & ImGuiColorEditFlags_InputHSV) && (flags & ImGuiColorEditFlags_DisplayRGB)) + ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); + else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV)) + { + // Hue is lost when converting from grayscale rgb (saturation=0). Restore it. + ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); + ColorEditRestoreHS(col, &f[0], &f[1], &f[2]); + } + int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) }; + + bool value_changed = false; + bool value_changed_as_float = false; + + const ImVec2 pos = window->DC.CursorPos; + const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f; + window->DC.CursorPos.x = pos.x + inputs_offset_x; + + if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) + { + // RGB/HSV 0..255 Sliders + const float w_items = w_inputs - style.ItemInnerSpacing.x * (components - 1); + + const bool hide_prefix = (IM_TRUNC(w_items / components) <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x); + static const char* ids[4] = { "##X", "##Y", "##Z", "##W" }; + static const char* fmt_table_int[3][4] = + { + { "%3d", "%3d", "%3d", "%3d" }, // Short display + { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA + { "H:%3d", "S:%3d", "V:%3d", "A:%3d" } // Long display for HSVA + }; + static const char* fmt_table_float[3][4] = + { + { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display + { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA + { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA + }; + const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1; + + float prev_split = 0.0f; + for (int n = 0; n < components; n++) + { + if (n > 0) + SameLine(0, style.ItemInnerSpacing.x); + float next_split = IM_TRUNC(w_items * (n + 1) / components); + SetNextItemWidth(ImMax(next_split - prev_split, 1.0f)); + prev_split = next_split; + + // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0. + if (flags & ImGuiColorEditFlags_Float) + { + value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]); + value_changed_as_float |= value_changed; + } + else + { + value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]); + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight); + } + } + else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) + { + // RGB Hexadecimal Input + char buf[64]; + if (alpha) + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255), ImClamp(i[3], 0, 255)); + else + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255)); + SetNextItemWidth(w_inputs); + if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsUppercase)) + { + value_changed = true; + char* p = buf; + while (*p == '#' || ImCharIsBlankA(*p)) + p++; + i[0] = i[1] = i[2] = 0; + i[3] = 0xFF; // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha) + int r; + if (alpha) + r = sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned) + else + r = sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]); + IM_UNUSED(r); // Fixes C6031: Return value ignored: 'sscanf'. + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight); + } + + ImGuiWindow* picker_active_window = NULL; + if (!(flags & ImGuiColorEditFlags_NoSmallPreview)) + { + const float button_offset_x = ((flags & ImGuiColorEditFlags_NoInputs) || (style.ColorButtonPosition == ImGuiDir_Left)) ? 0.0f : w_inputs + style.ItemInnerSpacing.x; + window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y); + + const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f); + if (ColorButton("##ColorButton", col_v4, flags)) + { + if (!(flags & ImGuiColorEditFlags_NoPicker)) + { + // Store current color and open a picker + g.ColorPickerRef = col_v4; + OpenPopup("picker"); + SetNextWindowPos(g.LastItemData.Rect.GetBL() + ImVec2(0.0f, style.ItemSpacing.y)); + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight); + + if (BeginPopup("picker")) + { + if (g.CurrentWindow->BeginCount == 1) + { + picker_active_window = g.CurrentWindow; + if (label != label_display_end) + { + TextEx(label, label_display_end); + Spacing(); + } + ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; + ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; + SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? + value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); + } + EndPopup(); + } + } + + if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel)) + { + // Position not necessarily next to last submitted button (e.g. if style.ColorButtonPosition == ImGuiDir_Left), + // but we need to use SameLine() to setup baseline correctly. Might want to refactor SameLine() to simplify this. + SameLine(0.0f, style.ItemInnerSpacing.x); + window->DC.CursorPos.x = pos.x + ((flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x); + TextEx(label, label_display_end); + } + + // Convert back + if (value_changed && picker_active_window == NULL) + { + if (!value_changed_as_float) + for (int n = 0; n < 4; n++) + f[n] = i[n] / 255.0f; + if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB)) + { + g.ColorEditSavedHue = f[0]; + g.ColorEditSavedSat = f[1]; + ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); + g.ColorEditSavedID = g.ColorEditCurrentID; + g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(f[0], f[1], f[2], 0)); + } + if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV)) + ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); + + col[0] = f[0]; + col[1] = f[1]; + col[2] = f[2]; + if (alpha) + col[3] = f[3]; + } + + if (set_current_color_edit_id) + g.ColorEditCurrentID = 0; + PopID(); + EndGroup(); + + // Drag and Drop Target + // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test. + if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget()) + { + bool accepted_drag_drop = false; + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) + { + memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512 //-V1086 + value_changed = accepted_drag_drop = true; + } + if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) + { + memcpy((float*)col, payload->Data, sizeof(float) * components); + value_changed = accepted_drag_drop = true; + } + + // Drag-drop payloads are always RGB + if (accepted_drag_drop && (flags & ImGuiColorEditFlags_InputHSV)) + ColorConvertRGBtoHSV(col[0], col[1], col[2], col[0], col[1], col[2]); + EndDragDropTarget(); + } + + // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4(). + if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window) + g.LastItemData.ID = g.ActiveId; + + if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId + MarkItemEdited(g.LastItemData.ID); + + return value_changed; +} + +bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags) +{ + float col4[4] = { col[0], col[1], col[2], 1.0f }; + if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha)) + return false; + col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2]; + return true; +} + +// Helper for ColorPicker4() +static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w, float alpha) +{ + ImU32 alpha8 = IM_F32_TO_INT8_SAT(alpha); + ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x + 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32(0,0,0,alpha8)); + ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32(255,255,255,alpha8)); + ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32(0,0,0,alpha8)); + ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32(255,255,255,alpha8)); +} + +// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +// (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.) +// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..) +// FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0) +bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImDrawList* draw_list = window->DrawList; + ImGuiStyle& style = g.Style; + ImGuiIO& io = g.IO; + + const float width = CalcItemWidth(); + const bool is_readonly = ((g.NextItemData.ItemFlags | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0; + g.NextItemData.ClearFlags(); + + PushID(label); + const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0); + if (set_current_color_edit_id) + g.ColorEditCurrentID = window->IDStack.back(); + BeginGroup(); + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + flags |= ImGuiColorEditFlags_NoSmallPreview; + + // Context menu: display and store options. + if (!(flags & ImGuiColorEditFlags_NoOptions)) + ColorPickerOptionsPopup(col, flags); + + // Read stored options + if (!(flags & ImGuiColorEditFlags_PickerMask_)) + flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_; + if (!(flags & ImGuiColorEditFlags_InputMask_)) + flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_InputMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check that only 1 is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check that only 1 is selected + if (!(flags & ImGuiColorEditFlags_NoOptions)) + flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar); + + // Setup + int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4; + bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha); + ImVec2 picker_pos = window->DC.CursorPos; + float square_sz = GetFrameHeight(); + float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars + float sv_picker_size = ImMax(bars_width * 1, width - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box + float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x; + float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x; + float bars_triangles_half_sz = IM_TRUNC(bars_width * 0.20f); + + float backup_initial_col[4]; + memcpy(backup_initial_col, col, components * sizeof(float)); + + float wheel_thickness = sv_picker_size * 0.08f; + float wheel_r_outer = sv_picker_size * 0.50f; + float wheel_r_inner = wheel_r_outer - wheel_thickness; + ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size * 0.5f); + + // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic. + float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f); + ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point. + ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point. + ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point. + + float H = col[0], S = col[1], V = col[2]; + float R = col[0], G = col[1], B = col[2]; + if (flags & ImGuiColorEditFlags_InputRGB) + { + // Hue is lost when converting from grayscale rgb (saturation=0). Restore it. + ColorConvertRGBtoHSV(R, G, B, H, S, V); + ColorEditRestoreHS(col, &H, &S, &V); + } + else if (flags & ImGuiColorEditFlags_InputHSV) + { + ColorConvertHSVtoRGB(H, S, V, R, G, B); + } + + bool value_changed = false, value_changed_h = false, value_changed_sv = false; + + PushItemFlag(ImGuiItemFlags_NoNav, true); + if (flags & ImGuiColorEditFlags_PickerHueWheel) + { + // Hue wheel + SV triangle logic + InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size)); + if (IsItemActive() && !is_readonly) + { + ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center; + ImVec2 current_off = g.IO.MousePos - wheel_center; + float initial_dist2 = ImLengthSqr(initial_off); + if (initial_dist2 >= (wheel_r_inner - 1) * (wheel_r_inner - 1) && initial_dist2 <= (wheel_r_outer + 1) * (wheel_r_outer + 1)) + { + // Interactive with Hue wheel + H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f; + if (H < 0.0f) + H += 1.0f; + value_changed = value_changed_h = true; + } + float cos_hue_angle = ImCos(-H * 2.0f * IM_PI); + float sin_hue_angle = ImSin(-H * 2.0f * IM_PI); + if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle))) + { + // Interacting with SV triangle + ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle); + if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated)) + current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated); + float uu, vv, ww; + ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww); + V = ImClamp(1.0f - vv, 0.0001f, 1.0f); + S = ImClamp(uu / V, 0.0001f, 1.0f); + value_changed = value_changed_sv = true; + } + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight); + } + else if (flags & ImGuiColorEditFlags_PickerHueBar) + { + // SV rectangle logic + InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size)); + if (IsItemActive() && !is_readonly) + { + S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1)); + V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1)); + ColorEditRestoreH(col, &H); // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square. + value_changed = value_changed_sv = true; + } + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight); + + // Hue bar logic + SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y)); + InvisibleButton("hue", ImVec2(bars_width, sv_picker_size)); + if (IsItemActive() && !is_readonly) + { + H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1)); + value_changed = value_changed_h = true; + } + } + + // Alpha bar logic + if (alpha_bar) + { + SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y)); + InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size)); + if (IsItemActive()) + { + col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1)); + value_changed = true; + } + } + PopItemFlag(); // ImGuiItemFlags_NoNav + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + { + SameLine(0, style.ItemInnerSpacing.x); + BeginGroup(); + } + + if (!(flags & ImGuiColorEditFlags_NoLabel)) + { + const char* label_display_end = FindRenderedTextEnd(label); + if (label != label_display_end) + { + if ((flags & ImGuiColorEditFlags_NoSidePreview)) + SameLine(0, style.ItemInnerSpacing.x); + TextEx(label, label_display_end); + } + } + + if (!(flags & ImGuiColorEditFlags_NoSidePreview)) + { + PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); + ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if ((flags & ImGuiColorEditFlags_NoLabel)) + Text("Current"); + + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip; + ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)); + if (ref_col != NULL) + { + Text("Original"); + ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]); + if (ColorButton("##original", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2))) + { + memcpy(col, ref_col, components * sizeof(float)); + value_changed = true; + } + } + PopItemFlag(); + EndGroup(); + } + + // Convert back color to RGB + if (value_changed_h || value_changed_sv) + { + if (flags & ImGuiColorEditFlags_InputRGB) + { + ColorConvertHSVtoRGB(H, S, V, col[0], col[1], col[2]); + g.ColorEditSavedHue = H; + g.ColorEditSavedSat = S; + g.ColorEditSavedID = g.ColorEditCurrentID; + g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)); + } + else if (flags & ImGuiColorEditFlags_InputHSV) + { + col[0] = H; + col[1] = S; + col[2] = V; + } + } + + // R,G,B and H,S,V slider color editor + bool value_changed_fix_hue_wrap = false; + if ((flags & ImGuiColorEditFlags_NoInputs) == 0) + { + PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x); + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf; + ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker; + if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags_DisplayMask_) == 0) + if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB)) + { + // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget. + // For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050) + value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap); + value_changed = true; + } + if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags_DisplayMask_) == 0) + value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_DisplayHSV); + if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags_DisplayMask_) == 0) + value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_DisplayHex); + PopItemWidth(); + } + + // Try to cancel hue wrap (after ColorEdit4 call), if any + if (value_changed_fix_hue_wrap && (flags & ImGuiColorEditFlags_InputRGB)) + { + float new_H, new_S, new_V; + ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V); + if (new_H <= 0 && H > 0) + { + if (new_V <= 0 && V != new_V) + ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]); + else if (new_S <= 0) + ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]); + } + } + + if (value_changed) + { + if (flags & ImGuiColorEditFlags_InputRGB) + { + R = col[0]; + G = col[1]; + B = col[2]; + ColorConvertRGBtoHSV(R, G, B, H, S, V); + ColorEditRestoreHS(col, &H, &S, &V); // Fix local Hue as display below will use it immediately. + } + else if (flags & ImGuiColorEditFlags_InputHSV) + { + H = col[0]; + S = col[1]; + V = col[2]; + ColorConvertHSVtoRGB(H, S, V, R, G, B); + } + } + + const int style_alpha8 = IM_F32_TO_INT8_SAT(style.Alpha); + const ImU32 col_black = IM_COL32(0,0,0,style_alpha8); + const ImU32 col_white = IM_COL32(255,255,255,style_alpha8); + const ImU32 col_midgrey = IM_COL32(128,128,128,style_alpha8); + const ImU32 col_hues[6 + 1] = { IM_COL32(255,0,0,style_alpha8), IM_COL32(255,255,0,style_alpha8), IM_COL32(0,255,0,style_alpha8), IM_COL32(0,255,255,style_alpha8), IM_COL32(0,0,255,style_alpha8), IM_COL32(255,0,255,style_alpha8), IM_COL32(255,0,0,style_alpha8) }; + + ImVec4 hue_color_f(1, 1, 1, style.Alpha); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z); + ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f); + ImU32 user_col32_striped_of_alpha = ColorConvertFloat4ToU32(ImVec4(R, G, B, style.Alpha)); // Important: this is still including the main rendering/style alpha!! + + ImVec2 sv_cursor_pos; + + if (flags & ImGuiColorEditFlags_PickerHueWheel) + { + // Render Hue Wheel + const float aeps = 0.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out). + const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12); + for (int n = 0; n < 6; n++) + { + const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps; + const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps; + const int vert_start_idx = draw_list->VtxBuffer.Size; + draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc); + draw_list->PathStroke(col_white, 0, wheel_thickness); + const int vert_end_idx = draw_list->VtxBuffer.Size; + + // Paint colors over existing vertices + ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner); + ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner); + ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col_hues[n], col_hues[n + 1]); + } + + // Render Cursor + preview on Hue Wheel + float cos_hue_angle = ImCos(H * 2.0f * IM_PI); + float sin_hue_angle = ImSin(H * 2.0f * IM_PI); + ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner + wheel_r_outer) * 0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner + wheel_r_outer) * 0.5f); + float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f; + int hue_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(hue_cursor_rad); // Lock segment count so the +1 one matches others. + draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments); + draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments); + draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments); + + // Render SV triangle (rotated according to hue) + ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle); + ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle); + ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle); + ImVec2 uv_white = GetFontTexUvWhitePixel(); + draw_list->PrimReserve(3, 3); + draw_list->PrimVtx(tra, uv_white, hue_color32); + draw_list->PrimVtx(trb, uv_white, col_black); + draw_list->PrimVtx(trc, uv_white, col_white); + draw_list->AddTriangle(tra, trb, trc, col_midgrey, 1.5f); + sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V)); + } + else if (flags & ImGuiColorEditFlags_PickerHueBar) + { + // Render SV Square + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white); + draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black); + RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f); + sv_cursor_pos.x = ImClamp(IM_ROUND(picker_pos.x + ImSaturate(S) * sv_picker_size), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much + sv_cursor_pos.y = ImClamp(IM_ROUND(picker_pos.y + ImSaturate(1 - V) * sv_picker_size), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2); + + // Render Hue Bar + for (int i = 0; i < 6; ++i) + draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), col_hues[i], col_hues[i], col_hues[i + 1], col_hues[i + 1]); + float bar0_line_y = IM_ROUND(picker_pos.y + H * sv_picker_size); + RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f); + RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha); + } + + // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range) + float sv_cursor_rad = value_changed_sv ? wheel_thickness * 0.55f : wheel_thickness * 0.40f; + int sv_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(sv_cursor_rad); // Lock segment count so the +1 one matches others. + draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, user_col32_striped_of_alpha, sv_cursor_segments); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, col_midgrey, sv_cursor_segments); + draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, sv_cursor_segments); + + // Render alpha bar + if (alpha_bar) + { + float alpha = ImSaturate(col[3]); + ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size); + RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); + draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, user_col32_striped_of_alpha, user_col32_striped_of_alpha, user_col32_striped_of_alpha & ~IM_COL32_A_MASK, user_col32_striped_of_alpha & ~IM_COL32_A_MASK); + float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size); + RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f); + RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha); + } + + EndGroup(); + + if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0) + value_changed = false; + if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId + MarkItemEdited(g.LastItemData.ID); + + if (set_current_color_edit_id) + g.ColorEditCurrentID = 0; + PopID(); + + return value_changed; +} + +// A little color square. Return true when clicked. +// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip. +// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip. +// Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set. +bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiID id = window->GetID(desc_id); + const float default_size = GetFrameHeight(); + const ImVec2 size(size_arg.x == 0.0f ? default_size : size_arg.x, size_arg.y == 0.0f ? default_size : size_arg.y); + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); + ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f); + if (!ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + + if (flags & ImGuiColorEditFlags_NoAlpha) + flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf); + + ImVec4 col_rgb = col; + if (flags & ImGuiColorEditFlags_InputHSV) + ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z); + + ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f); + float grid_step = ImMin(size.x, size.y) / 2.99f; + float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); + ImRect bb_inner = bb; + float off = 0.0f; + if ((flags & ImGuiColorEditFlags_NoBorder) == 0) + { + off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts. + bb_inner.Expand(off); + } + if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) + { + float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f); + RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight); + window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft); + } + else + { + // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha + ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col_rgb : col_rgb_without_alpha; + if (col_source.w < 1.0f) + RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); + else + window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding); + } + RenderNavCursor(bb, id); + if ((flags & ImGuiColorEditFlags_NoBorder) == 0) + { + if (g.Style.FrameBorderSize > 0.0f) + RenderFrameBorder(bb.Min, bb.Max, rounding); + else + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border + } + + // Drag and Drop Source + // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test. + if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource()) + { + if (flags & ImGuiColorEditFlags_NoAlpha) + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once); + else + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once); + ColorButton(desc_id, col, flags); + SameLine(); + TextEx("Color"); + EndDragDropSource(); + } + + // Tooltip + if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered && IsItemHovered(ImGuiHoveredFlags_ForTooltip)) + ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); + + return pressed; +} + +// Initialize/override default color options +void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) +{ + ImGuiContext& g = *GImGui; + if ((flags & ImGuiColorEditFlags_DisplayMask_) == 0) + flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DisplayMask_; + if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0) + flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_; + if ((flags & ImGuiColorEditFlags_PickerMask_) == 0) + flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_; + if ((flags & ImGuiColorEditFlags_InputMask_) == 0) + flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_; + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DataTypeMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check only 1 option is selected + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_)); // Check only 1 option is selected + g.ColorEditOptions = flags; +} + +// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. +void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags) +{ + ImGuiContext& g = *GImGui; + + if (!BeginTooltipEx(ImGuiTooltipFlags_OverridePrevious, ImGuiWindowFlags_None)) + return; + const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text; + if (text_end > text) + { + TextEx(text, text_end); + Separator(); + } + + ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2); + ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); + ColorButton("##preview", cf, (flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz); + SameLine(); + if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_)) + { + if (flags & ImGuiColorEditFlags_NoAlpha) + Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]); + else + Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]); + } + else if (flags & ImGuiColorEditFlags_InputHSV) + { + if (flags & ImGuiColorEditFlags_NoAlpha) + Text("H: %.3f, S: %.3f, V: %.3f", col[0], col[1], col[2]); + else + Text("H: %.3f, S: %.3f, V: %.3f, A: %.3f", col[0], col[1], col[2], col[3]); + } + EndTooltip(); +} + +void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) +{ + bool allow_opt_inputs = !(flags & ImGuiColorEditFlags_DisplayMask_); + bool allow_opt_datatype = !(flags & ImGuiColorEditFlags_DataTypeMask_); + if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context")) + return; + + ImGuiContext& g = *GImGui; + PushItemFlag(ImGuiItemFlags_NoMarkEdited, true); + ImGuiColorEditFlags opts = g.ColorEditOptions; + if (allow_opt_inputs) + { + if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayRGB; + if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHSV; + if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHex; + } + if (allow_opt_datatype) + { + if (allow_opt_inputs) Separator(); + if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Uint8; + if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Float; + } + + if (allow_opt_inputs || allow_opt_datatype) + Separator(); + if (Button("Copy as..", ImVec2(-1, 0))) + OpenPopup("Copy"); + if (BeginPopup("Copy")) + { + int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); + char buf[64]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); + if (Selectable(buf)) + SetClipboardText(buf); + ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb); + if (Selectable(buf)) + SetClipboardText(buf); + if (!(flags & ImGuiColorEditFlags_NoAlpha)) + { + ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca); + if (Selectable(buf)) + SetClipboardText(buf); + } + EndPopup(); + } + + g.ColorEditOptions = opts; + PopItemFlag(); + EndPopup(); +} + +void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags) +{ + bool allow_opt_picker = !(flags & ImGuiColorEditFlags_PickerMask_); + bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar); + if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context")) + return; + + ImGuiContext& g = *GImGui; + PushItemFlag(ImGuiItemFlags_NoMarkEdited, true); + if (allow_opt_picker) + { + ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function + PushItemWidth(picker_size.x); + for (int picker_type = 0; picker_type < 2; picker_type++) + { + // Draw small/thumbnail version of each picker type (over an invisible button for selection) + if (picker_type > 0) Separator(); + PushID(picker_type); + ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha); + if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar; + if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel; + ImVec2 backup_pos = GetCursorScreenPos(); + if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup + g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_); + SetCursorScreenPos(backup_pos); + ImVec4 previewing_ref_col; + memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4)); + ColorPicker4("##previewing_picker", &previewing_ref_col.x, picker_flags); + PopID(); + } + PopItemWidth(); + } + if (allow_opt_alpha_bar) + { + if (allow_opt_picker) Separator(); + CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar); + } + PopItemFlag(); + EndPopup(); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: TreeNode, CollapsingHeader, etc. +//------------------------------------------------------------------------- +// - TreeNode() +// - TreeNodeV() +// - TreeNodeEx() +// - TreeNodeExV() +// - TreeNodeBehavior() [Internal] +// - TreePush() +// - TreePop() +// - GetTreeNodeToLabelSpacing() +// - SetNextItemOpen() +// - CollapsingHeader() +//------------------------------------------------------------------------- + +bool ImGui::TreeNode(const char* str_id, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, 0, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNode(const char* label) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + ImGuiID id = window->GetID(label); + return TreeNodeBehavior(id, ImGuiTreeNodeFlags_None, label, NULL); +} + +bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args) +{ + return TreeNodeExV(str_id, 0, fmt, args); +} + +bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args) +{ + return TreeNodeExV(ptr_id, 0, fmt, args); +} + +bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + ImGuiID id = window->GetID(label); + return TreeNodeBehavior(id, flags, label, NULL); +} + +bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(str_id, flags, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool is_open = TreeNodeExV(ptr_id, flags, fmt, args); + va_end(args); + return is_open; +} + +bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiID id = window->GetID(str_id); + const char* label, *label_end; + ImFormatStringToTempBufferV(&label, &label_end, fmt, args); + return TreeNodeBehavior(id, flags, label, label_end); +} + +bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiID id = window->GetID(ptr_id); + const char* label, *label_end; + ImFormatStringToTempBufferV(&label, &label_end, fmt, args); + return TreeNodeBehavior(id, flags, label, label_end); +} + +bool ImGui::TreeNodeGetOpen(ImGuiID storage_id) +{ + ImGuiContext& g = *GImGui; + ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage; + return storage->GetInt(storage_id, 0) != 0; +} + +void ImGui::TreeNodeSetOpen(ImGuiID storage_id, bool open) +{ + ImGuiContext& g = *GImGui; + ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage; + storage->SetInt(storage_id, open ? 1 : 0); +} + +bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags) +{ + if (flags & ImGuiTreeNodeFlags_Leaf) + return true; + + // We only write to the tree storage if the user clicks, or explicitly use the SetNextItemOpen function + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiStorage* storage = window->DC.StateStorage; + + bool is_open; + if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasOpen) + { + if (g.NextItemData.OpenCond & ImGuiCond_Always) + { + is_open = g.NextItemData.OpenVal; + TreeNodeSetOpen(storage_id, is_open); + } + else + { + // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently. + const int stored_value = storage->GetInt(storage_id, -1); + if (stored_value == -1) + { + is_open = g.NextItemData.OpenVal; + TreeNodeSetOpen(storage_id, is_open); + } + else + { + is_open = stored_value != 0; + } + } + } + else + { + is_open = storage->GetInt(storage_id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0; + } + + // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior). + // NB- If we are above max depth we still allow manually opened nodes to be logged. + if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && (window->DC.TreeDepth - g.LogDepthRef) < g.LogDepthToExpand) + is_open = true; + + return is_open; +} + +// Store ImGuiTreeNodeStackData for just submitted node. +// Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase. +static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + g.TreeNodeStack.resize(g.TreeNodeStack.Size + 1); + ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.back(); + tree_node_data->ID = g.LastItemData.ID; + tree_node_data->TreeFlags = flags; + tree_node_data->ItemFlags = g.LastItemData.ItemFlags; + tree_node_data->NavRect = g.LastItemData.NavRect; + window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth); +} + +// When using public API, currently 'id == storage_id' is always true, but we separate the values to facilitate advanced user code doing storage queries outside of UI loop. +bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0; + const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y)); + + if (!label_end) + label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); + + const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2); // Collapsing arrow width + Spacing + const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset); // Latch before ItemSize changes it + const float text_width = g.FontSize + label_size.x + padding.x * 2; // Include collapsing arrow + + // We vertically grow up to current line height up the typical widget height. + const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2); + const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL); + const bool span_all_columns_label = (flags & ImGuiTreeNodeFlags_LabelSpanAllColumns) != 0 && (g.CurrentTable != NULL); + ImRect frame_bb; + frame_bb.Min.x = span_all_columns ? window->ParentWorkRect.Min.x : (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x; + frame_bb.Min.y = window->DC.CursorPos.y; + frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : (flags & ImGuiTreeNodeFlags_SpanLabelWidth) ? window->DC.CursorPos.x + text_width + padding.x : window->WorkRect.Max.x; + frame_bb.Max.y = window->DC.CursorPos.y + frame_height; + if (display_frame) + { + const float outer_extend = IM_TRUNC(window->WindowPadding.x * 0.5f); // Framed header expand a little outside of current limits + frame_bb.Min.x -= outer_extend; + frame_bb.Max.x += outer_extend; + } + + ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y); + ItemSize(ImVec2(text_width, frame_height), padding.y); + + // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing + ImRect interact_bb = frame_bb; + if ((flags & (ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanLabelWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0) + interact_bb.Max.x = frame_bb.Min.x + text_width + (label_size.x > 0.0f ? style.ItemSpacing.x * 2.0f : 0.0f); + + // Compute open and multi-select states before ItemAdd() as it clear NextItem data. + ImGuiID storage_id = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasStorageID) ? g.NextItemData.StorageId : id; + bool is_open = TreeNodeUpdateNextOpen(storage_id, flags); + + bool is_visible; + if (span_all_columns || span_all_columns_label) + { + // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable.. + const float backup_clip_rect_min_x = window->ClipRect.Min.x; + const float backup_clip_rect_max_x = window->ClipRect.Max.x; + window->ClipRect.Min.x = window->ParentWorkRect.Min.x; + window->ClipRect.Max.x = window->ParentWorkRect.Max.x; + is_visible = ItemAdd(interact_bb, id); + window->ClipRect.Min.x = backup_clip_rect_min_x; + window->ClipRect.Max.x = backup_clip_rect_max_x; + } + else + { + is_visible = ItemAdd(interact_bb, id); + } + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; + g.LastItemData.DisplayRect = frame_bb; + + // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsBackHere enabled: + // Store data for the current depth to allow returning to this node from any child item. + // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop(). + // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsBackHere by default or move it to ImGuiStyle. + bool store_tree_node_stack_data = false; + if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + { + if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && is_open && !g.NavIdIsAlive) + if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) + store_tree_node_stack_data = true; + } + + const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; + if (!is_visible) + { + if (store_tree_node_stack_data && is_open) + TreeNodeStoreStackData(flags); // Call before TreePushOverrideID() + if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + TreePushOverrideID(id); + IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); + return is_open; + } + + if (span_all_columns || span_all_columns_label) + { + TablePushBackgroundChannel(); + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect; + g.LastItemData.ClipRect = window->ClipRect; + } + + ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None; + if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap)) + button_flags |= ImGuiButtonFlags_AllowOverlap; + if (!is_leaf) + button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; + + // We allow clicking on the arrow section with keyboard modifiers held, in order to easily + // allow browsing a tree while preserving selection with code implementing multi-selection patterns. + // When clicking on the rest of the tree node we always disallow keyboard modifiers. + const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x; + const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x; + const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2); + + const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0; + if (is_multi_select) // We absolutely need to distinguish open vs select so _OpenOnArrow comes by default + flags |= (flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 ? ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick : ImGuiTreeNodeFlags_OpenOnArrow; + + // Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags. + // Some alteration have subtle effects (e.g. toggle on MouseUp vs MouseDown events) due to requirements for multi-selection and drag and drop support. + // - Single-click on label = Toggle on MouseUp (default, when _OpenOnArrow=0) + // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=0) + // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=1) + // - Double-click on label = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1) + // - Double-click on arrow = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1 and _OpenOnArrow=0) + // It is rather standard that arrow click react on Down rather than Up. + // We set ImGuiButtonFlags_PressedOnClickRelease on OpenOnDoubleClick because we want the item to be active on the initial MouseDown in order for drag and drop to work. + if (is_mouse_x_over_arrow) + button_flags |= ImGuiButtonFlags_PressedOnClick; + else if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) + button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; + else + button_flags |= ImGuiButtonFlags_PressedOnClickRelease; + + bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0; + const bool was_selected = selected; + + // Multi-selection support (header) + if (is_multi_select) + { + // Handle multi-select + alter button flags for it + MultiSelectItemHeader(id, &selected, &button_flags); + if (is_mouse_x_over_arrow) + button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease; + } + else + { + if (window != g.HoveredWindow || !is_mouse_x_over_arrow) + button_flags |= ImGuiButtonFlags_NoKeyModsAllowed; + } + + bool hovered, held; + bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags); + bool toggled = false; + if (!is_leaf) + { + if (pressed && g.DragDropHoldJustPressedId != id) + { + if ((flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 || (g.NavActivateId == id && !is_multi_select)) + toggled = true; // Single click + if (flags & ImGuiTreeNodeFlags_OpenOnArrow) + toggled |= is_mouse_x_over_arrow && !g.NavHighlightItemUnderNav; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job + if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseClickedCount[0] == 2) + toggled = true; // Double click + } + else if (pressed && g.DragDropHoldJustPressedId == id) + { + IM_ASSERT(button_flags & ImGuiButtonFlags_PressedOnDragDropHold); + if (!is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again. + toggled = true; + else + pressed = false; // Cancel press so it doesn't trigger selection. + } + + if (g.NavId == id && g.NavMoveDir == ImGuiDir_Left && is_open) + { + toggled = true; + NavClearPreferredPosForAxis(ImGuiAxis_X); + NavMoveRequestCancel(); + } + if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority? + { + toggled = true; + NavClearPreferredPosForAxis(ImGuiAxis_X); + NavMoveRequestCancel(); + } + + if (toggled) + { + is_open = !is_open; + window->DC.StateStorage->SetInt(storage_id, is_open); + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen; + } + } + + // Multi-selection support (footer) + if (is_multi_select) + { + bool pressed_copy = pressed && !toggled; + MultiSelectItemFooter(id, &selected, &pressed_copy); + if (pressed) + SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, interact_bb); + } + + if (selected != was_selected) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; + + // Render + { + const ImU32 text_col = GetColorU32(ImGuiCol_Text); + ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact; + if (is_multi_select) + nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle + if (display_frame) + { + // Framed type + const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding); + RenderNavCursor(frame_bb, id, nav_render_cursor_flags); + if (flags & ImGuiTreeNodeFlags_Bullet) + RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col); + else if (!is_leaf) + RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 1.0f); + else // Leaf without bullet, left-adjusted text + text_pos.x -= text_offset_x - padding.x; + if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton) + frame_bb.Max.x -= g.FontSize + style.FramePadding.x; + if (g.LogEnabled) + LogSetNextTextDecoration("###", "###"); + } + else + { + // Unframed typed for tree nodes + if (hovered || selected) + { + const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false); + } + RenderNavCursor(frame_bb, id, nav_render_cursor_flags); + if (flags & ImGuiTreeNodeFlags_Bullet) + RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col); + else if (!is_leaf) + RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 0.70f); + if (g.LogEnabled) + LogSetNextTextDecoration(">", NULL); + } + + if (span_all_columns && !span_all_columns_label) + TablePopBackgroundChannel(); + + // Label + if (display_frame) + RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); + else + RenderText(text_pos, label, label_end, false); + + if (span_all_columns_label) + TablePopBackgroundChannel(); + } + + if (store_tree_node_stack_data && is_open) + TreeNodeStoreStackData(flags); // Call before TreePushOverrideID() + if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) + TreePushOverrideID(id); // Could use TreePush(label) but this avoid computing twice + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); + return is_open; +} + +void ImGui::TreePush(const char* str_id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(str_id); +} + +void ImGui::TreePush(const void* ptr_id) +{ + ImGuiWindow* window = GetCurrentWindow(); + Indent(); + window->DC.TreeDepth++; + PushID(ptr_id); +} + +void ImGui::TreePushOverrideID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + Indent(); + window->DC.TreeDepth++; + PushOverrideID(id); +} + +void ImGui::TreePop() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + Unindent(); + + window->DC.TreeDepth--; + ImU32 tree_depth_mask = (1 << window->DC.TreeDepth); + + if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) // Only set during request + { + ImGuiTreeNodeStackData* data = &g.TreeNodeStack.back(); + IM_ASSERT(data->ID == window->IDStack.back()); + if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) + { + // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled) + if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) + NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, data); + } + g.TreeNodeStack.pop_back(); + window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask; + } + + IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much. + PopID(); +} + +// Horizontal distance preceding label when using TreeNode() or Bullet() +float ImGui::GetTreeNodeToLabelSpacing() +{ + ImGuiContext& g = *GImGui; + return g.FontSize + (g.Style.FramePadding.x * 2.0f); +} + +// Set next TreeNode/CollapsingHeader open state. +void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + if (g.CurrentWindow->SkipItems) + return; + g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasOpen; + g.NextItemData.OpenVal = is_open; + g.NextItemData.OpenCond = (ImU8)(cond ? cond : ImGuiCond_Always); +} + +// Set next TreeNode/CollapsingHeader storage id. +void ImGui::SetNextItemStorageID(ImGuiID storage_id) +{ + ImGuiContext& g = *GImGui; + if (g.CurrentWindow->SkipItems) + return; + g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasStorageID; + g.NextItemData.StorageId = storage_id; +} + +// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag). +// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode(). +bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + ImGuiID id = window->GetID(label); + return TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader, label); +} + +// p_visible == NULL : regular collapsing header +// p_visible != NULL && *p_visible == true : show a small close button on the corner of the header, clicking the button will set *p_visible = false +// p_visible != NULL && *p_visible == false : do not show the header at all +// Do not mistake this with the Open state of the header itself, which you can adjust with SetNextItemOpen() or ImGuiTreeNodeFlags_DefaultOpen. +bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + if (p_visible && !*p_visible) + return false; + + ImGuiID id = window->GetID(label); + flags |= ImGuiTreeNodeFlags_CollapsingHeader; + if (p_visible) + flags |= ImGuiTreeNodeFlags_AllowOverlap | (ImGuiTreeNodeFlags)ImGuiTreeNodeFlags_ClipLabelForTrailingButton; + bool is_open = TreeNodeBehavior(id, flags, label); + if (p_visible != NULL) + { + // Create a small overlapping close button + // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc. + // FIXME: CloseButton can overlap into text, need find a way to clip the text somehow. + ImGuiContext& g = *GImGui; + ImGuiLastItemData last_item_backup = g.LastItemData; + float button_size = g.FontSize; + float button_x = ImMax(g.LastItemData.Rect.Min.x, g.LastItemData.Rect.Max.x - g.Style.FramePadding.x - button_size); + float button_y = g.LastItemData.Rect.Min.y + g.Style.FramePadding.y; + ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id); + if (CloseButton(close_button_id, ImVec2(button_x, button_y))) + *p_visible = false; + g.LastItemData = last_item_backup; + } + + return is_open; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Selectable +//------------------------------------------------------------------------- +// - Selectable() +//------------------------------------------------------------------------- + +// Tip: pass a non-visible label (e.g. "##hello") then you can use the space to draw other text or image. +// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id. +// With this scheme, ImGuiSelectableFlags_SpanAllColumns and ImGuiSelectableFlags_AllowOverlap are also frequently used flags. +// FIXME: Selectable() with (size.x == 0.0f) and (SelectableTextAlign.x > 0.0f) followed by SameLine() is currently not supported. +bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + + // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle. + ImGuiID id = window->GetID(label); + ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); + ImVec2 pos = window->DC.CursorPos; + pos.y += window->DC.CurrLineTextBaseOffset; + ItemSize(size, 0.0f); + + // Fill horizontal space + // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets. + const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0; + const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x; + const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x; + if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth)) + size.x = ImMax(label_size.x, max_x - min_x); + + // Text stays at the submission position, but bounding box may be extended on both sides + const ImVec2 text_min = pos; + const ImVec2 text_max(min_x + size.x, pos.y + size.y); + + // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable. + // FIXME: Not part of layout so not included in clipper calculation, but ItemSize currently doesn't allow offsetting CursorPos. + ImRect bb(min_x, pos.y, text_max.x, text_max.y); + if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0) + { + const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x; + const float spacing_y = style.ItemSpacing.y; + const float spacing_L = IM_TRUNC(spacing_x * 0.50f); + const float spacing_U = IM_TRUNC(spacing_y * 0.50f); + bb.Min.x -= spacing_L; + bb.Min.y -= spacing_U; + bb.Max.x += (spacing_x - spacing_L); + bb.Max.y += (spacing_y - spacing_U); + } + //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); } + + const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0; + const ImGuiItemFlags extra_item_flags = disabled_item ? (ImGuiItemFlags)ImGuiItemFlags_Disabled : ImGuiItemFlags_None; + bool is_visible; + if (span_all_columns) + { + // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable.. + const float backup_clip_rect_min_x = window->ClipRect.Min.x; + const float backup_clip_rect_max_x = window->ClipRect.Max.x; + window->ClipRect.Min.x = window->ParentWorkRect.Min.x; + window->ClipRect.Max.x = window->ParentWorkRect.Max.x; + is_visible = ItemAdd(bb, id, NULL, extra_item_flags); + window->ClipRect.Min.x = backup_clip_rect_min_x; + window->ClipRect.Max.x = backup_clip_rect_max_x; + } + else + { + is_visible = ItemAdd(bb, id, NULL, extra_item_flags); + } + + const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0; + if (!is_visible) + if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(bb)) // Extra layer of "no logic clip" for box-select support (would be more overhead to add to ItemAdd) + return false; + + const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; + if (disabled_item && !disabled_global) // Only testing this as an optimization + BeginDisabled(); + + // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only, + // which would be advantageous since most selectable are not selected. + if (span_all_columns) + { + if (g.CurrentTable) + TablePushBackgroundChannel(); + else if (window->DC.CurrentColumns) + PushColumnsBackground(); + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect; + g.LastItemData.ClipRect = window->ClipRect; + } + + // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries + ImGuiButtonFlags button_flags = 0; + if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; } + if (flags & ImGuiSelectableFlags_NoSetKeyOwner) { button_flags |= ImGuiButtonFlags_NoSetKeyOwner; } + if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; } + if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; } + if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; } + if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; } + + // Multi-selection support (header) + const bool was_selected = selected; + if (is_multi_select) + { + // Handle multi-select + alter button flags for it + MultiSelectItemHeader(id, &selected, &button_flags); + } + + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + + // Multi-selection support (footer) + if (is_multi_select) + { + MultiSelectItemFooter(id, &selected, &pressed); + } + else + { + // Auto-select when moved into + // - This will be more fully fleshed in the range-select branch + // - This is not exposed as it won't nicely work with some user side handling of shift/control + // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons + // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope()) + // - (2) usage will fail with clipped items + // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. + if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId) + if (g.NavJustMovedToId == id) + selected = pressed = true; + } + + // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with keyboard/gamepad + if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover))) + { + if (!g.NavHighlightItemUnderNav && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) + { + SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect) + if (g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = false; + } + } + if (pressed) + MarkItemEdited(id); + + if (selected != was_selected) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; + + // Render + if (is_visible) + { + const bool highlighted = hovered || (flags & ImGuiSelectableFlags_Highlight); + if (highlighted || selected) + { + // Between 1.91.0 and 1.91.4 we made selected Selectable use an arbitrary lerp between _Header and _HeaderHovered. Removed that now. (#8106) + ImU32 col = GetColorU32((held && highlighted) ? ImGuiCol_HeaderActive : highlighted ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + } + if (g.NavId == id) + { + ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact | ImGuiNavRenderCursorFlags_NoRounding; + if (is_multi_select) + nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle + RenderNavCursor(bb, id, nav_render_cursor_flags); + } + } + + if (span_all_columns) + { + if (g.CurrentTable) + TablePopBackgroundChannel(); + else if (window->DC.CurrentColumns) + PopColumnsBackground(); + } + + if (is_visible) + RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb); + + // Automatically close popups + if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups)) + CloseCurrentPopup(); + + if (disabled_item && !disabled_global) + EndDisabled(); + + // Selectable() always returns a pressed state! + // Users of BeginMultiSelect()/EndMultiSelect() scope: you may call ImGui::IsItemToggledSelection() to retrieve + // selection toggle, only useful if you need that state updated (e.g. for rendering purpose) before reaching EndMultiSelect(). + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + return pressed; //-V1020 +} + +bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) +{ + if (Selectable(label, *p_selected, flags, size_arg)) + { + *p_selected = !*p_selected; + return true; + } + return false; +} + + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Typing-Select support +//------------------------------------------------------------------------- + +// [Experimental] Currently not exposed in public API. +// Consume character inputs and return search request, if any. +// This would typically only be called on the focused window or location you want to grab inputs for, e.g. +// if (ImGui::IsWindowFocused(...)) +// if (ImGuiTypingSelectRequest* req = ImGui::GetTypingSelectRequest()) +// focus_idx = ImGui::TypingSelectFindMatch(req, my_items.size(), [](void*, int n) { return my_items[n]->Name; }, &my_items, -1); +// However the code is written in a way where calling it from multiple locations is safe (e.g. to obtain buffer). +ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiTypingSelectState* data = &g.TypingSelectState; + ImGuiTypingSelectRequest* out_request = &data->Request; + + // Clear buffer + const float TYPING_SELECT_RESET_TIMER = 1.80f; // FIXME: Potentially move to IO config. + const int TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK = 4; // Lock single char matching when repeating same char 4 times + if (data->SearchBuffer[0] != 0) + { + bool clear_buffer = false; + clear_buffer |= (g.NavFocusScopeId != data->FocusScope); + clear_buffer |= (data->LastRequestTime + TYPING_SELECT_RESET_TIMER < g.Time); + clear_buffer |= g.NavAnyRequest; + clear_buffer |= g.ActiveId != 0 && g.NavActivateId == 0; // Allow temporary SPACE activation to not interfere + clear_buffer |= IsKeyPressed(ImGuiKey_Escape) || IsKeyPressed(ImGuiKey_Enter); + clear_buffer |= IsKeyPressed(ImGuiKey_Backspace) && (flags & ImGuiTypingSelectFlags_AllowBackspace) == 0; + //if (clear_buffer) { IMGUI_DEBUG_LOG("GetTypingSelectRequest(): Clear SearchBuffer.\n"); } + if (clear_buffer) + data->Clear(); + } + + // Append to buffer + const int buffer_max_len = IM_ARRAYSIZE(data->SearchBuffer) - 1; + int buffer_len = (int)strlen(data->SearchBuffer); + bool select_request = false; + for (ImWchar w : g.IO.InputQueueCharacters) + { + const int w_len = ImTextCountUtf8BytesFromStr(&w, &w + 1); + if (w < 32 || (buffer_len == 0 && ImCharIsBlankW(w)) || (buffer_len + w_len > buffer_max_len)) // Ignore leading blanks + continue; + char w_buf[5]; + ImTextCharToUtf8(w_buf, (unsigned int)w); + if (data->SingleCharModeLock && w_len == out_request->SingleCharSize && memcmp(w_buf, data->SearchBuffer, w_len) == 0) + { + select_request = true; // Same character: don't need to append to buffer. + continue; + } + if (data->SingleCharModeLock) + { + data->Clear(); // Different character: clear + buffer_len = 0; + } + memcpy(data->SearchBuffer + buffer_len, w_buf, w_len + 1); // Append + buffer_len += w_len; + select_request = true; + } + g.IO.InputQueueCharacters.resize(0); + + // Handle backspace + if ((flags & ImGuiTypingSelectFlags_AllowBackspace) && IsKeyPressed(ImGuiKey_Backspace, ImGuiInputFlags_Repeat)) + { + char* p = (char*)(void*)ImTextFindPreviousUtf8Codepoint(data->SearchBuffer, data->SearchBuffer + buffer_len); + *p = 0; + buffer_len = (int)(p - data->SearchBuffer); + } + + // Return request if any + if (buffer_len == 0) + return NULL; + if (select_request) + { + data->FocusScope = g.NavFocusScopeId; + data->LastRequestFrame = g.FrameCount; + data->LastRequestTime = (float)g.Time; + } + out_request->Flags = flags; + out_request->SearchBufferLen = buffer_len; + out_request->SearchBuffer = data->SearchBuffer; + out_request->SelectRequest = (data->LastRequestFrame == g.FrameCount); + out_request->SingleCharMode = false; + out_request->SingleCharSize = 0; + + // Calculate if buffer contains the same character repeated. + // - This can be used to implement a special search mode on first character. + // - Performed on UTF-8 codepoint for correctness. + // - SingleCharMode is always set for first input character, because it usually leads to a "next". + if (flags & ImGuiTypingSelectFlags_AllowSingleCharMode) + { + const char* buf_begin = out_request->SearchBuffer; + const char* buf_end = out_request->SearchBuffer + out_request->SearchBufferLen; + const int c0_len = ImTextCountUtf8BytesFromChar(buf_begin, buf_end); + const char* p = buf_begin + c0_len; + for (; p < buf_end; p += c0_len) + if (memcmp(buf_begin, p, (size_t)c0_len) != 0) + break; + const int single_char_count = (p == buf_end) ? (out_request->SearchBufferLen / c0_len) : 0; + out_request->SingleCharMode = (single_char_count > 0 || data->SingleCharModeLock); + out_request->SingleCharSize = (ImS8)c0_len; + data->SingleCharModeLock |= (single_char_count >= TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK); // From now on we stop search matching to lock to single char mode. + } + + return out_request; +} + +static int ImStrimatchlen(const char* s1, const char* s1_end, const char* s2) +{ + int match_len = 0; + while (s1 < s1_end && ImToUpper(*s1++) == ImToUpper(*s2++)) + match_len++; + return match_len; +} + +// Default handler for finding a result for typing-select. You may implement your own. +// You might want to display a tooltip to visualize the current request SearchBuffer +// When SingleCharMode is set: +// - it is better to NOT display a tooltip of other on-screen display indicator. +// - the index of the currently focused item is required. +// if your SetNextItemSelectionUserData() values are indices, you can obtain it from ImGuiMultiSelectIO::NavIdItem, otherwise from g.NavLastValidSelectionUserData. +int ImGui::TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx) +{ + if (req == NULL || req->SelectRequest == false) // Support NULL parameter so both calls can be done from same spot. + return -1; + int idx = -1; + if (req->SingleCharMode && (req->Flags & ImGuiTypingSelectFlags_AllowSingleCharMode)) + idx = TypingSelectFindNextSingleCharMatch(req, items_count, get_item_name_func, user_data, nav_item_idx); + else + idx = TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data); + if (idx != -1) + SetNavCursorVisibleAfterMove(); + return idx; +} + +// Special handling when a single character is repeated: perform search on a single letter and goes to next. +int ImGui::TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx) +{ + // FIXME: Assume selection user data is index. Would be extremely practical. + //if (nav_item_idx == -1) + // nav_item_idx = (int)g.NavLastValidSelectionUserData; + + int first_match_idx = -1; + bool return_next_match = false; + for (int idx = 0; idx < items_count; idx++) + { + const char* item_name = get_item_name_func(user_data, idx); + if (ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SingleCharSize, item_name) < req->SingleCharSize) + continue; + if (return_next_match) // Return next matching item after current item. + return idx; + if (first_match_idx == -1 && nav_item_idx == -1) // Return first match immediately if we don't have a nav_item_idx value. + return idx; + if (first_match_idx == -1) // Record first match for wrapping. + first_match_idx = idx; + if (nav_item_idx == idx) // Record that we encountering nav_item so we can return next match. + return_next_match = true; + } + return first_match_idx; // First result +} + +int ImGui::TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data) +{ + int longest_match_idx = -1; + int longest_match_len = 0; + for (int idx = 0; idx < items_count; idx++) + { + const char* item_name = get_item_name_func(user_data, idx); + const int match_len = ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SearchBufferLen, item_name); + if (match_len <= longest_match_len) + continue; + longest_match_idx = idx; + longest_match_len = match_len; + if (match_len == req->SearchBufferLen) + break; + } + return longest_match_idx; +} + +void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data) +{ +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + Text("SearchBuffer = \"%s\"", data->SearchBuffer); + Text("SingleCharMode = %d, Size = %d, Lock = %d", data->Request.SingleCharMode, data->Request.SingleCharSize, data->SingleCharModeLock); + Text("LastRequest = time: %.2f, frame: %d", data->LastRequestTime, data->LastRequestFrame); +#else + IM_UNUSED(data); +#endif +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Box-Select support +// This has been extracted away from Multi-Select logic in the hope that it could eventually be used elsewhere, but hasn't been yet. +//------------------------------------------------------------------------- +// Extra logic in MultiSelectItemFooter() and ImGuiListClipper::Step() +//------------------------------------------------------------------------- +// - BoxSelectPreStartDrag() [Internal] +// - BoxSelectActivateDrag() [Internal] +// - BoxSelectDeactivateDrag() [Internal] +// - BoxSelectScrollWithMouseDrag() [Internal] +// - BeginBoxSelect() [Internal] +// - EndBoxSelect() [Internal] +//------------------------------------------------------------------------- + +// Call on the initial click. +static void BoxSelectPreStartDrag(ImGuiID id, ImGuiSelectionUserData clicked_item) +{ + ImGuiContext& g = *GImGui; + ImGuiBoxSelectState* bs = &g.BoxSelectState; + bs->ID = id; + bs->IsStarting = true; // Consider starting box-select. + bs->IsStartedFromVoid = (clicked_item == ImGuiSelectionUserData_Invalid); + bs->IsStartedSetNavIdOnce = bs->IsStartedFromVoid; + bs->KeyMods = g.IO.KeyMods; + bs->StartPosRel = bs->EndPosRel = ImGui::WindowPosAbsToRel(g.CurrentWindow, g.IO.MousePos); + bs->ScrollAccum = ImVec2(0.0f, 0.0f); +} + +static void BoxSelectActivateDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Activate\n", bs->ID); + bs->IsActive = true; + bs->Window = window; + bs->IsStarting = false; + ImGui::SetActiveID(bs->ID, window); + ImGui::SetActiveIdUsingAllKeyboardKeys(); + if (bs->IsStartedFromVoid && (bs->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0) + bs->RequestClear = true; +} + +static void BoxSelectDeactivateDrag(ImGuiBoxSelectState* bs) +{ + ImGuiContext& g = *GImGui; + bs->IsActive = bs->IsStarting = false; + if (g.ActiveId == bs->ID) + { + IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Deactivate\n", bs->ID); + ImGui::ClearActiveID(); + } + bs->ID = 0; +} + +static void BoxSelectScrollWithMouseDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window, const ImRect& inner_r) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(bs->Window == window); + for (int n = 0; n < 2; n++) // each axis + { + const float mouse_pos = g.IO.MousePos[n]; + const float dist = (mouse_pos > inner_r.Max[n]) ? mouse_pos - inner_r.Max[n] : (mouse_pos < inner_r.Min[n]) ? mouse_pos - inner_r.Min[n] : 0.0f; + const float scroll_curr = window->Scroll[n]; + if (dist == 0.0f || (dist < 0.0f && scroll_curr < 0.0f) || (dist > 0.0f && scroll_curr >= window->ScrollMax[n])) + continue; + + const float speed_multiplier = ImLinearRemapClamp(g.FontSize, g.FontSize * 5.0f, 1.0f, 4.0f, ImAbs(dist)); // x1 to x4 depending on distance + const float scroll_step = g.FontSize * 35.0f * speed_multiplier * ImSign(dist) * g.IO.DeltaTime; + bs->ScrollAccum[n] += scroll_step; + + // Accumulate into a stored value so we can handle high-framerate + const float scroll_step_i = ImFloor(bs->ScrollAccum[n]); + if (scroll_step_i == 0.0f) + continue; + if (n == 0) + ImGui::SetScrollX(window, scroll_curr + scroll_step_i); + else + ImGui::SetScrollY(window, scroll_curr + scroll_step_i); + bs->ScrollAccum[n] -= scroll_step_i; + } +} + +bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiBoxSelectState* bs = &g.BoxSelectState; + KeepAliveID(box_select_id); + if (bs->ID != box_select_id) + return false; + + // IsStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry. + bs->UnclipMode = false; + bs->RequestClear = false; + if (bs->IsStarting && IsMouseDragPastThreshold(0)) + BoxSelectActivateDrag(bs, window); + else if ((bs->IsStarting || bs->IsActive) && g.IO.MouseDown[0] == false) + BoxSelectDeactivateDrag(bs); + if (!bs->IsActive) + return false; + + // Current frame absolute prev/current rectangles are used to toggle selection. + // They are derived from positions relative to scrolling space. + ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel); + ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already + ImVec2 curr_end_pos_abs = g.IO.MousePos; + if (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) // Box-select scrolling only happens with ScopeWindow + curr_end_pos_abs = ImClamp(curr_end_pos_abs, scope_rect.Min, scope_rect.Max); + bs->BoxSelectRectPrev.Min = ImMin(start_pos_abs, prev_end_pos_abs); + bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs); + bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs); + bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs); + + // Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper) + // Storing an extra rect used by widgets supporting box-select. + if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d) + if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x) + { + bs->UnclipMode = true; + bs->UnclipRect = bs->BoxSelectRectPrev; // FIXME-OPT: UnclipRect x coordinates could be intersection of Prev and Curr rect on X axis. + bs->UnclipRect.Add(bs->BoxSelectRectCurr); + } + + //GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); + //GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); + //GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f); + return true; +} + +void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiBoxSelectState* bs = &g.BoxSelectState; + IM_ASSERT(bs->IsActive); + bs->UnclipMode = false; + + // Render selection rectangle + bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view + ImRect box_select_r = bs->BoxSelectRectCurr; + box_select_r.ClipWith(scope_rect); + window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling + window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT: Styling + + // Scroll + const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0; + if (enable_scroll) + { + ImRect scroll_r = scope_rect; + scroll_r.Expand(-g.FontSize); + //GetForegroundDrawList()->AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255)); + if (!scroll_r.Contains(g.IO.MousePos)) + BoxSelectScrollWithMouseDrag(bs, window, scroll_r); + } +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Multi-Select support +//------------------------------------------------------------------------- +// - DebugLogMultiSelectRequests() [Internal] +// - CalcScopeRect() [Internal] +// - BeginMultiSelect() +// - EndMultiSelect() +// - SetNextItemSelectionUserData() +// - MultiSelectItemHeader() [Internal] +// - MultiSelectItemFooter() [Internal] +// - DebugNodeMultiSelectState() [Internal] +//------------------------------------------------------------------------- + +static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSelectIO* io) +{ + ImGuiContext& g = *GImGui; + IM_UNUSED(function); + for (const ImGuiSelectionRequest& req : io->Requests) + { + if (req.Type == ImGuiSelectionRequestType_SetAll) IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetAll %d (= %s)\n", function, req.Selected, req.Selected ? "SelectAll" : "Clear"); + if (req.Type == ImGuiSelectionRequestType_SetRange) IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetRange %" IM_PRId64 "..%" IM_PRId64 " (0x%" IM_PRIX64 "..0x%" IM_PRIX64 ") = %d (dir %d)\n", function, req.RangeFirstItem, req.RangeLastItem, req.RangeFirstItem, req.RangeLastItem, req.Selected, req.RangeDirection); + } +} + +static ImRect CalcScopeRect(ImGuiMultiSelectTempData* ms, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect) + { + // Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only + return ImRect(ms->ScopeRectMin, ImMax(window->DC.CursorMaxPos, ms->ScopeRectMin)); + } + else + { + // When a table, pull HostClipRect, which allows us to predict ClipRect before first row/layout is performed. (#7970) + ImRect scope_rect = window->InnerClipRect; + if (g.CurrentTable != NULL) + scope_rect = g.CurrentTable->HostClipRect; + + // Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect? + scope_rect.Min = ImMin(scope_rect.Min + ImVec2(window->DecoInnerSizeX1, window->DecoInnerSizeY1), scope_rect.Max); + return scope_rect; + } +} + +// Return ImGuiMultiSelectIO structure. +// Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect(). +// Passing 'selection_size' and 'items_count' parameters is currently optional. +// - 'selection_size' is useful to disable some shortcut routing: e.g. ImGuiMultiSelectFlags_ClearOnEscape won't claim Escape key when selection_size 0, +// allowing a first press to clear selection THEN the second press to leave child window and return to parent. +// - 'items_count' is stored in ImGuiMultiSelectIO which makes it a convenient way to pass the information to your ApplyRequest() handler (but you may pass it differently). +// - If they are costly for you to compute (e.g. external intrusive selection without maintaining size), you may avoid them and pass -1. +// - If you can easily tell if your selection is empty or not, you may pass 0/1, or you may enable ImGuiMultiSelectFlags_ClearOnEscape flag dynamically. +ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int selection_size, int items_count) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + if (++g.MultiSelectTempDataStacked > g.MultiSelectTempData.Size) + g.MultiSelectTempData.resize(g.MultiSelectTempDataStacked, ImGuiMultiSelectTempData()); + ImGuiMultiSelectTempData* ms = &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1]; + IM_STATIC_ASSERT(offsetof(ImGuiMultiSelectTempData, IO) == 0); // Clear() relies on that. + g.CurrentMultiSelect = ms; + if ((flags & (ImGuiMultiSelectFlags_ScopeWindow | ImGuiMultiSelectFlags_ScopeRect)) == 0) + flags |= ImGuiMultiSelectFlags_ScopeWindow; + if (flags & ImGuiMultiSelectFlags_SingleSelect) + flags &= ~(ImGuiMultiSelectFlags_BoxSelect2d | ImGuiMultiSelectFlags_BoxSelect1d); + if (flags & ImGuiMultiSelectFlags_BoxSelect2d) + flags &= ~ImGuiMultiSelectFlags_BoxSelect1d; + + // FIXME: Workaround to the fact we override CursorMaxPos, meaning size measurement are lost. (#8250) + // They should perhaps be stacked properly? + if (ImGuiTable* table = g.CurrentTable) + if (table->CurrentColumn != -1) + TableEndCell(table); // This is currently safe to call multiple time. If that properly is lost we can extract the "save measurement" part of it. + + // FIXME: BeginFocusScope() + const ImGuiID id = window->IDStack.back(); + ms->Clear(); + ms->FocusScopeId = id; + ms->Flags = flags; + ms->IsFocused = (ms->FocusScopeId == g.NavFocusScopeId); + ms->BackupCursorMaxPos = window->DC.CursorMaxPos; + ms->ScopeRectMin = window->DC.CursorMaxPos = window->DC.CursorPos; + PushFocusScope(ms->FocusScopeId); + if (flags & ImGuiMultiSelectFlags_ScopeWindow) // Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items. + window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main; + + // Use copy of keyboard mods at the time of the request, otherwise we would requires mods to be held for an extra frame. + ms->KeyMods = g.NavJustMovedToId ? (g.NavJustMovedToIsTabbing ? 0 : g.NavJustMovedToKeyMods) : g.IO.KeyMods; + if (flags & ImGuiMultiSelectFlags_NoRangeSelect) + ms->KeyMods &= ~ImGuiMod_Shift; + + // Bind storage + ImGuiMultiSelectState* storage = g.MultiSelectStorage.GetOrAddByKey(id); + storage->ID = id; + storage->LastFrameActive = g.FrameCount; + storage->LastSelectionSize = selection_size; + storage->Window = window; + ms->Storage = storage; + + // Output to user + ms->IO.Requests.resize(0); + ms->IO.RangeSrcItem = storage->RangeSrcItem; + ms->IO.NavIdItem = storage->NavIdItem; + ms->IO.NavIdSelected = (storage->NavIdSelected == 1) ? true : false; + ms->IO.ItemsCount = items_count; + + // Clear when using Navigation to move within the scope + // (we compare FocusScopeId so it possible to use multiple selections inside a same window) + bool request_clear = false; + bool request_select_all = false; + if (g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == ms->FocusScopeId && g.NavJustMovedToHasSelectionData) + { + if (ms->KeyMods & ImGuiMod_Shift) + ms->IsKeyboardSetRange = true; + if (ms->IsKeyboardSetRange) + IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid); // Not ready -> could clear? + if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0) + request_clear = true; + } + else if (g.NavJustMovedFromFocusScopeId == ms->FocusScopeId) + { + // Also clear on leaving scope (may be optional?) + if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0) + request_clear = true; + } + + // Box-select handling: update active state. + ImGuiBoxSelectState* bs = &g.BoxSelectState; + if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) + { + ms->BoxSelectId = GetID("##BoxSelect"); + if (BeginBoxSelect(CalcScopeRect(ms, window), window, ms->BoxSelectId, flags)) + request_clear |= bs->RequestClear; + } + + if (ms->IsFocused) + { + // Shortcut: Clear selection (Escape) + // - Only claim shortcut if selection is not empty, allowing further presses on Escape to e.g. leave current child window. + // - Box select also handle Escape and needs to pass an id to bypass ActiveIdUsingAllKeyboardKeys lock. + if (flags & ImGuiMultiSelectFlags_ClearOnEscape) + { + if (selection_size != 0 || bs->IsActive) + if (Shortcut(ImGuiKey_Escape, ImGuiInputFlags_None, bs->IsActive ? bs->ID : 0)) + { + request_clear = true; + if (bs->IsActive) + BoxSelectDeactivateDrag(bs); + } + } + + // Shortcut: Select all (CTRL+A) + if (!(flags & ImGuiMultiSelectFlags_SingleSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll)) + if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_A)) + request_select_all = true; + } + + if (request_clear || request_select_all) + { + MultiSelectAddSetAll(ms, request_select_all); + if (!request_select_all) + storage->LastSelectionSize = 0; + } + ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1; + ms->LastSubmittedItem = ImGuiSelectionUserData_Invalid; + + if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) + DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO); + + return &ms->IO; +} + +// Return updated ImGuiMultiSelectIO structure. +// Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect(). +ImGuiMultiSelectIO* ImGui::EndMultiSelect() +{ + ImGuiContext& g = *GImGui; + ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect; + ImGuiMultiSelectState* storage = ms->Storage; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT_USER_ERROR(ms->FocusScopeId == g.CurrentFocusScopeId, "EndMultiSelect() FocusScope mismatch!"); + IM_ASSERT(g.CurrentMultiSelect != NULL && storage->Window == g.CurrentWindow); + IM_ASSERT(g.MultiSelectTempDataStacked > 0 && &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] == g.CurrentMultiSelect); + + ImRect scope_rect = CalcScopeRect(ms, window); + if (ms->IsFocused) + { + // We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here. + if (ms->IO.RangeSrcReset || (ms->RangeSrcPassedBy == false && ms->IO.RangeSrcItem != ImGuiSelectionUserData_Invalid)) // Can't read storage->RangeSrcItem here -> we want the state at begining of the scope (see tests for easy failure) + { + IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset RangeSrcItem.\n"); // Will set be to NavId. + storage->RangeSrcItem = ImGuiSelectionUserData_Invalid; + } + if (ms->NavIdPassedBy == false && storage->NavIdItem != ImGuiSelectionUserData_Invalid) + { + IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset NavIdItem.\n"); + storage->NavIdItem = ImGuiSelectionUserData_Invalid; + storage->NavIdSelected = -1; + } + + if ((ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) && GetBoxSelectState(ms->BoxSelectId)) + EndBoxSelect(scope_rect, ms->Flags); + } + + if (ms->IsEndIO == false) + ms->IO.Requests.resize(0); + + // Clear selection when clicking void? + // We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection! + // The InnerRect test is necessary for non-child/decorated windows. + bool scope_hovered = IsWindowHovered() && window->InnerRect.Contains(g.IO.MousePos); + if (scope_hovered && (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)) + scope_hovered &= scope_rect.Contains(g.IO.MousePos); + if (scope_hovered && g.HoveredId == 0 && g.ActiveId == 0) + { + if (ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) + { + if (!g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && g.IO.MouseClickedCount[0] == 1) + { + BoxSelectPreStartDrag(ms->BoxSelectId, ImGuiSelectionUserData_Invalid); + FocusWindow(window, ImGuiFocusRequestFlags_UnlessBelowModal); + SetHoveredID(ms->BoxSelectId); + if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect) + SetNavID(0, ImGuiNavLayer_Main, ms->FocusScopeId, ImRect(g.IO.MousePos, g.IO.MousePos)); // Automatically switch FocusScope for initial click from void to box-select. + } + } + + if (ms->Flags & ImGuiMultiSelectFlags_ClearOnClickVoid) + if (IsMouseReleased(0) && IsMouseDragPastThreshold(0) == false && g.IO.KeyMods == ImGuiMod_None) + MultiSelectAddSetAll(ms, false); + } + + // Courtesy nav wrapping helper flag + if (ms->Flags & ImGuiMultiSelectFlags_NavWrapX) + { + IM_ASSERT(ms->Flags & ImGuiMultiSelectFlags_ScopeWindow); // Only supported at window scope + ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX); + } + + // Unwind + window->DC.CursorMaxPos = ImMax(ms->BackupCursorMaxPos, window->DC.CursorMaxPos); + PopFocusScope(); + + if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) + DebugLogMultiSelectRequests("EndMultiSelect", &ms->IO); + + ms->FocusScopeId = 0; + ms->Flags = ImGuiMultiSelectFlags_None; + g.CurrentMultiSelect = (--g.MultiSelectTempDataStacked > 0) ? &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] : NULL; + + return &ms->IO; +} + +void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data) +{ + // Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code! + // This designed so widgets can also cheaply set this before calling ItemAdd(), so we are not tied to MultiSelect api. + ImGuiContext& g = *GImGui; + g.NextItemData.SelectionUserData = selection_user_data; + g.NextItemData.FocusScopeId = g.CurrentFocusScopeId; + + if (ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect) + { + // Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping) + g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect; + if (ms->IO.RangeSrcItem == selection_user_data) + ms->RangeSrcPassedBy = true; + } + else + { + g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData; + } +} + +// In charge of: +// - Applying SetAll for submitted items. +// - Applying SetRange for submitted items and record end points. +// - Altering button behavior flags to facilitate use with drag and drop. +void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect; + + bool selected = *p_selected; + if (ms->IsFocused) + { + ImGuiMultiSelectState* storage = ms->Storage; + ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData; + IM_ASSERT(g.NextItemData.FocusScopeId == g.CurrentFocusScopeId && "Forgot to call SetNextItemSelectionUserData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope"); + + // Apply SetAll (Clear/SelectAll) requests requested by BeginMultiSelect(). + // This is only useful if the user hasn't processed them already, and this only works if the user isn't using the clipper. + // If you are using a clipper you need to process the SetAll request after calling BeginMultiSelect() + if (ms->LoopRequestSetAll != -1) + selected = (ms->LoopRequestSetAll == 1); + + // When using SHIFT+Nav: because it can incur scrolling we cannot afford a frame of lag with the selection highlight (otherwise scrolling would happen before selection) + // For this to work, we need someone to set 'RangeSrcPassedBy = true' at some point (either clipper either SetNextItemSelectionUserData() function) + if (ms->IsKeyboardSetRange) + { + IM_ASSERT(id != 0 && (ms->KeyMods & ImGuiMod_Shift) != 0); + const bool is_range_dst = (ms->RangeDstPassedBy == false) && g.NavJustMovedToId == id; // Assume that g.NavJustMovedToId is not clipped. + if (is_range_dst) + ms->RangeDstPassedBy = true; + if (is_range_dst && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) // If we don't have RangeSrc, assign RangeSrc = RangeDst + { + storage->RangeSrcItem = item_data; + storage->RangeSelected = selected ? 1 : 0; + } + const bool is_range_src = storage->RangeSrcItem == item_data; + if (is_range_src || is_range_dst || ms->RangeSrcPassedBy != ms->RangeDstPassedBy) + { + // Apply range-select value to visible items + IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid && storage->RangeSelected != -1); + selected = (storage->RangeSelected != 0); + } + else if ((ms->KeyMods & ImGuiMod_Ctrl) == 0 && (ms->Flags & ImGuiMultiSelectFlags_NoAutoClear) == 0) + { + // Clear other items + selected = false; + } + } + *p_selected = selected; + } + + // Alter button behavior flags + // To handle drag and drop of multiple items we need to avoid clearing selection on click. + // Enabling this test makes actions using CTRL+SHIFT delay their effect on MouseUp which is annoying, but it allows drag and drop of multiple items. + if (p_button_flags != NULL) + { + ImGuiButtonFlags button_flags = *p_button_flags; + button_flags |= ImGuiButtonFlags_NoHoveredOnFocus; + if ((!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore)) && !(ms->Flags & ImGuiMultiSelectFlags_SelectOnClickRelease)) + button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease; + else + button_flags |= ImGuiButtonFlags_PressedOnClickRelease; + *p_button_flags = button_flags; + } +} + +// In charge of: +// - Auto-select on navigation. +// - Box-select toggle handling. +// - Right-click handling. +// - Altering selection based on Ctrl/Shift modifiers, both for keyboard and mouse. +// - Record current selection state for RangeSrc +// This is all rather complex, best to run and refer to "widgets_multiselect_xxx" tests in imgui_test_suite. +void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + bool selected = *p_selected; + bool pressed = *p_pressed; + ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect; + ImGuiMultiSelectState* storage = ms->Storage; + if (pressed) + ms->IsFocused = true; + + bool hovered = false; + if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) + hovered = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); + if (!ms->IsFocused && !hovered) + return; + + ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData; + + ImGuiMultiSelectFlags flags = ms->Flags; + const bool is_singleselect = (flags & ImGuiMultiSelectFlags_SingleSelect) != 0; + bool is_ctrl = (ms->KeyMods & ImGuiMod_Ctrl) != 0; + bool is_shift = (ms->KeyMods & ImGuiMod_Shift) != 0; + + bool apply_to_range_src = false; + + if (g.NavId == id && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) + apply_to_range_src = true; + if (ms->IsEndIO == false) + { + ms->IO.Requests.resize(0); + ms->IsEndIO = true; + } + + // Auto-select as you navigate a list + if (g.NavJustMovedToId == id) + { + if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) + { + if (is_ctrl && is_shift) + pressed = true; + else if (!is_ctrl) + selected = pressed = true; + } + else + { + // With NoAutoSelect, using Shift+keyboard performs a write/copy + if (is_shift) + pressed = true; + else if (!is_ctrl) + apply_to_range_src = true; // Since if (pressed) {} main block is not running we update this + } + } + + if (apply_to_range_src) + { + storage->RangeSrcItem = item_data; + storage->RangeSelected = selected; // Will be updated at the end of this function anyway. + } + + // Box-select toggle handling + if (ms->BoxSelectId != 0) + if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId)) + { + const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(g.LastItemData.Rect); + const bool rect_overlap_prev = bs->BoxSelectRectPrev.Overlaps(g.LastItemData.Rect); + if ((rect_overlap_curr && !rect_overlap_prev && !selected) || (rect_overlap_prev && !rect_overlap_curr)) + { + if (storage->LastSelectionSize <= 0 && bs->IsStartedSetNavIdOnce) + { + pressed = true; // First item act as a pressed: code below will emit selection request and set NavId (whatever we emit here will be overridden anyway) + bs->IsStartedSetNavIdOnce = false; + } + else + { + selected = !selected; + MultiSelectAddSetRange(ms, selected, +1, item_data, item_data); + } + storage->LastSelectionSize = ImMax(storage->LastSelectionSize + 1, 1); + } + } + + // Right-click handling. + // FIXME-MULTISELECT: Currently filtered out by ImGuiMultiSelectFlags_NoAutoSelect but maybe should be moved to Selectable(). See https://github.com/ocornut/imgui/pull/5816 + if (hovered && IsMouseClicked(1) && (flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) + { + if (g.ActiveId != 0 && g.ActiveId != id) + ClearActiveID(); + SetFocusID(id, window); + if (!pressed && !selected) + { + pressed = true; + is_ctrl = is_shift = false; + } + } + + // Unlike Space, Enter doesn't alter selection (but can still return a press) unless current item is not selected. + // The later, "unless current item is not select", may become optional? It seems like a better default if Enter doesn't necessarily open something + // (unlike e.g. Windows explorer). For use case where Enter always open something, we might decide to make this optional? + const bool enter_pressed = pressed && (g.NavActivateId == id) && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput); + + // Alter selection + if (pressed && (!enter_pressed || !selected)) + { + // Box-select + ImGuiInputSource input_source = (g.NavJustMovedToId == id || g.NavActivateId == id) ? g.NavInputSource : ImGuiInputSource_Mouse; + if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) + if (selected == false && !g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1) + BoxSelectPreStartDrag(ms->BoxSelectId, item_data); + + //---------------------------------------------------------------------------------------- + // ACTION | Begin | Pressed/Activated | End + //---------------------------------------------------------------------------------------- + // Keys Navigated: | Clear | Src=item, Sel=1 SetRange 1 + // Keys Navigated: Ctrl | n/a | n/a + // Keys Navigated: Shift | n/a | Dst=item, Sel=1, => Clear + SetRange 1 + // Keys Navigated: Ctrl+Shift | n/a | Dst=item, Sel=Src => Clear + SetRange Src-Dst + // Keys Activated: | n/a | Src=item, Sel=1 => Clear + SetRange 1 + // Keys Activated: Ctrl | n/a | Src=item, Sel=!Sel => SetSange 1 + // Keys Activated: Shift | n/a | Dst=item, Sel=1 => Clear + SetSange 1 + //---------------------------------------------------------------------------------------- + // Mouse Pressed: | n/a | Src=item, Sel=1, => Clear + SetRange 1 + // Mouse Pressed: Ctrl | n/a | Src=item, Sel=!Sel => SetRange 1 + // Mouse Pressed: Shift | n/a | Dst=item, Sel=1, => Clear + SetRange 1 + // Mouse Pressed: Ctrl+Shift | n/a | Dst=item, Sel=!Sel => SetRange Src-Dst + //---------------------------------------------------------------------------------------- + + if ((flags & ImGuiMultiSelectFlags_NoAutoClear) == 0) + { + bool request_clear = false; + if (is_singleselect) + request_clear = true; + else if ((input_source == ImGuiInputSource_Mouse || g.NavActivateId == id) && !is_ctrl) + request_clear = (flags & ImGuiMultiSelectFlags_NoAutoClearOnReselect) ? !selected : true; + else if ((input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Gamepad) && is_shift && !is_ctrl) + request_clear = true; // With is_shift==false the RequestClear was done in BeginIO, not necessary to do again. + if (request_clear) + MultiSelectAddSetAll(ms, false); + } + + int range_direction; + bool range_selected; + if (is_shift && !is_singleselect) + { + //IM_ASSERT(storage->HasRangeSrc && storage->HasRangeValue); + if (storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) + storage->RangeSrcItem = item_data; + if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) + { + // Shift+Arrow always select + // Ctrl+Shift+Arrow copy source selection state (already stored by BeginMultiSelect() in storage->RangeSelected) + range_selected = (is_ctrl && storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true; + } + else + { + // Shift+Arrow copy source selection state + // Shift+Click always copy from target selection state + if (ms->IsKeyboardSetRange) + range_selected = (storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true; + else + range_selected = !selected; + } + range_direction = ms->RangeSrcPassedBy ? +1 : -1; + } + else + { + // Ctrl inverts selection, otherwise always select + if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) + selected = is_ctrl ? !selected : true; + else + selected = !selected; + storage->RangeSrcItem = item_data; + range_selected = selected; + range_direction = +1; + } + MultiSelectAddSetRange(ms, range_selected, range_direction, storage->RangeSrcItem, item_data); + } + + // Update/store the selection state of the Source item (used by CTRL+SHIFT, when Source is unselected we perform a range unselect) + if (storage->RangeSrcItem == item_data) + storage->RangeSelected = selected ? 1 : 0; + + // Update/store the selection state of focused item + if (g.NavId == id) + { + storage->NavIdItem = item_data; + storage->NavIdSelected = selected ? 1 : 0; + } + if (storage->NavIdItem == item_data) + ms->NavIdPassedBy = true; + ms->LastSubmittedItem = item_data; + + *p_selected = selected; + *p_pressed = pressed; +} + +void ImGui::MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected) +{ + ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, selected, 0, ImGuiSelectionUserData_Invalid, ImGuiSelectionUserData_Invalid }; + ms->IO.Requests.resize(0); // Can always clear previous requests + ms->IO.Requests.push_back(req); // Add new request +} + +void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item) +{ + // Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges) + if (ms->IO.Requests.Size > 0 && first_item == last_item && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0) + { + ImGuiSelectionRequest* prev = &ms->IO.Requests.Data[ms->IO.Requests.Size - 1]; + if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->LastSubmittedItem && prev->Selected == selected) + { + prev->RangeLastItem = last_item; + return; + } + } + + ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item }; + ms->IO.Requests.push_back(req); // Add new request +} + +void ImGui::DebugNodeMultiSelectState(ImGuiMultiSelectState* storage) +{ +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + const bool is_active = (storage->LastFrameActive >= GetFrameCount() - 2); // Note that fully clipped early out scrolling tables will appear as inactive here. + if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); } + bool open = TreeNode((void*)(intptr_t)storage->ID, "MultiSelect 0x%08X in '%s'%s", storage->ID, storage->Window ? storage->Window->Name : "N/A", is_active ? "" : " *Inactive*"); + if (!is_active) { PopStyleColor(); } + if (!open) + return; + Text("RangeSrcItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), RangeSelected = %d", storage->RangeSrcItem, storage->RangeSrcItem, storage->RangeSelected); + Text("NavIdItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), NavIdSelected = %d", storage->NavIdItem, storage->NavIdItem, storage->NavIdSelected); + Text("LastSelectionSize = %d", storage->LastSelectionSize); // Provided by user + TreePop(); +#else + IM_UNUSED(storage); +#endif +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Multi-Select helpers +//------------------------------------------------------------------------- +// - ImGuiSelectionBasicStorage +// - ImGuiSelectionExternalStorage +//------------------------------------------------------------------------- + +ImGuiSelectionBasicStorage::ImGuiSelectionBasicStorage() +{ + Size = 0; + PreserveOrder = false; + UserData = NULL; + AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; }; + _SelectionOrder = 1; // Always >0 +} + +void ImGuiSelectionBasicStorage::Clear() +{ + Size = 0; + _SelectionOrder = 1; // Always >0 + _Storage.Data.resize(0); +} + +void ImGuiSelectionBasicStorage::Swap(ImGuiSelectionBasicStorage& r) +{ + ImSwap(Size, r.Size); + ImSwap(_SelectionOrder, r._SelectionOrder); + _Storage.Data.swap(r._Storage.Data); +} + +bool ImGuiSelectionBasicStorage::Contains(ImGuiID id) const +{ + return _Storage.GetInt(id, 0) != 0; +} + +static int IMGUI_CDECL PairComparerByValueInt(const void* lhs, const void* rhs) +{ + int lhs_v = ((const ImGuiStoragePair*)lhs)->val_i; + int rhs_v = ((const ImGuiStoragePair*)rhs)->val_i; + return (lhs_v > rhs_v ? +1 : lhs_v < rhs_v ? -1 : 0); +} + +// GetNextSelectedItem() is an abstraction allowing us to change our underlying actual storage system without impacting user. +// (e.g. store unselected vs compact down, compact down on demand, use raw ImVector instead of ImGuiStorage...) +bool ImGuiSelectionBasicStorage::GetNextSelectedItem(void** opaque_it, ImGuiID* out_id) +{ + ImGuiStoragePair* it = (ImGuiStoragePair*)*opaque_it; + ImGuiStoragePair* it_end = _Storage.Data.Data + _Storage.Data.Size; + if (PreserveOrder && it == NULL && it_end != NULL) + ImQsort(_Storage.Data.Data, (size_t)_Storage.Data.Size, sizeof(ImGuiStoragePair), PairComparerByValueInt); // ~ImGuiStorage::BuildSortByValueInt() + if (it == NULL) + it = _Storage.Data.Data; + IM_ASSERT(it >= _Storage.Data.Data && it <= it_end); + if (it != it_end) + while (it->val_i == 0 && it < it_end) + it++; + const bool has_more = (it != it_end); + *opaque_it = has_more ? (void**)(it + 1) : (void**)(it); + *out_id = has_more ? it->key : 0; + if (PreserveOrder && !has_more) + _Storage.BuildSortByKey(); + return has_more; +} + +void ImGuiSelectionBasicStorage::SetItemSelected(ImGuiID id, bool selected) +{ + int* p_int = _Storage.GetIntRef(id, 0); + if (selected && *p_int == 0) { *p_int = _SelectionOrder++; Size++; } + else if (!selected && *p_int != 0) { *p_int = 0; Size--; } +} + +// Optimized for batch edits (with same value of 'selected') +static void ImGuiSelectionBasicStorage_BatchSetItemSelected(ImGuiSelectionBasicStorage* selection, ImGuiID id, bool selected, int size_before_amends, int selection_order) +{ + ImGuiStorage* storage = &selection->_Storage; + ImGuiStoragePair* it = ImLowerBound(storage->Data.Data, storage->Data.Data + size_before_amends, id); + const bool is_contained = (it != storage->Data.Data + size_before_amends) && (it->key == id); + if (selected == (is_contained && it->val_i != 0)) + return; + if (selected && !is_contained) + storage->Data.push_back(ImGuiStoragePair(id, selection_order)); // Push unsorted at end of vector, will be sorted in SelectionMultiAmendsFinish() + else if (is_contained) + it->val_i = selected ? selection_order : 0; // Modify in-place. + selection->Size += selected ? +1 : -1; +} + +static void ImGuiSelectionBasicStorage_BatchFinish(ImGuiSelectionBasicStorage* selection, bool selected, int size_before_amends) +{ + ImGuiStorage* storage = &selection->_Storage; + if (selected && selection->Size != size_before_amends) + storage->BuildSortByKey(); // When done selecting: sort everything +} + +// Apply requests coming from BeginMultiSelect() and EndMultiSelect(). +// - Enable 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen. +// - Honoring SetRange requests requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem. +// - In this demo we often submit indices to SetNextItemSelectionUserData() + store the same indices in persistent selection. +// - Your code may do differently. If you store pointers or objects ID in ImGuiSelectionUserData you may need to perform +// a lookup in order to have some way to iterate/interpolate between two items. +// - A full-featured application is likely to allow search/filtering which is likely to lead to using indices +// and constructing a view index <> object id/ptr data structure anyway. +// WHEN YOUR APPLICATION SETTLES ON A CHOICE, YOU WILL PROBABLY PREFER TO GET RID OF THIS UNNECESSARY 'ImGuiSelectionBasicStorage' INDIRECTION LOGIC. +// Notice that with the simplest adapter (using indices everywhere), all functions return their parameters. +// The most simple implementation (using indices everywhere) would look like: +// for (ImGuiSelectionRequest& req : ms_io->Requests) +// { +// if (req.Type == ImGuiSelectionRequestType_SetAll) { Clear(); if (req.Selected) { for (int n = 0; n < items_count; n++) { SetItemSelected(n, true); } } +// if (req.Type == ImGuiSelectionRequestType_SetRange) { for (int n = (int)ms_io->RangeFirstItem; n <= (int)ms_io->RangeLastItem; n++) { SetItemSelected(n, ms_io->Selected); } } +// } +void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) +{ + // For convenience we obtain ItemsCount as passed to BeginMultiSelect(), which is optional. + // It makes sense when using ImGuiSelectionBasicStorage to simply pass your items count to BeginMultiSelect(). + // Other scheme may handle SetAll differently. + IM_ASSERT(ms_io->ItemsCount != -1 && "Missing value for items_count in BeginMultiSelect() call!"); + IM_ASSERT(AdapterIndexToStorageId != NULL); + + // This is optimized/specialized to cope with very large selections (e.g. 100k+ items) + // - A simpler version could call SetItemSelected() directly instead of ImGuiSelectionBasicStorage_BatchSetItemSelected() + ImGuiSelectionBasicStorage_BatchFinish(). + // - Optimized select can append unsorted, then sort in a second pass. Optimized unselect can clear in-place then compact in a second pass. + // - A more optimal version wouldn't even use ImGuiStorage but directly a ImVector to reduce bandwidth, but this is a reasonable trade off to reuse code. + // - There are many ways this could be better optimized. The worse case scenario being: using BoxSelect2d in a grid, box-select scrolling down while wiggling + // left and right: it affects coarse clipping + can emit multiple SetRange with 1 item each.) + // FIXME-OPT: For each block of consecutive SetRange request: + // - add all requests to a sorted list, store ID, selected, offset in ImGuiStorage. + // - rewrite sorted storage a single time. + for (ImGuiSelectionRequest& req : ms_io->Requests) + { + if (req.Type == ImGuiSelectionRequestType_SetAll) + { + Clear(); + if (req.Selected) + { + _Storage.Data.reserve(ms_io->ItemsCount); + const int size_before_amends = _Storage.Data.Size; + for (int idx = 0; idx < ms_io->ItemsCount; idx++, _SelectionOrder++) + ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, _SelectionOrder); + ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends); + } + } + else if (req.Type == ImGuiSelectionRequestType_SetRange) + { + const int selection_changes = (int)req.RangeLastItem - (int)req.RangeFirstItem + 1; + //ImGuiContext& g = *GImGui; IMGUI_DEBUG_LOG_SELECTION("Req %d/%d: set %d to %d\n", ms_io->Requests.index_from_ptr(&req), ms_io->Requests.Size, selection_changes, req.Selected); + if (selection_changes == 1 || (selection_changes < Size / 100)) + { + // Multiple sorted insertion + copy likely to be faster. + // Technically we could do a single copy with a little more work (sort sequential SetRange requests) + for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++) + SetItemSelected(GetStorageIdFromIndex(idx), req.Selected); + } + else + { + // Append insertion + single sort likely be faster. + // Use req.RangeDirection to set order field so that shift+clicking from 1 to 5 is different than shift+clicking from 5 to 1 + const int size_before_amends = _Storage.Data.Size; + int selection_order = _SelectionOrder + ((req.RangeDirection < 0) ? selection_changes - 1 : 0); + for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++, selection_order += req.RangeDirection) + ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, selection_order); + if (req.Selected) + _SelectionOrder += selection_changes; + ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends); + } + } + } +} + +//------------------------------------------------------------------------- + +ImGuiSelectionExternalStorage::ImGuiSelectionExternalStorage() +{ + UserData = NULL; + AdapterSetItemSelected = NULL; +} + +// Apply requests coming from BeginMultiSelect() and EndMultiSelect(). +// We also pull 'ms_io->ItemsCount' as passed for BeginMultiSelect() for consistency with ImGuiSelectionBasicStorage +// This makes no assumption about underlying storage. +void ImGuiSelectionExternalStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) +{ + IM_ASSERT(AdapterSetItemSelected); + for (ImGuiSelectionRequest& req : ms_io->Requests) + { + if (req.Type == ImGuiSelectionRequestType_SetAll) + for (int idx = 0; idx < ms_io->ItemsCount; idx++) + AdapterSetItemSelected(this, idx, req.Selected); + if (req.Type == ImGuiSelectionRequestType_SetRange) + for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++) + AdapterSetItemSelected(this, idx, req.Selected); + } +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: ListBox +//------------------------------------------------------------------------- +// - BeginListBox() +// - EndListBox() +// - ListBox() +//------------------------------------------------------------------------- + +// This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label. +// This handle some subtleties with capturing info from the label. +// If you don't need a label you can pretty much directly use ImGui::BeginChild() with ImGuiChildFlags_FrameStyle. +// Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty" +// Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.5f * item_height). +bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + const ImGuiID id = GetID(label); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + + // Size default to hold ~7.25 items. + // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. + ImVec2 size = ImTrunc(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f)); + ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); + ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); + ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); + g.NextItemData.ClearFlags(); + + if (!IsRectVisible(bb.Min, bb.Max)) + { + ItemSize(bb.GetSize(), style.FramePadding.y); + ItemAdd(bb, 0, &frame_bb); + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values + return false; + } + + // FIXME-OPT: We could omit the BeginGroup() if label_size.x == 0.0f but would need to omit the EndGroup() as well. + BeginGroup(); + if (label_size.x > 0.0f) + { + ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y); + RenderText(label_pos, label); + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size); + AlignTextToFramePadding(); + } + + BeginChild(id, frame_bb.GetSize(), ImGuiChildFlags_FrameStyle); + return true; +} + +void ImGui::EndListBox() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) && "Mismatched BeginListBox/EndListBox calls. Did you test the return value of BeginListBox?"); + IM_UNUSED(window); + + EndChild(); + EndGroup(); // This is only required to be able to do IsItemXXX query on the whole ListBox including label +} + +bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items) +{ + const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items); + return value_changed; +} + +// This is merely a helper around BeginListBox(), EndListBox(). +// Considering using those directly to submit custom data or store selection differently. +bool ImGui::ListBox(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int height_in_items) +{ + ImGuiContext& g = *GImGui; + + // Calculate size from "height_in_items" + if (height_in_items < 0) + height_in_items = ImMin(items_count, 7); + float height_in_items_f = height_in_items + 0.25f; + ImVec2 size(0.0f, ImTrunc(GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f)); + + if (!BeginListBox(label, size)) + return false; + + // Assume all items have even height (= 1 line of text). If you need items of different height, + // you can create a custom version of ListBox() in your code without using the clipper. + bool value_changed = false; + ImGuiListClipper clipper; + clipper.Begin(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to. + clipper.IncludeItemByIndex(*current_item); + while (clipper.Step()) + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + const char* item_text = getter(user_data, i); + if (item_text == NULL) + item_text = "*Unknown item*"; + + PushID(i); + const bool item_selected = (i == *current_item); + if (Selectable(item_text, item_selected)) + { + *current_item = i; + value_changed = true; + } + if (item_selected) + SetItemDefaultFocus(); + PopID(); + } + EndListBox(); + + if (value_changed) + MarkItemEdited(g.LastItemData.ID); + + return value_changed; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: PlotLines, PlotHistogram +//------------------------------------------------------------------------- +// - PlotEx() [Internal] +// - PlotLines() +// - PlotHistogram() +//------------------------------------------------------------------------- +// Plot/Graph widgets are not very good. +// Consider writing your own, or using a third-party one, see: +// - ImPlot https://github.com/epezent/implot +// - others https://github.com/ocornut/imgui/wiki/Useful-Extensions +//------------------------------------------------------------------------- + +int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, const ImVec2& size_arg) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return -1; + + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f); + + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); + const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); + const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0)); + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_NoNav)) + return -1; + bool hovered; + ButtonBehavior(frame_bb, id, &hovered, NULL); + + // Determine scale from values if not specified + if (scale_min == FLT_MAX || scale_max == FLT_MAX) + { + float v_min = FLT_MAX; + float v_max = -FLT_MAX; + for (int i = 0; i < values_count; i++) + { + const float v = values_getter(data, i); + if (v != v) // Ignore NaN values + continue; + v_min = ImMin(v_min, v); + v_max = ImMax(v_max, v); + } + if (scale_min == FLT_MAX) + scale_min = v_min; + if (scale_max == FLT_MAX) + scale_max = v_max; + } + + RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + + const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1; + int idx_hovered = -1; + if (values_count >= values_count_min) + { + int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); + int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); + + // Tooltip on hover + if (hovered && inner_bb.Contains(g.IO.MousePos)) + { + const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f); + const int v_idx = (int)(t * item_count); + IM_ASSERT(v_idx >= 0 && v_idx < values_count); + + const float v0 = values_getter(data, (v_idx + values_offset) % values_count); + const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count); + if (plot_type == ImGuiPlotType_Lines) + SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1); + else if (plot_type == ImGuiPlotType_Histogram) + SetTooltip("%d: %8.4g", v_idx, v0); + idx_hovered = v_idx; + } + + const float t_step = 1.0f / (float)res_w; + const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min)); + + float v0 = values_getter(data, (0 + values_offset) % values_count); + float t0 = 0.0f; + ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle + float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (1 + scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands + + const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram); + const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered); + + for (int n = 0; n < res_w; n++) + { + const float t1 = t0 + t_step; + const int v1_idx = (int)(t0 * item_count + 0.5f); + IM_ASSERT(v1_idx >= 0 && v1_idx < values_count); + const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count); + const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) ); + + // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU. + ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0); + ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t)); + if (plot_type == ImGuiPlotType_Lines) + { + window->DrawList->AddLine(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base); + } + else if (plot_type == ImGuiPlotType_Histogram) + { + if (pos1.x >= pos0.x + 2.0f) + pos1.x -= 1.0f; + window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base); + } + + t0 = t1; + tp0 = tp1; + } + } + + // Text overlay + if (overlay_text) + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f, 0.0f)); + + if (label_size.x > 0.0f) + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); + + // Return hovered index or -1 if none are hovered. + // This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx(). + return idx_hovered; +} + +struct ImGuiPlotArrayGetterData +{ + const float* Values; + int Stride; + + ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; } +}; + +static float Plot_ArrayGetter(void* data, int idx) +{ + ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data; + const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride); + return v; +} + +void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) +{ + ImGuiPlotArrayGetterData data(values, stride); + PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) +{ + ImGuiPlotArrayGetterData data(values, stride); + PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) +{ + PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Value helpers +// Those is not very useful, legacy API. +//------------------------------------------------------------------------- +// - Value() +//------------------------------------------------------------------------- + +void ImGui::Value(const char* prefix, bool b) +{ + Text("%s: %s", prefix, (b ? "true" : "false")); +} + +void ImGui::Value(const char* prefix, int v) +{ + Text("%s: %d", prefix, v); +} + +void ImGui::Value(const char* prefix, unsigned int v) +{ + Text("%s: %d", prefix, v); +} + +void ImGui::Value(const char* prefix, float v, const char* float_format) +{ + if (float_format) + { + char fmt[64]; + ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format); + Text(fmt, prefix, v); + } + else + { + Text("%s: %.3f", prefix, v); + } +} + +//------------------------------------------------------------------------- +// [SECTION] MenuItem, BeginMenu, EndMenu, etc. +//------------------------------------------------------------------------- +// - ImGuiMenuColumns [Internal] +// - BeginMenuBar() +// - EndMenuBar() +// - BeginMainMenuBar() +// - EndMainMenuBar() +// - BeginMenu() +// - EndMenu() +// - MenuItemEx() [Internal] +// - MenuItem() +//------------------------------------------------------------------------- + +// Helpers for internal use +void ImGuiMenuColumns::Update(float spacing, bool window_reappearing) +{ + if (window_reappearing) + memset(Widths, 0, sizeof(Widths)); + Spacing = (ImU16)spacing; + CalcNextTotalWidth(true); + memset(Widths, 0, sizeof(Widths)); + TotalWidth = NextTotalWidth; + NextTotalWidth = 0; +} + +void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets) +{ + ImU16 offset = 0; + bool want_spacing = false; + for (int i = 0; i < IM_ARRAYSIZE(Widths); i++) + { + ImU16 width = Widths[i]; + if (want_spacing && width > 0) + offset += Spacing; + want_spacing |= (width > 0); + if (update_offsets) + { + if (i == 1) { OffsetLabel = offset; } + if (i == 2) { OffsetShortcut = offset; } + if (i == 3) { OffsetMark = offset; } + } + offset += width; + } + NextTotalWidth = offset; +} + +float ImGuiMenuColumns::DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark) +{ + Widths[0] = ImMax(Widths[0], (ImU16)w_icon); + Widths[1] = ImMax(Widths[1], (ImU16)w_label); + Widths[2] = ImMax(Widths[2], (ImU16)w_shortcut); + Widths[3] = ImMax(Widths[3], (ImU16)w_mark); + CalcNextTotalWidth(false); + return (float)ImMax(TotalWidth, NextTotalWidth); +} + +// FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere.. +// Currently the main responsibility of this function being to setup clip-rect + horizontal layout + menu navigation layer. +// Ideally we also want this to be responsible for claiming space out of the main window scrolling rectangle, in which case ImGuiWindowFlags_MenuBar will become unnecessary. +// Then later the same system could be used for multiple menu-bars, scrollbars, side-bars. +bool ImGui::BeginMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + if (!(window->Flags & ImGuiWindowFlags_MenuBar)) + return false; + + IM_ASSERT(!window->DC.MenuBarAppending); + BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore + PushID("##menubar"); + + // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect. + // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy. + ImRect bar_rect = window->MenuBarRect(); + ImRect clip_rect(ImFloor(bar_rect.Min.x + window->WindowBorderSize), ImFloor(bar_rect.Min.y + window->WindowBorderSize), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize))), ImFloor(bar_rect.Max.y)); + clip_rect.ClipWith(window->OuterRectClipped); + PushClipRect(clip_rect.Min, clip_rect.Max, false); + + // We overwrite CursorMaxPos because BeginGroup sets it to CursorPos (essentially the .EmitItem hack in EndMenuBar() would need something analogous here, maybe a BeginGroupEx() with flags). + window->DC.CursorPos = window->DC.CursorMaxPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y); + window->DC.LayoutType = ImGuiLayoutType_Horizontal; + window->DC.IsSameLine = false; + window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; + window->DC.MenuBarAppending = true; + AlignTextToFramePadding(); + return true; +} + +void ImGui::EndMenuBar() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + ImGuiContext& g = *GImGui; + + // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings. + if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) + { + // Try to find out if the request is for one of our child menu + ImGuiWindow* nav_earliest_child = g.NavWindow; + while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu)) + nav_earliest_child = nav_earliest_child->ParentWindow; + if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0) + { + // To do so we claim focus back, restore NavId and then process the movement request for yet another frame. + // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth bothering) + const ImGuiNavLayer layer = ImGuiNavLayer_Menu; + IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check (FIXME: Seems unnecessary) + FocusWindow(window); + SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); + // FIXME-NAV: How to deal with this when not using g.IO.ConfigNavCursorVisibleAuto? + if (g.NavCursorVisible) + { + g.NavCursorVisible = false; // Hide nav cursor for the current frame so we don't see the intermediary selection. Will be set again + g.NavCursorHideFrames = 2; + } + g.NavHighlightItemUnderNav = g.NavMousePosDirty = true; + NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); // Repeat + } + } + + IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'" + IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); + IM_ASSERT(window->DC.MenuBarAppending); + PopClipRect(); + PopID(); + window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->Pos.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos. + + // FIXME: Extremely confusing, cleanup by (a) working on WorkRect stack system (b) not using a Group confusingly here. + ImGuiGroupData& group_data = g.GroupStack.back(); + group_data.EmitItem = false; + ImVec2 restore_cursor_max_pos = group_data.BackupCursorMaxPos; + window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, window->DC.CursorMaxPos.x - window->Scroll.x); // Convert ideal extents for scrolling layer equivalent. + EndGroup(); // Restore position on layer 0 // FIXME: Misleading to use a group for that backup/restore + window->DC.LayoutType = ImGuiLayoutType_Vertical; + window->DC.IsSameLine = false; + window->DC.NavLayerCurrent = ImGuiNavLayer_Main; + window->DC.MenuBarAppending = false; + window->DC.CursorMaxPos = restore_cursor_max_pos; +} + +// Important: calling order matters! +// FIXME: Somehow overlapping with docking tech. +// FIXME: The "rect-cut" aspect of this could be formalized into a lower-level helper (rect-cut: https://halt.software/dead-simple-layouts) +bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, ImGuiDir dir, float axis_size, ImGuiWindowFlags window_flags) +{ + IM_ASSERT(dir != ImGuiDir_None); + + ImGuiWindow* bar_window = FindWindowByName(name); + ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport()); + if (bar_window == NULL || bar_window->BeginCount == 0) + { + // Calculate and set window size/position + ImRect avail_rect = viewport->GetBuildWorkRect(); + ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X; + ImVec2 pos = avail_rect.Min; + if (dir == ImGuiDir_Right || dir == ImGuiDir_Down) + pos[axis] = avail_rect.Max[axis] - axis_size; + ImVec2 size = avail_rect.GetSize(); + size[axis] = axis_size; + SetNextWindowPos(pos); + SetNextWindowSize(size); + + // Report our size into work area (for next frame) using actual window size + if (dir == ImGuiDir_Up || dir == ImGuiDir_Left) + viewport->BuildWorkInsetMin[axis] += axis_size; + else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right) + viewport->BuildWorkInsetMax[axis] += axis_size; + } + + window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking; + SetNextWindowViewport(viewport->ID); // Enforce viewport so we don't create our own viewport when ImGuiConfigFlags_ViewportsNoMerge is set. + PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint + bool is_open = Begin(name, NULL, window_flags); + PopStyleVar(2); + + return is_open; +} + +bool ImGui::BeginMainMenuBar() +{ + ImGuiContext& g = *GImGui; + ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport(); + + // Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change + SetCurrentViewport(NULL, viewport); + + // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set. + // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea? + // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings. + g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f)); + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; + float height = GetFrameHeight(); + bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags); + g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); + + if (!is_open) + { + End(); + return false; + } + + // Temporarily disable _NoSavedSettings, in the off-chance that tables or child windows submitted within the menu-bar may want to use settings. (#8356) + g.CurrentWindow->Flags &= ~ImGuiWindowFlags_NoSavedSettings; + BeginMenuBar(); + return is_open; +} + +void ImGui::EndMainMenuBar() +{ + ImGuiContext& g = *GImGui; + if (!g.CurrentWindow->DC.MenuBarAppending) + { + IM_ASSERT_USER_ERROR(0, "Calling EndMainMenuBar() not from a menu-bar!"); // Not technically testing that it is the main menu bar + return; + } + + EndMenuBar(); + g.CurrentWindow->Flags |= ImGuiWindowFlags_NoSavedSettings; // Restore _NoSavedSettings (#8356) + + // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window + // FIXME: With this strategy we won't be able to restore a NULL focus. + if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest && g.ActiveId == 0) + FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild); + + End(); +} + +static bool IsRootOfOpenMenuSet() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if ((g.OpenPopupStack.Size <= g.BeginPopupStack.Size) || (window->Flags & ImGuiWindowFlags_ChildMenu)) + return false; + + // Initially we used 'upper_popup->OpenParentId == window->IDStack.back()' to differentiate multiple menu sets from each others + // (e.g. inside menu bar vs loose menu items) based on parent ID. + // This would however prevent the use of e.g. PushID() user code submitting menus. + // Previously this worked between popup and a first child menu because the first child menu always had the _ChildWindow flag, + // making hovering on parent popup possible while first child menu was focused - but this was generally a bug with other side effects. + // Instead we don't treat Popup specifically (in order to consistently support menu features in them), maybe the first child menu of a Popup + // doesn't have the _ChildWindow flag, and we rely on this IsRootOfOpenMenuSet() check to allow hovering between root window/popup and first child menu. + // In the end, lack of ID check made it so we could no longer differentiate between separate menu sets. To compensate for that, we at least check parent window nav layer. + // This fixes the most common case of menu opening on hover when moving between window content and menu bar. Multiple different menu sets in same nav layer would still + // open on hover, but that should be a lesser problem, because if such menus are close in proximity in window content then it won't feel weird and if they are far apart + // it likely won't be a problem anyone runs into. + const ImGuiPopupData* upper_popup = &g.OpenPopupStack[g.BeginPopupStack.Size]; + if (window->DC.NavLayerCurrent != upper_popup->ParentNavLayer) + return false; + return upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu) && ImGui::IsWindowChildOf(upper_popup->Window, window, true, false); +} + +bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None); + + // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu) + // The first menu in a hierarchy isn't so hovering doesn't get across (otherwise e.g. resizing borders with ImGuiButtonFlags_FlattenChildren would react), but top-most BeginMenu() will bypass that limitation. + ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus; + if (window->Flags & ImGuiWindowFlags_ChildMenu) + window_flags |= ImGuiWindowFlags_ChildWindow; + + // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin(). + // We are relying on a O(N) search - so O(N log N) over the frame - which seems like the most efficient for the expected small amount of BeginMenu() calls per frame. + // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used. + if (g.MenusIdSubmittedThisFrame.contains(id)) + { + if (menu_is_open) + menu_is_open = BeginPopupEx(id, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + else + g.NextWindowData.ClearFlags(); // we behave like Begin() and need to consume those values + return menu_is_open; + } + + // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu + g.MenusIdSubmittedThisFrame.push_back(id); + + ImVec2 label_size = CalcTextSize(label, NULL, true); + + // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent without always being a Child window) + // This is only done for items for the menu set and not the full parent window. + const bool menuset_is_open = IsRootOfOpenMenuSet(); + if (menuset_is_open) + PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true); + + // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu, + // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup(). + // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering. + ImVec2 popup_pos, pos = window->DC.CursorPos; + PushID(label); + if (!enabled) + BeginDisabled(); + const ImGuiMenuColumns* offsets = &window->DC.MenuColumns; + bool pressed; + + // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another. + const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + // Menu inside an horizontal menu bar + // Selectable extend their highlight by half ItemSpacing in each direction. + // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin() + popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight); + window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f); + PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f); + float w = label_size.x; + ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + pressed = Selectable("", menu_is_open, selectable_flags, ImVec2(w, label_size.y)); + LogSetNextTextDecoration("[", "]"); + RenderText(text_pos, label); + PopStyleVar(); + window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + // Menu inside a regular/vertical menu + // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. + // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. + popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); + float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f; + float checkmark_w = IM_TRUNC(g.FontSize * 1.20f); + float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame + float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); + ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); + LogSetNextTextDecoration("", ">"); + RenderText(text_pos, label); + if (icon_w > 0.0f) + RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); + RenderArrow(window->DrawList, pos + ImVec2(offsets->OffsetMark + extra_w + g.FontSize * 0.30f, 0.0f), GetColorU32(ImGuiCol_Text), ImGuiDir_Right); + } + if (!enabled) + EndDisabled(); + + const bool hovered = (g.HoveredId == id) && enabled && !g.NavHighlightItemUnderNav; + if (menuset_is_open) + PopItemFlag(); + + bool want_open = false; + bool want_open_nav_init = false; + bool want_close = false; + if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) + { + // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu + // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. + bool moving_toward_child_menu = false; + ImGuiPopupData* child_popup = (g.BeginPopupStack.Size < g.OpenPopupStack.Size) ? &g.OpenPopupStack[g.BeginPopupStack.Size] : NULL; // Popup candidate (testing below) + ImGuiWindow* child_menu_window = (child_popup && child_popup->Window && child_popup->Window->ParentWindow == window) ? child_popup->Window : NULL; + if (g.HoveredWindow == window && child_menu_window != NULL) + { + const float ref_unit = g.FontSize; // FIXME-DPI + const float child_dir = (window->Pos.x < child_menu_window->Pos.x) ? 1.0f : -1.0f; + const ImRect next_window_rect = child_menu_window->Rect(); + ImVec2 ta = (g.IO.MousePos - g.IO.MouseDelta); + ImVec2 tb = (child_dir > 0.0f) ? next_window_rect.GetTL() : next_window_rect.GetTR(); + ImVec2 tc = (child_dir > 0.0f) ? next_window_rect.GetBL() : next_window_rect.GetBR(); + const float pad_farmost_h = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, ref_unit * 0.5f, ref_unit * 2.5f); // Add a bit of extra slack. + ta.x += child_dir * -0.5f; + tb.x += child_dir * ref_unit; + tc.x += child_dir * ref_unit; + tb.y = ta.y + ImMax((tb.y - pad_farmost_h) - ta.y, -ref_unit * 8.0f); // Triangle has maximum height to limit the slope and the bias toward large sub-menus + tc.y = ta.y + ImMin((tc.y + pad_farmost_h) - ta.y, +ref_unit * 8.0f); + moving_toward_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); + //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_toward_child_menu ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG] + } + + // The 'HovereWindow == window' check creates an inconsistency (e.g. moving away from menu slowly tends to hit same window, whereas moving away fast does not) + // But we also need to not close the top-menu menu when moving over void. Perhaps we should extend the triangle check to a larger polygon. + // (Remember to test this on BeginPopup("A")->BeginMenu("B") sequence which behaves slightly differently as B isn't a Child of A and hovering isn't shared.) + if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu && !g.NavHighlightItemUnderNav && g.ActiveId == 0) + want_close = true; + + // Open + // (note: at this point 'hovered' actually includes the NavDisableMouseHover == false test) + if (!menu_is_open && pressed) // Click/activate to open + want_open = true; + else if (!menu_is_open && hovered && !moving_toward_child_menu) // Hover to open + want_open = true; + else if (!menu_is_open && hovered && g.HoveredIdTimer >= 0.30f && g.MouseStationaryTimer >= 0.30f) // Hover to open (timer fallback) + want_open = true; + if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open + { + want_open = want_open_nav_init = true; + NavMoveRequestCancel(); + SetNavCursorVisibleAfterMove(); + } + } + else + { + // Menu bar + if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it + { + want_close = true; + want_open = menu_is_open = false; + } + else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others + { + want_open = true; + } + else if (g.NavId == id && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open + { + want_open = true; + NavMoveRequestCancel(); + } + } + + if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }' + want_close = true; + if (want_close && IsPopupOpen(id, ImGuiPopupFlags_None)) + ClosePopupToLevel(g.BeginPopupStack.Size, true); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0)); + PopID(); + + if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size) + { + // Don't reopen/recycle same menu level in the same frame if it is a different menu ID, first close the other menu and yield for a frame. + OpenPopup(label); + } + else if (want_open) + { + menu_is_open = true; + OpenPopup(label, ImGuiPopupFlags_NoReopen);// | (want_open_nav_init ? ImGuiPopupFlags_NoReopenAlwaysNavInit : 0)); + } + + if (menu_is_open) + { + ImGuiLastItemData last_item_in_parent = g.LastItemData; + SetNextWindowPos(popup_pos, ImGuiCond_Always); // Note: misleading: the value will serve as reference for FindBestWindowPosForPopup(), not actual pos. + PushStyleVar(ImGuiStyleVar_ChildRounding, style.PopupRounding); // First level will use _PopupRounding, subsequent will use _ChildRounding + menu_is_open = BeginPopupEx(id, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + PopStyleVar(); + if (menu_is_open) + { + // Implement what ImGuiPopupFlags_NoReopenAlwaysNavInit would do: + // Perform an init request in the case the popup was already open (via a previous mouse hover) + if (want_open && want_open_nav_init && !g.NavInitRequest) + { + FocusWindow(g.CurrentWindow, ImGuiFocusRequestFlags_UnlessBelowModal); + NavInitWindow(g.CurrentWindow, false); + } + + // Restore LastItemData so IsItemXXXX functions can work after BeginMenu()/EndMenu() + // (This fixes using IsItemClicked() and IsItemHovered(), but IsItemHovered() also relies on its support for ImGuiItemFlags_NoWindowHoverableCheck) + g.LastItemData = last_item_in_parent; + if (g.HoveredWindow == window) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + } + } + else + { + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values + } + + return menu_is_open; +} + +bool ImGui::BeginMenu(const char* label, bool enabled) +{ + return BeginMenuEx(label, NULL, enabled); +} + +void ImGui::EndMenu() +{ + // Nav: When a left move request our menu failed, close ourselves. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginMenu()/EndMenu() calls + ImGuiWindow* parent_window = window->ParentWindow; // Should always be != NULL is we passed assert. + if (window->BeginCount == window->BeginCountPreviousFrame) + if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet()) + if (g.NavWindow && (g.NavWindow->RootWindowForNav == window) && parent_window->DC.LayoutType == ImGuiLayoutType_Vertical) + { + ClosePopupToLevel(g.BeginPopupStack.Size - 1, true); + NavMoveRequestCancel(); + } + + EndPopup(); +} + +bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut, bool selected, bool enabled) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImVec2 pos = window->DC.CursorPos; + ImVec2 label_size = CalcTextSize(label, NULL, true); + + // See BeginMenuEx() for comments about this. + const bool menuset_is_open = IsRootOfOpenMenuSet(); + if (menuset_is_open) + PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true); + + // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73), + // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only. + bool pressed; + PushID(label); + if (!enabled) + BeginDisabled(); + + // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another. + const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SetNavIdOnHover; + const ImGuiMenuColumns* offsets = &window->DC.MenuColumns; + if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) + { + // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful + // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark. + float w = label_size.x; + window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f); + ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f); + pressed = Selectable("", selected, selectable_flags, ImVec2(w, 0.0f)); + PopStyleVar(); + if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) + RenderText(text_pos, label); + window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). + } + else + { + // Menu item inside a vertical menu + // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. + // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. + float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f; + float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f; + float checkmark_w = IM_TRUNC(g.FontSize * 1.20f); + float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame + float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); + pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); + if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) + { + RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label); + if (icon_w > 0.0f) + RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); + if (shortcut_w > 0.0f) + { + PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); + LogSetNextTextDecoration("(", ")"); + RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false); + PopStyleColor(); + } + if (selected) + RenderCheckMark(window->DrawList, pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f); + } + } + IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0)); + if (!enabled) + EndDisabled(); + PopID(); + if (menuset_is_open) + PopItemFlag(); + + return pressed; +} + +bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) +{ + return MenuItemEx(label, NULL, shortcut, selected, enabled); +} + +bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) +{ + if (MenuItemEx(label, NULL, shortcut, p_selected ? *p_selected : false, enabled)) + { + if (p_selected) + *p_selected = !*p_selected; + return true; + } + return false; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: BeginTabBar, EndTabBar, etc. +//------------------------------------------------------------------------- +// - BeginTabBar() +// - BeginTabBarEx() [Internal] +// - EndTabBar() +// - TabBarLayout() [Internal] +// - TabBarCalcTabID() [Internal] +// - TabBarCalcMaxTabWidth() [Internal] +// - TabBarFindTabById() [Internal] +// - TabBarFindTabByOrder() [Internal] +// - TabBarFindMostRecentlySelectedTabForActiveWindow() [Internal] +// - TabBarGetCurrentTab() [Internal] +// - TabBarGetTabName() [Internal] +// - TabBarAddTab() [Internal] +// - TabBarRemoveTab() [Internal] +// - TabBarCloseTab() [Internal] +// - TabBarScrollClamp() [Internal] +// - TabBarScrollToTab() [Internal] +// - TabBarQueueFocus() [Internal] +// - TabBarQueueReorder() [Internal] +// - TabBarProcessReorderFromMousePos() [Internal] +// - TabBarProcessReorder() [Internal] +// - TabBarScrollingButtons() [Internal] +// - TabBarTabListPopupButton() [Internal] +//------------------------------------------------------------------------- + +struct ImGuiTabBarSection +{ + int TabCount; // Number of tabs in this section. + float Width; // Sum of width of tabs in this section (after shrinking down) + float Spacing; // Horizontal spacing at the end of the section. + + ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); } +}; + +namespace ImGui +{ + static void TabBarLayout(ImGuiTabBar* tab_bar); + static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window); + static float TabBarCalcMaxTabWidth(); + static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling); + static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections); + static ImGuiTabItem* TabBarScrollingButtons(ImGuiTabBar* tab_bar); + static ImGuiTabItem* TabBarTabListPopupButton(ImGuiTabBar* tab_bar); +} + +ImGuiTabBar::ImGuiTabBar() +{ + memset(this, 0, sizeof(*this)); + CurrFrameVisible = PrevFrameVisible = -1; + LastTabItemIdx = -1; +} + +static inline int TabItemGetSectionIdx(const ImGuiTabItem* tab) +{ + return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1; +} + +static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs) +{ + const ImGuiTabItem* a = (const ImGuiTabItem*)lhs; + const ImGuiTabItem* b = (const ImGuiTabItem*)rhs; + const int a_section = TabItemGetSectionIdx(a); + const int b_section = TabItemGetSectionIdx(b); + if (a_section != b_section) + return a_section - b_section; + return (int)(a->IndexDuringLayout - b->IndexDuringLayout); +} + +static int IMGUI_CDECL TabItemComparerByBeginOrder(const void* lhs, const void* rhs) +{ + const ImGuiTabItem* a = (const ImGuiTabItem*)lhs; + const ImGuiTabItem* b = (const ImGuiTabItem*)rhs; + return (int)(a->BeginOrder - b->BeginOrder); +} + +static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref) +{ + ImGuiContext& g = *GImGui; + return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index); +} + +static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar) +{ + ImGuiContext& g = *GImGui; + if (g.TabBars.Contains(tab_bar)) + return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar)); + return ImGuiPtrOrIndex(tab_bar); +} + +bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + ImGuiID id = window->GetID(str_id); + ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id); + ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->WorkRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2); + tab_bar->ID = id; + tab_bar->SeparatorMinX = tab_bar->BarRect.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f); + tab_bar->SeparatorMaxX = tab_bar->BarRect.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f); + //if (g.NavWindow && IsWindowChildOf(g.NavWindow, window, false, false)) + flags |= ImGuiTabBarFlags_IsFocused; + return BeginTabBarEx(tab_bar, tab_bar_bb, flags); +} + +bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + IM_ASSERT(tab_bar->ID != 0); + if ((flags & ImGuiTabBarFlags_DockNode) == 0) + PushOverrideID(tab_bar->ID); + + // Add to stack + g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar)); + g.CurrentTabBar = tab_bar; + tab_bar->Window = window; + + // Append with multiple BeginTabBar()/EndTabBar() pairs. + tab_bar->BackupCursorPos = window->DC.CursorPos; + if (tab_bar->CurrFrameVisible == g.FrameCount) + { + window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY); + tab_bar->BeginCount++; + return true; + } + + // Ensure correct ordering when toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable + if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable))) + if ((flags & ImGuiTabBarFlags_DockNode) == 0) // FIXME: TabBar with DockNode can now be hybrid + ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder); + tab_bar->TabsAddedNew = false; + + // Flags + if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0) + flags |= ImGuiTabBarFlags_FittingPolicyDefault_; + + tab_bar->Flags = flags; + tab_bar->BarRect = tab_bar_bb; + tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab() + tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible; + tab_bar->CurrFrameVisible = g.FrameCount; + tab_bar->PrevTabsContentsHeight = tab_bar->CurrTabsContentsHeight; + tab_bar->CurrTabsContentsHeight = 0.0f; + tab_bar->ItemSpacingY = g.Style.ItemSpacing.y; + tab_bar->FramePadding = g.Style.FramePadding; + tab_bar->TabsActiveCount = 0; + tab_bar->LastTabItemIdx = -1; + tab_bar->BeginCount = 1; + + // Set cursor pos in a way which only be used in the off-chance the user erroneously submits item before BeginTabItem(): items will overlap + window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY); + + // Draw separator + // (it would be misleading to draw this in EndTabBar() suggesting that it may be drawn over tabs, as tab bar are appendable) + const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected); + if (g.Style.TabBarBorderSize > 0.0f) + { + const float y = tab_bar->BarRect.Max.y; + window->DrawList->AddRectFilled(ImVec2(tab_bar->SeparatorMinX, y - g.Style.TabBarBorderSize), ImVec2(tab_bar->SeparatorMaxX, y), col); + } + return true; +} + +void ImGui::EndTabBar() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + ImGuiTabBar* tab_bar = g.CurrentTabBar; + if (tab_bar == NULL) + { + IM_ASSERT_USER_ERROR(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!"); + return; + } + + // Fallback in case no TabItem have been submitted + if (tab_bar->WantLayout) + TabBarLayout(tab_bar); + + // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed(). + const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount); + if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing) + { + tab_bar->CurrTabsContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, tab_bar->CurrTabsContentsHeight); + window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->CurrTabsContentsHeight; + } + else + { + window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->PrevTabsContentsHeight; + } + if (tab_bar->BeginCount > 1) + window->DC.CursorPos = tab_bar->BackupCursorPos; + + tab_bar->LastTabItemIdx = -1; + if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) + PopID(); + + g.CurrentTabBarStack.pop_back(); + g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back()); +} + +// Scrolling happens only in the central section (leading/trailing sections are not scrolling) +static float TabBarCalcScrollableWidth(ImGuiTabBar* tab_bar, ImGuiTabBarSection* sections) +{ + return tab_bar->BarRect.GetWidth() - sections[0].Width - sections[2].Width - sections[1].Spacing; +} + +// This is called only once a frame before by the first call to ItemTab() +// The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions. +static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) +{ + ImGuiContext& g = *GImGui; + tab_bar->WantLayout = false; + + // Garbage collect by compacting list + // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section) + int tab_dst_n = 0; + bool need_sort_by_section = false; + ImGuiTabBarSection sections[3]; // Layout sections: Leading, Central, Trailing + for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n]; + if (tab->LastFrameVisible < tab_bar->PrevFrameVisible || tab->WantClose) + { + // Remove tab + if (tab_bar->VisibleTabId == tab->ID) { tab_bar->VisibleTabId = 0; } + if (tab_bar->SelectedTabId == tab->ID) { tab_bar->SelectedTabId = 0; } + if (tab_bar->NextSelectedTabId == tab->ID) { tab_bar->NextSelectedTabId = 0; } + continue; + } + if (tab_dst_n != tab_src_n) + tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n]; + + tab = &tab_bar->Tabs[tab_dst_n]; + tab->IndexDuringLayout = (ImS16)tab_dst_n; + + // We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another) + int curr_tab_section_n = TabItemGetSectionIdx(tab); + if (tab_dst_n > 0) + { + ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1]; + int prev_tab_section_n = TabItemGetSectionIdx(prev_tab); + if (curr_tab_section_n == 0 && prev_tab_section_n != 0) + need_sort_by_section = true; + if (prev_tab_section_n == 2 && curr_tab_section_n != 2) + need_sort_by_section = true; + } + + sections[curr_tab_section_n].TabCount++; + tab_dst_n++; + } + if (tab_bar->Tabs.Size != tab_dst_n) + tab_bar->Tabs.resize(tab_dst_n); + + if (need_sort_by_section) + ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection); + + // Calculate spacing between sections + sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; + sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f; + + // Setup next selected tab + ImGuiID scroll_to_tab_id = 0; + if (tab_bar->NextSelectedTabId) + { + tab_bar->SelectedTabId = tab_bar->NextSelectedTabId; + tab_bar->NextSelectedTabId = 0; + scroll_to_tab_id = tab_bar->SelectedTabId; + } + + // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot). + if (tab_bar->ReorderRequestTabId != 0) + { + if (TabBarProcessReorder(tab_bar)) + if (tab_bar->ReorderRequestTabId == tab_bar->SelectedTabId) + scroll_to_tab_id = tab_bar->ReorderRequestTabId; + tab_bar->ReorderRequestTabId = 0; + } + + // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!) + const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0; + if (tab_list_popup_button) + if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x! + scroll_to_tab_id = tab_bar->SelectedTabId = tab_to_select->ID; + + // Leading/Trailing tabs will be shrink only if central one aren't visible anymore, so layout the shrink data as: leading, trailing, central + // (whereas our tabs are stored as: leading, central, trailing) + int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount }; + g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); + + // Compute ideal tabs widths + store them into shrink buffer + ImGuiTabItem* most_recently_selected_tab = NULL; + int curr_section_n = -1; + bool found_selected_tab_id = false; + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible); + + if ((most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) && !(tab->Flags & ImGuiTabItemFlags_Button)) + most_recently_selected_tab = tab; + if (tab->ID == tab_bar->SelectedTabId) + found_selected_tab_id = true; + if (scroll_to_tab_id == 0 && g.NavJustMovedToId == tab->ID) + scroll_to_tab_id = tab->ID; + + // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar. + // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet, + // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window. + const char* tab_name = TabBarGetTabName(tab_bar, tab); + const bool has_close_button_or_unsaved_marker = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) == 0 || (tab->Flags & ImGuiTabItemFlags_UnsavedDocument); + tab->ContentWidth = (tab->RequestedWidth >= 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button_or_unsaved_marker).x; + + int section_n = TabItemGetSectionIdx(tab); + ImGuiTabBarSection* section = §ions[section_n]; + section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f); + curr_section_n = section_n; + + // Store data so we can build an array sorted by width if we need to shrink tabs down + IM_MSVC_WARNING_SUPPRESS(6385); + ImGuiShrinkWidthItem* shrink_width_item = &g.ShrinkWidthBuffer[shrink_buffer_indexes[section_n]++]; + shrink_width_item->Index = tab_n; + shrink_width_item->Width = shrink_width_item->InitialWidth = tab->ContentWidth; + tab->Width = ImMax(tab->ContentWidth, 1.0f); + } + + // Compute total ideal width (used for e.g. auto-resizing a window) + tab_bar->WidthAllTabsIdeal = 0.0f; + for (int section_n = 0; section_n < 3; section_n++) + tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing; + + // Horizontal scrolling buttons + // (note that TabBarScrollButtons() will alter BarRect.Max.x) + if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll)) + if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar)) + { + scroll_to_tab_id = scroll_and_select_tab->ID; + if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0) + tab_bar->SelectedTabId = scroll_to_tab_id; + } + + // Shrink widths if full tabs don't fit in their allocated space + float section_0_w = sections[0].Width + sections[0].Spacing; + float section_1_w = sections[1].Width + sections[1].Spacing; + float section_2_w = sections[2].Width + sections[2].Spacing; + bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth(); + float width_excess; + if (central_section_is_visible) + width_excess = ImMax(section_1_w - (tab_bar->BarRect.GetWidth() - section_0_w - section_2_w), 0.0f); // Excess used to shrink central section + else + width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section + + // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore + if (width_excess >= 1.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible)) + { + int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount); + int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0); + ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess); + + // Apply shrunk values into tabs and sections + for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index]; + float shrinked_width = IM_TRUNC(g.ShrinkWidthBuffer[tab_n].Width); + if (shrinked_width < 0.0f) + continue; + + shrinked_width = ImMax(1.0f, shrinked_width); + int section_n = TabItemGetSectionIdx(tab); + sections[section_n].Width -= (tab->Width - shrinked_width); + tab->Width = shrinked_width; + } + } + + // Layout all active tabs + int section_tab_index = 0; + float tab_offset = 0.0f; + tab_bar->WidthAllTabs = 0.0f; + for (int section_n = 0; section_n < 3; section_n++) + { + ImGuiTabBarSection* section = §ions[section_n]; + if (section_n == 2) + tab_offset = ImMin(ImMax(0.0f, tab_bar->BarRect.GetWidth() - section->Width), tab_offset); + + for (int tab_n = 0; tab_n < section->TabCount; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n]; + tab->Offset = tab_offset; + tab->NameOffset = -1; + tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f); + } + tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f); + tab_offset += section->Spacing; + section_tab_index += section->TabCount; + } + + // Clear name buffers + tab_bar->TabsNames.Buf.resize(0); + + // If we have lost the selected tab, select the next most recently active one + if (found_selected_tab_id == false) + tab_bar->SelectedTabId = 0; + if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL) + scroll_to_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID; + + // Lock in visible tab + tab_bar->VisibleTabId = tab_bar->SelectedTabId; + tab_bar->VisibleTabWasSubmitted = false; + + // CTRL+TAB can override visible tab temporarily + if (g.NavWindowingTarget != NULL && g.NavWindowingTarget->DockNode && g.NavWindowingTarget->DockNode->TabBar == tab_bar) + tab_bar->VisibleTabId = scroll_to_tab_id = g.NavWindowingTarget->TabId; + + // Apply request requests + if (scroll_to_tab_id != 0) + TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections); + else if ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) && IsMouseHoveringRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, true) && IsWindowContentHoverable(g.CurrentWindow)) + { + const float wheel = g.IO.MouseWheelRequestAxisSwap ? g.IO.MouseWheel : g.IO.MouseWheelH; + const ImGuiKey wheel_key = g.IO.MouseWheelRequestAxisSwap ? ImGuiKey_MouseWheelY : ImGuiKey_MouseWheelX; + if (TestKeyOwner(wheel_key, tab_bar->ID) && wheel != 0.0f) + { + const float scroll_step = wheel * TabBarCalcScrollableWidth(tab_bar, sections) / 3.0f; + tab_bar->ScrollingTargetDistToVisibility = 0.0f; + tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget - scroll_step); + } + SetKeyOwner(wheel_key, tab_bar->ID); + } + + // Update scrolling + tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim); + tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget); + if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget) + { + // Scrolling speed adjust itself so we can always reach our target in 1/3 seconds. + // Teleport if we are aiming far off the visible line + tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, 70.0f * g.FontSize); + tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, ImFabs(tab_bar->ScrollingTarget - tab_bar->ScrollingAnim) / 0.3f); + const bool teleport = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) || (tab_bar->ScrollingTargetDistToVisibility > 10.0f * g.FontSize); + tab_bar->ScrollingAnim = teleport ? tab_bar->ScrollingTarget : ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, g.IO.DeltaTime * tab_bar->ScrollingSpeed); + } + else + { + tab_bar->ScrollingSpeed = 0.0f; + } + tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing; + tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing; + + // Actual layout in host window (we don't do it in BeginTabBar() so as not to waste an extra frame) + ImGuiWindow* window = g.CurrentWindow; + window->DC.CursorPos = tab_bar->BarRect.Min; + ItemSize(ImVec2(tab_bar->WidthAllTabs, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y); + window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, tab_bar->BarRect.Min.x + tab_bar->WidthAllTabsIdeal); +} + +// Dockable windows uses Name/ID in the global namespace. Non-dockable items use the ID stack. +static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window) +{ + if (docked_window != NULL) + { + IM_UNUSED(tab_bar); + IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode); + ImGuiID id = docked_window->TabId; + KeepAliveID(id); + return id; + } + else + { + ImGuiWindow* window = GImGui->CurrentWindow; + return window->GetID(label); + } +} + +static float ImGui::TabBarCalcMaxTabWidth() +{ + ImGuiContext& g = *GImGui; + return g.FontSize * 20.0f; +} + +ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id) +{ + if (tab_id != 0) + for (int n = 0; n < tab_bar->Tabs.Size; n++) + if (tab_bar->Tabs[n].ID == tab_id) + return &tab_bar->Tabs[n]; + return NULL; +} + +// Order = visible order, not submission order! (which is tab->BeginOrder) +ImGuiTabItem* ImGui::TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order) +{ + if (order < 0 || order >= tab_bar->Tabs.Size) + return NULL; + return &tab_bar->Tabs[order]; +} + +// FIXME: See references to #2304 in TODO.txt +ImGuiTabItem* ImGui::TabBarFindMostRecentlySelectedTabForActiveWindow(ImGuiTabBar* tab_bar) +{ + ImGuiTabItem* most_recently_selected_tab = NULL; + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) + if (tab->Window && tab->Window->WasActive) + most_recently_selected_tab = tab; + } + return most_recently_selected_tab; +} + +ImGuiTabItem* ImGui::TabBarGetCurrentTab(ImGuiTabBar* tab_bar) +{ + if (tab_bar->LastTabItemIdx < 0 || tab_bar->LastTabItemIdx >= tab_bar->Tabs.Size) + return NULL; + return &tab_bar->Tabs[tab_bar->LastTabItemIdx]; +} + +const char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) +{ + if (tab->Window) + return tab->Window->Name; + if (tab->NameOffset == -1) + return "N/A"; + IM_ASSERT(tab->NameOffset < tab_bar->TabsNames.Buf.Size); + return tab_bar->TabsNames.Buf.Data + tab->NameOffset; +} + +// The purpose of this call is to register tab in advance so we can control their order at the time they appear. +// Otherwise calling this is unnecessary as tabs are appending as needed by the BeginTabItem() function. +void ImGui::TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(TabBarFindTabByID(tab_bar, window->TabId) == NULL); + IM_ASSERT(g.CurrentTabBar != tab_bar); // Can't work while the tab bar is active as our tab doesn't have an X offset yet, in theory we could/should test something like (tab_bar->CurrFrameVisible < g.FrameCount) but we'd need to solve why triggers the commented early-out assert in BeginTabBarEx() (probably dock node going from implicit to explicit in same frame) + + if (!window->HasCloseButton) + tab_flags |= ImGuiTabItemFlags_NoCloseButton; // Set _NoCloseButton immediately because it will be used for first-frame width calculation. + + ImGuiTabItem new_tab; + new_tab.ID = window->TabId; + new_tab.Flags = tab_flags; + new_tab.LastFrameVisible = tab_bar->CurrFrameVisible; // Required so BeginTabBar() doesn't ditch the tab + if (new_tab.LastFrameVisible == -1) + new_tab.LastFrameVisible = g.FrameCount - 1; + new_tab.Window = window; // Required so tab bar layout can compute the tab width before tab submission + tab_bar->Tabs.push_back(new_tab); +} + +// The *TabId fields are already set by the docking system _before_ the actual TabItem was created, so we clear them regardless. +void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) +{ + if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id)) + tab_bar->Tabs.erase(tab); + if (tab_bar->VisibleTabId == tab_id) { tab_bar->VisibleTabId = 0; } + if (tab_bar->SelectedTabId == tab_id) { tab_bar->SelectedTabId = 0; } + if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; } +} + +// Called on manual closure attempt +void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) +{ + if (tab->Flags & ImGuiTabItemFlags_Button) + return; // A button appended with TabItemButton(). + + if ((tab->Flags & (ImGuiTabItemFlags_UnsavedDocument | ImGuiTabItemFlags_NoAssumedClosure)) == 0) + { + // This will remove a frame of lag for selecting another tab on closure. + // However we don't run it in the case where the 'Unsaved' flag is set, so user gets a chance to fully undo the closure + tab->WantClose = true; + if (tab_bar->VisibleTabId == tab->ID) + { + tab->LastFrameVisible = -1; + tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0; + } + } + else + { + // Actually select before expecting closure attempt (on an UnsavedDocument tab user is expect to e.g. show a popup) + if (tab_bar->VisibleTabId != tab->ID) + TabBarQueueFocus(tab_bar, tab); + } +} + +static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling) +{ + scrolling = ImMin(scrolling, tab_bar->WidthAllTabs - tab_bar->BarRect.GetWidth()); + return ImMax(scrolling, 0.0f); +} + +// Note: we may scroll to tab that are not selected! e.g. using keyboard arrow keys +static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections) +{ + ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id); + if (tab == NULL) + return; + if (tab->Flags & ImGuiTabItemFlags_SectionMask_) + return; + + ImGuiContext& g = *GImGui; + float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar) + int order = TabBarGetTabOrder(tab_bar, tab); + + // Scrolling happens only in the central section (leading/trailing sections are not scrolling) + float scrollable_width = TabBarCalcScrollableWidth(tab_bar, sections); + + // We make all tabs positions all relative Sections[0].Width to make code simpler + float tab_x1 = tab->Offset - sections[0].Width + (order > sections[0].TabCount - 1 ? -margin : 0.0f); + float tab_x2 = tab->Offset - sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - sections[2].TabCount ? margin : 1.0f); + tab_bar->ScrollingTargetDistToVisibility = 0.0f; + if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width)) + { + // Scroll to the left + tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f); + tab_bar->ScrollingTarget = tab_x1; + } + else if (tab_bar->ScrollingTarget < tab_x2 - scrollable_width) + { + // Scroll to the right + tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - scrollable_width) - tab_bar->ScrollingAnim, 0.0f); + tab_bar->ScrollingTarget = tab_x2 - scrollable_width; + } +} + +void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) +{ + tab_bar->NextSelectedTabId = tab->ID; +} + +void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, const char* tab_name) +{ + IM_ASSERT((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0); // Only supported for manual/explicit tab bars + ImGuiID tab_id = TabBarCalcTabID(tab_bar, tab_name, NULL); + tab_bar->NextSelectedTabId = tab_id; +} + +void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset) +{ + IM_ASSERT(offset != 0); + IM_ASSERT(tab_bar->ReorderRequestTabId == 0); + tab_bar->ReorderRequestTabId = tab->ID; + tab_bar->ReorderRequestOffset = (ImS16)offset; +} + +void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* src_tab, ImVec2 mouse_pos) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(tab_bar->ReorderRequestTabId == 0); + if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0) + return; + + const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0; + const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0); + + // Count number of contiguous tabs we are crossing over + const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1; + const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab); + int dst_idx = src_idx; + for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir) + { + // Reordered tabs must share the same section + const ImGuiTabItem* dst_tab = &tab_bar->Tabs[i]; + if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder) + break; + if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_)) + break; + dst_idx = i; + + // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered. + const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x; + const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x; + //GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255)); + if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2)) + break; + } + + if (dst_idx != src_idx) + TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx); +} + +bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar) +{ + ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId); + if (tab1 == NULL || (tab1->Flags & ImGuiTabItemFlags_NoReorder)) + return false; + + //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools + int tab2_order = TabBarGetTabOrder(tab_bar, tab1) + tab_bar->ReorderRequestOffset; + if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size) + return false; + + // Reordered tabs must share the same section + // (Note: TabBarQueueReorderFromMousePos() also has a similar test but since we allow direct calls to TabBarQueueReorder() we do it here too) + ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order]; + if (tab2->Flags & ImGuiTabItemFlags_NoReorder) + return false; + if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_)) + return false; + + ImGuiTabItem item_tmp = *tab1; + ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2; + ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1; + const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset; + memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem)); + *tab2 = item_tmp; + + if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings) + MarkIniSettingsDirty(); + return true; +} + +static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f); + const float scrolling_buttons_width = arrow_button_size.x * 2.0f; + + const ImVec2 backup_cursor_pos = window->DC.CursorPos; + //window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x, tab_bar->BarRect.Max.y), IM_COL32(255,0,0,255)); + + int select_dir = 0; + ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text]; + arrow_col.w *= 0.5f; + + PushStyleColor(ImGuiCol_Text, arrow_col); + PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); + const float backup_repeat_delay = g.IO.KeyRepeatDelay; + const float backup_repeat_rate = g.IO.KeyRepeatRate; + g.IO.KeyRepeatDelay = 0.250f; + g.IO.KeyRepeatRate = 0.200f; + float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width); + window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y); + if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick)) + select_dir = -1; + window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y); + if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick)) + select_dir = +1; + PopItemFlag(); + PopStyleColor(2); + g.IO.KeyRepeatRate = backup_repeat_rate; + g.IO.KeyRepeatDelay = backup_repeat_delay; + + ImGuiTabItem* tab_to_scroll_to = NULL; + if (select_dir != 0) + if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId)) + { + int selected_order = TabBarGetTabOrder(tab_bar, tab_item); + int target_order = selected_order + select_dir; + + // Skip tab item buttons until another tab item is found or end is reached + while (tab_to_scroll_to == NULL) + { + // If we are at the end of the list, still scroll to make our tab visible + tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order]; + + // Cross through buttons + // (even if first/last item is a button, return it so we can update the scroll) + if (tab_to_scroll_to->Flags & ImGuiTabItemFlags_Button) + { + target_order += select_dir; + selected_order += select_dir; + tab_to_scroll_to = (target_order < 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL; + } + } + } + window->DC.CursorPos = backup_cursor_pos; + tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f; + + return tab_to_scroll_to; +} + +static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // We use g.Style.FramePadding.y to match the square ArrowButton size + const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y; + const ImVec2 backup_cursor_pos = window->DC.CursorPos; + window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y); + tab_bar->BarRect.Min.x += tab_list_popup_button_width; + + ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text]; + arrow_col.w *= 0.5f; + PushStyleColor(ImGuiCol_Text, arrow_col); + PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLargest); + PopStyleColor(2); + + ImGuiTabItem* tab_to_select = NULL; + if (open) + { + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + if (tab->Flags & ImGuiTabItemFlags_Button) + continue; + + const char* tab_name = TabBarGetTabName(tab_bar, tab); + if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID)) + tab_to_select = tab; + } + EndCombo(); + } + + window->DC.CursorPos = backup_cursor_pos; + return tab_to_select; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: BeginTabItem, EndTabItem, etc. +//------------------------------------------------------------------------- +// - BeginTabItem() +// - EndTabItem() +// - TabItemButton() +// - TabItemEx() [Internal] +// - SetTabItemClosed() +// - TabItemCalcSize() [Internal] +// - TabItemBackground() [Internal] +// - TabItemLabelAndCloseButton() [Internal] +//------------------------------------------------------------------------- + +bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + ImGuiTabBar* tab_bar = g.CurrentTabBar; + if (tab_bar == NULL) + { + IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!"); + return false; + } + IM_ASSERT((flags & ImGuiTabItemFlags_Button) == 0); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead! + + bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL); + if (ret && !(flags & ImGuiTabItemFlags_NoPushId)) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx]; + PushOverrideID(tab->ID); // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label) + } + return ret; +} + +void ImGui::EndTabItem() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + ImGuiTabBar* tab_bar = g.CurrentTabBar; + if (tab_bar == NULL) + { + IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); + return; + } + IM_ASSERT(tab_bar->LastTabItemIdx >= 0); + ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx]; + if (!(tab->Flags & ImGuiTabItemFlags_NoPushId)) + PopID(); +} + +bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + ImGuiTabBar* tab_bar = g.CurrentTabBar; + if (tab_bar == NULL) + { + IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); + return false; + } + return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL); +} + +bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window) +{ + // Layout whole tab bar if not already done + ImGuiContext& g = *GImGui; + if (tab_bar->WantLayout) + { + ImGuiNextItemData backup_next_item_data = g.NextItemData; + TabBarLayout(tab_bar); + g.NextItemData = backup_next_item_data; + } + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + const ImGuiID id = TabBarCalcTabID(tab_bar, label, docked_window); + + // If the user called us with *p_open == false, we early out and don't render. + // We make a call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID. + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + if (p_open && !*p_open) + { + ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav); + return false; + } + + IM_ASSERT(!p_open || !(flags & ImGuiTabItemFlags_Button)); + IM_ASSERT((flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)); // Can't use both Leading and Trailing + + // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented) + if (flags & ImGuiTabItemFlags_NoCloseButton) + p_open = NULL; + else if (p_open == NULL) + flags |= ImGuiTabItemFlags_NoCloseButton; + + // Acquire tab data + ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id); + bool tab_is_new = false; + if (tab == NULL) + { + tab_bar->Tabs.push_back(ImGuiTabItem()); + tab = &tab_bar->Tabs.back(); + tab->ID = id; + tab_bar->TabsAddedNew = tab_is_new = true; + } + tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab); + + // Calculate tab contents size + ImVec2 size = TabItemCalcSize(label, (p_open != NULL) || (flags & ImGuiTabItemFlags_UnsavedDocument)); + tab->RequestedWidth = -1.0f; + if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasWidth) + size.x = tab->RequestedWidth = g.NextItemData.Width; + if (tab_is_new) + tab->Width = ImMax(1.0f, size.x); + tab->ContentWidth = size.x; + tab->BeginOrder = tab_bar->TabsActiveCount++; + + const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount); + const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0; + const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount); + const bool tab_just_unsaved = (flags & ImGuiTabItemFlags_UnsavedDocument) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument); + const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0; + tab->LastFrameVisible = g.FrameCount; + tab->Flags = flags; + tab->Window = docked_window; + + // Append name _WITH_ the zero-terminator + // (regular tabs are permitted in a DockNode tab bar, but window tabs not permitted in a non-DockNode tab bar) + if (docked_window != NULL) + { + IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode); + tab->NameOffset = -1; + } + else + { + tab->NameOffset = (ImS32)tab_bar->TabsNames.size(); + tab_bar->TabsNames.append(label, label + strlen(label) + 1); + } + + // Update selected tab + if (!is_tab_button) + { + if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0) + if (!tab_bar_appearing || tab_bar->SelectedTabId == 0) + TabBarQueueFocus(tab_bar, tab); // New tabs gets activated + if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // _SetSelected can only be passed on explicit tab bar + TabBarQueueFocus(tab_bar, tab); + } + + // Lock visibility + // (Note: tab_contents_visible != tab_selected... because CTRL+TAB operations may preview some tabs without selecting them!) + bool tab_contents_visible = (tab_bar->VisibleTabId == id); + if (tab_contents_visible) + tab_bar->VisibleTabWasSubmitted = true; + + // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches + if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing && docked_window == NULL) + if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs)) + tab_contents_visible = true; + + // Note that tab_is_new is not necessarily the same as tab_appearing! When a tab bar stops being submitted + // and then gets submitted again, the tabs will have 'tab_appearing=true' but 'tab_is_new=false'. + if (tab_appearing && (!tab_bar_appearing || tab_is_new)) + { + ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav); + if (is_tab_button) + return false; + return tab_contents_visible; + } + + if (tab_bar->SelectedTabId == id) + tab->LastFrameSelected = g.FrameCount; + + // Backup current layout position + const ImVec2 backup_main_cursor_pos = window->DC.CursorPos; + + // Layout + const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0; + size.x = tab->Width; + if (is_central_section) + window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_TRUNC(tab->Offset - tab_bar->ScrollingAnim), 0.0f); + else + window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f); + ImVec2 pos = window->DC.CursorPos; + ImRect bb(pos, pos + size); + + // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation) + const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX); + if (want_clip_rect) + PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true); + + ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos; + ItemSize(bb.GetSize(), style.FramePadding.y); + window->DC.CursorMaxPos = backup_cursor_max_pos; + + if (!ItemAdd(bb, id)) + { + if (want_clip_rect) + PopClipRect(); + window->DC.CursorPos = backup_main_cursor_pos; + return tab_contents_visible; + } + + // Click to Select a tab + ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap); + if (g.DragDropActive && !g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW)) // FIXME: May be an opt-in property of the payload to disable this + button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + if (pressed && !is_tab_button) + TabBarQueueFocus(tab_bar, tab); + + // Transfer active id window so the active id is not owned by the dock host (as StartMouseMovingWindow() + // will only do it on the drag). This allows FocusWindow() to be more conservative in how it clears active id. + if (held && docked_window && g.ActiveId == id && g.ActiveIdIsJustActivated) + g.ActiveIdWindow = docked_window; + + // Drag and drop a single floating window node moves it + ImGuiDockNode* node = docked_window ? docked_window->DockNode : NULL; + const bool single_floating_window_node = node && node->IsFloatingNode() && (node->Windows.Size == 1); + if (held && single_floating_window_node && IsMouseDragging(0, 0.0f)) + { + // Move + StartMouseMovingWindow(docked_window); + } + else if (held && !tab_appearing && IsMouseDragging(0)) + { + // Drag and drop: re-order tabs + int drag_dir = 0; + float drag_distance_from_edge_x = 0.0f; + if (!g.DragDropActive && ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (docked_window != NULL))) + { + // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x + if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x) + { + drag_dir = -1; + drag_distance_from_edge_x = bb.Min.x - g.IO.MousePos.x; + TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos); + } + else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x) + { + drag_dir = +1; + drag_distance_from_edge_x = g.IO.MousePos.x - bb.Max.x; + TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos); + } + } + + // Extract a Dockable window out of it's tab bar + const bool can_undock = docked_window != NULL && !(docked_window->Flags & ImGuiWindowFlags_NoMove) && !(node->MergedFlags & ImGuiDockNodeFlags_NoUndocking); + if (can_undock) + { + // We use a variable threshold to distinguish dragging tabs within a tab bar and extracting them out of the tab bar + bool undocking_tab = (g.DragDropActive && g.DragDropPayload.SourceId == id); + if (!undocking_tab) //&& (!g.IO.ConfigDockingWithShift || g.IO.KeyShift) + { + float threshold_base = g.FontSize; + float threshold_x = (threshold_base * 2.2f); + float threshold_y = (threshold_base * 1.5f) + ImClamp((ImFabs(g.IO.MouseDragMaxDistanceAbs[0].x) - threshold_base * 2.0f) * 0.20f, 0.0f, threshold_base * 4.0f); + //GetForegroundDrawList()->AddRect(ImVec2(bb.Min.x - threshold_x, bb.Min.y - threshold_y), ImVec2(bb.Max.x + threshold_x, bb.Max.y + threshold_y), IM_COL32_WHITE); // [DEBUG] + + float distance_from_edge_y = ImMax(bb.Min.y - g.IO.MousePos.y, g.IO.MousePos.y - bb.Max.y); + if (distance_from_edge_y >= threshold_y) + undocking_tab = true; + if (drag_distance_from_edge_x > threshold_x) + if ((drag_dir < 0 && TabBarGetTabOrder(tab_bar, tab) == 0) || (drag_dir > 0 && TabBarGetTabOrder(tab_bar, tab) == tab_bar->Tabs.Size - 1)) + undocking_tab = true; + } + + if (undocking_tab) + { + // Undock + // FIXME: refactor to share more code with e.g. StartMouseMovingWindow + DockContextQueueUndockWindow(&g, docked_window); + g.MovingWindow = docked_window; + SetActiveID(g.MovingWindow->MoveId, g.MovingWindow); + g.ActiveIdClickOffset -= g.MovingWindow->Pos - bb.Min; + g.ActiveIdNoClearOnFocusLoss = true; + SetActiveIdUsingAllKeyboardKeys(); + } + } + } + +#if 0 + if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth) + { + // Enlarge tab display when hovering + bb.Max.x = bb.Min.x + IM_TRUNC(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f))); + display_draw_list = GetForegroundDrawList(window); + TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive)); + } +#endif + + // Render tab shape + ImDrawList* display_draw_list = window->DrawList; + const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabDimmed)); + TabItemBackground(display_draw_list, bb, flags, tab_col); + if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f) + { + float x_offset = IM_TRUNC(0.4f * style.TabRounding); + if (x_offset < 2.0f * g.CurrentDpiScale) + x_offset = 0.0f; + float y_offset = 1.0f * g.CurrentDpiScale; + display_draw_list->AddLine(bb.GetTL() + ImVec2(x_offset, y_offset), bb.GetTR() + ImVec2(-x_offset, y_offset), GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline), style.TabBarOverlineSize); + } + RenderNavCursor(bb, id); + + // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget. + const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); + if (tab_bar->SelectedTabId != tab->ID && hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button) + TabBarQueueFocus(tab_bar, tab); + + if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) + flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; + + // Render tab label, process close button + const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, docked_window ? docked_window->ID : id) : 0; + bool just_closed; + bool text_clipped; + TabItemLabelAndCloseButton(display_draw_list, bb, tab_just_unsaved ? (flags & ~ImGuiTabItemFlags_UnsavedDocument) : flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped); + if (just_closed && p_open != NULL) + { + *p_open = false; + TabBarCloseTab(tab_bar, tab); + } + + // Forward Hovered state so IsItemHovered() after Begin() can work (even though we are technically hovering our parent) + // That state is copied to window->DockTabItemStatusFlags by our caller. + if (docked_window && (hovered || g.HoveredId == close_button_id)) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + + // Restore main window position so user can draw there + if (want_clip_rect) + PopClipRect(); + window->DC.CursorPos = backup_main_cursor_pos; + + // Tooltip + // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok) + // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores) + // FIXME: This is a mess. + // FIXME: We may want disabled tab to still display the tooltip? + if (text_clipped && g.HoveredId == id && !held) + if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip)) + SetItemTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label); + + IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected + if (is_tab_button) + return pressed; + return tab_contents_visible; +} + +// [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed. +// To use it to need to call the function SetTabItemClosed() between BeginTabBar() and EndTabBar(). +// Tabs closed by the close button will automatically be flagged to avoid this issue. +void ImGui::SetTabItemClosed(const char* label) +{ + ImGuiContext& g = *GImGui; + bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode); + if (is_within_manual_tab_bar) + { + ImGuiTabBar* tab_bar = g.CurrentTabBar; + ImGuiID tab_id = TabBarCalcTabID(tab_bar, label, NULL); + if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id)) + tab->WantClose = true; // Will be processed by next call to TabBarLayout() + } + else if (ImGuiWindow* window = FindWindowByName(label)) + { + if (window->DockIsActive) + if (ImGuiDockNode* node = window->DockNode) + { + ImGuiID tab_id = TabBarCalcTabID(node->TabBar, label, window); + TabBarRemoveTab(node->TabBar, tab_id); + window->DockTabWantClose = true; + } + } +} + +ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker) +{ + ImGuiContext& g = *GImGui; + ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f); + if (has_close_button_or_unsaved_marker) + size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle. + else + size.x += g.Style.FramePadding.x + 1.0f; + return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y); +} + +ImVec2 ImGui::TabItemCalcSize(ImGuiWindow* window) +{ + return TabItemCalcSize(window->Name, window->HasCloseButton || (window->Flags & ImGuiWindowFlags_UnsavedDocument)); +} + +void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col) +{ + // While rendering tabs, we trim 1 pixel off the top of our bounding box so they can fit within a regular frame height while looking "detached" from it. + ImGuiContext& g = *GImGui; + const float width = bb.GetWidth(); + IM_UNUSED(flags); + IM_ASSERT(width > 0.0f); + const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f)); + const float y1 = bb.Min.y + 1.0f; + const float y2 = bb.Max.y - g.Style.TabBarBorderSize; + draw_list->PathLineTo(ImVec2(bb.Min.x, y2)); + draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9); + draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12); + draw_list->PathLineTo(ImVec2(bb.Max.x, y2)); + draw_list->PathFillConvex(col); + if (g.Style.TabBorderSize > 0.0f) + { + draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2)); + draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9); + draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12); + draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2)); + draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize); + } +} + +// Render text label (with custom clipping) + Unsaved Document marker + Close Button logic +// We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter. +void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped) +{ + ImGuiContext& g = *GImGui; + ImVec2 label_size = CalcTextSize(label, NULL, true); + + if (out_just_closed) + *out_just_closed = false; + if (out_text_clipped) + *out_text_clipped = false; + + if (bb.GetWidth() <= 1.0f) + return; + + // In Style V2 we'll have full override of all colors per state (e.g. focused, selected) + // But right now if you want to alter text color of tabs this is what you need to do. +#if 0 + const float backup_alpha = g.Style.Alpha; + if (!is_contents_visible) + g.Style.Alpha *= 0.7f; +#endif + + // Render text label (with clipping + alpha gradient) + unsaved marker + ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y); + ImRect text_ellipsis_clip_bb = text_pixel_clip_bb; + + // Return clipped state ignoring the close button + if (out_text_clipped) + { + *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_pixel_clip_bb.Max.x; + //draw_list->AddCircle(text_ellipsis_clip_bb.Min, 3.0f, *out_text_clipped ? IM_COL32(255, 0, 0, 255) : IM_COL32(0, 255, 0, 255)); + } + + const float button_sz = g.FontSize; + const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x - button_sz), bb.Min.y + frame_padding.y); + + // Close Button & Unsaved Marker + // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap() + // 'hovered' will be true when hovering the Tab but NOT when hovering the close button + // 'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button + // 'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false + bool close_button_pressed = false; + bool close_button_visible = false; + if (close_button_id != 0) + if (is_contents_visible || bb.GetWidth() >= ImMax(button_sz, g.Style.TabMinWidthForCloseButton)) + if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id) + close_button_visible = true; + bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x); + + if (close_button_visible) + { + ImGuiLastItemData last_item_backup = g.LastItemData; + if (CloseButton(close_button_id, button_pos)) + close_button_pressed = true; + g.LastItemData = last_item_backup; + + // Close with middle mouse button + if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2)) + close_button_pressed = true; + } + else if (unsaved_marker_visible) + { + const ImRect bullet_bb(button_pos, button_pos + ImVec2(button_sz, button_sz)); + RenderBullet(draw_list, bullet_bb.GetCenter(), GetColorU32(ImGuiCol_Text)); + } + + // This is all rather complicated + // (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position) + // FIXME: if FramePadding is noticeably large, ellipsis_max_x will be wrong here (e.g. #3497), maybe for consistency that parameter of RenderTextEllipsis() shouldn't exist.. + float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f; + if (close_button_visible || unsaved_marker_visible) + { + text_pixel_clip_bb.Max.x -= close_button_visible ? (button_sz) : (button_sz * 0.80f); + text_ellipsis_clip_bb.Max.x -= unsaved_marker_visible ? (button_sz * 0.80f) : 0.0f; + ellipsis_max_x = text_pixel_clip_bb.Max.x; + } + LogSetNextTextDecoration("/", "\\"); + RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size); + +#if 0 + if (!is_contents_visible) + g.Style.Alpha = backup_alpha; +#endif + + if (out_just_closed) + *out_just_closed = close_button_pressed; +} + + +#endif // #ifndef IMGUI_DISABLE diff --git a/Plugins/CogAI/Source/CogAI/CogAI.Build.cs b/Plugins/CogAI/Source/CogAI/CogAI.Build.cs index 5227c1f..2857b83 100644 --- a/Plugins/CogAI/Source/CogAI/CogAI.Build.cs +++ b/Plugins/CogAI/Source/CogAI/CogAI.Build.cs @@ -25,7 +25,7 @@ public class CogAI : ModuleRules "CogCommon", "CogImgui", "CogDebug", - "CogWindow", + "Cog", } ); diff --git a/Plugins/CogAI/Source/CogAI/Private/CogAIWindow_BehaviorTree.cpp b/Plugins/CogAI/Source/CogAI/Private/CogAIWindow_BehaviorTree.cpp index 278cdab..abb1761 100644 --- a/Plugins/CogAI/Source/CogAI/Private/CogAIWindow_BehaviorTree.cpp +++ b/Plugins/CogAI/Source/CogAI/Private/CogAIWindow_BehaviorTree.cpp @@ -8,7 +8,7 @@ #include "CogAIModule.h" #include "CogDebugDraw.h" #include "CogImguiHelper.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "GameFramework/Pawn.h" #include "imgui_internal.h" #include "Navigation/PathFollowingComponent.h" @@ -135,12 +135,12 @@ void FCogAIWindow_BehaviorTree::RenderContent() { if (ImGui::BeginMenu("Options")) { - ImGui::ColorEdit4("Active Color", (float*)&Config->ActiveColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Inactive Color", (float*)&Config->InactiveColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Active Color", &Config->ActiveColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Inactive Color", &Config->InactiveColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); ImGui::EndMenu(); } - FCogWindowWidgets::SearchBar(Filter); + FCogWidgets::SearchBar("##Filter", Filter); ImGui::EndMenuBar(); } @@ -269,14 +269,14 @@ void FCogAIWindow_BehaviorTree::RenderNode(UBehaviorTreeComponent& BehaviorTreeC //------------------------ // Tooltip //------------------------ - if (FCogWindowWidgets::BeginItemTableTooltip()) + if (FCogWidgets::BeginItemTableTooltip()) { if (ImGui::BeginTable("Effect", 2, ImGuiTableFlags_Borders)) { ImGui::TableSetupColumn("Property"); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); - const ImVec4 TextColor(1.0f, 1.0f, 1.0f, 0.5f); + constexpr ImVec4 TextColor(1.0f, 1.0f, 1.0f, 0.5f); //------------------------ // Name @@ -328,14 +328,14 @@ void FCogAIWindow_BehaviorTree::RenderNode(UBehaviorTreeComponent& BehaviorTreeC ImGui::EndTable(); } - FCogWindowWidgets::EndItemTableTooltip(); + FCogWidgets::EndItemTableTooltip(); } //------------------------ // Checkbox //------------------------ ImGui::SameLine(); - FCogWindowWidgets::PushStyleCompact(); + FCogWidgets::PushStyleCompact(); if (IsActive == false) { ImGui::BeginDisabled(); @@ -347,7 +347,7 @@ void FCogAIWindow_BehaviorTree::RenderNode(UBehaviorTreeComponent& BehaviorTreeC { ImGui::EndDisabled(); } - FCogWindowWidgets::PopStyleCompact(); + FCogWidgets::PopStyleCompact(); //------------------------ // Name diff --git a/Plugins/CogAI/Source/CogAI/Private/CogAIWindow_Blackboard.cpp b/Plugins/CogAI/Source/CogAI/Private/CogAIWindow_Blackboard.cpp index 7453fa0..0725f54 100644 --- a/Plugins/CogAI/Source/CogAI/Private/CogAIWindow_Blackboard.cpp +++ b/Plugins/CogAI/Source/CogAI/Private/CogAIWindow_Blackboard.cpp @@ -1,7 +1,7 @@ #include "CogAIWindow_Blackboard.h" #include "AIController.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "BrainComponent.h" #include "GameFramework/Pawn.h" #include "BehaviorTree/BlackboardComponent.h" @@ -12,7 +12,6 @@ void FCogAIWindow_Blackboard::Initialize() Super::Initialize(); bHasMenu = true; - bNoPadding = true; Config = GetConfig(); } @@ -26,11 +25,16 @@ void FCogAIWindow_Blackboard::RenderHelp() } //-------------------------------------------------------------------------------------------------------------------------- -void FCogAIWindow_Blackboard::ResetConfig() +void FCogAIWindow_Blackboard::PreBegin(ImGuiWindowFlags& WindowFlags) { - Super::ResetConfig(); - - Config->Reset(); + WindowFlags |= ImGuiWindowFlags_NoScrollbar; + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogAIWindow_Blackboard::PostBegin() +{ + ImGui::PopStyleVar(); } //-------------------------------------------------------------------------------------------------------------------------- @@ -46,7 +50,7 @@ void FCogAIWindow_Blackboard::RenderContent() ImGui::EndMenu(); } - FCogWindowWidgets::SearchBar(Filter); + FCogWidgets::SearchBar("##Filter", Filter); ImGui::EndMenuBar(); } diff --git a/Plugins/CogAI/Source/CogAI/Public/CogAIWindow_Blackboard.h b/Plugins/CogAI/Source/CogAI/Public/CogAIWindow_Blackboard.h index 166818c..b6d0e18 100644 --- a/Plugins/CogAI/Source/CogAI/Public/CogAIWindow_Blackboard.h +++ b/Plugins/CogAI/Source/CogAI/Public/CogAIWindow_Blackboard.h @@ -18,10 +18,12 @@ public: protected: - virtual void ResetConfig() override; - virtual void RenderHelp() override; + virtual void PreBegin(ImGuiWindowFlags& WindowFlags) override; + + virtual void PostBegin() override; + virtual void RenderContent() override; private: diff --git a/Plugins/CogAbility/Source/CogAbility/CogAbility.Build.cs b/Plugins/CogAbility/Source/CogAbility/CogAbility.Build.cs index b81d069..e1d1db2 100644 --- a/Plugins/CogAbility/Source/CogAbility/CogAbility.Build.cs +++ b/Plugins/CogAbility/Source/CogAbility/CogAbility.Build.cs @@ -26,7 +26,7 @@ public class CogAbility : ModuleRules "CogCommon", "CogDebug", "CogEngine", - "CogWindow", + "Cog", "GameplayAbilities", "GameplayTags", "NetCore", diff --git a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityCheat_Execution_ActivateAbility.cpp b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityCheat_Execution_ActivateAbility.cpp new file mode 100644 index 0000000..0a2ec45 --- /dev/null +++ b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityCheat_Execution_ActivateAbility.cpp @@ -0,0 +1,114 @@ +#include "CogAbilityCheat_Execution_ActivateAbility.h" + +#include "CogAbilityDataAsset.h" + +#include "AbilitySystemComponent.h" +#include "AbilitySystemGlobals.h" +#include "CogAbilityConfig_Alignment.h" +#include "CogCommon.h" +#include "CogImguiHelper.h" +#include "CogWindow.h" + +//-------------------------------------------------------------------------------------------------------------------------- +void UCogAbilityCheat_Execution_ActivateAbility::Execute_Implementation(const UObject* WorldContextObject, const AActor* Instigator, const TArray& Targets) const +{ + if (Ability == nullptr) + { return; } + + for (AActor* Target : Targets) + { + UAbilitySystemComponent* AbilitySystem = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(Target, true); + if (AbilitySystem == nullptr) + { + COG_LOG_FUNC(LogCogImGui, ELogVerbosity::Warning, TEXT("Target:%s | Invalid Target AbilitySystem"), *GetNameSafe(Target)); + continue; + } + + FGameplayAbilitySpecHandle Handle; + const FGameplayAbilitySpec* Spec = AbilitySystem->FindAbilitySpecFromClass(Ability); + if (Spec != nullptr) + { + if (Spec->IsActive()) + { + AbilitySystem->CancelAbilityHandle(Spec->Handle); + if (RemoveAbilityOnEnd) + { + AbilitySystem->ClearAbility(Handle); + } + return; + } + + Handle = Spec->Handle; + } + else + { + Handle = AbilitySystem->GiveAbility(FGameplayAbilitySpec(Ability, 1, INDEX_NONE, Target)); + Spec = AbilitySystem->FindAbilitySpecFromHandle(Handle, EConsiderPending::All); + } + + AbilitySystem->TryActivateAbility(Handle); + + if (Spec->IsActive() == false) + { + if (RemoveAbilityOnEnd) + { + AbilitySystem->ClearAbility(Handle); + } + } + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +ECogEngineCheat_ActiveState UCogAbilityCheat_Execution_ActivateAbility::IsActiveOnTargets_Implementation(const UObject* WorldContextObject, const TArray& Targets) const +{ + if (Ability == nullptr) + { + return ECogEngineCheat_ActiveState::Inactive; + } + + int32 NumActiveAbilities = 0; + for (const AActor* Target : Targets) + { + const UAbilitySystemComponent* AbilitySystem = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(Target, true); + if (AbilitySystem == nullptr) + { continue; } + + const FGameplayAbilitySpec* Spec = AbilitySystem->FindAbilitySpecFromClass(Ability); + if (Spec == nullptr) + { continue; } + + if (Spec->IsActive()) + { + NumActiveAbilities++; + } + } + + if (NumActiveAbilities == 0) + { + return ECogEngineCheat_ActiveState::Inactive; + } + + if (NumActiveAbilities == Targets.Num()) + { + return ECogEngineCheat_ActiveState::Active; + } + + return ECogEngineCheat_ActiveState::Partial; +} + + +//-------------------------------------------------------------------------------------------------------------------------- +bool UCogAbilityCheat_Execution_ActivateAbility::GetColor(const FCogWindow& InCallingWindow, FLinearColor& OutColor) const +{ + if (Ability == nullptr) + { return false; } + + const UGameplayAbility* GameplayAbility = Ability->GetDefaultObject(); + if (GameplayAbility == nullptr) + { return false; } + + const UCogAbilityConfig_Alignment* Config = InCallingWindow.GetConfig(); + const UCogAbilityDataAsset* Asset = InCallingWindow.GetAsset(); + + return Config->GetAbilityColor(Asset, *GameplayAbility, OutColor); +} diff --git a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityCheat_Execution_ApplyEffect.cpp b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityCheat_Execution_ApplyEffect.cpp index 1636fee..e61250a 100644 --- a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityCheat_Execution_ApplyEffect.cpp +++ b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityCheat_Execution_ApplyEffect.cpp @@ -9,7 +9,7 @@ #include "CogWindow.h" //-------------------------------------------------------------------------------------------------------------------------- -void UCogAbilityCheat_Execution_ApplyEffect::Execute_Implementation(const AActor* Instigator, const TArray& Targets) const +void UCogAbilityCheat_Execution_ApplyEffect::Execute_Implementation(const UObject* WorldContextObject, const AActor* Instigator, const TArray& Targets) const { UAbilitySystemComponent* DefaultInstigatorAbilitySystem = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(Instigator, true); @@ -56,7 +56,7 @@ void UCogAbilityCheat_Execution_ApplyEffect::Execute_Implementation(const AActor } //-------------------------------------------------------------------------------------------------------------------------- -ECogEngineCheat_ActiveState UCogAbilityCheat_Execution_ApplyEffect::IsActiveOnTargets_Implementation(const TArray& Targets) const +ECogEngineCheat_ActiveState UCogAbilityCheat_Execution_ApplyEffect::IsActiveOnTargets_Implementation(const UObject* WorldContextObject, const TArray& Targets) const { if (Effect == nullptr) { @@ -106,6 +106,5 @@ bool UCogAbilityCheat_Execution_ApplyEffect::GetColor(const FCogWindow& InCallin const UCogAbilityConfig_Alignment* Config = InCallingWindow.GetConfig(); const UCogAbilityDataAsset* Asset = InCallingWindow.GetAsset(); - OutColor = Config->GetEffectColor(Asset, *GameplayEffect); - return true; + return Config->GetEffectColor(Asset, *GameplayEffect, OutColor); } diff --git a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityConfig_Alignment.cpp b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityConfig_Alignment.cpp index 286722e..8b501f2 100644 --- a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityConfig_Alignment.cpp +++ b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityConfig_Alignment.cpp @@ -24,26 +24,55 @@ FVector4f UCogAbilityConfig_Alignment::GetAttributeColor(const UAbilitySystemCom } //-------------------------------------------------------------------------------------------------------------------------- -FVector4f UCogAbilityConfig_Alignment::GetEffectColor(const UCogAbilityDataAsset* Asset, const UGameplayEffect& Effect) const +bool UCogAbilityConfig_Alignment::GetAbilityColor(const UCogAbilityDataAsset* Asset, const UGameplayAbility& Ability, FLinearColor& OutColor) const { if (Asset == nullptr) { - return NeutralColor; + OutColor = NeutralColor; + return false; + } + + const FGameplayTagContainer& Tags = Ability.GetAssetTags(); + if (Tags.HasTag(Asset->NegativeAbilityTag)) + { + OutColor = NegativeColor; + return true; + } + + if (Tags.HasTag(Asset->PositiveAbilityTag)) + { + OutColor = PositiveColor; + return true; + } + + OutColor = NeutralColor; + return true; +} + +//-------------------------------------------------------------------------------------------------------------------------- +bool UCogAbilityConfig_Alignment::GetEffectColor(const UCogAbilityDataAsset* Asset, const UGameplayEffect& Effect, FLinearColor& OutColor) const +{ + if (Asset == nullptr) + { + OutColor = NeutralColor; + return false; } const FGameplayTagContainer& Tags = Effect.GetAssetTags(); - if (Tags.HasTag(Asset->NegativeEffectTag)) { - return NegativeColor; + OutColor = NegativeColor; + return true; } if (Tags.HasTag(Asset->PositiveEffectTag)) { - return PositiveColor; + OutColor = PositiveColor; + return true; } - return NeutralColor; + OutColor = NeutralColor; + return true; } //-------------------------------------------------------------------------------------------------------------------------- @@ -57,62 +86,53 @@ FVector4f UCogAbilityConfig_Alignment::GetEffectModifierColor(float ModifierValu { switch (ModifierOp) { - case EGameplayModOp::Additive: + case EGameplayModOp::AddBase: + case EGameplayModOp::AddFinal: { if (ModifierValue > 0.0f) - { - return PositiveColor; - } + { return PositiveColor; } if (ModifierValue < 0.0f) - { - return NegativeColor; - } - break; + { return NegativeColor; } + + return NeutralColor; } - case EGameplayModOp::Multiplicitive: + case EGameplayModOp::MultiplyAdditive: + case EGameplayModOp::MultiplyCompound: { if (ModifierValue > 1.0f) - { - return PositiveColor; - } - else if (ModifierValue < 1.0f) - { - return NegativeColor; - } - break; + { return PositiveColor; } + + if (ModifierValue < 1.0f) + { return NegativeColor; } + + return NeutralColor; } - case EGameplayModOp::Division: + case EGameplayModOp::DivideAdditive: { if (ModifierValue < 1.0f) - { - return PositiveColor; - } + { return PositiveColor; } if (ModifierValue > 1.0f) - { - return NegativeColor; - } - break; + { return NegativeColor; } + + return NeutralColor; } case EGameplayModOp::Override: { if (ModifierValue > BaseValue) - { - return PositiveColor; - } + { return PositiveColor; } if (ModifierValue < BaseValue) - { - return NegativeColor; - } - break; - } - } + { return NegativeColor; } - return NeutralColor; + return NeutralColor; + } + + default: return NeutralColor; + } } diff --git a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityHelper.cpp b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityHelper.cpp index bfe67fa..c589de6 100644 --- a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityHelper.cpp +++ b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityHelper.cpp @@ -1,6 +1,7 @@ #include "CogAbilityHelper.h" -#include "CogWindowWidgets.h" +#include "CogAbilityDataAsset.h" +#include "CogWidgets.h" #include "GameplayTagContainer.h" #include "imgui.h" @@ -19,7 +20,7 @@ void FCogAbilityHelper::RenderTagContainer(const FGameplayTagContainer& Containe Container.GetGameplayTagArray(GameplayTags); for (const FGameplayTag& Tag : GameplayTags) { - FCogWindowWidgets::SmallButton(StringCast(*Tag.ToString()).Get(), Color); + FCogWidgets::SmallButton(StringCast(*Tag.ToString()).Get(), Color); if (Inline) { ImGui::SameLine(); @@ -50,10 +51,23 @@ void FCogAbilityHelper::RenderTagContainer( } const ImVec4 Color = hasTag ? MatchColor : NormalColor; - FCogWindowWidgets::SmallButton(StringCast(*Tag.ToString()).Get(), Color); + FCogWidgets::SmallButton(StringCast(*Tag.ToString()).Get(), Color); if (Inline) { ImGui::SameLine(); } } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogAbilityHelper::RenderConfigureMessage(const TWeakObjectPtr InAsset) +{ + if (InAsset == nullptr) + { + ImGui::Text("Create a DataAsset child of '%s' to configure. ", StringCast(*UCogAbilityDataAsset::StaticClass()->GetName()).Get()); + } + else + { + ImGui::Text("Can be configured in the '%s' DataAsset. ", StringCast(*GetNameSafe(InAsset.Get())).Get()); + } } \ No newline at end of file diff --git a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityReplicator.cpp b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityReplicator.cpp index 213f803..fd29a9d 100644 --- a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityReplicator.cpp +++ b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityReplicator.cpp @@ -4,7 +4,7 @@ #include "AbilitySystemGlobals.h" #include "CogAbilityDataAsset.h" #include "CogImguiHelper.h" -#include "CogWindowHelper.h" +#include "CogHelper.h" #include "Components/SceneComponent.h" #include "Engine/World.h" #include "EngineUtils.h" @@ -59,7 +59,7 @@ ACogAbilityReplicator::ACogAbilityReplicator(const FObjectInitializer& ObjectIni bReplicates = true; bOnlyRelevantToOwner = true; - AbilityAsset = FCogWindowHelper::GetFirstAssetByClass(); + AbilityAsset = FCogHelper::GetFirstAssetByClass(); #endif // !UE_BUILD_SHIPPING } diff --git a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Abilities.cpp b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Abilities.cpp index 2788e86..f4121b8 100644 --- a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Abilities.cpp +++ b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Abilities.cpp @@ -6,7 +6,7 @@ #include "CogAbilityHelper.h" #include "CogAbilityReplicator.h" #include "CogImguiHelper.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "CogDebugRob.h" #include "imgui.h" @@ -19,7 +19,6 @@ void FCogAbilityWindow_Abilities::Initialize() Super::Initialize(); bHasMenu = true; - bNoPadding = true; Asset = GetAsset(); Config = GetConfig(); @@ -32,16 +31,20 @@ void FCogAbilityWindow_Abilities::RenderHelp() "This window displays the gameplay abilities of the selected actor. " "Click the ability check box to force its activation or deactivation. " "Right click an ability to open or close the ability separate window. " - "Use the 'Give Ability' menu to manually give an ability from a list defined in the '%s' data asset. " - , TCHAR_TO_ANSI(*GetNameSafe(Asset.Get()))); + ); } //-------------------------------------------------------------------------------------------------------------------------- -void FCogAbilityWindow_Abilities::ResetConfig() +void FCogAbilityWindow_Abilities::PreBegin(ImGuiWindowFlags& WindowFlags) { - Super::ResetConfig(); + WindowFlags |= ImGuiWindowFlags_NoScrollbar; + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); +} - Config->Reset(); +//-------------------------------------------------------------------------------------------------------------------------- +void FCogAbilityWindow_Abilities::PostBegin() +{ + ImGui::PopStyleVar(); } //-------------------------------------------------------------------------------------------------------------------------- @@ -175,7 +178,7 @@ void FCogAbilityWindow_Abilities::RenderAbilitiesMenu(AActor* Selection) ImGui::EndMenu(); } - FCogWindowWidgets::SearchBar(Filter); + FCogWidgets::SearchBar("##Filter", Filter); ImGui::EndMenuBar(); } @@ -193,24 +196,24 @@ void FCogAbilityWindow_Abilities::RenderAbilitiesMenuFilters() //-------------------------------------------------------------------------------------------------------------------------- void FCogAbilityWindow_Abilities::RenderAbilitiesMenuColorSettings() { - ImGui::ColorEdit4("Active Color", (float*)&Config->ActiveAbilityColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Inactive Color", (float*)&Config->InactiveAbilityColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Blocked Color", (float*)&Config->BlockedAbilityColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Default Tag Color", (float*)&Config->DefaultTagsColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Blocked Tag Color", (float*)&Config->BlockedTagsColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Input Pressed Color", (float*)&Config->InputPressedColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Active Color", &Config->ActiveAbilityColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Inactive Color", &Config->InactiveAbilityColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Blocked Color", &Config->BlockedAbilityColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Default Tag Color", &Config->DefaultTagsColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Blocked Tag Color", &Config->BlockedTagsColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Input Pressed Color", &Config->InputPressedColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); } //-------------------------------------------------------------------------------------------------------------------------- void FCogAbilityWindow_Abilities::RenderAbilityActivation(FGameplayAbilitySpec& Spec) { - FCogWindowWidgets::PushStyleCompact(); + FCogWidgets::PushStyleCompact(); bool IsActive = Spec.IsActive(); if (ImGui::Checkbox("##Activation", &IsActive)) { AbilityHandleToActivate = Spec.Handle; } - FCogWindowWidgets::PopStyleCompact(); + FCogWidgets::PopStyleCompact(); } //-------------------------------------------------------------------------------------------------------------------------- @@ -393,10 +396,10 @@ void FCogAbilityWindow_Abilities::RenderAbilitiesTableRow(UAbilitySystemComponen //------------------------ // Popup //------------------------ - if (FCogWindowWidgets::BeginItemTableTooltip()) + if (FCogWidgets::BeginItemTableTooltip()) { RenderAbilityInfo(AbilitySystemComponent, Spec); - FCogWindowWidgets::EndItemTableTooltip(); + FCogWidgets::EndItemTableTooltip(); } //------------------------ @@ -467,7 +470,7 @@ void FCogAbilityWindow_Abilities::RenderAbilityInputPressed(FGameplayAbilitySpec { if (Spec.InputPressed) { - FCogWindowWidgets::SmallButton("Pressed", FCogImguiHelper::ToImVec4(Config->ActiveAbilityColor)); + FCogWidgets::SmallButton("Pressed", FCogImguiHelper::ToImVec4(Config->ActiveAbilityColor)); } } @@ -502,7 +505,7 @@ void FCogAbilityWindow_Abilities::RenderAbilityContextMenu(UAbilitySystemCompone AbilityHandleToRemove = Spec.Handle; } - FCogWindowWidgets::OpenObjectAssetButton(Spec.Ability, ButtonsSize); + FCogWidgets::OpenObjectAssetButton(Spec.Ability, ButtonsSize); ImGui::EndPopup(); } @@ -573,13 +576,13 @@ void FCogAbilityWindow_Abilities::RenderAbilityInfo(const UAbilitySystemComponen ImGui::TableNextColumn(); ImGui::TextColored(TextColor, "Activation"); ImGui::TableNextColumn(); - FCogWindowWidgets::PushStyleCompact(); + FCogWidgets::PushStyleCompact(); bool IsActive = Spec.IsActive(); if (ImGui::Checkbox("##Activation", &IsActive)) { AbilityHandleToActivate = Spec.Handle; } - FCogWindowWidgets::PopStyleCompact(); + FCogWidgets::PopStyleCompact(); //------------------------ // Active Count diff --git a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Attributes.cpp b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Attributes.cpp index 69c00f7..2415c78 100644 --- a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Attributes.cpp +++ b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Attributes.cpp @@ -6,7 +6,7 @@ #include "CogAbilityDataAsset.h" #include "CogAbilityHelper.h" #include "CogImguiHelper.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "AttributeSet.h" #include "CogAbilityWindow_Abilities.h" #include "imgui_internal.h" @@ -17,7 +17,6 @@ void FCogAbilityWindow_Attributes::Initialize() Super::Initialize(); bHasMenu = true; - bNoPadding = true; Config = GetConfig(); AlignmentConfig = GetConfig(); @@ -36,11 +35,15 @@ void FCogAbilityWindow_Attributes::RenderHelp() } //-------------------------------------------------------------------------------------------------------------------------- -void FCogAbilityWindow_Attributes::ResetConfig() +void FCogAbilityWindow_Attributes::PreBegin(ImGuiWindowFlags& WindowFlags) { - Super::ResetConfig(); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); +} - Config->Reset(); +//-------------------------------------------------------------------------------------------------------------------------- +void FCogAbilityWindow_Attributes::PostBegin() +{ + ImGui::PopStyleVar(); } //-------------------------------------------------------------------------------------------------------------------------- @@ -90,16 +93,16 @@ void FCogAbilityWindow_Attributes::RenderContent() ImGui::Checkbox("Group by Category", &Config->GroupByCategory); ImGui::Checkbox("Show Only Modified", &Config->ShowOnlyModified); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::InputText("Attribute Set Prefixes", Config->AttributeSetPrefixes); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::InputText("Attribute Set Prefixes", Config->AttributeSetPrefixes); ImGui::SetItemTooltip("Prefixes to remove from the attribute set name. Separate multiple prefixes with the semicolon character ';'"); ImGui::Separator(); - ImGui::ColorEdit4("Positive Color", (float*)&AlignmentConfig->PositiveColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Negative Color", (float*)&AlignmentConfig->NegativeColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Neutral Color", (float*)&AlignmentConfig->NeutralColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("AttributeSet Color", (float*)&Config->AttributeSetColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Category Color", (float*)&Config->CategoryColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Positive Color", &AlignmentConfig->PositiveColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Negative Color", &AlignmentConfig->NegativeColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Neutral Color", &AlignmentConfig->NeutralColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("AttributeSet Color", &Config->AttributeSetColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Category Color", &Config->CategoryColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); ImGui::Separator(); if (ImGui::MenuItem("Reset")) { @@ -108,7 +111,7 @@ void FCogAbilityWindow_Attributes::RenderContent() ImGui::EndMenu(); } - FCogWindowWidgets::SearchBar(Filter); + FCogWidgets::SearchBar("##Filter", Filter); ImGui::EndMenuBar(); } @@ -116,7 +119,7 @@ void FCogAbilityWindow_Attributes::RenderContent() const bool bGroupByAttributeSetValue = Filter.IsActive() == false && Config->ShowOnlyModified == false && Config->GroupByAttributeSet; const bool bGroupByCategoryValue = Filter.IsActive() == false && Config->ShowOnlyModified == false && Config->GroupByCategory; const float bShowGroup = bGroupByAttributeSetValue | bGroupByCategoryValue; - const float FirstColWidth = ((int32)bGroupByAttributeSetValue + (int32)bGroupByCategoryValue) * ImGui::GetFontSize() * 2; + const float FirstColWidth = (static_cast(bGroupByAttributeSetValue) + static_cast(bGroupByCategoryValue)) * ImGui::GetFontSize() * 2; if (ImGui::BeginTable("Attributes", 5, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable @@ -280,7 +283,7 @@ void FCogAbilityWindow_Attributes::RenderContent() { Selected = Index; - if (ImGui::IsMouseDoubleClicked(0)) + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { OpenAttributeDetails(Attribute); } @@ -289,10 +292,10 @@ void FCogAbilityWindow_Attributes::RenderContent() //------------------------ // Popup //------------------------ - if (FCogWindowWidgets::BeginItemTableTooltip()) + if (FCogWidgets::BeginItemTableTooltip()) { RenderAttributeDetails(*AbilitySystemComponent, AttributeSetNameStr.Get(), Attribute, true); - FCogWindowWidgets::EndItemTableTooltip(); + FCogWidgets::EndItemTableTooltip(); } //------------------------ @@ -446,7 +449,7 @@ void FCogAbilityWindow_Attributes::RenderAttributeDetails(const UAbilitySystemCo char Buffer[128]; ImFormatString(Buffer, IM_ARRAYSIZE(Buffer), "Modifier %d", ModifierIndex); - if (FCogWindowWidgets::DarkCollapsingHeader(Buffer, ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_DefaultOpen)) + if (FCogWidgets::DarkCollapsingHeader(Buffer, ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_DefaultOpen)) { if (ImGui::BeginTable("Details", 2, TableFlags)) { diff --git a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Cheats.cpp b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Cheats.cpp index fed189a..7adb40c 100644 --- a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Cheats.cpp +++ b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Cheats.cpp @@ -1,146 +1,11 @@ #include "CogAbilityWindow_Cheats.h" -#include "CogAbilityConfig_Alignment.h" -#include "CogAbilityDataAsset.h" -#include "CogAbilityReplicator.h" -#include "CogCommonAllegianceActorInterface.h" -#include "CogImguiHelper.h" -#include "CogWindowConsoleCommandManager.h" -#include "CogWindowWidgets.h" -#include "EngineUtils.h" -#include "GameFramework/Character.h" #include "imgui.h" -#include "imgui_internal.h" - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogAbilityWindow_Cheats::Initialize() -{ - Super::Initialize(); - - bHasMenu = true; - - Asset = GetAsset(); - Config = GetConfig(); - AlignmentConfig = GetConfig(); - - FCogWindowConsoleCommandManager::RegisterWorldConsoleCommand( - TEXT("Cog.Cheat"), - TEXT("Apply a cheat to the selection. Cog.Cheat -Allies -Enemies -Controlled"), - GetWorld(), - FCogWindowConsoleCommandDelegate::CreateLambda([this](const TArray& InArgs, UWorld* InWorld) - { - if (InArgs.Num() > 0) - { - if (const FCogAbilityCheat* cheat = FindCheatByName(InArgs[0])) - { - const bool ApplyToEnemies = InArgs.Contains("-Enemies"); - const bool ApplyToAllies = InArgs.Contains("-Allies"); - const bool ApplyToControlled = InArgs.Contains("-Controlled"); - - RequestCheat(GetLocalPlayerPawn(), GetSelection(), *cheat, ApplyToEnemies, ApplyToAllies, ApplyToControlled); - } - else - { - UE_LOG(LogCogImGui, Warning, TEXT("Cog.Cheat %s | Cheat not found"), *InArgs[0]); - } - } - })); -} //-------------------------------------------------------------------------------------------------------------------------- void FCogAbilityWindow_Cheats::RenderHelp() { - ImGui::Text( - "This window can be used to apply cheats to the selected actor (by default). " - "The cheats can be configured in the '%s' data asset. " - "When clicking a cheat button, press:\n" - " [CTRL] to apply the cheat to controlled actor\n" - " [ALT] to apply the cheat to the allies of the selected actor\n" - " [SHIFT] to apply the cheat to the enemies of the selected actor\n" - , TCHAR_TO_ANSI(*GetNameSafe(Asset.Get())) - ); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogAbilityWindow_Cheats::ResetConfig() -{ - Super::ResetConfig(); - - Config->Reset(); - AlignmentConfig->Reset(); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogAbilityWindow_Cheats::GameTick(float DeltaTime) -{ - Super::GameTick(DeltaTime); - - TryReapplyCheats(); -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogAbilityWindow_Cheats::TryReapplyCheats() -{ - if (Config == nullptr) - { - return; - } - - if (bHasReappliedCheats) - { - return; - } - - if (Config->bReapplyCheatsBetweenPlays == false) - { - return; - } - - static int32 IsFirstLaunch = true; - if (IsFirstLaunch && Config->bReapplyCheatsBetweenLaunches == false) - { - return; - } - IsFirstLaunch = false; - - if (Asset == nullptr) - { - return; - } - - APawn* ControlledActor = GetLocalPlayerPawn(); - if (ControlledActor == nullptr) - { - return; - } - - ACogAbilityReplicator* Replicator = ACogAbilityReplicator::GetFirstReplicator(*GetWorld()); - if (Replicator == nullptr) - { - return; - } - - TArray Targets { ControlledActor }; - - for (int32 i = Config->AppliedCheats.Num() - 1; i >= 0; i--) - { - const FString& AppliedCheatName = Config->AppliedCheats[i]; - - if (const FCogAbilityCheat* Cheat = Asset->PersistentEffects.FindByPredicate( - [AppliedCheatName](const FCogAbilityCheat& Cheat) { return Cheat.Name == AppliedCheatName; })) - { - Replicator->Server_ApplyCheat(ControlledActor, Targets, *Cheat); - } - else - { - //----------------------------------------------------- - // This cheat doesn't exist anymore. We can remove it. - //----------------------------------------------------- - Config->AppliedCheats.RemoveAt(i); - } - } - - bHasReappliedCheats = true; + ImGui::TextDisabled("This window is deprecated. Please use the CogEngineWindow_Cheat instead as it provide more functionalities."); } //-------------------------------------------------------------------------------------------------------------------------- @@ -148,235 +13,5 @@ void FCogAbilityWindow_Cheats::RenderContent() { Super::RenderContent(); - if (Config == nullptr) - { - ImGui::TextDisabled("Invalid Config"); - return; - } - - AActor* SelectedActor = GetSelection(); - if (SelectedActor == nullptr) - { - ImGui::TextDisabled("Invalid Selection"); - return; - } - - if (Asset == nullptr) - { - ImGui::TextDisabled("Invalid Asset"); - return; - } - - if (ImGui::BeginMenuBar()) - { - if (ImGui::BeginMenu("Options")) - { - ImGui::Checkbox("Reapply Cheats Between Plays", &Config->bReapplyCheatsBetweenPlays); - - if (Config->bReapplyCheatsBetweenPlays == false) - { - ImGui::BeginDisabled(); - } - ImGui::Checkbox("Reapply Cheats Between Launches", &Config->bReapplyCheatsBetweenLaunches); - - if (Config->bReapplyCheatsBetweenPlays == false) - { - ImGui::EndDisabled(); - } - - ImGui::Separator(); - ImGui::ColorEdit4("Positive Color", (float*)&AlignmentConfig->PositiveColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Negative Color", (float*)&AlignmentConfig->NegativeColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Neutral Color", (float*)&AlignmentConfig->NeutralColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - - ImGui::Separator(); - if (ImGui::MenuItem("Reset")) - { - ResetConfig(); - } - - ImGui::EndMenu(); - } - - ImGui::EndMenuBar(); - } - - APawn* ControlledActor = GetLocalPlayerPawn(); - - if (ImGui::BeginTable("Cheats", 2, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_NoBordersInBodyUntilResize)) - { - ImGui::TableSetupColumn("Toggle", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("Instant", ImGuiTableColumnFlags_WidthStretch); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - - - int Index = 0; - for (const FCogAbilityCheat& CheatEffect : Asset->PersistentEffects) - { - ImGui::PushID(Index); - AddCheat(ControlledActor, SelectedActor, CheatEffect, true); - ImGui::PopID(); - Index++; - } - - //---------------------------------------------------------------------------- - // Update the config of applied cheat to reapply them on the next launch. - // We do not updated them only when the the user input is pressed because - // the state of the cheat is lagging when connected to a server. - // So we check if the array should be updated all the time. - //---------------------------------------------------------------------------- - if (SelectedActor == ControlledActor) - { - for (const FCogAbilityCheat& CheatEffect : Asset->PersistentEffects) - { - if (ACogAbilityReplicator::IsCheatActive(SelectedActor, CheatEffect)) - { - Config->AppliedCheats.AddUnique(CheatEffect.Name); - } - else - { - Config->AppliedCheats.Remove(CheatEffect.Name); - } - } - } - - ImGui::TableNextColumn(); - - for (const FCogAbilityCheat& CheatEffect : Asset->InstantEffects) - { - ImGui::PushID(Index); - AddCheat(ControlledActor, SelectedActor, CheatEffect, false); - ImGui::PopID(); - Index++; - } - - ImGui::EndTable(); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -bool FCogAbilityWindow_Cheats::AddCheat(AActor* ControlledActor, AActor* SelectedActor, const FCogAbilityCheat& Cheat, bool IsPersistent) -{ - if (Cheat.Effect == nullptr) - { - return false; - } - - const UGameplayEffect* EffectCDO = Cheat.Effect->GetDefaultObject(); - - if (EffectCDO != nullptr) - { - FCogWindowWidgets::PushBackColor(FCogImguiHelper::ToImVec4(AlignmentConfig->GetEffectColor(Asset, *EffectCDO))); - } - - const bool IsShiftDown = (ImGui::GetCurrentContext()->IO.KeyMods & ImGuiMod_Shift) != 0; - const bool IsAltDown = (ImGui::GetCurrentContext()->IO.KeyMods & ImGuiMod_Alt) != 0; - const bool IsControlDown = (ImGui::GetCurrentContext()->IO.KeyMods & ImGuiMod_Ctrl) != 0; - - bool bIsPressed = false; - if (IsPersistent) - { - bool isEnabled = ACogAbilityReplicator::IsCheatActive(SelectedActor, Cheat); - if (ImGui::Checkbox(TCHAR_TO_ANSI(*Cheat.Name), &isEnabled)) - { - RequestCheat(ControlledActor, SelectedActor, Cheat, IsShiftDown, IsAltDown, IsControlDown); - bIsPressed = true; - } - } - else - { - if (ImGui::Button(TCHAR_TO_ANSI(*Cheat.Name), ImVec2(-1, 0))) - { - RequestCheat(ControlledActor, SelectedActor, Cheat, IsShiftDown, IsAltDown, IsControlDown); - bIsPressed = true; - } - } - - if (ImGui::IsItemHovered()) - { - ImGui::BeginTooltip(); - ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, IsShiftDown || IsAltDown || IsControlDown ? 0.5f : 1.0f), "On Selection"); - ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, IsShiftDown ? 1.0f : 0.5f), "On Enemies [SHIFT]"); - ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, IsAltDown ? 1.0f : 0.5f), "On Allies [ALT]"); - ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, IsControlDown ? 1.0f : 0.5f), "On Controlled [CTRL]"); - ImGui::EndTooltip(); - } - - if (EffectCDO != nullptr) - { - FCogWindowWidgets::PopBackColor(); - } - - return bIsPressed; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogAbilityWindow_Cheats::RequestCheat(AActor* ControlledActor, AActor* SelectedActor, const FCogAbilityCheat& Cheat, bool ApplyToEnemies, bool ApplyToAllies, bool ApplyToControlled) -{ - TArray Actors; - - if (ApplyToControlled) - { - Actors.Add(ControlledActor); - } - - if (ApplyToEnemies || ApplyToAllies) - { - for (TActorIterator It(GetWorld(), ACharacter::StaticClass()); It; ++It) - { - if (AActor* OtherActor = *It) - { - ECogCommonAllegiance Allegiance = ECogCommonAllegiance::Enemy; - - if (ICogCommonAllegianceActorInterface* AllegianceInterface = Cast(OtherActor)) - { - Allegiance = AllegianceInterface->GetAllegianceWithOtherActor(ControlledActor); - } - - if ((ApplyToEnemies && (Allegiance == ECogCommonAllegiance::Enemy)) - || (ApplyToAllies && (Allegiance == ECogCommonAllegiance::Friendly))) - { - Actors.Add(OtherActor); - } - } - } - } - - if ((ApplyToControlled || ApplyToEnemies || ApplyToAllies) == false) - { - Actors.Add(SelectedActor); - } - - if (ACogAbilityReplicator* Replicator = ACogAbilityReplicator::GetFirstReplicator(*GetWorld())) - { - Replicator->Server_ApplyCheat(ControlledActor, Actors, Cheat); - } - else - { - UE_LOG(LogCogImGui, Warning, TEXT("FCogAbilityWindow_Cheats::RequestCheat | Replicator not found")); - } -} - -//-------------------------------------------------------------------------------------------------------------------------- -const FCogAbilityCheat* FCogAbilityWindow_Cheats::FindCheatByName(const FString& CheatName) -{ - for (const FCogAbilityCheat& cheat : Asset->PersistentEffects) - { - if (cheat.Name == CheatName) - { - return &cheat; - } - } - - for (const FCogAbilityCheat& cheat : Asset->InstantEffects) - { - if (cheat.Name == CheatName) - { - return &cheat; - } - } - - return nullptr; -} + RenderHelp(); +} \ No newline at end of file diff --git a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Effects.cpp b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Effects.cpp index 2df399e..9d573b6 100644 --- a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Effects.cpp +++ b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Effects.cpp @@ -7,7 +7,7 @@ #include "CogAbilityDataAsset.h" #include "CogAbilityHelper.h" #include "CogImguiHelper.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "Engine/World.h" //-------------------------------------------------------------------------------------------------------------------------- @@ -16,7 +16,6 @@ void FCogAbilityWindow_Effects::Initialize() Super::Initialize(); bHasMenu = true; - bNoPadding = true; Asset = GetAsset(); Config = GetConfig(); @@ -32,15 +31,6 @@ void FCogAbilityWindow_Effects::RenderHelp() ); } -//-------------------------------------------------------------------------------------------------------------------------- -void FCogAbilityWindow_Effects::ResetConfig() -{ - Super::ResetConfig(); - - Config->Reset(); - AlignmentConfig->Reset(); -} - //-------------------------------------------------------------------------------------------------------------------------- void FCogAbilityWindow_Effects::RenderTick(float DeltaTime) { @@ -49,6 +39,19 @@ void FCogAbilityWindow_Effects::RenderTick(float DeltaTime) RenderOpenEffects(); } +//-------------------------------------------------------------------------------------------------------------------------- +void FCogAbilityWindow_Effects::PreBegin(ImGuiWindowFlags& WindowFlags) +{ + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogAbilityWindow_Effects::PostBegin() +{ + ImGui::PopStyleVar(); +} + + //-------------------------------------------------------------------------------------------------------------------------- void FCogAbilityWindow_Effects::RenderContent() { @@ -62,9 +65,9 @@ void FCogAbilityWindow_Effects::RenderContent() ImGui::Checkbox("Sort by Alignment", &Config->SortByAlignment); ImGui::Separator(); - ImGui::ColorEdit4("Positive Color", (float*)&AlignmentConfig->PositiveColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Negative Color", (float*)&AlignmentConfig->NegativeColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Neutral Color", (float*)&AlignmentConfig->NeutralColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Positive Color", &AlignmentConfig->PositiveColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Negative Color", &AlignmentConfig->NegativeColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Neutral Color", &AlignmentConfig->NeutralColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); ImGui::Separator(); if (ImGui::MenuItem("Reset")) @@ -74,7 +77,7 @@ void FCogAbilityWindow_Effects::RenderContent() ImGui::EndMenu(); } - FCogWindowWidgets::SearchBar(Filter); + FCogWidgets::SearchBar("##Filter", Filter); ImGui::EndMenuBar(); } @@ -137,7 +140,6 @@ void FCogAbilityWindow_Effects::RenderEffectsTable() } } - bool AlignmentOrder = false; if (Config->SortByAlignment && Asset != nullptr) { const FGameplayTagContainer& Tags1 = Effect1->GetAssetTags(); @@ -201,7 +203,9 @@ void FCogAbilityWindow_Effects::RenderEffectRow(UAbilitySystemComponent& Ability //------------------------ ImGui::TableNextColumn(); - ImGui::PushStyleColor(ImGuiCol_Text, FCogImguiHelper::ToImVec4(AlignmentConfig->GetEffectColor(Asset, Effect))); + FLinearColor Color; + AlignmentConfig->GetEffectColor(Asset, Effect, Color); + ImGui::PushStyleColor(ImGuiCol_Text, FCogImguiHelper::ToImVec4(Color)); if (ImGui::Selectable(EffectName.Get(), Selected == Index, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap | ImGuiSelectableFlags_AllowDoubleClick)) { @@ -213,10 +217,10 @@ void FCogAbilityWindow_Effects::RenderEffectRow(UAbilitySystemComponent& Ability //------------------------ // Popup //------------------------ - if (FCogWindowWidgets::BeginItemTableTooltip()) + if (FCogWidgets::BeginItemTableTooltip()) { RenderEffectInfo(AbilitySystemComponent, ActiveEffect, Effect); - FCogWindowWidgets::EndItemTableTooltip(); + FCogWidgets::EndItemTableTooltip(); } //------------------------ @@ -246,7 +250,7 @@ void FCogAbilityWindow_Effects::RenderEffectRow(UAbilitySystemComponent& Ability ImGui::CloseCurrentPopup(); } - FCogWindowWidgets::OpenObjectAssetButton(EffectPtr, ButtonsSize); + FCogWidgets::OpenObjectAssetButton(EffectPtr, ButtonsSize); ImGui::EndPopup(); } @@ -454,7 +458,7 @@ void FCogAbilityWindow_Effects::RenderStacks(const FActiveGameplayEffect& Active ImGui::PushStyleColor(ImGuiCol_PlotHistogram, IM_COL32(100, 100, 100, 255)); ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 0, 0, 100)); ImGui::SetNextItemWidth(-1); - ImGui::ProgressBar(CurrentStackCount / (float)Effect.StackLimitCount, ImVec2(FCogWindowWidgets::GetFontWidth() * 15, ImGui::GetTextLineHeightWithSpacing() * 0.8f), TCHAR_TO_ANSI(*FString::Printf(TEXT("%d / %d"), CurrentStackCount, Effect.StackLimitCount))); + ImGui::ProgressBar(CurrentStackCount / (float)Effect.StackLimitCount, ImVec2(FCogWidgets::GetFontWidth() * 15, ImGui::GetTextLineHeightWithSpacing() * 0.8f), TCHAR_TO_ANSI(*FString::Printf(TEXT("%d / %d"), CurrentStackCount, Effect.StackLimitCount))); ImGui::PopStyleColor(2); } } @@ -487,7 +491,7 @@ void FCogAbilityWindow_Effects::RenderInhibition(const FActiveGameplayEffect& Ac { if (ActiveEffect.bIsInhibited) { - FCogWindowWidgets::SmallButton("Yes", ImVec4(1, 0, 0, 1)); + FCogWidgets::SmallButton("Yes", ImVec4(1, 0, 0, 1)); } else { diff --git a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Pools.cpp b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Pools.cpp index 0374bdd..264cd4c 100644 --- a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Pools.cpp +++ b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Pools.cpp @@ -3,8 +3,9 @@ #include "AbilitySystemComponent.h" #include "AbilitySystemGlobals.h" #include "CogAbilityDataAsset.h" +#include "CogAbilityHelper.h" #include "CogImguiHelper.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "imgui_internal.h" //-------------------------------------------------------------------------------------------------------------------------- @@ -18,11 +19,8 @@ void FCogAbilityWindow_Pools::Initialize() //-------------------------------------------------------------------------------------------------------------------------- void FCogAbilityWindow_Pools::RenderHelp() { - ImGui::Text( - "This window displays attributes of the selected actor as pools. " - "The pools can be configured in the '%s' data asset." - , TCHAR_TO_ANSI(*GetNameSafe(Asset.Get())) - ); + ImGui::Text("This window displays attributes of the selected actor as pools. "); + FCogAbilityHelper::RenderConfigureMessage(Asset); } //-------------------------------------------------------------------------------------------------------------------------- @@ -87,19 +85,19 @@ void FCogAbilityWindow_Pools::DrawPool(const UAbilitySystemComponent* AbilitySys //------------------------------------------------------------------------------------------- // Use a different format base on max value for all pools to be nicely aligned at the center //------------------------------------------------------------------------------------------- - const char* format = nullptr; - if (Max >= 100) { format = "%3.0f / %3.0f"; } // |200 / 200| |__1 / 200| 3 characters with 0 floating point - else if (Max >= 10) { format = "%4.1f / %4.1f"; } // |20.0 / 20.0| |_1.1 / 20.0| 4 characters with 1 floating point - else { format = "%3.2f / %3.2f"; } // |2.00 / 2.00| |1.11 / 2.00| 3 characters with 2 floating points + const char* Format; + if (Max >= 100) { Format = "%3.0f / %3.0f"; } // |200 / 200| |__1 / 200| 3 characters with 0 floating point + else if (Max >= 10) { Format = "%4.1f / %4.1f"; } // |20.0 / 20.0| |_1.1 / 20.0| 4 characters with 1 floating point + else { Format = "%3.2f / %3.2f"; } // |2.00 / 2.00| |1.11 / 2.00| 3 characters with 2 floating points char Buffer[64]; - ImFormatString(Buffer, IM_ARRAYSIZE(Buffer), format, Value, Max); + ImFormatString(Buffer, IM_ARRAYSIZE(Buffer), Format, Value, Max); const float Ratio = Max > 0.0f ? Value / Max : 0.0f; ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_PlotHistogram, FCogImguiHelper::ToImVec4(Pool.Color)); ImGui::PushStyleColor(ImGuiCol_FrameBg, FCogImguiHelper::ToImVec4(Pool.BackColor)); - FCogWindowWidgets::ProgressBarCentered(Ratio, ImVec2(-1, 0), Buffer); + FCogWidgets::ProgressBarCentered(Ratio, ImVec2(-1, 0), Buffer); ImGui::PopStyleColor(2); } diff --git a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Tags.cpp b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Tags.cpp index 6cf697f..5c05b4b 100644 --- a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Tags.cpp +++ b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Tags.cpp @@ -3,7 +3,7 @@ #include "AbilitySystemComponent.h" #include "AbilitySystemGlobals.h" #include "CogAbilityHelper.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" //-------------------------------------------------------------------------------------------------------------------------- void FCogAbilityWindow_Tags::Initialize() @@ -11,15 +11,6 @@ void FCogAbilityWindow_Tags::Initialize() Super::Initialize(); bHasMenu = true; - bNoPadding = true; -} - -//-------------------------------------------------------------------------------------------------------------------------- -void FCogAbilityWindow_Tags::ResetConfig() -{ - Super::ResetConfig(); - - Config->Reset(); } //-------------------------------------------------------------------------------------------------------------------------- @@ -28,6 +19,18 @@ void FCogAbilityWindow_Tags::RenderHelp() ImGui::Text("This window displays gameplay tags of the selected actor. "); } +//-------------------------------------------------------------------------------------------------------------------------- +void FCogAbilityWindow_Tags::PreBegin(ImGuiWindowFlags& WindowFlags) +{ + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogAbilityWindow_Tags::PostBegin() +{ + ImGui::PopStyleVar(); +} + //-------------------------------------------------------------------------------------------------------------------------- void FCogAbilityWindow_Tags::RenderContent() { @@ -67,7 +70,7 @@ void FCogAbilityWindow_Tags::RenderMenu() ImGui::EndMenu(); } - FCogWindowWidgets::SearchBar(Filter); + FCogWidgets::SearchBar("##Filter", Filter); ImGui::EndMenuBar(); } @@ -134,10 +137,10 @@ void FCogAbilityWindow_Tags::RenderTagContainer(const UAbilitySystemComponent& A //------------------------ // Tooltip //------------------------ - if (FCogWindowWidgets::BeginItemTableTooltip()) + if (FCogWidgets::BeginItemTableTooltip()) { RenderTag(AbilitySystemComponent, Tag); - FCogWindowWidgets::EndItemTableTooltip(); + FCogWidgets::EndItemTableTooltip(); } ImGui::PopID(); @@ -153,7 +156,7 @@ void FCogAbilityWindow_Tags::RenderTag(const UAbilitySystemComponent& AbilitySys { if (ImGui::BeginTable("Tag", 2, ImGuiTableFlags_Borders)) { - const ImVec4 TextColor(1.0f, 1.0f, 1.0f, 0.5f); + constexpr ImVec4 TextColor(1.0f, 1.0f, 1.0f, 0.5f); ImGui::TableSetupColumn("Property"); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); diff --git a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Tasks.cpp b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Tasks.cpp index bea5113..99c81a6 100644 --- a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Tasks.cpp +++ b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Tasks.cpp @@ -4,7 +4,7 @@ #include "AbilitySystemComponent.h" #include "CogAbilityHelper.h" #include "CogImguiHelper.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "imgui.h" class UCogAbilityConfig_Tasks; @@ -14,7 +14,6 @@ void FCogAbilityWindow_Tasks::Initialize() Super::Initialize(); bHasMenu = true; - bNoPadding = true; Config = GetConfig(); } @@ -27,17 +26,15 @@ void FCogAbilityWindow_Tasks::RenderHelp() } //-------------------------------------------------------------------------------------------------------------------------- -void FCogAbilityWindow_Tasks::ResetConfig() +void FCogAbilityWindow_Tasks::PreBegin(ImGuiWindowFlags& WindowFlags) { - Super::ResetConfig(); - - Config->Reset(); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); } //-------------------------------------------------------------------------------------------------------------------------- -void FCogAbilityWindow_Tasks::RenderTick(float DetlaTime) +void FCogAbilityWindow_Tasks::PostBegin() { - Super::RenderTick(DetlaTime); + ImGui::PopStyleVar(); } //-------------------------------------------------------------------------------------------------------------------------- @@ -82,11 +79,11 @@ void FCogAbilityWindow_Tasks::RenderTaskMenu(AActor* Selection) ImGui::Separator(); - ImGui::ColorEdit4("Uninitialized Color", (float*)&Config->UninitializedColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Awaiting Activation Color", (float*)&Config->AwaitingActivationColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Active Color", (float*)&Config->ActiveColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Paused Color", (float*)&Config->PausedColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Finished Color", (float*)&Config->FinishedColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Uninitialized Color", &Config->UninitializedColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Awaiting Activation Color", &Config->AwaitingActivationColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Active Color", &Config->ActiveColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Paused Color", &Config->PausedColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Finished Color", &Config->FinishedColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); ImGui::Separator(); @@ -98,7 +95,7 @@ void FCogAbilityWindow_Tasks::RenderTaskMenu(AActor* Selection) ImGui::EndMenu(); } - FCogWindowWidgets::SearchBar(Filter); + FCogWidgets::SearchBar("##Filter", Filter); ImGui::EndMenuBar(); } @@ -110,8 +107,6 @@ void FCogAbilityWindow_Tasks::RenderTasksTable(UAbilitySystemComponent& AbilityS TArray FilteredTasks; FilteredTasks.Reserve(16); - const AActor* Selection = GetSelection(); - for (FConstGameplayTaskIterator it = AbilitySystemComponent.GetKnownTaskIterator(); it; ++it) { const UGameplayTask* Task = Cast(*it); @@ -234,10 +229,10 @@ void FCogAbilityWindow_Tasks::RenderTasksTable(UAbilitySystemComponent& AbilityS //------------------------ // Popup //------------------------ - if (FCogWindowWidgets::BeginItemTableTooltip()) + if (FCogWidgets::BeginItemTableTooltip()) { RenderTaskInfo(Task); - FCogWindowWidgets::EndItemTableTooltip(); + FCogWidgets::EndItemTableTooltip(); } //------------------------ @@ -319,7 +314,7 @@ void FCogAbilityWindow_Tasks::RenderTaskInfo(const UGameplayTask* Task) ImGui::TableNextColumn(); ImGui::TextColored(TextColor, "Priority"); ImGui::TableNextColumn(); - ImGui::Text("%d", (int32)Task->GetPriority()); + ImGui::Text("%d", static_cast(Task->GetPriority())); //------------------------ // IsTicking @@ -355,7 +350,7 @@ void FCogAbilityWindow_Tasks::RenderTaskInfo(const UGameplayTask* Task) ImGui::TableNextColumn(); ImGui::TextColored(TextColor, "Debug"); ImGui::TableNextColumn(); - ImGui::PushTextWrapPos(FCogWindowWidgets::GetFontWidth() * 80); + ImGui::PushTextWrapPos(FCogWidgets::GetFontWidth() * 80); ImGui::Text("%s", StringCast(*Task->GetDebugString()).Get()); ImGui::PopTextWrapPos(); @@ -388,23 +383,23 @@ void FCogAbilityWindow_Tasks::RenderTaskState(const UGameplayTask* Task) switch (Task->GetState()) { case EGameplayTaskState::Uninitialized: - FCogWindowWidgets::SmallButton("Uninitialized", FCogImguiHelper::ToImVec4(Config->UninitializedColor)); + FCogWidgets::SmallButton("Uninitialized", FCogImguiHelper::ToImVec4(Config->UninitializedColor)); break; case EGameplayTaskState::AwaitingActivation: - FCogWindowWidgets::SmallButton("Awaiting Activation", FCogImguiHelper::ToImVec4(Config->AwaitingActivationColor)); + FCogWidgets::SmallButton("Awaiting Activation", FCogImguiHelper::ToImVec4(Config->AwaitingActivationColor)); break; case EGameplayTaskState::Active: - FCogWindowWidgets::SmallButton("Active", FCogImguiHelper::ToImVec4(Config->ActiveColor)); + FCogWidgets::SmallButton("Active", FCogImguiHelper::ToImVec4(Config->ActiveColor)); break; case EGameplayTaskState::Paused: - FCogWindowWidgets::SmallButton("Paused", FCogImguiHelper::ToImVec4(Config->PausedColor)); + FCogWidgets::SmallButton("Paused", FCogImguiHelper::ToImVec4(Config->PausedColor)); break; case EGameplayTaskState::Finished: - FCogWindowWidgets::SmallButton("Finished", FCogImguiHelper::ToImVec4(Config->FinishedColor)); + FCogWidgets::SmallButton("Finished", FCogImguiHelper::ToImVec4(Config->FinishedColor)); break; } } diff --git a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Tweaks.cpp b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Tweaks.cpp index 14e3592..e750912 100644 --- a/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Tweaks.cpp +++ b/Plugins/CogAbility/Source/CogAbility/Private/CogAbilityWindow_Tweaks.cpp @@ -1,9 +1,10 @@ #include "CogAbilityWindow_Tweaks.h" #include "CogAbilityDataAsset.h" +#include "CogAbilityHelper.h" #include "CogAbilityReplicator.h" #include "CogImguiHelper.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" //-------------------------------------------------------------------------------------------------------------------------- void FCogAbilityWindow_Tweaks::Initialize() @@ -20,10 +21,10 @@ void FCogAbilityWindow_Tweaks::RenderHelp() { ImGui::Text( "This window can be used to apply tweaks to all the loaded actors. " - "The tweaks are used to test various gameplay settings by actor category. " - "The tweaks can be configured in the '%s' data asset. " - , TCHAR_TO_ANSI(*GetNameSafe(Asset.Get())) + "Tweaks are used to test various gameplay settings by actor category. " ); + + FCogAbilityHelper::RenderConfigureMessage(Asset); } //-------------------------------------------------------------------------------------------------------------------------- @@ -141,11 +142,11 @@ void FCogAbilityWindow_Tweaks::DrawTweak(ACogAbilityReplicator* Replicator, int3 const FCogAbilityTweakCategory& Category = Asset->TweaksCategories[TweakCategoryIndex]; - FCogWindowWidgets::PushBackColor(FCogImguiHelper::ToImVec4(Category.Color)); + FCogWidgets::PushBackColor(FCogImguiHelper::ToImVec4(Category.Color)); ImGui::PushItemWidth(-1); ImGui::SliderFloat("##Value", Value, Asset->TweakMinValue, Asset->TweakMaxValue, "%+0.0f%%", 1.0f); ImGui::PopItemWidth(); - FCogWindowWidgets::PopBackColor(); + FCogWidgets::PopBackColor(); bool bUpdateValue = ImGui::IsItemDeactivatedAfterEdit(); diff --git a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityCheat_Execution_ActivateAbility.h b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityCheat_Execution_ActivateAbility.h new file mode 100644 index 0000000..e454e73 --- /dev/null +++ b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityCheat_Execution_ActivateAbility.h @@ -0,0 +1,27 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CogEngineDataAsset.h" +#include "GameplayEffect.h" +#include "CogAbilityCheat_Execution_ActivateAbility.generated.h" + +//-------------------------------------------------------------------------------------------------------------------------- +UCLASS(DisplayName = "Activate Ability") +class COGABILITY_API UCogAbilityCheat_Execution_ActivateAbility + : public UCogEngineCheat_Execution +{ + GENERATED_BODY() + +public: + virtual void Execute_Implementation(const UObject* WorldContextObject, const AActor* Instigator, const TArray& Targets) const override; + + virtual ECogEngineCheat_ActiveState IsActiveOnTargets_Implementation(const UObject* WorldContextObject, const TArray& Targets) const override; + + virtual bool GetColor(const FCogWindow& InCallingWindow, FLinearColor& OutColor) const override; + + UPROPERTY(EditAnywhere) + TSubclassOf Ability; + + UPROPERTY(EditAnywhere) + bool RemoveAbilityOnEnd = true; +}; diff --git a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityCheat_Execution_ApplyEffect.h b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityCheat_Execution_ApplyEffect.h index 583cdb0..159f610 100644 --- a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityCheat_Execution_ApplyEffect.h +++ b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityCheat_Execution_ApplyEffect.h @@ -13,9 +13,9 @@ class COGABILITY_API UCogAbilityCheat_Execution_ApplyEffect GENERATED_BODY() public: - void Execute_Implementation(const AActor* Instigator, const TArray& Targets) const override; + virtual void Execute_Implementation(const UObject* WorldContextObject, const AActor* Instigator, const TArray& Targets) const override; - ECogEngineCheat_ActiveState IsActiveOnTargets_Implementation(const TArray& Targets) const override; + virtual ECogEngineCheat_ActiveState IsActiveOnTargets_Implementation(const UObject* WorldContextObject, const TArray& Targets) const override; virtual bool GetColor(const FCogWindow& InCallingWindow, FLinearColor& OutColor) const override; diff --git a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityConfig_Alignment.h b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityConfig_Alignment.h index 9b055da..e2153b4 100644 --- a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityConfig_Alignment.h +++ b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityConfig_Alignment.h @@ -2,6 +2,7 @@ #include "CoreMinimal.h" #include "CogCommonConfig.h" +#include "Abilities/GameplayAbility.h" #include "CogAbilityConfig_Alignment.generated.h" class UAbilitySystemComponent; @@ -22,12 +23,13 @@ public: FVector4f GetAttributeColor(const UAbilitySystemComponent& AbilitySystemComponent, const FGameplayAttribute& Attribute) const; + bool GetAbilityColor(const UCogAbilityDataAsset* Asset, const UGameplayAbility& Ability, FLinearColor& OutColor) const; + FVector4f GetEffectModifierColor(const FModifierSpec& ModSpec, const FGameplayModifierInfo& ModInfo, float BaseValue) const; FVector4f GetEffectModifierColor(float ModifierValue, EGameplayModOp::Type ModifierOp, float BaseValue) const; - FVector4f GetEffectColor(const UCogAbilityDataAsset* Asset, const UGameplayEffect& Effect) const; - + bool GetEffectColor(const UCogAbilityDataAsset* Asset, const UGameplayEffect& Effect, FLinearColor& OutColor) const; UPROPERTY(Config) FVector4f PositiveColor = FVector4f(0.0f, 1.0f, 0.5f, 1.0f); diff --git a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityDataAsset.h b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityDataAsset.h index 42d6aee..3652cbd 100644 --- a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityDataAsset.h +++ b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityDataAsset.h @@ -151,6 +151,12 @@ public: UPROPERTY(Category = "Cheats", EditAnywhere) FGameplayTag PositiveEffectTag; + UPROPERTY(Category="Cheats", EditAnywhere) + FGameplayTag NegativeAbilityTag; + + UPROPERTY(Category = "Cheats", EditAnywhere) + FGameplayTag PositiveAbilityTag; + UPROPERTY(Category = "Cheats", EditAnywhere, meta = (TitleProperty = "Name")) TArray PersistentEffects; diff --git a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityHelper.h b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityHelper.h index f0a7fd4..c24e6bb 100644 --- a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityHelper.h +++ b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityHelper.h @@ -3,7 +3,6 @@ #include "CoreMinimal.h" #include "imgui.h" - class UCogAbilityDataAsset; class UGameplayEffect; namespace EGameplayModOp { enum Type : int; } @@ -25,5 +24,6 @@ public: const bool Inline = false, const ImVec4& DefaultColor = ImVec4(0.4f, 0.4f, 0.4f, 1.0f), const ImVec4& MatchColor = ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); - + + static void RenderConfigureMessage(TWeakObjectPtr InAsset); }; diff --git a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilitySubsystem.h b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilitySubsystem.h new file mode 100644 index 0000000..f047604 --- /dev/null +++ b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilitySubsystem.h @@ -0,0 +1,22 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CogAbilityReplicator.h" +#include "CogDebugPluginSubsystem.h" +#include "CogAbilitySubsystem.generated.h" + +UCLASS() +class COGABILITY_API UCogAbilitySubsystem : public UCogDebugPluginSubsystem +{ + GENERATED_BODY() + +public: + + virtual void OnPlayerControllerReady(APlayerController* InController) override + { + if (InController != nullptr) + { + ACogAbilityReplicator::Spawn(InController); + } + } +}; diff --git a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Abilities.h b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Abilities.h index 868a820..0a48b1d 100644 --- a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Abilities.h +++ b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Abilities.h @@ -22,10 +22,12 @@ public: protected: - virtual void ResetConfig() override; - virtual void RenderHelp() override; + virtual void PreBegin(ImGuiWindowFlags& WindowFlags) override; + + virtual void PostBegin() override; + virtual void RenderTick(float DeltaTime) override; virtual void RenderContent() override; diff --git a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Attributes.h b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Attributes.h index 0b7980e..ccf78cb 100644 --- a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Attributes.h +++ b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Attributes.h @@ -24,11 +24,13 @@ public: protected: - virtual void ResetConfig() override; - virtual void RenderHelp() override; + + virtual void PreBegin(ImGuiWindowFlags& WindowFlags) override; - virtual void RenderTick(float DeltaTime); + virtual void PostBegin() override; + + virtual void RenderTick(float DeltaTime) override; virtual void RenderContent() override; diff --git a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Cheats.h b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Cheats.h index dafa15b..f204fc2 100644 --- a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Cheats.h +++ b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Cheats.h @@ -16,36 +16,11 @@ class COGABILITY_API FCogAbilityWindow_Cheats : public FCogWindow { typedef FCogWindow Super; -public: - - virtual void Initialize() override; - protected: - virtual void GameTick(float DeltaTime); - - virtual void ResetConfig() override; - virtual void RenderHelp() override; virtual void RenderContent() override; - - virtual void TryReapplyCheats(); - APawn* GetCheatInstigator(); - - virtual bool AddCheat(AActor* ControlledActor, AActor* TargetActor, const FCogAbilityCheat& CheatEffect, bool IsPersistent); - - virtual void RequestCheat(AActor* ControlledActor, AActor* SelectedActor, const FCogAbilityCheat& Cheat, bool ApplyToEnemies, bool ApplyToAllies, bool ApplyToControlled); - - virtual const FCogAbilityCheat* FindCheatByName(const FString& CheatName); - - TObjectPtr Asset = nullptr; - - TObjectPtr Config = nullptr; - - TObjectPtr AlignmentConfig = nullptr; - - bool bHasReappliedCheats = false; }; //-------------------------------------------------------------------------------------------------------------------------- diff --git a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Effects.h b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Effects.h index afcd673..627c4a8 100644 --- a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Effects.h +++ b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Effects.h @@ -34,7 +34,9 @@ protected: virtual void RenderTick(float DeltaTime) override; - virtual void ResetConfig() override; + virtual void PreBegin(ImGuiWindowFlags& WindowFlags) override; + + virtual void PostBegin() override; virtual void RenderEffectsTable(); diff --git a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Pools.h b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Pools.h index bf66e8e..5887bc3 100644 --- a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Pools.h +++ b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Pools.h @@ -1,7 +1,6 @@ #pragma once #include "CoreMinimal.h" -#include "GameplayTagContainer.h" #include "CogWindow.h" class UCogAbilityDataAsset; diff --git a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Tags.h b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Tags.h index 010ecf1..f632371 100644 --- a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Tags.h +++ b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Tags.h @@ -23,10 +23,12 @@ protected: virtual void GetTagContainer(FGameplayTagContainer& TagContainer) {} - virtual void ResetConfig(); - virtual void RenderHelp() override; + virtual void PreBegin(ImGuiWindowFlags& WindowFlags) override; + + virtual void PostBegin() override; + virtual void RenderContent() override; virtual void RenderTagContainer(const UAbilitySystemComponent& AbilitySystemComponent, FGameplayTagContainer& TagContainer); @@ -49,7 +51,7 @@ class COGABILITY_API FCogAbilityWindow_OwnedTags : public FCogAbilityWindow_Tags virtual void RenderHelp() override; - virtual void GetTagContainer(FGameplayTagContainer& TagContainer); + virtual void GetTagContainer(FGameplayTagContainer& TagContainer) override; }; @@ -62,7 +64,7 @@ class COGABILITY_API FCogAbilityWindow_BlockedTags : public FCogAbilityWindow_Ta virtual void RenderHelp() override; - virtual void GetTagContainer(FGameplayTagContainer& TagContainer); + virtual void GetTagContainer(FGameplayTagContainer& TagContainer) override; }; //-------------------------------------------------------------------------------------------------------------------------- diff --git a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Tasks.h b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Tasks.h index b0519df..eea7f90 100644 --- a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Tasks.h +++ b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Tasks.h @@ -19,11 +19,11 @@ public: protected: - virtual void ResetConfig() override; - virtual void RenderHelp() override; - virtual void RenderTick(float DetlaTime) override; + virtual void PreBegin(ImGuiWindowFlags& WindowFlags) override; + + virtual void PostBegin() override; virtual void RenderContent() override; diff --git a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Tweaks.h b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Tweaks.h index d84c04c..ec655e3 100644 --- a/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Tweaks.h +++ b/Plugins/CogAbility/Source/CogAbility/Public/CogAbilityWindow_Tweaks.h @@ -25,5 +25,5 @@ protected: private: - TObjectPtr Asset = nullptr; + TWeakObjectPtr Asset; }; diff --git a/Plugins/CogAll/Source/CogAll/CogAll.Build.cs b/Plugins/CogAll/Source/CogAll/CogAll.Build.cs index bdadfe4..5543517 100644 --- a/Plugins/CogAll/Source/CogAll/CogAll.Build.cs +++ b/Plugins/CogAll/Source/CogAll/CogAll.Build.cs @@ -16,7 +16,7 @@ public class CogAll : ModuleRules "CogAI", "CogEngine", "CogInput", - "CogWindow", + "Cog", "Engine", } ); diff --git a/Plugins/CogAll/Source/CogAll/Private/CogAll.cpp b/Plugins/CogAll/Source/CogAll/Private/CogAll.cpp index ad4b176..046e3e9 100644 --- a/Plugins/CogAll/Source/CogAll/Private/CogAll.cpp +++ b/Plugins/CogAll/Source/CogAll/Private/CogAll.cpp @@ -9,17 +9,21 @@ #include "CogAbilityWindow_Tweaks.h" #include "CogAIWindow_BehaviorTree.h" #include "CogAIWindow_Blackboard.h" +#include "CogEngineWindow_BuildInfo.h" #include "CogEngineWindow_Cheats.h" #include "CogEngineWindow_CollisionTester.h" #include "CogEngineWindow_CollisionViewer.h" #include "CogEngineWindow_CommandBindings.h" +#include "CogEngineWindow_Console.h" #include "CogEngineWindow_DebugSettings.h" #include "CogEngineWindow_ImGui.h" #include "CogEngineWindow_Inspector.h" +#include "CogEngineWindow_Levels.h" #include "CogEngineWindow_LogCategories.h" #include "CogEngineWindow_Metrics.h" #include "CogEngineWindow_NetImgui.h" #include "CogEngineWindow_NetEmulation.h" +#include "CogEngineWindow_Notifications.h" #include "CogEngineWindow_OutputLog.h" #include "CogEngineWindow_Plots.h" #include "CogEngineWindow_Scalability.h" @@ -32,30 +36,32 @@ #include "CogEngineWindow_Transform.h" #include "CogInputWindow_Actions.h" #include "CogInputWindow_Gamepad.h" -#include "CogWindowManager.h" +#include "CogSubsystem.h" #include "Engine/Engine.h" -#include "GameFramework/Character.h" -#include "GameFramework/GameStateBase.h" #include "GameFramework/GameUserSettings.h" //-------------------------------------------------------------------------------------------------------------------------- -void Cog::AddAllWindows(UCogWindowManager& CogWindowManager) +void Cog::AddAllWindows(UCogSubsystem& CogSubsystem) { //--------------------------------------- // Engine //--------------------------------------- - CogWindowManager.AddWindow("Engine.Collision Tester"); + CogSubsystem.AddWindow("Engine.Build Info"); - CogWindowManager.AddWindow("Engine.Collision Viewer"); + CogSubsystem.AddWindow("Engine.Collision Tester"); - CogWindowManager.AddWindow("Engine.Command Bindings"); + CogSubsystem.AddWindow("Engine.Collision Viewer"); - CogWindowManager.AddWindow("Engine.Debug Settings"); + CogSubsystem.AddWindow("Engine.Command Bindings"); - CogWindowManager.AddWindow("Engine.ImGui"); + CogSubsystem.AddWindow("Engine.Console"); - FCogEngineWindow_Inspector* Inspector = CogWindowManager.AddWindow("Engine.Inspector"); + CogSubsystem.AddWindow("Engine.Debug Settings"); + + CogSubsystem.AddWindow("Engine.ImGui"); + + FCogEngineWindow_Inspector* Inspector = CogSubsystem.AddWindow("Engine.Inspector"); Inspector->AddFavorite(GEngine->GetGameUserSettings(), [](UObject* Object) { if (UGameUserSettings* UserSettings = Cast(Object)) @@ -64,70 +70,70 @@ void Cog::AddAllWindows(UCogWindowManager& CogWindowManager) } }); - CogWindowManager.AddWindow("Engine.Log Categories"); + CogSubsystem.AddWindow("Engine.Levels"); - CogWindowManager.AddWindow("Engine.Metrics"); + CogSubsystem.AddWindow("Engine.Log Categories"); - CogWindowManager.AddWindow("Engine.Net Emulation"); + CogSubsystem.AddWindow("Engine.Metrics"); - CogWindowManager.AddWindow("Engine.Net ImGui"); + CogSubsystem.AddWindow("Engine.Net Emulation"); - CogWindowManager.AddWindow("Engine.Output Log"); + CogSubsystem.AddWindow("Engine.Net ImGui"); - CogWindowManager.AddWindow("Engine.Plots"); + CogSubsystem.AddWindow("Engine.Notifications"); - FCogEngineWindow_Selection* SelectionWindow = CogWindowManager.AddWindow("Engine.Selection"); - SelectionWindow->SetActorClasses({ ACharacter::StaticClass(), AActor::StaticClass(), AGameModeBase::StaticClass(), AGameStateBase::StaticClass() }); - SelectionWindow->SetTraceType(UEngineTypes::ConvertToTraceType(ECC_Pawn)); + CogSubsystem.AddWindow("Engine.Output Log"); - CogWindowManager.AddWindow("Engine.Scalability"); + CogSubsystem.AddWindow("Engine.Plots"); - CogWindowManager.AddWindow("Engine.Skeleton"); + CogSubsystem.AddWindow("Engine.Selection"); - CogWindowManager.AddWindow("Engine.Slate"); + CogSubsystem.AddWindow("Engine.Scalability"); - CogWindowManager.AddWindow("Engine.Spawns"); + CogSubsystem.AddWindow("Engine.Skeleton"); - FCogEngineWindow_Stats* StatsWindow = CogWindowManager.AddWindow("Engine.Stats"); + CogSubsystem.AddWindow("Engine.Slate"); - CogWindowManager.AddWindow("Engine.Time Scale"); + CogSubsystem.AddWindow("Engine.Spawns"); - CogWindowManager.AddWindow("Engine.Transform"); + CogSubsystem.AddWindow("Engine.Stats"); + CogSubsystem.AddWindow("Engine.Time Scale"); + + CogSubsystem.AddWindow("Engine.Transform"); + //--------------------------------------- // Abilities //--------------------------------------- - CogWindowManager.AddWindow("Gameplay.Abilities"); + CogSubsystem.AddWindow("Gameplay.Abilities"); - CogWindowManager.AddWindow("Gameplay.Attributes"); + CogSubsystem.AddWindow("Gameplay.Attributes"); - CogWindowManager.AddWindow("Gameplay.Blocking Tags"); + CogSubsystem.AddWindow("Gameplay.Blocking Tags"); - //CogWindowManager.AddWindow("Gameplay.Cheats"); + CogSubsystem.AddWindow("Gameplay.Cheats"); - CogWindowManager.AddWindow("Gameplay.Cheats"); + CogSubsystem.AddWindow("Gameplay.Effects"); - CogWindowManager.AddWindow("Gameplay.Effects"); + CogSubsystem.AddWindow("Gameplay.Pools"); - CogWindowManager.AddWindow("Gameplay.Pools"); + CogSubsystem.AddWindow("Gameplay.Owned Tags"); - CogWindowManager.AddWindow("Gameplay.Owned Tags"); + CogSubsystem.AddWindow("Gameplay.Tasks"); - CogWindowManager.AddWindow("Gameplay.Tasks"); - - CogWindowManager.AddWindow("Gameplay.Tweaks"); + CogSubsystem.AddWindow("Gameplay.Tweaks"); //--------------------------------------- // AI //--------------------------------------- - CogWindowManager.AddWindow("AI.Behavior Tree"); + CogSubsystem.AddWindow("AI.Behavior Tree"); - CogWindowManager.AddWindow("AI.Blackboard"); + CogSubsystem.AddWindow("AI.Blackboard"); //--------------------------------------- // Input //--------------------------------------- - CogWindowManager.AddWindow("Input.Actions"); + CogSubsystem.AddWindow("Input.Actions"); - CogWindowManager.AddWindow("Input.Gamepad"); + CogSubsystem.AddWindow("Input.Gamepad"); } diff --git a/Plugins/CogAll/Source/CogAll/Public/CogAll.h b/Plugins/CogAll/Source/CogAll/Public/CogAll.h index 8cc3c82..1ffe646 100644 --- a/Plugins/CogAll/Source/CogAll/Public/CogAll.h +++ b/Plugins/CogAll/Source/CogAll/Public/CogAll.h @@ -2,9 +2,9 @@ #include "CoreMinimal.h" -class UCogWindowManager; +class UCogSubsystem; namespace Cog { - void COGALL_API AddAllWindows(UCogWindowManager& CogWindowManager); + void COGALL_API AddAllWindows(UCogSubsystem& CogSubsystem); } diff --git a/Plugins/CogAll/Source/CogAll/Public/CogAllModule.h b/Plugins/CogAll/Source/CogAll/Public/CogAllModule.h index b3c8ac0..63b5832 100644 --- a/Plugins/CogAll/Source/CogAll/Public/CogAllModule.h +++ b/Plugins/CogAll/Source/CogAll/Public/CogAllModule.h @@ -8,7 +8,7 @@ class COGALL_API FCogAllModule : public IModuleInterface public: - static inline FCogAllModule &Get() { return FModuleManager::LoadModuleChecked("CogAll"); } + static FCogAllModule &Get() { return FModuleManager::LoadModuleChecked("CogAll"); } /** IModuleInterface implementation */ virtual void StartupModule() override; diff --git a/Plugins/CogCommonUI/CogCommonUI.uplugin b/Plugins/CogCommonUI/CogCommonUI.uplugin new file mode 100644 index 0000000..db41d04 --- /dev/null +++ b/Plugins/CogCommonUI/CogCommonUI.uplugin @@ -0,0 +1,34 @@ +{ + "FileVersion": 1, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "CogCommonUI", + "Description": "", + "Category": "Other", + "CreatedBy": "Arnaud Jamin", + "CreatedByURL": "", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": false, + "IsBetaVersion": false, + "IsExperimentalVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "CogCommonUI", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "Cog", + "Enabled": true + }, + { + "Name": "CommonUI", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/Plugins/CogCommonUI/Source/CogCommonUI/CogCommonUI.Build.cs b/Plugins/CogCommonUI/Source/CogCommonUI/CogCommonUI.Build.cs new file mode 100644 index 0000000..3868fc0 --- /dev/null +++ b/Plugins/CogCommonUI/Source/CogCommonUI/CogCommonUI.Build.cs @@ -0,0 +1,50 @@ +using UnrealBuildTool; + +public class CogCommonUI : ModuleRules +{ + public CogCommonUI(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CogCommon", + "CogImgui", + "CogDebug", + "Cog", + "CommonUI", + "InputCore", + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + } + ); + } +} diff --git a/Plugins/CogCommonUI/Source/CogCommonUI/Private/CogCommonUIModule.cpp b/Plugins/CogCommonUI/Source/CogCommonUI/Private/CogCommonUIModule.cpp new file mode 100644 index 0000000..7f03502 --- /dev/null +++ b/Plugins/CogCommonUI/Source/CogCommonUI/Private/CogCommonUIModule.cpp @@ -0,0 +1,17 @@ +#include "CogCommonUIModule.h" + +#define LOCTEXT_NAMESPACE "FCogCommonUIModule" + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogCommonUIModule::StartupModule() +{ +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogCommonUIModule::ShutdownModule() +{ +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FCogCommonUIModule, CogCommonUI) \ No newline at end of file diff --git a/Plugins/CogCommonUI/Source/CogCommonUI/Private/CogCommonUI_ActionRouter.cpp b/Plugins/CogCommonUI/Source/CogCommonUI/Private/CogCommonUI_ActionRouter.cpp new file mode 100644 index 0000000..a2e0e84 --- /dev/null +++ b/Plugins/CogCommonUI/Source/CogCommonUI/Private/CogCommonUI_ActionRouter.cpp @@ -0,0 +1,19 @@ +#include "CogCommonUI_ActionRouter.h" + +#include "CogImguiInputHelper.h" + +ERouteUIInputResult UCogCommonUI_ActionRouter::ProcessInput(FKey Key, EInputEvent InputEvent) const +{ + if (const UWorld* World = GetWorld()) + { + if (const UPlayerInput* PlayerInput = FCogImguiInputHelper::GetPlayerInput(*World)) + { + if (FCogImguiInputHelper::IsTopPriorityKey(*PlayerInput, Key)) + { + return ERouteUIInputResult::Unhandled; + } + } + } + + return UCommonUIActionRouterBase::ProcessInput(Key, InputEvent); +} diff --git a/Plugins/CogCommonUI/Source/CogCommonUI/Public/CogCommonUIModule.h b/Plugins/CogCommonUI/Source/CogCommonUI/Public/CogCommonUIModule.h new file mode 100644 index 0000000..001c5c4 --- /dev/null +++ b/Plugins/CogCommonUI/Source/CogCommonUI/Public/CogCommonUIModule.h @@ -0,0 +1,20 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class COGCOMMONUI_API FCogCommonUIModule : public IModuleInterface +{ +public: + + static inline FCogCommonUIModule& Get() + { + return FModuleManager::LoadModuleChecked("CogCommonUI"); + } + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + +private: +}; diff --git a/Plugins/CogCommonUI/Source/CogCommonUI/Public/CogCommonUI_ActionRouter.h b/Plugins/CogCommonUI/Source/CogCommonUI/Public/CogCommonUI_ActionRouter.h new file mode 100644 index 0000000..e245e2d --- /dev/null +++ b/Plugins/CogCommonUI/Source/CogCommonUI/Public/CogCommonUI_ActionRouter.h @@ -0,0 +1,13 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Input/CommonUIActionRouterBase.h" +#include "CogCommonUI_ActionRouter.generated.h" + +UCLASS() +class COGCOMMONUI_API UCogCommonUI_ActionRouter : public UCommonUIActionRouterBase +{ + GENERATED_BODY() + + virtual ERouteUIInputResult ProcessInput(FKey Key, EInputEvent InputEvent) const override; +}; diff --git a/Plugins/CogInput/Source/CogInput/CogInput.Build.cs b/Plugins/CogInput/Source/CogInput/CogInput.Build.cs index 4fa3374..6f19a42 100644 --- a/Plugins/CogInput/Source/CogInput/CogInput.Build.cs +++ b/Plugins/CogInput/Source/CogInput/CogInput.Build.cs @@ -25,7 +25,7 @@ public class CogInput : ModuleRules "CogCommon", "CogImgui", "CogDebug", - "CogWindow", + "Cog", "EnhancedInput", "InputCore", } diff --git a/Plugins/CogInput/Source/CogInput/Private/CogInputWindow_Actions.cpp b/Plugins/CogInput/Source/CogInput/Private/CogInputWindow_Actions.cpp index 747643f..66cc1ec 100644 --- a/Plugins/CogInput/Source/CogInput/Private/CogInputWindow_Actions.cpp +++ b/Plugins/CogInput/Source/CogInput/Private/CogInputWindow_Actions.cpp @@ -1,6 +1,6 @@ #include "CogInputWindow_Actions.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "Engine/LocalPlayer.h" #include "Engine/World.h" #include "EnhancedInputSubsystems.h" @@ -27,14 +27,6 @@ void FCogInputWindow_Actions::RenderHelp() "It can also be used to inject inputs to help debugging."); } -//-------------------------------------------------------------------------------------------------------------------------- -void FCogInputWindow_Actions::ResetConfig() -{ - Super::ResetConfig(); - - Config->Reset(); -} - //-------------------------------------------------------------------------------------------------------------------------- void FCogInputWindow_Actions::RenderContent() { @@ -74,7 +66,7 @@ void FCogInputWindow_Actions::RenderContent() if (ImGui::MenuItem("Reset")) { - for (FCogInputMappingContextInfo Mapping : Mappings) + for (FCogInputMappingContextInfo& Mapping : Mappings) { for (FCogInputActionInfo& Action : Mapping.Actions) { @@ -204,16 +196,16 @@ void FCogInputWindow_Actions::RenderContent() ImGui::TableNextColumn(); ImGui::BeginDisabled(); bool Value = ActionValue.Get(); - FCogWindowWidgets::PushBackColor(ImVec4(0.8f, 0.8f, 0.8f, 1)); + FCogWidgets::PushBackColor(ImVec4(0.8f, 0.8f, 0.8f, 1)); ImGui::Checkbox("##Current", &Value); - FCogWindowWidgets::PopBackColor(); + FCogWidgets::PopBackColor(); ImGui::EndDisabled(); ImGui::TableNextColumn(); ECheckBoxState State = ActionInfo.bRepeat ? ECheckBoxState::Undetermined : ActionInfo.bPressed ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - if (FCogWindowWidgets::CheckBoxState("##Inject", State, false)) + if (FCogWidgets::CheckBoxState("##Inject", State, false)) { if (IsControlDown) { @@ -328,13 +320,13 @@ void FCogInputWindow_Actions::DrawAxis(const char* Format, const char* ActionNam ImGui::TableNextColumn(); ImGui::SetNextItemWidth(-1); ImGui::BeginDisabled(); - FCogWindowWidgets::PushBackColor(ImVec4(0.8f, 0.8f, 0.8f, 1)); + FCogWidgets::PushBackColor(ImVec4(0.8f, 0.8f, 0.8f, 1)); ImGui::SliderFloat("##Value", &CurrentValue, -1.0f, 1.0f, "%0.2f"); - FCogWindowWidgets::PopBackColor(); + FCogWidgets::PopBackColor(); ImGui::EndDisabled(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(-1); - FCogWindowWidgets::SliderWithReset("##Inject", InjectValue, -1.0f, 1.0f, 0.0f, "%0.2f"); + FCogWidgets::SliderWithReset("##Inject", InjectValue, -1.0f, 1.0f, 0.0f, "%0.2f"); ImGui::PopID(); } diff --git a/Plugins/CogInput/Source/CogInput/Private/CogInputWindow_Gamepad.cpp b/Plugins/CogInput/Source/CogInput/Private/CogInputWindow_Gamepad.cpp index d8f81a8..08d2dd2 100644 --- a/Plugins/CogInput/Source/CogInput/Private/CogInputWindow_Gamepad.cpp +++ b/Plugins/CogInput/Source/CogInput/Private/CogInputWindow_Gamepad.cpp @@ -1,7 +1,7 @@ #include "CogInputWindow_Gamepad.h" #include "CogImguiHelper.h" -#include "CogWindowWidgets.h" +#include "CogWidgets.h" #include "Engine/LocalPlayer.h" #include "Engine/World.h" #include "EnhancedInputSubsystems.h" @@ -13,31 +13,76 @@ void FCogInputWindow_Gamepad::Initialize() { Super::Initialize(); + bUseCustomContextMenu = true; + Config = GetConfig(); + + Actions.FindOrAdd(EKeys::Gamepad_Left2D); + Actions.FindOrAdd(EKeys::Gamepad_LeftX); + Actions.FindOrAdd(EKeys::Gamepad_LeftY); + Actions.FindOrAdd(EKeys::Gamepad_Right2D); + Actions.FindOrAdd(EKeys::Gamepad_RightX); + Actions.FindOrAdd(EKeys::Gamepad_RightY); + Actions.FindOrAdd(EKeys::Gamepad_LeftTriggerAxis); + Actions.FindOrAdd(EKeys::Gamepad_RightTriggerAxis); + Actions.FindOrAdd(EKeys::Gamepad_LeftThumbstick); + Actions.FindOrAdd(EKeys::Gamepad_RightThumbstick); + Actions.FindOrAdd(EKeys::Gamepad_Special_Left); + Actions.FindOrAdd(EKeys::Gamepad_Special_Left_X); + Actions.FindOrAdd(EKeys::Gamepad_Special_Left_Y); + Actions.FindOrAdd(EKeys::Gamepad_Special_Right); + Actions.FindOrAdd(EKeys::Gamepad_FaceButton_Bottom); + Actions.FindOrAdd(EKeys::Gamepad_FaceButton_Right); + Actions.FindOrAdd(EKeys::Gamepad_FaceButton_Left); + Actions.FindOrAdd(EKeys::Gamepad_FaceButton_Top); + Actions.FindOrAdd(EKeys::Gamepad_LeftShoulder); + Actions.FindOrAdd(EKeys::Gamepad_RightShoulder); + Actions.FindOrAdd(EKeys::Gamepad_LeftTrigger); + Actions.FindOrAdd(EKeys::Gamepad_RightTrigger); + Actions.FindOrAdd(EKeys::Gamepad_DPad_Up); + Actions.FindOrAdd(EKeys::Gamepad_DPad_Down); + Actions.FindOrAdd(EKeys::Gamepad_DPad_Right); + Actions.FindOrAdd(EKeys::Gamepad_DPad_Left); } //-------------------------------------------------------------------------------------------------------------------------- -void FCogInputWindow_Gamepad::PreRender(ImGuiWindowFlags& WindowFlags) +void FCogInputWindow_Gamepad::PreBegin(ImGuiWindowFlags& WindowFlags) { + Super::PreBegin(WindowFlags); + + WindowFlags |= ImGuiWindowFlags_NoScrollbar; + if (Config->bShowAsOverlay) { WindowFlags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse - | ImGuiWindowFlags_NoBackground - | ImGuiWindowFlags_NoResize; + | ImGuiWindowFlags_NoBackground; } + + if (Config->bLockPosition) + { + ImVec2 WindowPos = FCogWidgets::ComputeScreenCornerLocation(Config->Alignment, Config->Padding); + WindowPos.y -= Config->bShowAsOverlay && Config->Alignment.Y < 0.5f ? ImGui::GetFrameHeight() : 0.0f; + ImGui::SetNextWindowPos(WindowPos, ImGuiCond_Always, FCogImguiHelper::ToImVec2(Config->Alignment)); + } + + ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(FLT_MAX, FLT_MAX), FCogInputWindow_Gamepad::ConstrainAspectRatio); + ImGui::PushStyleColor(ImGuiCol_ResizeGrip, IM_COL32_BLACK_TRANS); } //-------------------------------------------------------------------------------------------------------------------------- -void FCogInputWindow_Gamepad::ResetConfig() +void FCogInputWindow_Gamepad::ConstrainAspectRatio(ImGuiSizeCallbackData* InData) { - Super::ResetConfig(); + constexpr float AspectRatio = 0.60f; + InData->DesiredSize.y = static_cast(InData->DesiredSize.x * AspectRatio + ImGui::GetFrameHeight()); +} - if (Config != nullptr) - { - Config->Reset(); - } +//-------------------------------------------------------------------------------------------------------------------------- +void FCogInputWindow_Gamepad::PostBegin() +{ + Super::PostBegin(); + ImGui::PopStyleColor(); } //-------------------------------------------------------------------------------------------------------------------------- @@ -50,16 +95,16 @@ void FCogInputWindow_Gamepad::RenderButtonContextMenu(const FKey& Key, FCogInput ImGui::Checkbox("Pressed", &ActionInfoButton->bPressed); ImGui::Checkbox("##Repeat", &ActionInfoButton->bRepeat); ImGui::SameLine(); - ImGui::SetNextItemWidth(FCogWindowWidgets::GetShortWidth() - ImGui::GetCursorPosX() + ImGui::GetStyle().ItemSpacing.x); - FCogWindowWidgets::SliderWithReset("Repeat", &Config->RepeatPeriod, 0.0f, 10.0f, 0.5f, "%0.1fs"); + ImGui::SetNextItemWidth(FCogWidgets::GetShortWidth() - ImGui::GetCursorPosX() + ImGui::GetStyle().ItemSpacing.x); + FCogWidgets::SliderWithReset("Repeat", &Config->RepeatPeriod, 0.0f, 10.0f, 0.5f, "%0.1fs"); } if (ActionInfoButton != nullptr && ActionInfoButton->Action != nullptr && ActionInfoButton->Action->ValueType == EInputActionValueType::Axis1D) { ImGui::Separator(); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::SliderWithReset("X", &ActionInfoButton->X, -1.0f, 1.0f, 0.0f, "%0.2f"); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::SliderWithReset("X", &ActionInfoButton->X, -1.0f, 1.0f, 0.0f, "%0.2f"); } } @@ -70,13 +115,13 @@ void FCogInputWindow_Gamepad::RenderStickContextMenu(const FKey& Key, FCogInputA { ImGui::Separator(); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::SliderWithReset("X", &ActionInfo2D->X, -1.0f, 1.0f, 0.0f, "%0.2f"); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::SliderWithReset("Y", &ActionInfo2D->Y, -1.0f, 1.0f, 0.0f, "%0.2f"); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::SliderWithReset("X", &ActionInfo2D->X, -1.0f, 1.0f, 0.0f, "%0.2f"); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::SliderWithReset("Y", &ActionInfo2D->Y, -1.0f, 1.0f, 0.0f, "%0.2f"); ImGui::Checkbox("Invert Stick Y", &InvertY); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::SliderWithReset("Sensitivity", &Sensitivity, 0.0f, 10.0f, 5.0f, "%0.1f"); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::SliderWithReset("Sensitivity", &Sensitivity, 0.0f, 10.0f, 5.0f, "%0.1f"); } } @@ -84,9 +129,7 @@ void FCogInputWindow_Gamepad::RenderStickContextMenu(const FKey& Key, FCogInputA void FCogInputWindow_Gamepad::OnButtonClicked(FCogInputActionInfo* ActionInfo) { if (ActionInfo == nullptr) - { - return; - } + { return; } if (ActionInfo->bRepeat) { @@ -108,8 +151,6 @@ void FCogInputWindow_Gamepad::RenderButton(const FKey& Key, const ImVec2& Relati { ImGui::PushID((void*)(&Key)); - const float Value = Input->GetKeyValue(Key); - const ImVec2& Size = RelativeSize * CanvasSize.x; const ImVec2 Position = (CanvasMin + CanvasSize * RelativePosition) - Alignment * Size; @@ -259,6 +300,29 @@ void FCogInputWindow_Gamepad::RenderStick(const FKey& Key2D, const FKey& KeyBool ImGui::PopID(); } +//-------------------------------------------------------------------------------------------------------------------------- +void FCogInputWindow_Gamepad::TryRefreshActions() +{ + const CogInputMappingContextMap* AppliedMappingContexts = &PRIVATE_ACCESS_PTR(Input, UEnhancedPlayerInput_AppliedInputContexts); + + if (AppliedMappingContexts == nullptr) + { return; } + + if (AppliedMappingContexts->Num() == 0) + { return; } + + for (auto& kv : *AppliedMappingContexts) + { + for (const FEnhancedActionKeyMapping& Mapping : kv.Key->GetMappings()) + { + if (Mapping.Action != nullptr) + { + FCogInputActionInfo& ActionInfo = Actions.FindOrAdd(Mapping.Key); + ActionInfo.Action = Mapping.Action; + } + } + } +} //-------------------------------------------------------------------------------------------------------------------------- void FCogInputWindow_Gamepad::RenderContent() @@ -286,50 +350,7 @@ void FCogInputWindow_Gamepad::RenderContent() return; } - if (Actions.Num() == 0) - { - Actions.FindOrAdd(EKeys:: Gamepad_Left2D); - Actions.FindOrAdd(EKeys:: Gamepad_LeftX); - Actions.FindOrAdd(EKeys:: Gamepad_LeftY); - Actions.FindOrAdd(EKeys:: Gamepad_Right2D); - Actions.FindOrAdd(EKeys:: Gamepad_RightX); - Actions.FindOrAdd(EKeys:: Gamepad_RightY); - Actions.FindOrAdd(EKeys:: Gamepad_LeftTriggerAxis); - Actions.FindOrAdd(EKeys:: Gamepad_RightTriggerAxis); - Actions.FindOrAdd(EKeys:: Gamepad_LeftThumbstick); - Actions.FindOrAdd(EKeys:: Gamepad_RightThumbstick); - Actions.FindOrAdd(EKeys:: Gamepad_Special_Left); - Actions.FindOrAdd(EKeys:: Gamepad_Special_Left_X); - Actions.FindOrAdd(EKeys:: Gamepad_Special_Left_Y); - Actions.FindOrAdd(EKeys:: Gamepad_Special_Right); - Actions.FindOrAdd(EKeys:: Gamepad_FaceButton_Bottom); - Actions.FindOrAdd(EKeys:: Gamepad_FaceButton_Right); - Actions.FindOrAdd(EKeys:: Gamepad_FaceButton_Left); - Actions.FindOrAdd(EKeys:: Gamepad_FaceButton_Top); - Actions.FindOrAdd(EKeys:: Gamepad_LeftShoulder); - Actions.FindOrAdd(EKeys:: Gamepad_RightShoulder); - Actions.FindOrAdd(EKeys:: Gamepad_LeftTrigger); - Actions.FindOrAdd(EKeys:: Gamepad_RightTrigger); - Actions.FindOrAdd(EKeys:: Gamepad_DPad_Up); - Actions.FindOrAdd(EKeys:: Gamepad_DPad_Down); - Actions.FindOrAdd(EKeys:: Gamepad_DPad_Right); - Actions.FindOrAdd(EKeys:: Gamepad_DPad_Left); - - if (const CogInputMappingContextMap* AppliedMappingContexts = &PRIVATE_ACCESS_PTR(Input, UEnhancedPlayerInput_AppliedInputContexts)) - { - for (auto& kv : *AppliedMappingContexts) - { - for (const FEnhancedActionKeyMapping& Mapping : kv.Key->GetMappings()) - { - if (Mapping.Action != nullptr) - { - FCogInputActionInfo& ActionInfo = Actions.FindOrAdd(Mapping.Key); - ActionInfo.Action = Mapping.Action; - } - } - } - } - } + TryRefreshActions(); constexpr float AspectRatio = 0.55f; constexpr float StickAmplitude = 0.04f; @@ -351,7 +372,7 @@ void FCogInputWindow_Gamepad::RenderContent() const ImVec2 ContentMin = ImGui::GetCursorScreenPos(); const ImVec2 ContentSize = ImGui::GetContentRegionAvail(); const ImVec2 ContentMax = ContentMin + ContentSize; - const ImVec2 OverlayOffset = ImVec2(0.0f, Config->bShowAsOverlay ? ImGui::GetFrameHeight() : 0.0f); + const ImVec2 OverlayOffset = ImVec2(0.0f, Config->bShowAsOverlay && IsWindowRenderedInMainMenu() == false ? ImGui::GetFrameHeight() : 0.0f); const ImVec2 Padding = ImVec2(Config->Border * 0.5f * ContentSize.x, Config->Border * 0.5f * ContentSize.x); CanvasMin = ContentMin + OverlayOffset + Padding; @@ -366,7 +387,7 @@ void FCogInputWindow_Gamepad::RenderContent() ImGui::EndPopup(); } - DrawList->PushClipRect(ContentMin, ContentMax, true); + //DrawList->PushClipRect(ContentMin, ContentMax, true); constexpr ImVec2 LS_Pos(0.35f, 0.7f); constexpr ImVec2 RS_Pos(0.65f, 0.7f); @@ -433,7 +454,7 @@ void FCogInputWindow_Gamepad::RenderContent() RenderButton(EKeys::Gamepad_Special_Left, ImVec2(0.5f - SpecialButtonDistance * 0.5f, 0.35f), SpecialButtonSize, ImVec2(0.5f, 0.5f), SpecialButtonRound); RenderButton(EKeys::Gamepad_Special_Right, ImVec2(0.5f + SpecialButtonDistance * 0.5f, 0.35f), SpecialButtonSize, ImVec2(0.5f, 0.5f), SpecialButtonRound); - DrawList->PopClipRect(); + //DrawList->PopClipRect(); } //-------------------------------------------------------------------------------------------------------------------------- @@ -442,21 +463,15 @@ void FCogInputWindow_Gamepad::RenderTick(float DeltaSeconds) Super::RenderTick(DeltaSeconds); if (GetWorld() == nullptr) - { - return; - } + { return; } const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController(); if (LocalPlayer == nullptr) - { - return; - } + { return; } UEnhancedInputLocalPlayerSubsystem* EnhancedInput = ULocalPlayer::GetSubsystem(LocalPlayer); if (EnhancedInput == nullptr) - { - return; - } + { return; } bool IsTimeToRepeat = false; const float WorldTime = GetWorld()->GetTimeSeconds(); @@ -476,30 +491,39 @@ void FCogInputWindow_Gamepad::RenderTick(float DeltaSeconds) //-------------------------------------------------------------------------------------------------------------------------- void FCogInputWindow_Gamepad::RenderMainContextMenu() { - ImGui::MenuItem("Overlay", nullptr, &Config->bShowAsOverlay); - if (ImGui::MenuItem("Close")) { SetIsVisible(false); } - if (ImGui::MenuItem("Reset")) + if (ImGui::BeginMenu("Display")) { - ResetConfig(); + ImGui::Checkbox("Overlay", &Config->bShowAsOverlay); + ImGui::Checkbox("Lock Position", &Config->bLockPosition); + ImGui::BeginDisabled(!Config->bLockPosition); + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderFloat2("Alignment", &Config->Alignment.X, 0, 1.0f, "%.2f"); + FCogWidgets::SetNextItemToShortWidth(); + ImGui::SliderInt2("Padding", &Config->Padding.X, 0, 100); + ImGui::EndDisabled(); + + ImGui::Separator(); + + ImGui::ColorEdit4("Background Color", &Config->BackgroundColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Border Color", &Config->BorderColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Button Color", &Config->ButtonColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Pressed Color", &Config->PressedColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Hovered Color", &Config->HoveredColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + ImGui::ColorEdit4("Inject Color", &Config->InjectColor.X, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + FCogWidgets::SetNextItemToShortWidth(); + FCogWidgets::SliderWithReset("Border", &Config->Border, 0.0f, 0.1f, 0.02f, "%0.3f"); + ImGui::EndMenu(); } ImGui::Separator(); - if (ImGui::BeginMenu("Display")) + if (ImGui::MenuItem("Reset Settings")) { - ImGui::ColorEdit4("Background Color", (float*)&Config->BackgroundColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Border Color", (float*)&Config->BorderColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Button Color", (float*)&Config->ButtonColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Pressed Color", (float*)&Config->PressedColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Hovered Color", (float*)&Config->HoveredColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - ImGui::ColorEdit4("Inject Color", (float*)&Config->InjectColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); - FCogWindowWidgets::SetNextItemToShortWidth(); - FCogWindowWidgets::SliderWithReset("Border", &Config->Border, 0.0f, 0.1f, 0.02f, "%0.3f"); - ImGui::EndMenu(); + ResetConfig(); } } \ No newline at end of file diff --git a/Plugins/CogInput/Source/CogInput/Public/CogInputWindow_Actions.h b/Plugins/CogInput/Source/CogInput/Public/CogInputWindow_Actions.h index a13835e..08de29e 100644 --- a/Plugins/CogInput/Source/CogInput/Public/CogInputWindow_Actions.h +++ b/Plugins/CogInput/Source/CogInput/Public/CogInputWindow_Actions.h @@ -20,8 +20,6 @@ public: protected: - virtual void ResetConfig() override; - virtual void RenderHelp() override; virtual void RenderContent() override; diff --git a/Plugins/CogInput/Source/CogInput/Public/CogInputWindow_Gamepad.h b/Plugins/CogInput/Source/CogInput/Public/CogInputWindow_Gamepad.h index cbab139..8bd864a 100644 --- a/Plugins/CogInput/Source/CogInput/Public/CogInputWindow_Gamepad.h +++ b/Plugins/CogInput/Source/CogInput/Public/CogInputWindow_Gamepad.h @@ -25,9 +25,9 @@ public: protected: - virtual void ResetConfig() override; + virtual void PreBegin(ImGuiWindowFlags& WindowFlags) override; - virtual void PreRender(ImGuiWindowFlags& WindowFlags) override; + virtual void PostBegin() override; virtual void RenderContent() override; @@ -36,7 +36,7 @@ protected: virtual void RenderButton(const FKey& Key, const ImVec2& Position, const ImVec2& Size, const ImVec2& Alignment, float Rounding, ImDrawFlags Flags = 0); virtual void RenderStick(const FKey& Key2D, const FKey& KeyBool, bool& InvertY, float& Sensitivity, float Amplitude, const ImVec2& Position, float Radius); - + virtual void OnButtonClicked(FCogInputActionInfo* ActionInfo); virtual void RenderMainContextMenu(); @@ -45,6 +45,10 @@ protected: virtual void RenderStickContextMenu(const FKey& Key, FCogInputActionInfo* ActionInfo2D, bool& InvertY, float& Sensitivity); + virtual void TryRefreshActions(); + + static void ConstrainAspectRatio(ImGuiSizeCallbackData* InData); + TObjectPtr Config; TMap Actions; @@ -73,6 +77,15 @@ public: UPROPERTY(Config) bool bShowAsOverlay = false; + UPROPERTY(Config) + bool bLockPosition = false; + + UPROPERTY(Config) + FVector2f Alignment = FVector2f(0.0f, 0.0f); + + UPROPERTY(Config) + FIntVector2 Padding = FIntVector2(10, 10); + UPROPERTY(Config) bool bInvertRightStickY = false; @@ -112,8 +125,11 @@ public: virtual void Reset() override { Super::Reset(); - + bShowAsOverlay = false; + bLockPosition = false; + Alignment = { 0, 0 }; + Padding = { 10, 10 }; bInvertRightStickY = false; bInvertLeftStickY = false; LeftStickSensitivity = 5.0f; diff --git a/README.md b/README.md index 565a64c..b76aa53 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +> [!IMPORTANT] +> The way Cog integrates in your project has been simplified. If you update Cog, please read the [integration guide](#Integrating-Cog-in-your-project). + # Cog ## FORK OF ORIGINAL @@ -14,11 +17,11 @@ Cog is a set of debug tools for Unreal Engine built on top of [Dear ImGui](https ![Cog](https://github.com/arnaud-jamin/Cog/assets/13844285/21659aea-2cd8-4ef6-b3b0-5795f5f3246b) - [Video](https://www.youtube.com/watch?v=ea5hz3cFcMM) -- [Sample Executable (Windows)](https://drive.google.com/file/d/1T7jQFoZ5rd6goBtDH-FCbjn6Kr1RzUCE/view?usp=sharing) (300 MB) +- [Sample Executable (Windows)](https://drive.google.com/file/d/1T7jQFoZ5rd6goBtDH-FCbjn6Kr1RzUCE/view?usp=sharing) (500 MB) Cog provides: - ImGui windows to inspect and configure various Unreal systems (Core Engine, Enhanced Inputs, Gameplay Abilities, AI) -- Window mangement with persistent configuration and layouts. +- Window management with persistent configuration and layouts. - C++ and Blueprint functions to log and debug draw within Log Categories. - Control over the server regarding debug draw, logging, spawning, cheats. - NetImgui support to ease the debugging of game server. @@ -27,7 +30,7 @@ General Info: - Cog can be used both in editor and package builds. It is disabled by default on shipping builds. - Press the `[F1]` key or use the `Cog.ToggleInput` console command to open the ImGui Main Menu. - Many windows display their contents based on a selected actor. The selector actor can be chosen using the `Engine/Selection` window or widget. -- Widgets such as Stats (FPS, Ping), or Actor Selection, can be added in the main menu bar from the 'Window/Widgets" menu. +- Widgets such as Stats (FPS, Ping), Time Scale, Actor Selection, Console, can be added in the main menu bar from the `Window/Widgets` menu. ## Cog Windows @@ -35,52 +38,83 @@ General Info: Displays the gameplay abilities of the selected actor. -[![Abilities](https://github.com/arnaud-jamin/Cog/assets/13844285/cc6cb2af-eb9a-42fd-8ae5-80b5c7b361e9)]() +![Abilities](https://github.com/user-attachments/assets/b332cf3a-9fee-408f-a86e-157c513c0ee2) - Click the ability checkbox to force its activation or deactivation. -- Right click an ability to remove it, or open/close the ability separate window. +- Right-click an ability to remove it, or open/close the ability separate window. - Use the 'Give Ability' menu to manually give an ability from a list defined in a Data Asset. +--- + ### Actions Displays the state of Input Action. -![Actions](https://github.com/arnaud-jamin/Cog/assets/13844285/6323e78b-2ee4-43e2-bec6-19aa15716d2c) +![Actions](https://github.com/user-attachments/assets/e4b60f69-efa0-4a23-b78e-9f261e5f78f5) - Can be used to inject inputs to help debugging, as loosing window focus when breaking in the code doesn't affect the state of injected inputs unlike real inputs. -- The display input action are read from a Input Mapping Context defined in a Data Asset. + +https://github.com/user-attachments/assets/2497900e-d2d9-4af9-abef-44f7f31c2726 + +--- ### Attributes Displays the gameplay attributes of the selected actor. -![Attributes](https://github.com/arnaud-jamin/Cog/assets/13844285/a6329ef1-f775-4e6e-9581-6389f9f4b39c) +![Attributes](https://github.com/user-attachments/assets/ff010ac5-d8e5-44ca-b46f-263c45a0fc47) - Attributes can be sorted by name, category or attribute set. - Attributes with the Current value greater than the Base value are displayed in green. - Attributes with the Current value lower than the Base value are displayed in red. - Use the options 'Show Only Modified' to only show the attributes that have modifiers. +--- + ### Behavior Tree Displays the behavior tree of the selected actor. ![Behavior Tree](https://github.com/arnaud-jamin/Cog/assets/13844285/c799e85f-b641-4d6f-9476-54a5cbd73c65) +--- + ### Blackboard Displays the blackboard of the selected actor. ![Blackboard](https://github.com/arnaud-jamin/Cog/assets/13844285/649d46d5-386c-4990-9f45-e4eb95a6b81a) +--- + +### Build Info +Display the build information such as the build version, changelist, date, target, and so on. + +![image](https://github.com/user-attachments/assets/b0383dea-a372-4ce2-94e2-6e1f9d3d6807) + +--- + ### Cheats Used to apply cheats to the selected actor. -![Cheats](https://github.com/arnaud-jamin/Cog/assets/13844285/b7b89635-7924-49b7-98c0-311199947dfc) -- The cheats are gameplay effects configured in a data asset. +![Cheats](https://github.com/user-attachments/assets/e3bbf157-904f-40e9-a0bf-e8152ff24af4) +- Cheats are replicated to the game server to be executed. - Key modifiers can be used to change which actor should be affected by the cheat: - `[CTRL]` Apply the cheat to the controlled actor - `[ALT]` Apply the cheat to the allies of the selected actor - `[SHIFT]` Apply the cheat to the enemies of the selected actor +- Optionally, cheats applied to the local player character are automatically reapplied. + +![Cheats Settings](https://github.com/user-attachments/assets/006c4cf0-34e2-4e0d-8be7-be27db8796aa) + +- Cheats can be defined in a Data Asset. Cog provide two default cheat execution (Apply Effect, Activate Ability). +- Cheat executions can be defined in blueprint. + +![Cheats Asset](https://github.com/user-attachments/assets/46a14b89-ebab-4628-b97b-e5ccd2b63576) + +--- ### Collisions Tester -Used to test a collision query +Used to test collision queries ![Collisions Tester](https://github.com/arnaud-jamin/Cog/assets/13844285/12f7cb7e-13c9-4eed-8ea4-f3eb7475cf34) ![Collisions Tester](https://github.com/arnaud-jamin/Cog/assets/13844285/4d01fd01-d0b3-41a7-9344-662f190ebaf0) +