From b56d7bf4a9b08dab29472b66c6274843423f91cf Mon Sep 17 00:00:00 2001 From: Arnaud Jamin Date: Thu, 30 Jan 2025 13:16:17 -0500 Subject: [PATCH] CogEngine: Add first iteration on a Console window --- .../Private/CogEngineWindow_Console.cpp | 406 ++++++++++++++++++ .../Public/CogEngineWindow_Console.h | 99 +++++ .../CogAll/Source/CogAll/Private/CogAll.cpp | 5 +- 3 files changed, 508 insertions(+), 2 deletions(-) create mode 100644 Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Console.cpp create mode 100644 Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Console.h 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..d57e540 --- /dev/null +++ b/Plugins/Cog/Source/CogEngine/Private/CogEngineWindow_Console.cpp @@ -0,0 +1,406 @@ +#include "CogEngineWindow_Console.h" + +#include + +#include "CogImguiHelper.h" +#include "CogWindowManager.h" +#include "CogWindowWidgets.h" +#include "GameFramework/PlayerController.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(); + + bNoPadding = true; + bHasMenu = true; + bHasWidget = 1; + + RefreshVisibleCommands(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::RenderContent() +{ + Super::RenderContent(); + + const APlayerController* PlayerController = GetLocalPlayerController(); + if (PlayerController == nullptr) + { + return; + } + + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Options")) + { + if (ImGui::Checkbox("Sort Commands", &Config->SortCommands)) + { + RefreshVisibleCommands(); + } + + if (ImGui::Checkbox("Show Console Input In Menu Bar", &Config->ShowConsoleInputInMenuBar)) + { + RefreshVisibleCommands(); + } + + FCogWindowWidgets::SetNextItemToShortWidth(); + if (ImGui::SliderInt("Num History Commands", &Config->NumHistoryCommands, 0, 100)) + { + RefreshVisibleCommands(); + } + + FCogWindowWidgets::SetNextItemToShortWidth(); + if (ImGui::SliderInt("Completion Minimum Characters", &Config->CompletionMinimumCharacters, 0, 3)) + { + RefreshVisibleCommands(); + } + + ImGui::ColorEdit4("History Color", (float*)&Config->HistoryColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaPreviewHalf); + + ImGui::EndMenu(); + } + + if (Config->ShowConsoleInputInMenuBar) + { + ImGui::SetNextItemWidth(-1); + RenderConsoleTextInput(); + } + + ImGui::EndMenuBar(); + } + + ImGui::Spacing(); + + if (Config->ShowConsoleInputInMenuBar == false) + { + ImGui::SetNextItemWidth(-1); + RenderConsoleTextInput(); + } + + const float Indent = ImGui::GetFontSize() * 0.5f; + int32 Index = 0; + + if (ImGui::BeginChild("Commands", ImVec2(0.0f, -1.0f), ImGuiChildFlags_NavFlattened)) + { + ImGui::Indent(Indent); + + for (const FString& CommandName : VisibleHistory) + { + ImGui::PushStyleColor(ImGuiCol_Text, FCogImguiHelper::ToImVec4(Config->HistoryColor)); + ImGui::PushID(Index); + RenderCommand(CommandName, Index); + ImGui::PopID(); + ImGui::PopStyleColor(); + + Index++; + } + + if (Index > 1) + { + ImGui::Separator(); + } + + ImGuiListClipper Clipper; + Clipper.Begin(VisibleCommands.Num()); + + // int32 ClipperIndex = 0; + // while (Clipper.Step()) + // { + // for (ClipperIndex = Clipper.DisplayStart; ClipperIndex < Clipper.DisplayEnd; ClipperIndex++) + // { + // if (VisibleCommands.IsValidIndex(ClipperIndex)) + // { + // ImGui::PushID(Index); + // const FString& CommandName = VisibleCommands[ClipperIndex]; + // RenderCommand(CommandName, Index); + // if (SelectedCommandIndex == Index) + // { + // SelectedCommand = CommandName; + // } + // ImGui::PopID(); + // Index++; + // } + // } + // } + // + // Clipper.End(); + // + // // If any is available, draw an additional command below the clipper to be able to scroll when pressing bottom + // if (VisibleCommands.IsValidIndex(ClipperIndex + 1)) + // { + // const FString& Command = VisibleCommands[ClipperIndex + 1]; + // RenderCommand(Command, Index); + // Index++; + // } + + for (const FString& CommandName : VisibleCommands) + { + RenderCommand(CommandName, Index); + Index++; + } + + ImGui::Unindent(Indent); + } + ImGui::EndChild(); + + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) + { + if (bIsWindowFocused == false) + { + bRequestTextInputFocus = true; + } + bIsWindowFocused = true; + + if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) + { + SelectedCommandIndex += 1; + bScroll = true; + + if (SelectedCommandIndex >= Index) + { + SelectedCommandIndex = 0; + } + } + else if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) + { + SelectedCommandIndex -= 1; + bScroll = true; + + if (SelectedCommandIndex < 0) + { + SelectedCommandIndex = Index - 1; + } + } + } + else + { + bIsWindowFocused = false; + } + + // ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.2f, 0.2f, 0.2f, 0.5f)); + // if (ImGui::BeginChild("Description", ImVec2(0.0f, ImGui::GetContentRegionAvail().y))) + // { + // ImGui::Indent(Indent); + // ImGui::BeginDisabled(); + // + // const FString Help = GetConsoleCommandHelp(CurrentUserInput); + // const auto& HelpStr = StringCast(*Help); + // ImGui::TextWrapped(HelpStr.Get()); + // + // ImGui::EndDisabled(); + // ImGui::Unindent(Indent); + // } + // ImGui::EndChild(); + // ImGui::PopStyleColor(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::RenderConsoleTextInput() +{ + constexpr ImGuiInputTextFlags InputFlags = + ImGuiInputTextFlags_EnterReturnsTrue + | ImGuiInputTextFlags_EscapeClearsAll + | ImGuiInputTextFlags_CallbackCompletion + | ImGuiInputTextFlags_CallbackEdit; + + if (FCogWindowWidgets::InputTextWithHint("##Command", "Command", CurrentUserInput, InputFlags, &OnTextInputCallbackStub, this)) + { + ExecuteCommand(CurrentUserInput); + bRequestTextInputFocus = true; + } + + ImGui::SetItemDefaultFocus(); + + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) + && ImGui::IsItemActive() == false + && bRequestTextInputFocus + && IsWindowRenderedInMainMenu() == false) + { + ImGui::SetKeyboardFocusHere(-1); + } + + if (ImGui::IsItemActive()) + { + bRequestTextInputFocus = false; + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +FString FCogEngineWindow_Console::GetConsoleCommandHelp(const FString& InCommandName) +{ + TArray CommandSplitWithSpaces; + InCommandName.ParseIntoArrayWS(CommandSplitWithSpaces); + + if (CommandSplitWithSpaces.Num() > 0) + { + if (const IConsoleObject* ConsoleObject = IConsoleManager::Get().FindConsoleObject(*CommandSplitWithSpaces[0])) + { + return FString(ConsoleObject->GetHelp()); + } + } + + return FString("Unknown command."); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::RenderCommand(const FString& CommandName, const int32 Index) +{ + const auto& CommandNameStr = StringCast(*CommandName); + + bool IsSelected = Index == SelectedCommandIndex; + + // ImGui::Text("%d - ", Index); + // ImGui::SameLine(); + + if (ImGui::Selectable(CommandNameStr.Get(), &IsSelected, ImGuiSelectableFlags_AllowDoubleClick)) + { + SelectedCommandIndex = Index; + CurrentUserInput = CommandName; + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + { + ExecuteCommand(CommandName); + } + } + + if (IsSelected) + { + SelectedCommand = CommandName; + if (bScroll) + { + ImGui::SetScrollHereY(1.0f); + bScroll = false; + } + } + + 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(); + } +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::RefreshVisibleCommands() +{ + auto OnConsoleObject = [this](const TCHAR *InName, const IConsoleObject* InConsoleObject) + { + if (InConsoleObject->TestFlags(ECVF_Unregistered) || InConsoleObject->TestFlags(ECVF_ReadOnly)) + { return; } + + VisibleCommands.Add(InName); + }; + + FString CommandStart = 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) + { + CommandStart = UserInputSplitWithSpaces[0]; + } + + VisibleCommands.Empty(); + if (CommandStart.Len() >= Config->CompletionMinimumCharacters) + { + IConsoleManager::Get().ForEachConsoleObjectThatStartsWith(FConsoleObjectVisitor::CreateLambda(OnConsoleObject), *CommandStart); + } + + if (Config->SortCommands) + { + VisibleCommands.Sort(); + } + + TArray AllHistory; + IConsoleManager::Get().GetConsoleHistory(TEXT(""), AllHistory); + + VisibleHistory.Empty(); + for (int32 i = AllHistory.Num() - 1; i >= 0; i--) + { + FString Command = AllHistory[i]; + if (Command.IsEmpty()) + { continue; } + + if (CurrentUserInput.IsEmpty() == false && Command.StartsWith(CurrentUserInput) == false) + { continue; } + + if (VisibleHistory.Num() >= Config->NumHistoryCommands) + { break; } + + VisibleHistory.Add(Command); + } + + SelectedCommandIndex = 0; +} + +//-------------------------------------------------------------------------------------------------------------------------- +int FCogEngineWindow_Console::OnTextInputCallback(ImGuiInputTextCallbackData* InData) +{ + if (InData->EventFlag == ImGuiInputTextFlags_CallbackCompletion) + { + FString CleanupCommand = SelectedCommand.TrimEnd(); + const auto& CommandStr = StringCast(*CleanupCommand); + InData->DeleteChars(0, InData->BufTextLen); + InData->InsertChars(0, CommandStr.Get()); + InData->InsertChars(InData->CursorPos, " "); + } + else if (InData->EventFlag == ImGuiInputTextFlags_CallbackEdit) + { + CurrentUserInput = FString(InData->Buf); + RefreshVisibleCommands(); + } + + return 0; +} + +//-------------------------------------------------------------------------------------------------------------------------- +int FCogEngineWindow_Console::OnTextInputCallbackStub(ImGuiInputTextCallbackData* InData) +{ + FCogEngineWindow_Console& ConsoleWindow = *static_cast(InData->UserData); + return ConsoleWindow.OnTextInputCallback(InData); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::ExecuteCommand(const FString& InCommand) +{ + FString CleanupCommand = InCommand.TrimEnd(); + if (CleanupCommand.IsEmpty() == false) + { + if (VisibleHistory.Num() == 0 || (VisibleHistory.Last() != CleanupCommand)) + { + IConsoleManager::Get().AddConsoleHistoryEntry(TEXT(""), *CleanupCommand); + } + + GEngine->DeferredCommands.Add(CleanupCommand); + } + + CurrentUserInput = FString(); + RefreshVisibleCommands(); +} + +//-------------------------------------------------------------------------------------------------------------------------- +void FCogEngineWindow_Console::RenderMainMenuWidget() +{ + ImGui::SetNextItemWidth(100); + RenderConsoleTextInput(); +} + 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..ba5c53f --- /dev/null +++ b/Plugins/Cog/Source/CogEngine/Public/CogEngineWindow_Console.h @@ -0,0 +1,99 @@ +#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; + +public: + +protected: + + virtual void RenderHelp() override; + + virtual void Initialize() override; + + virtual void RenderMainMenuWidget() override; + + virtual void RenderContent() override; + +private: + + static int OnTextInputCallbackStub(ImGuiInputTextCallbackData* InData); + + static FString GetConsoleCommandHelp(const FString& InCommandName); + + void RenderConsoleTextInput(); + + void RenderCommand(const FString& CommandName, int32 Index); + + void RefreshVisibleCommands(); + + int OnTextInputCallback(ImGuiInputTextCallbackData* InData); + + void ExecuteCommand(const FString& InCommand); + + int32 SelectedCommandIndex = 0; + + FString SelectedCommand; + + TArray VisibleCommands; + + TArray VisibleHistory; + + FString CurrentUserInput; + + bool bScroll = false; + + bool bRequestTextInputFocus = false; + + bool bIsWindowFocused = false; + + TWeakObjectPtr Config; +}; + +//-------------------------------------------------------------------------------------------------------------------------- +UCLASS(Config = Cog) +class UCogEngineConfig_Console : public UCogCommonConfig +{ + GENERATED_BODY() + +public: + + UPROPERTY(Config) + bool SortCommands = false; + + UPROPERTY(Config) + bool ShowConsoleInputInMenuBar = false; + + UPROPERTY(Config) + int32 NumHistoryCommands = 10; + + UPROPERTY(Config) + int32 CompletionMinimumCharacters = 0; + + UPROPERTY(Config) + FVector4f HistoryColor = FVector4f(1.0f, 1.0f, 1.0f, 0.5f); + + UCogEngineConfig_Console() + { + Reset(); + } + + virtual void Reset() override + { + Super::Reset(); + + SortCommands = false; + NumHistoryCommands = 10; + CompletionMinimumCharacters = 0; + ShowConsoleInputInMenuBar = false; + HistoryColor = FVector4f(1.0f, 1.0f, 1.0f, 0.5f); + } +}; \ No newline at end of file diff --git a/Plugins/CogAll/Source/CogAll/Private/CogAll.cpp b/Plugins/CogAll/Source/CogAll/Private/CogAll.cpp index ad4b176..e90507e 100644 --- a/Plugins/CogAll/Source/CogAll/Private/CogAll.cpp +++ b/Plugins/CogAll/Source/CogAll/Private/CogAll.cpp @@ -13,6 +13,7 @@ #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" @@ -51,6 +52,8 @@ void Cog::AddAllWindows(UCogWindowManager& CogWindowManager) CogWindowManager.AddWindow("Engine.Command Bindings"); + CogWindowManager.AddWindow("Engine.Console"); + CogWindowManager.AddWindow("Engine.Debug Settings"); CogWindowManager.AddWindow("Engine.ImGui"); @@ -103,8 +106,6 @@ void Cog::AddAllWindows(UCogWindowManager& CogWindowManager) CogWindowManager.AddWindow("Gameplay.Blocking Tags"); - //CogWindowManager.AddWindow("Gameplay.Cheats"); - CogWindowManager.AddWindow("Gameplay.Cheats"); CogWindowManager.AddWindow("Gameplay.Effects");